rewindable battle class

main
Mari 12 months ago
parent 189957a456
commit 30a5b452d4
  1. 189
      src/commonMain/kotlin/model/Battle.kt
  2. 298
      src/commonMain/kotlin/model/Battler.kt
  3. 29
      src/commonMain/kotlin/model/HistoryEntry.kt
  4. 31
      src/commonMain/kotlin/model/TimedEvent.kt
  5. 102
      src/commonMain/kotlin/scenes/BattleScene.kt
  6. 17
      src/commonMain/kotlin/views/BattlerView.kt

@ -0,0 +1,189 @@
package model
import korlibs.datastructure.*
import korlibs.time.*
class Battle(initializer: LoadAction = LoadAction.ZERO) {
private val _battlers: MutableList<Battler> =
initializer.battlers.map { Battler(it, initializer.timestamp) }.toMutableList()
private val _pendingEffects: PriorityQueue<BattleEffect> =
PriorityQueue<BattleEffect>().apply { addAll(initializer.pendingEffects) }
var currentTime: TimeSpan = initializer.timestamp
private set
/** Full history entries needed for rewinding. Stored from oldest to newest. */
private val _history: Deque<HistoryEntry> = Deque()
/** Actions which will be executed at a later timestamp. */
private val _pendingActions: PriorityQueue<BattleAction> = PriorityQueue()
/** Abbreviated history entries used only for saving replay data. Stored from oldest to newest. */
private val _pastHistory: Queue<BattleAction> = Queue()
val battlers: List<Battler.Readonly> = _battlers.map { it.asReadonly }
val pendingEffects: List<BattleEffect> = _pendingEffects.toList()
val history: List<HistoryEntry> = _history.toList()
val pendingActions: List<BattleAction> = _pendingActions.toList()
val pastHistory: List<BattleAction> = _pastHistory.toList()
init {
saveHistoryEntry(initializer)
}
constructor(actions: List<BattleAction>, timestamp: TimeSpan? = null): this(
actions.firstOrNull().run {
when (this) {
is LoadAction -> this
else -> LoadAction.ZERO
}
}
) {
if (actions.firstOrNull() is LoadAction) {
injectActions(actions.drop(1))
} else {
injectActions(actions)
}
if (timestamp != null) {
setTimeTo(timestamp)
}
}
fun injectAction(action: BattleAction) {
when {
action.timestamp > currentTime -> _pendingActions.add(action)
action.timestamp == currentTime -> applyEvent(action)
action.timestamp < currentTime -> {
val present = currentTime
rewindTo(action.timestamp)
applyEvent(action)
advanceTo(present)
}
}
}
fun injectActions(actions: Iterable<BattleAction>) {
actions.forEach { injectAction(it) }
}
/** Abbreviates [_history] up to and including the given [timestamp], moving it to [_pastHistory]. */
fun abbreviateHistoryUpTo(timestamp: TimeSpan) {
while (_history.isNotEmpty() && _history.first.timestamp <= timestamp) {
val previous = _history.removeFirst()
if (previous is ActionHistoryEntry) {
_pastHistory.enqueue(previous.action)
}
}
}
fun setTimeTo(timestamp: TimeSpan) {
if (timestamp == currentTime) {
// then we're done already!
} else if (timestamp > currentTime){
advanceTo(timestamp)
} else {
rewindTo(timestamp)
}
}
private fun rewindTo(timestamp: TimeSpan) {
if (_history.isEmpty()) {
throw IllegalStateException(
"tried to rewind to ${timestamp.inWholeMilliseconds}ms, but have no full history at all")
}
if (_history.first.timestamp > timestamp) {
throw IllegalStateException(
"tried to rewind to ${timestamp.inWholeMilliseconds}ms, " +
"but only have full history back to ${_history.first.timestamp.inWholeMilliseconds}")
}
while (_history.isNotEmpty() && _history.last.timestamp > timestamp) {
val previous = _history.removeLast()
if (previous is ActionHistoryEntry) {
_pendingActions.add(previous.action)
}
}
applyHistoryEntry(_history.last)
if (timestamp > currentTime) {
advanceTo(timestamp)
}
}
private fun advanceTo(timestamp: TimeSpan) {
while (nextEventAt < timestamp) {
val nextEvent = if (nextActionAt < nextEffectAt) {
_pendingActions.removeHead()
} else {
_pendingEffects.removeHead()
}
applyEvent(nextEvent)
}
advanceTimeTo(timestamp)
}
val nextActionAt: TimeSpan get() = nextAction?.timestamp ?: TimeSpan.INFINITE
val nextEffectAt: TimeSpan get() = nextEffect?.timestamp ?: TimeSpan.INFINITE
val nextEventAt: TimeSpan get() = nextEvent?.timestamp ?: TimeSpan.INFINITE
val nextAction: BattleAction? get() = when {
_pendingActions.isNotEmpty() -> _pendingActions.head
else -> null
}
val nextEffect: BattleEffect? get() = when {
_pendingEffects.isNotEmpty() -> _pendingEffects.head
else -> null
}
val nextEvent: TimedEvent? get() {
val action = nextAction
val effect = nextEffect
return when {
action == null || (effect != null && effect < action) -> effect
else -> action
}
}
private fun advanceTimeTo(timestamp: TimeSpan) {
if (currentTime == timestamp) {
return
}
currentTime = timestamp
_battlers.forEach { it.advanceTo(currentTime) }
}
private fun applyEvent(event: TimedEvent) {
advanceTimeTo(event.timestamp)
with(event) { execute() }
saveHistoryEntry(event)
}
private fun saveHistoryEntry(entry: TimedEvent) {
val archivedBattlers = _battlers.map { it.asSaved }
val archivedEffects = _pendingEffects.toList()
_history.add(when (entry) {
is LoadAction -> ActionHistoryEntry(
action = entry,
battlers = entry.battlers,
pendingEffects = entry.pendingEffects,
)
is BattleAction -> ActionHistoryEntry(
action = entry,
battlers = archivedBattlers,
pendingEffects = archivedEffects,
)
is BattleEffect -> EffectHistoryEntry(
effect = entry,
battlers = archivedBattlers,
pendingEffects = archivedEffects,
)
})
}
private fun applyHistoryEntry(entry: HistoryEntry) {
currentTime = entry.timestamp
_pendingEffects.clear()
_pendingEffects.addAll(entry.pendingEffects)
_battlers.clear()
_battlers.addAll(entry.battlers.map { Battler(it, entry.timestamp) })
}
}

