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.
254 lines
8.9 KiB
254 lines
8.9 KiB
package model
|
|
|
|
import korlibs.datastructure.*
|
|
import korlibs.datastructure.iterators.*
|
|
import korlibs.io.lang.*
|
|
import korlibs.time.*
|
|
import kotlinx.serialization.Serializable
|
|
|
|
class Battle(val initializer: HistoryEntry.Load = HistoryEntry.Load.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<HistoryEntry>().also { it.add(initializer) }
|
|
/** 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> get() = _battlers.map { it.asReadonly }
|
|
val pendingEffects: List<BattleEffect> get() = _pendingEffects.toList()
|
|
val history: List<HistoryEntry> get() = _history.toList()
|
|
val pendingActions: List<BattleAction> get() = _pendingActions.toList()
|
|
val pastHistory: List<BattleAction> get() = _pastHistory.toList()
|
|
|
|
private val eventAPI: EventAPI by lazy { EventAPI(this) }
|
|
|
|
constructor(initializer: HistoryEntry.Load, actions: List<BattleAction>, timestamp: TimeSpan? = null):
|
|
this(initializer) {
|
|
injectActions(actions)
|
|
if (timestamp != null) {
|
|
setTimeTo(timestamp)
|
|
}
|
|
}
|
|
|
|
class EventAPI(private val battle: Battle) {
|
|
val battlers: List<Battler.Readonly> get() = battle.battlers
|
|
|
|
fun addBattler(battler: Battler.Saved) {
|
|
if (battle._battlers.any { it.id == battler.id }) {
|
|
throw IllegalArgumentException("Can't add a battler who's already in the fight!")
|
|
}
|
|
battle._battlers.add(Battler(saved = battler, at = battle.currentTime))
|
|
}
|
|
|
|
fun removeBattler(id: Battler.Id) {
|
|
battle._battlers.fastIterateRemove { it.id == id }
|
|
}
|
|
|
|
fun battlers(block: Battler.EventAPI.() -> Unit) {
|
|
battle._battlers.forEach { it.updateAt(battle.currentTime, block) }
|
|
}
|
|
|
|
fun battler(id: Battler.Id): Battler.Readonly? = battle._battlers.find { it.id == id }?.asReadonly
|
|
|
|
fun battler(id: Battler.Id, block: Battler.EventAPI.() -> Unit) {
|
|
battle._battlers.find { it.id == id }?.updateAt(battle.currentTime, block)
|
|
}
|
|
|
|
fun Battler.Readonly.update(block: Battler.EventAPI.() -> Unit) {
|
|
battler(id, block)
|
|
}
|
|
|
|
fun Battler.Readonly.remove() {
|
|
removeBattler(id)
|
|
}
|
|
|
|
fun injectEffect(effect: BattleEffect) {
|
|
when {
|
|
effect.timestamp > battle.currentTime -> battle._pendingEffects.add(effect)
|
|
else -> throw InvalidArgumentException(
|
|
"can't inject an effect that would take place now or in the past: $effect")
|
|
}
|
|
}
|
|
|
|
fun injectEffects(effects: Iterable<BattleEffect>) {
|
|
effects.forEach { injectEffect(it) }
|
|
}
|
|
}
|
|
|
|
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 HistoryEntry.Action) {
|
|
_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 HistoryEntry.Action) {
|
|
_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) { with(eventAPI) {
|
|
execute()
|
|
} }
|
|
saveHistoryEntry(event)
|
|
}
|
|
|
|
private fun saveHistoryEntry(entry: TimedEvent) {
|
|
val archivedBattlers = _battlers.map { it.asSaved }
|
|
val archivedEffects = _pendingEffects.toList()
|
|
_history.add(when (entry) {
|
|
is BattleAction -> HistoryEntry.Action(
|
|
action = entry,
|
|
battlers = archivedBattlers,
|
|
pendingEffects = archivedEffects,
|
|
)
|
|
is BattleEffect -> HistoryEntry.Effect(
|
|
effect = entry,
|
|
battlers = archivedBattlers,
|
|
pendingEffects = archivedEffects,
|
|
)
|
|
})
|
|
}
|
|
|
|
private fun applyHistoryEntry(entry: HistoryEntry) {
|
|
currentTime = entry.timestamp
|
|
_pendingEffects.clear()
|
|
_pendingEffects.addAll(entry.pendingEffects)
|
|
_battlers.retainAll { live -> entry.battlers.any { saved -> saved.id == live.id } }
|
|
_battlers.fastForEach {
|
|
live -> live.revertTo(entry.battlers.find { saved -> saved.id == live.id }!!, entry.timestamp) }
|
|
if (_battlers.size < entry.battlers.size) {
|
|
entry.battlers.forEach { saved ->
|
|
if (!_battlers.any { live -> live.id == saved.id }) {
|
|
_battlers.add(Battler(saved, entry.timestamp))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
val asSaved: Saved get() = Saved(
|
|
currentTime = currentTime,
|
|
initializer = initializer,
|
|
history = buildList {
|
|
addAll(pastHistory)
|
|
_history.forEach {
|
|
if (it is HistoryEntry.Action) {
|
|
add(it.action)
|
|
}
|
|
}
|
|
addAll(pendingActions)
|
|
}
|
|
)
|
|
|
|
constructor(saved: Saved):
|
|
this(initializer = saved.initializer, actions = saved.history, timestamp = saved.currentTime)
|
|
|
|
@Serializable
|
|
data class Saved(
|
|
val currentTime: TimeSpan,
|
|
val initializer: HistoryEntry.Load,
|
|
val history: List<BattleAction>,
|
|
)
|
|
}
|
|
|