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.
334 lines
12 KiB
334 lines
12 KiB
package model
|
|
|
|
import korlibs.math.*
|
|
import korlibs.time.*
|
|
import kotlinx.serialization.Serializable
|
|
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 @Serializable
|
|
value class Id(val v: Int)
|
|
|
|
private val eventAPI: EventAPI by lazy { EventAPI(this) }
|
|
|
|
class EventAPI (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
|
|
|
|
val asSaved: Saved get() = battler.asSaved
|
|
|
|
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)
|
|
}
|
|
|
|
fun revertTo(saved: Saved) {
|
|
battler.revertTo(previous = saved, at = currentUpdate)
|
|
}
|
|
}
|
|
|
|
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: EventAPI.() -> Unit) {
|
|
advanceTo(t)
|
|
|
|
block(eventAPI)
|
|
|
|
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,
|
|
)
|
|
|
|
@Serializable
|
|
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
|
|
}
|
|
}
|
|
|