@ -2,31 +2,116 @@ package model
import korlibs.math.* import korlibs.math.*
import korlibs.time.* import korlibs.time.*
import kotlin.jvm.*
import kotlin.math.* import kotlin.math.*
data class Battler( data class Battler (
var name: String, val id: Id,
var composure: Double,
var health: Double, private var _name: String,
var maxHealth: Double, private var _composure: Double,
var energy: Double, private var _health: Double,
var stamina: Double, private var _maxHealth: Double,
var maxStamina: Double, private var _energy: Double,
private var _stamina: Double,
var shaken: Boolean = false, private var _maxStamina: Double,
var shakenTimes: Int = 0,
var composurePausedUntil: TimeSpan = TimeSpan.ZERO, private var _shaken: Boolean = false,
var energyPausedUntil: TimeSpan = TimeSpan.ZERO, private var _shakenTimes: Int = 0,
var currentUpdate: TimeSpan = TimeSpan.ZERO, private var _composurePausedUntil: TimeSpan = TimeSpan.ZERO,
private var _energyPausedUntil: TimeSpan = TimeSpan.ZERO,
private var _currentUpdate: TimeSpan = TimeSpan.ZERO,
private var lastUpdate: TimeSpan = TimeSpan.ZERO, private var lastUpdate: TimeSpan = TimeSpan.ZERO,
private var lastShaken: Boolean = shaken, private var lastShaken: Boolean = _shaken,
private var lastComposure: Double = composure, private var lastComposure: Double = _composure,
private var lastEnergy: Double = energy, private var lastEnergy: Double = _energy,
private var lastStamina: Double = stamina, private var lastStamina: Double = _stamina,
) { ) {
constructor(name: String, health: Double, stamina: Double) : @JvmInline
this(name = name, composure = health, health = health, maxHealth = health, value class Id(val v: Int)
energy = stamina, stamina = stamina, maxStamina = stamina)
class Editable (private val battler: Battler){
val id: Id get() = battler.id
var name: String get() = battler.name
set(v) { battler._name = v }
var composure: Double get() = battler.composure
set(v) { battler._composure = v }
var health: Double get() = battler.health
set(v) { battler._health = v }
var maxHealth: Double get() = battler.maxHealth
set(v) { battler._maxHealth = v }
var energy: Double get() = battler.energy
set(v) { battler._energy = v }
var stamina: Double get() = battler.stamina
set(v) { battler._stamina = v }
var maxStamina: Double get() = battler.maxStamina
set(v) { battler._maxStamina = v }
var shaken: Boolean get() = battler.shaken
set(v) { battler._shaken = v }
var shakenTimes: Int get() = battler.shakenTimes
set(v) { battler._shakenTimes = v }
var composurePausedUntil: TimeSpan get() = battler.composurePausedUntil
set(v) { battler._composurePausedUntil = v }
var energyPausedUntil: TimeSpan get() = battler.energyPausedUntil
set(v) { battler._energyPausedUntil = v }
val currentUpdate: TimeSpan get() = battler.currentUpdate
fun becomeShaken() {
composure = health.coerceAtMost(0.0)
if (shaken) {
return
}
composurePausedUntil = (currentUpdate + battler.composureRecoveryDelay).coerceAtLeast(composurePausedUntil)
shaken = true
shakenTimes += 1
}
fun inflictComposureDamage(damage: Double) {
if (shaken) {
return
}
composurePausedUntil =
(currentUpdate + battler.composureChargeDelay).coerceAtLeast(composurePausedUntil)
composure -= damage
if (composure <= 0) {
becomeShaken()
}
}
fun inflictHealthDamage(damage: Double) {
composurePausedUntil = (currentUpdate + battler.composureRecoveryDelay).coerceAtLeast(composurePausedUntil)
health -= damage
composure -= damage
if (composure <= 0) {
becomeShaken()
}
}
fun spendEnergy(cost: Double) {
energyPausedUntil = (currentUpdate + battler.energyChargeDelay).coerceAtLeast(energyPausedUntil)
energy = (energy - cost).coerceAtLeast(0.0)
}
fun spendStamina(cost: Double) {
energyPausedUntil = (currentUpdate + battler.energyChargeDelay).coerceAtLeast(energyPausedUntil)
stamina = (stamina - cost).coerceAtLeast(0.0)
energy = (energy - cost).coerceAtLeast(0.0)
}
}
val name: String get() = _name
val composure: Double get() = _composure
val health: Double get() = _health
val maxHealth: Double get() = _maxHealth
val energy: Double get() = _energy
val stamina: Double get() = _stamina
val maxStamina: Double get() = _maxStamina
val shaken: Boolean get() = _shaken
val shakenTimes: Int get() = _shakenTimes
val composurePausedUntil: TimeSpan get() = _composurePausedUntil
val energyPausedUntil: TimeSpan get() = _energyPausedUntil
val currentUpdate: TimeSpan get() = _currentUpdate
private val composureChargeTime: TimeSpan get() = TimeSpan(45_000.0 * 1.2.pow(shakenTimes - 1)) private val composureChargeTime: TimeSpan get() = TimeSpan(45_000.0 * 1.2.pow(shakenTimes - 1))
private val composureChargeDelay: TimeSpan get() = TimeSpan(2_000.0 * 1.2.pow(shakenTimes - 1)) private val composureChargeDelay: TimeSpan get() = TimeSpan(2_000.0 * 1.2.pow(shakenTimes - 1))
@ -53,31 +138,31 @@ data class Battler(
val halfHealth = health/2.0 val halfHealth = health/2.0
val dtRecover = composureRecoveryTime * (halfHealth - lastComposure.coerceAtLeast(0.0)) / halfHealth val dtRecover = composureRecoveryTime * (halfHealth - lastComposure.coerceAtLeast(0.0)) / halfHealth
if (dtComposure > dtRecover) { if (dtComposure > dtRecover) {
composure = halfHealth _composure = halfHealth
shaken = false _shaken = false
dtComposure -= dtRecover dtComposure -= dtRecover
} else { } else {
composure = ( _composure = (
lastComposure.coerceAtLeast(0.0) + dtComposure.milliseconds * halfHealth / composureRecoveryTime.milliseconds) lastComposure.coerceAtLeast(0.0) + dtComposure.milliseconds * halfHealth / composureRecoveryTime.milliseconds)
shaken = lastShaken _shaken = lastShaken
dtComposure = TimeSpan.ZERO dtComposure = TimeSpan.ZERO
} }
} else { } else {
composure = lastComposure _composure = lastComposure
shaken = lastShaken _shaken = lastShaken
} }
if (dtComposure.isPositive()) { if (dtComposure.isPositive()) {
if (health > 0) { if (health > 0) {
val lastChargeTime = TimeSpan( val lastChargeTime = TimeSpan(
sqrt(4 * composureChargeCoefficient * composure) / (2 * composureChargeCoefficient) sqrt(4 * composureChargeCoefficient * composure) / (2 * composureChargeCoefficient)
) )
composure = if (dtComposure + lastChargeTime >= composureChargeTime) { _composure = if (dtComposure + lastChargeTime >= composureChargeTime) {
health health
} else { } else {
(composureChargeCoefficient * (lastChargeTime + dtComposure).milliseconds.squared()) (composureChargeCoefficient * (lastChargeTime + dtComposure).milliseconds.squared())
} }
} else { } else {
composure = health _composure = health
} }
} }
} }
@ -97,27 +182,27 @@ data class Battler(
if (lastStamina > 0) { if (lastStamina > 0) {
val dtToFull = energyChargeTime * (lastStamina - lastEnergy) / lastStamina val dtToFull = energyChargeTime * (lastStamina - lastEnergy) / lastStamina
if (dtEnergy >= dtToFull) { if (dtEnergy >= dtToFull) {
energy = lastStamina _energy = lastStamina
dtEnergy -= dtToFull dtEnergy -= dtToFull
} else { } else {
energy = lastEnergy + (lastStamina * dtEnergy.milliseconds) / energyChargeTime.milliseconds _energy = lastEnergy + (lastStamina * dtEnergy.milliseconds) / energyChargeTime.milliseconds
dtEnergy = TimeSpan.ZERO dtEnergy = TimeSpan.ZERO
} }
} else { } else {
energy = lastStamina _energy = lastStamina
} }
} }
if (dtEnergy.isPositive() && lastStamina < maxStamina) { if (dtEnergy.isPositive() && lastStamina < maxStamina) {
stamina = min( _stamina = min(
maxStamina, maxStamina,
lastStamina + (maxStamina * dtEnergy.milliseconds) / staminaChargeTime.milliseconds) lastStamina + (maxStamina * dtEnergy.milliseconds) / staminaChargeTime.milliseconds)
energy = stamina _energy = stamina
} }
} }
} }
fun advanceTo(t: TimeSpan) { fun advanceTo(t: TimeSpan) {
currentUpdate = t _currentUpdate = t
if (composure < health) { if (composure < health) {
advanceComposureTo(t) advanceComposureTo(t)
} }
@ -126,57 +211,114 @@ data class Battler(
} }
} }
fun becomeShaken() { fun updateAt(t: TimeSpan, block: Editable.() -> Unit) {
composure = health.coerceAtMost(0.0) advanceTo(t)
if (shaken) {
return
}
composurePausedUntil = (currentUpdate + composureRecoveryDelay).coerceAtLeast(composurePausedUntil)
shaken = true
shakenTimes += 1
}
fun inflictComposureDamage(damage: Double) { block(Editable(this))
if (shaken) {
return
}
composurePausedUntil = (currentUpdate + composureChargeDelay).coerceAtLeast(composurePausedUntil)
composure -= damage
if (composure <= 0) {
becomeShaken()
}
}
fun inflictHealthDamage(damage: Double) { lastUpdate = currentUpdate
composurePausedUntil = (currentUpdate + composureRecoveryDelay).coerceAtLeast(composurePausedUntil) lastShaken = shaken
health -= damage lastComposure = composure
composure -= damage lastEnergy = energy
if (composure <= 0) { lastStamina = stamina
becomeShaken()
}
} }
fun spendEnergy(cost: Double) { val asSaved: Saved get() = Saved(
energyPausedUntil = (currentUpdate + energyChargeDelay).coerceAtLeast(energyPausedUntil) id = id,
energy = (energy - cost).coerceAtLeast(0.0)
} name = name,
composure = composure,
health = health,
maxHealth = maxHealth,
energy = energy,
stamina = stamina,
maxStamina = maxStamina,
shaken = shaken,
shakenTimes = shakenTimes,
composurePausedUntil = composurePausedUntil,
energyPausedUntil = energyPausedUntil
)
fun spendStamina(cost: Double) { fun revertTo(previous: Saved, at: TimeSpan) {
energyPausedUntil = (currentUpdate + energyChargeDelay).coerceAtLeast(energyPausedUntil) _name = previous.name
stamina = (stamina - cost).coerceAtLeast(0.0) _composure = previous.composure
energy = (energy - cost).coerceAtLeast(0.0) _health = previous.health
_maxHealth = previous.maxHealth
_energy = previous.energy
_stamina = previous.stamina
_maxStamina = previous.maxStamina
_shaken = previous.shaken
_shakenTimes = previous.shakenTimes
_composurePausedUntil = previous.composurePausedUntil
_energyPausedUntil = previous.energyPausedUntil
_currentUpdate = at
lastUpdate = at
lastShaken = previous.shaken
lastComposure = previous.composure
lastEnergy = previous.energy
lastStamina = previous.stamina
} }
fun updateAt(t: TimeSpan, block: Battler.() -> Unit) { constructor(saved: Saved, at: TimeSpan) : this(
advanceTo(t) id = saved.id,
block(this) _name = saved.name,
_composure = saved.composure,
_health = saved.health,
_maxHealth = saved.maxHealth,
_energy = saved.energy,
_stamina = saved.stamina,
_maxStamina = saved.maxStamina,
lastUpdate = currentUpdate _shaken = saved.shaken,
lastShaken = shaken _shakenTimes = saved.shakenTimes,
lastComposure = composure _composurePausedUntil = saved.composurePausedUntil,
lastEnergy = energy _energyPausedUntil = saved.energyPausedUntil,
lastStamina = stamina
_currentUpdate = at,
lastUpdate = at,
lastShaken = saved.shaken,
lastComposure = saved.composure,
lastEnergy = saved.energy,
lastStamina = saved.stamina,
)
data class Saved(
val id: Id,
val name: String,
val composure: Double,
val health: Double = composure,
val maxHealth: Double = health,
val energy: Double,
val stamina: Double = energy,
val maxStamina: Double = stamina,
val shaken: Boolean = false,
val shakenTimes: Int = 0,
val composurePausedUntil: TimeSpan = TimeSpan.ZERO,
val energyPausedUntil: TimeSpan = TimeSpan.ZERO,
)
val asReadonly get() = Readonly(this)
class Readonly (private val battler: Battler) {
val id: Id get() = battler.id
val name: String get() = battler.name
val composure: Double get() = battler.composure
val health: Double get() = battler.health
val maxHealth: Double get() = battler.maxHealth
val energy: Double get() = battler.energy
val stamina: Double get() = battler.stamina
val maxStamina: Double get() = battler.maxStamina
val shaken: Boolean get() = battler.shaken
val shakenTimes: Int get() = battler.shakenTimes
val composurePausedUntil: TimeSpan get() = battler.composurePausedUntil
val energyPausedUntil: TimeSpan get() = battler.energyPausedUntil
val currentUpdate: TimeSpan get() = battler.currentUpdate
val asSaved: Saved get() = battler.asSaved
} }
} }

