rewindable battle class

main
Mari 5 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.time.*
import kotlin.jvm.*
import kotlin.math.*
data class Battler(
var name: String,
var composure: Double,
var health: Double,
var maxHealth: Double,
var energy: Double,
var stamina: Double,
var maxStamina: Double,
var shaken: Boolean = false,
var shakenTimes: Int = 0,
var composurePausedUntil: TimeSpan = TimeSpan.ZERO,
var energyPausedUntil: TimeSpan = TimeSpan.ZERO,
var currentUpdate: TimeSpan = TimeSpan.ZERO,
data class Battler (
val id: Id,
private var _name: String,
private var _composure: Double,
private var _health: Double,
private var _maxHealth: Double,
private var _energy: Double,
private var _stamina: Double,
private var _maxStamina: Double,
private var _shaken: Boolean = false,
private var _shakenTimes: Int = 0,
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 lastShaken: Boolean = shaken,
private var lastComposure: Double = composure,
private var lastEnergy: Double = energy,
private var lastStamina: Double = stamina,
private var lastShaken: Boolean = _shaken,
private var lastComposure: Double = _composure,
private var lastEnergy: Double = _energy,
private var lastStamina: Double = _stamina,
) {
constructor(name: String, health: Double, stamina: Double) :
this(name = name, composure = health, health = health, maxHealth = health,
energy = stamina, stamina = stamina, maxStamina = stamina)
@JvmInline
value class Id(val v: Int)
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 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 dtRecover = composureRecoveryTime * (halfHealth - lastComposure.coerceAtLeast(0.0)) / halfHealth
if (dtComposure > dtRecover) {
composure = halfHealth
shaken = false
_composure = halfHealth
_shaken = false
dtComposure -= dtRecover
} else {
composure = (
_composure = (
lastComposure.coerceAtLeast(0.0) + dtComposure.milliseconds * halfHealth / composureRecoveryTime.milliseconds)
shaken = lastShaken
_shaken = lastShaken
dtComposure = TimeSpan.ZERO
}
} else {
composure = lastComposure
shaken = lastShaken
_composure = lastComposure
_shaken = lastShaken
}
if (dtComposure.isPositive()) {
if (health > 0) {
val lastChargeTime = TimeSpan(
sqrt(4 * composureChargeCoefficient * composure) / (2 * composureChargeCoefficient)
)
composure = if (dtComposure + lastChargeTime >= composureChargeTime) {
_composure = if (dtComposure + lastChargeTime >= composureChargeTime) {
health
} else {
(composureChargeCoefficient * (lastChargeTime + dtComposure).milliseconds.squared())
}
} else {
composure = health
_composure = health
}
}
}
@ -97,27 +182,27 @@ data class Battler(
if (lastStamina > 0) {
val dtToFull = energyChargeTime * (lastStamina - lastEnergy) / lastStamina
if (dtEnergy >= dtToFull) {
energy = lastStamina
_energy = lastStamina
dtEnergy -= dtToFull
} else {
energy = lastEnergy + (lastStamina * dtEnergy.milliseconds) / energyChargeTime.milliseconds
_energy = lastEnergy + (lastStamina * dtEnergy.milliseconds) / energyChargeTime.milliseconds
dtEnergy = TimeSpan.ZERO
}
} else {
energy = lastStamina
_energy = lastStamina
}
}
if (dtEnergy.isPositive() && lastStamina < maxStamina) {
stamina = min(
_stamina = min(
maxStamina,
lastStamina + (maxStamina * dtEnergy.milliseconds) / staminaChargeTime.milliseconds)
energy = stamina
_energy = stamina
}
}
}
fun advanceTo(t: TimeSpan) {
currentUpdate = t
_currentUpdate = t
if (composure < health) {
advanceComposureTo(t)
}
@ -126,57 +211,114 @@ data class Battler(
}
}
fun becomeShaken() {
composure = health.coerceAtMost(0.0)
if (shaken) {
return
}
composurePausedUntil = (currentUpdate + composureRecoveryDelay).coerceAtLeast(composurePausedUntil)
shaken = true
shakenTimes += 1
}
fun updateAt(t: TimeSpan, block: Editable.() -> Unit) {
advanceTo(t)
fun inflictComposureDamage(damage: Double) {
if (shaken) {
return
}
composurePausedUntil = (currentUpdate + composureChargeDelay).coerceAtLeast(composurePausedUntil)
composure -= damage
if (composure <= 0) {
becomeShaken()
}
}
block(Editable(this))
fun inflictHealthDamage(damage: Double) {
composurePausedUntil = (currentUpdate + composureRecoveryDelay).coerceAtLeast(composurePausedUntil)
health -= damage
composure -= damage
if (composure <= 0) {
becomeShaken()
}
lastUpdate = currentUpdate
lastShaken = shaken
lastComposure = composure
lastEnergy = energy
lastStamina = stamina
}
fun spendEnergy(cost: Double) {
energyPausedUntil = (currentUpdate + energyChargeDelay).coerceAtLeast(energyPausedUntil)
energy = (energy - cost).coerceAtLeast(0.0)
}
val asSaved: Saved get() = Saved(
id = id,
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) {
energyPausedUntil = (currentUpdate + energyChargeDelay).coerceAtLeast(energyPausedUntil)
stamina = (stamina - cost).coerceAtLeast(0.0)
energy = (energy - cost).coerceAtLeast(0.0)
fun revertTo(previous: Saved, at: TimeSpan) {
_name = previous.name
_composure = previous.composure
_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) {
advanceTo(t)
constructor(saved: Saved, at: TimeSpan) : this(
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
lastShaken = shaken
lastComposure = composure
lastEnergy = energy
lastStamina = stamina
_shaken = saved.shaken,
_shakenTimes = saved.shakenTimes,
_composurePausedUntil = saved.composurePausedUntil,
_energyPausedUntil = saved.energyPausedUntil,
_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
import korlibs.event.*
import korlibs.korge.input.*
import korlibs.korge.scene.*
import korlibs.korge.view.*
import korlibs.time.*
import model.*
import views.*
import kotlin.math.*
class BattleScene : Scene() {
private val us = BattlerView(Battler("Us", 200.0, 300.0))
private val them = BattlerView(Battler("Them", 200.0, 300.0))
private val battle = Battle(LoadAction(
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() {
us.xy(sceneWidth - (us.width + 5), sceneHeight - (us.height + 5))
addChild(us)
them.xy(5, 5)
addChild(them)
val usView = BattlerView(battle.battlers[0])
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 fractional = 0.0
addUpdater {
time += it
}
keys {
downRepeating(Key.Q) {
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)
}
}
val step = it.milliseconds + fractional
val whole = floor(step)
fractional = step - whole
time += TimeSpan(whole)
battle.setTimeTo(time)
}
}

@ -1,6 +1,6 @@
package views
import korlibs.image.paint.*
import korlibs.image.color.*
import korlibs.image.vector.*
import korlibs.korge.ui.*
import korlibs.korge.view.*
@ -8,7 +8,7 @@ import korlibs.math.geom.*
import model.*
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 {
drawStaminaBar()
@ -26,7 +26,6 @@ class BattlerView (val battler: Battler) : UIContainer(Size(200.0, 40.0)) {
init {
addUpdater {
battler.advanceTo(battler.currentUpdate + it)
updateBattler()
}
}
@ -34,28 +33,28 @@ class BattlerView (val battler: Battler) : UIContainer(Size(200.0, 40.0)) {
private fun ShapeBuilder.drawHealthBar() {
if (battler.health > -battler.maxHealth) {
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) {
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) {
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() {
fillRect(50, 30, 150, 10)
fill(ColorPaint(0x10, 0x10, 0x10, 0xff))
fill(RGBA(0x10, 0x10, 0x10, 0xff))
if (battler.stamina > 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) {
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