import uk.co.nickthecoder.glok.event.Key
import uk.co.nickthecoder.glok.event.KeyEvent
import uk.co.nickthecoder.glok.property.Property
import uk.co.nickthecoder.glok.property.boilerplate.booleanProperty
import uk.co.nickthecoder.glok.property.boilerplate.intProperty
import uk.co.nickthecoder.kyd.Actor
import uk.co.nickthecoder.kyd.Director
import uk.co.nickthecoder.kyd.Management
import uk.co.nickthecoder.kyd.Role
import uk.co.nickthecoder.kyd.nodes.PlayActView
import uk.co.nickthecoder.kyd.util.*
import kotlin.math.min
import kotlin.random.Random
/**
* Coordinates play.
*/
class PlayDirector : Director {
@Suppress(CustomWarning)
@Custom
val blastsProperty by intProperty(99)
var blasts by blastsProperty
/**
* When we collect [Fuel], should [Monster]s unstick themselves if they are "trapped" in dead-end?
* This makes the level harder!
*/
@Suppress(CustomWarning)
@Custom
val unstickMonstersProperty by booleanProperty(false)
var unstickMonsters by unstickMonstersProperty
/**
* How close to the edge of the view does the Player need to be before scrolling to reveal
* more of the world? Part of the skill is to plan your route without seeing what is yet to be discovered.
* So these constants should be quite small.
*/
private var scrollMarginX = gridSize * 4
private var scrollMarginY = gridSize * 3
val grid = Grid(gridSize)
/**
* Used by [Monster]s to home in on their prey.
* Set by [Player] when it enters the stage using [registerPlayer].
*/
var player: Player? = null
private set
var playerActor: Actor? = null
private set
/**
* The main aim of the game is to collect fuel and then return to the ship to leave the planet.
* Set by [Fuel]. Checked by [Player] when it attempts to enter the [Ship].
*/
val fueledProperty by booleanProperty(false)
var fueled by fueledProperty
private val spawnPoints = mutableListOf<Vector2>()
init {
instance = this
}
override fun customProperties() = listOf(blastsProperty, unstickMonstersProperty)
fun registerPlayer(actor: Actor, player: Player) {
this.playerActor = actor
this.player = player
player.blastsProperty.bidirectionalBind(blastsProperty)
}
fun registerBlastsCounter(blastsCounter: BlastsCounter) {
blastsCounter.blastsProperty.bidirectionalBind(blastsProperty)
}
fun registerFuelStatus(fuelStatus: FuelStatus) {
fuelStatus.fueledProperty.bidirectionalBind(fueledProperty)
}
fun addSpawnPoint(point: Vector2) {
spawnPoints.add(point)
}
fun collectedDynamite(amount: Int) {
blasts = min(CavernQuest.instance.maxBlasts, blasts + amount * CavernQuest.instance.blastsPerDynamite)
}
fun collectedFuel() {
fueled = true
if (unstickMonsters) {
unstickMonsters()
}
}
/**
* A nasty surprise, which I don't think is in the original.
* When you collect fuel (or die, or take off), monsters. which are "stuck" in a dead-end,
* are allowed to reverse direction.
*/
private fun unstickMonsters() {
val mainStage = PlayActView.instance.act?.stages?.firstOrNull { it.name == "main" } ?: return
for (actor in mainStage.actors) {
(actor.behaviour as? Monster)?.allowReverse = true
}
}
fun blastOff(shipActor: Actor) {
unstickMonsters()
}
fun playerDied() {
unstickMonsters()
// TODO Merge a "Game Over" act.
}
/**
* Called by [Rock] when it squashes a [Monster].
*
* Pick one of the [spawnPoints] randomly. If it is empty create a new [Monster].
* If it's not empty, try again up to 10 times. Then give up.
*/
fun spawnNewMonster() {
if (spawnPoints.isEmpty()) return
val mainStage = PlayActView.instance.act?.stages?.firstOrNull { it.name == "main" } ?: return
for (attempt in 0 until 10) {
val index = Random.nextInt(spawnPoints.size)
val point = spawnPoints[index]
if (grid.getAt(point) == null) {
val monster = PlayActView.instance.game.find("/roles/monster") as? Role
if (monster == null) {
println("Monster Role not found. Not spawning")
return
}
val newActor = Actor(mainStage).apply {
behaviour = Monster()
role = monster
position = point
}
mainStage.addActor(newActor)
return
}
}
}
override fun onKeyPressed(keyEvent: KeyEvent) {
when (keyEvent.key) {
Key.F5 -> Management.instance.reloadAct()
Key.ESCAPE -> Management.instance.loadAct("/menu")
Key.PRINT_SCREEN -> if (keyEvent.isControlDown) {
createScreenshot("screenshot.png")
createThumbnail("thumbnail.png")
}
else -> Unit
}
}
/**
* When the player gets neat an edge of the screen, scroll to reveal more of the world.
*/
override fun postTick(seconds: Float) {
val actView = PlayActView.instance
playerActor?.let { actor ->
val scenePosition = actor.position * actView.worldToScene
val reqX = if (scenePosition.x < actView.sceneX + scrollMarginX) {
actView.worldPin.x - gridSize
} else if (scenePosition.x + scrollMarginX > actView.sceneX + actView.width) {
actView.worldPin.x + gridSize
} else {
actView.worldPin.x
}
val reqY = if (fueled) {
actor.position.y
} else {
if (scenePosition.y < actView.sceneY + scrollMarginY) {
actView.worldPin.y + gridSize
} else if (scenePosition.y + scrollMarginY > actView.sceneY + actView.height) {
actView.worldPin.y - gridSize
} else {
actView.worldPin.y
}
}
if (fueled) {
val speed = Vector2(0.1f, 0.02f)
actView.worldPin = (actView.worldPin + Vector2(reqX, reqY) * speed) / (Vector2.ONE + speed)
} else {
actView.worldPin = Vector2(reqX, reqY)
}
}
}
companion object {
lateinit var instance: PlayDirector
}
}