@ -0,0 +1,29 @@
package model
import korlibs.time.*
sealed interface HistoryEntry: Comparable<HistoryEntry> {
val timestamp: TimeSpan
val pendingEffects: List<BattleEffect>
val battlers: List<Battler.Saved>
override fun compareTo(other: HistoryEntry): Int {
return timestamp.compareTo(other.timestamp)
}
}
data class ActionHistoryEntry(
val action: BattleAction,
override val pendingEffects: List<BattleEffect>,
override val battlers: List<Battler.Saved>,
): HistoryEntry {
override val timestamp: TimeSpan get() = action.timestamp
}
data class EffectHistoryEntry(
val effect: BattleEffect,
override val pendingEffects: List<BattleEffect>,
override val battlers: List<Battler.Saved>,
): HistoryEntry {
override val timestamp: TimeSpan get() = effect.timestamp
}

@ -0,0 +1,31 @@
package model
import korlibs.time.*
sealed interface TimedEvent: Comparable<TimedEvent> {
val timestamp: TimeSpan
fun Battle.execute()
override fun compareTo(other: TimedEvent): Int {
return timestamp.compareTo(other.timestamp)
}
}
/** A [BattleEffect] represents some kind of timed impact on the game as a result of a [BattleAction]. */
sealed interface BattleEffect: TimedEvent
/** A [BattleAction] represents some kind of input from a player. */
sealed interface BattleAction: TimedEvent
data class LoadAction(
override val timestamp: TimeSpan,
val battlers: List<Battler.Saved>,
val pendingEffects: List<BattleEffect>) : BattleAction {
override fun Battle.execute() {
throw AssertionError("LoadActions should never be executed normally.")
}
companion object{
val ZERO = LoadAction(timestamp = TimeSpan.ZERO, battlers = listOf(), pendingEffects = listOf())
}
}

