package model import korlibs.math.* import korlibs.time.* import kotlin.jvm.* import kotlin.math.* 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, ) { @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)) private val composureRecoveryTime: TimeSpan get() = TimeSpan(7_000.0 * 1.2.pow(shakenTimes - 1)) private val composureRecoveryDelay: TimeSpan get() = TimeSpan(3_000.0 * 1.2.pow(shakenTimes - 1)) private val composureChargeCoefficient: Double get() = health / composureChargeTime.milliseconds.squared() private val energyChargeTime: TimeSpan get() = TimeSpan(30_000.0) private val energyChargeDelay: TimeSpan get() = TimeSpan(1_000.0) private val staminaChargeTime: TimeSpan get() = TimeSpan(300_000.0) private fun advanceComposureTo(t: TimeSpan) { var dtComposure = if (lastUpdate < composurePausedUntil) { if (t > composurePausedUntil) { t - composurePausedUntil } else { TimeSpan.ZERO } } else { t - lastUpdate } if (lastShaken && dtComposure.isPositive() && health > 0) { val halfHealth = health/2.0 val dtRecover = composureRecoveryTime * (halfHealth - lastComposure.coerceAtLeast(0.0)) / halfHealth if (dtComposure > dtRecover) { _composure = halfHealth _shaken = false dtComposure -= dtRecover } else { _composure = ( lastComposure.coerceAtLeast(0.0) + dtComposure.milliseconds * halfHealth / composureRecoveryTime.milliseconds) _shaken = lastShaken dtComposure = TimeSpan.ZERO } } else { _composure = lastComposure _shaken = lastShaken } if (dtComposure.isPositive()) { if (health > 0) { val lastChargeTime = TimeSpan( sqrt(4 * composureChargeCoefficient * composure) / (2 * composureChargeCoefficient) ) _composure = if (dtComposure + lastChargeTime >= composureChargeTime) { health } else { (composureChargeCoefficient * (lastChargeTime + dtComposure).milliseconds.squared()) } } else { _composure = health } } } private fun advanceEnergyAndStaminaTo(t: TimeSpan) { var dtEnergy = if (lastUpdate < energyPausedUntil) { if (t > energyPausedUntil) { t - energyPausedUntil } else { TimeSpan.ZERO } } else { t - lastUpdate } if (dtEnergy.isPositive()) { if (lastEnergy < lastStamina) { if (lastStamina > 0) { val dtToFull = energyChargeTime * (lastStamina - lastEnergy) / lastStamina if (dtEnergy >= dtToFull) { _energy = lastStamina dtEnergy -= dtToFull } else { _energy = lastEnergy + (lastStamina * dtEnergy.milliseconds) / energyChargeTime.milliseconds dtEnergy = TimeSpan.ZERO } } else { _energy = lastStamina } } if (dtEnergy.isPositive() && lastStamina < maxStamina) { _stamina = min( maxStamina, lastStamina + (maxStamina * dtEnergy.milliseconds) / staminaChargeTime.milliseconds) _energy = stamina } } } fun advanceTo(t: TimeSpan) { _currentUpdate = t if (composure < health) { advanceComposureTo(t) } if (energy < stamina || stamina < maxStamina) { advanceEnergyAndStaminaTo(t) } } fun updateAt(t: TimeSpan, block: Editable.() -> Unit) { advanceTo(t) block(Editable(this)) lastUpdate = currentUpdate lastShaken = shaken lastComposure = composure lastEnergy = energy lastStamina = stamina } 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 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 } constructor(saved: Saved, at: TimeSpan) : this( id = saved.id, _name = saved.name, _composure = saved.composure, _health = saved.health, _maxHealth = saved.maxHealth, _energy = saved.energy, _stamina = saved.stamina, _maxStamina = saved.maxStamina, _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 } }