You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

324 lines
12 KiB

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
}
}