@ -1,70 +1,68 @@
package scenes package scenes
import korlibs.event.*
import korlibs.korge.input.*
import korlibs.korge.scene.* import korlibs.korge.scene.*
import korlibs.korge.view.* import korlibs.korge.view.*
import korlibs.time.* import korlibs.time.*
import model.* import model.*
import views.* import views.*
import kotlin.math.*
class BattleScene : Scene() { class BattleScene : Scene() {
private val us = BattlerView(Battler("Us", 200.0, 300.0)) private val battle = Battle(LoadAction(
private val them = BattlerView(Battler("Them", 200.0, 300.0)) timestamp = TimeSpan.ZERO,
battlers = listOf(
Battler.Saved(
id = Battler.Id(0),
name = "Us",
composure = 0.0,
health = 150.0,
maxHealth = 200.0,
energy = 0.0,
stamina = 150.0,
maxStamina = 300.0,
shaken = false,
shakenTimes = 0,
composurePausedUntil = TimeSpan.ZERO,
energyPausedUntil = TimeSpan.ZERO,
),
Battler.Saved(
id = Battler.Id(1),
name = "Them",
composure = 0.0,
health = 150.0,
maxHealth = 200.0,
energy = 0.0,
stamina = 150.0,
maxStamina = 300.0,
shaken = false,
shakenTimes = 0,
composurePausedUntil = TimeSpan.ZERO,
energyPausedUntil = TimeSpan.ZERO,
)
),
pendingEffects = listOf()
))
override suspend fun SContainer.sceneInit() { override suspend fun SContainer.sceneInit() {
us.xy(sceneWidth - (us.width + 5), sceneHeight - (us.height + 5))
addChild(us)
them.xy(5, 5) val usView = BattlerView(battle.battlers[0])
addChild(them) usView.xy(sceneWidth - (usView.width + 5), sceneHeight - (usView.height + 5))
addChild(usView)
val themView = BattlerView(battle.battlers[1])
themView.xy(5, 5)
addChild(themView)
var time = TimeSpan.ZERO var time = TimeSpan.ZERO
var fractional = 0.0
addUpdater { addUpdater {
time += it val step = it.milliseconds + fractional
} val whole = floor(step)
fractional = step - whole
keys { time += TimeSpan(whole)
downRepeating(Key.Q) { battle.setTimeTo(time)
them.battler.updateAt(time) {
them.battler.composure = (them.battler.composure + 5.0).coerceAtMost(them.battler.health)
}
}
downRepeating(Key.A) {
them.battler.updateAt(time) {
inflictComposureDamage(5.0)
}
}
downRepeating(Key.W) {
them.battler.updateAt(time) {
them.battler.health = (them.battler.health + 5.0).coerceAtMost(them.battler.maxHealth)
}
}
downRepeating(Key.S) {
them.battler.updateAt(time) {
inflictHealthDamage(5.0)
}
}
downRepeating(Key.E) {
them.battler.updateAt(time) {
them.battler.energy = (them.battler.energy + 5.0).coerceAtMost(them.battler.stamina)
}
}
downRepeating(Key.D) {
them.battler.updateAt(time) {
spendEnergy(5.0)
}
}
downRepeating(Key.R) {
them.battler.updateAt(time) {
them.battler.stamina = (them.battler.stamina + 5.0).coerceAtMost(them.battler.maxStamina)
}
}
downRepeating(Key.F) {
them.battler.updateAt(time) {
spendStamina(5.0)
}
}
} }
} }

@ -1,6 +1,6 @@
package views package views
import korlibs.image.paint.* import korlibs.image.color.*
import korlibs.image.vector.* import korlibs.image.vector.*
import korlibs.korge.ui.* import korlibs.korge.ui.*
import korlibs.korge.view.* import korlibs.korge.view.*
@ -8,7 +8,7 @@ import korlibs.math.geom.*
import model.* import model.*
import kotlin.math.* import kotlin.math.*
class BattlerView (val battler: Battler) : UIContainer(Size(200.0, 40.0)) { class BattlerView (val battler: Battler.Readonly) : UIContainer(Size(200.0, 40.0)) {
private val staminaBar = graphics { private val staminaBar = graphics {
drawStaminaBar() drawStaminaBar()
@ -26,7 +26,6 @@ class BattlerView (val battler: Battler) : UIContainer(Size(200.0, 40.0)) {
init { init {
addUpdater { addUpdater {
battler.advanceTo(battler.currentUpdate + it)
updateBattler() updateBattler()
} }
} }
@ -34,28 +33,28 @@ class BattlerView (val battler: Battler) : UIContainer(Size(200.0, 40.0)) {
private fun ShapeBuilder.drawHealthBar() { private fun ShapeBuilder.drawHealthBar() {
if (battler.health > -battler.maxHealth) { if (battler.health > -battler.maxHealth) {
fillRect(0, 10, 200 * (1 + battler.health.coerceAtMost(0.0) / battler.maxHealth), 20) fillRect(0, 10, 200 * (1 + battler.health.coerceAtMost(0.0) / battler.maxHealth), 20)
fill(ColorPaint(0x5f, 0x08, 0x08, 0xff)) fill(RGBA(0x5f, 0x08, 0x08, 0xff))
} }
if (battler.health > 0) { if (battler.health > 0) {
fillRect(0.0, 10.0, battler.health * 200.0 / battler.maxHealth, 20.0) fillRect(0.0, 10.0, battler.health * 200.0 / battler.maxHealth, 20.0)
fill(ColorPaint(0x8f, 0x10, 0x10, 0xff)) fill(RGBA(0x8f, 0x10, 0x10, 0xff))
} }
if (battler.composure > 0) { if (battler.composure > 0) {
fillRect(0.0, 10.0, battler.composure * 200.0 / battler.maxHealth, 20.0) fillRect(0.0, 10.0, battler.composure * 200.0 / battler.maxHealth, 20.0)
fill(ColorPaint(0x10, 0xcf, 0x5f, 0xff)) fill(RGBA(0x10, 0xcf, 0x5f, 0xff))
} }
} }
private fun ShapeBuilder.drawStaminaBar() { private fun ShapeBuilder.drawStaminaBar() {
fillRect(50, 30, 150, 10) fillRect(50, 30, 150, 10)
fill(ColorPaint(0x10, 0x10, 0x10, 0xff)) fill(RGBA(0x10, 0x10, 0x10, 0xff))
if (battler.stamina > 0) { if (battler.stamina > 0) {
fillRect(50.0, 30.0, battler.stamina * 150.0 / battler.maxStamina, 10.0) fillRect(50.0, 30.0, battler.stamina * 150.0 / battler.maxStamina, 10.0)
fill(ColorPaint(0x10, 0x5f, 0x7f, 0xff)) fill(RGBA(0x10, 0x5f, 0x7f, 0xff))
} }
if (battler.energy > 0) { if (battler.energy > 0) {
fillRect(50.0, 30.0, battler.energy * 150.0 / battler.maxStamina, 10.0) fillRect(50.0, 30.0, battler.energy * 150.0 / battler.maxStamina, 10.0)
fill(ColorPaint(0x10, 0x9f, 0xaf, 0xff)) fill(RGBA(0x10, 0x9f, 0xaf, 0xff))
} }
} }

Loading…
Cancel
Save