|
|
@ -1,9 +1,12 @@ |
|
|
|
package model |
|
|
|
package model |
|
|
|
|
|
|
|
|
|
|
|
import korlibs.datastructure.* |
|
|
|
import korlibs.datastructure.* |
|
|
|
|
|
|
|
import korlibs.datastructure.iterators.* |
|
|
|
|
|
|
|
import korlibs.io.lang.* |
|
|
|
import korlibs.time.* |
|
|
|
import korlibs.time.* |
|
|
|
|
|
|
|
import kotlinx.serialization.Serializable |
|
|
|
|
|
|
|
|
|
|
|
class Battle(initializer: LoadAction = LoadAction.ZERO) { |
|
|
|
class Battle(val initializer: HistoryEntry.Load = HistoryEntry.Load.ZERO) { |
|
|
|
private val _battlers: MutableList<Battler> = |
|
|
|
private val _battlers: MutableList<Battler> = |
|
|
|
initializer.battlers.map { Battler(it, initializer.timestamp) }.toMutableList() |
|
|
|
initializer.battlers.map { Battler(it, initializer.timestamp) }.toMutableList() |
|
|
|
private val _pendingEffects: PriorityQueue<BattleEffect> = |
|
|
|
private val _pendingEffects: PriorityQueue<BattleEffect> = |
|
|
@ -12,37 +15,70 @@ class Battle(initializer: LoadAction = LoadAction.ZERO) { |
|
|
|
private set |
|
|
|
private set |
|
|
|
|
|
|
|
|
|
|
|
/** Full history entries needed for rewinding. Stored from oldest to newest. */ |
|
|
|
/** Full history entries needed for rewinding. Stored from oldest to newest. */ |
|
|
|
private val _history: Deque<HistoryEntry> = Deque() |
|
|
|
private val _history: Deque<HistoryEntry> = Deque<HistoryEntry>().also { it.add(initializer) } |
|
|
|
/** Actions which will be executed at a later timestamp. */ |
|
|
|
/** Actions which will be executed at a later timestamp. */ |
|
|
|
private val _pendingActions: PriorityQueue<BattleAction> = PriorityQueue() |
|
|
|
private val _pendingActions: PriorityQueue<BattleAction> = PriorityQueue() |
|
|
|
/** Abbreviated history entries used only for saving replay data. Stored from oldest to newest. */ |
|
|
|
/** Abbreviated history entries used only for saving replay data. Stored from oldest to newest. */ |
|
|
|
private val _pastHistory: Queue<BattleAction> = Queue() |
|
|
|
private val _pastHistory: Queue<BattleAction> = Queue() |
|
|
|
|
|
|
|
|
|
|
|
val battlers: List<Battler.Readonly> = _battlers.map { it.asReadonly } |
|
|
|
val battlers: List<Battler.Readonly> get() = _battlers.map { it.asReadonly } |
|
|
|
val pendingEffects: List<BattleEffect> = _pendingEffects.toList() |
|
|
|
val pendingEffects: List<BattleEffect> get() = _pendingEffects.toList() |
|
|
|
val history: List<HistoryEntry> = _history.toList() |
|
|
|
val history: List<HistoryEntry> get() = _history.toList() |
|
|
|
val pendingActions: List<BattleAction> = _pendingActions.toList() |
|
|
|
val pendingActions: List<BattleAction> get() = _pendingActions.toList() |
|
|
|
val pastHistory: List<BattleAction> = _pastHistory.toList() |
|
|
|
val pastHistory: List<BattleAction> get() = _pastHistory.toList() |
|
|
|
|
|
|
|
|
|
|
|
init { |
|
|
|
private val eventAPI: EventAPI by lazy { EventAPI(this) } |
|
|
|
saveHistoryEntry(initializer) |
|
|
|
|
|
|
|
|
|
|
|
constructor(initializer: HistoryEntry.Load, actions: List<BattleAction>, timestamp: TimeSpan? = null): |
|
|
|
|
|
|
|
this(initializer) { |
|
|
|
|
|
|
|
injectActions(actions) |
|
|
|
|
|
|
|
if (timestamp != null) { |
|
|
|
|
|
|
|
setTimeTo(timestamp) |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
constructor(actions: List<BattleAction>, timestamp: TimeSpan? = null): this( |
|
|
|
class EventAPI(private val battle: Battle) { |
|
|
|
actions.firstOrNull().run { |
|
|
|
val battlers: List<Battler.Readonly> get() = battle.battlers |
|
|
|
when (this) { |
|
|
|
|
|
|
|
is LoadAction -> this |
|
|
|
fun addBattler(battler: Battler.Saved) { |
|
|
|
else -> LoadAction.ZERO |
|
|
|
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)) |
|
|
|
} |
|
|
|
} |
|
|
|
) { |
|
|
|
|
|
|
|
if (actions.firstOrNull() is LoadAction) { |
|
|
|
fun removeBattler(id: Battler.Id) { |
|
|
|
injectActions(actions.drop(1)) |
|
|
|
battle._battlers.fastIterateRemove { it.id == id } |
|
|
|
} else { |
|
|
|
|
|
|
|
injectActions(actions) |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
if (timestamp != null) { |
|
|
|
|
|
|
|
setTimeTo(timestamp) |
|
|
|
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) } |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -67,7 +103,7 @@ class Battle(initializer: LoadAction = LoadAction.ZERO) { |
|
|
|
fun abbreviateHistoryUpTo(timestamp: TimeSpan) { |
|
|
|
fun abbreviateHistoryUpTo(timestamp: TimeSpan) { |
|
|
|
while (_history.isNotEmpty() && _history.first.timestamp <= timestamp) { |
|
|
|
while (_history.isNotEmpty() && _history.first.timestamp <= timestamp) { |
|
|
|
val previous = _history.removeFirst() |
|
|
|
val previous = _history.removeFirst() |
|
|
|
if (previous is ActionHistoryEntry) { |
|
|
|
if (previous is HistoryEntry.Action) { |
|
|
|
_pastHistory.enqueue(previous.action) |
|
|
|
_pastHistory.enqueue(previous.action) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
@ -97,7 +133,7 @@ class Battle(initializer: LoadAction = LoadAction.ZERO) { |
|
|
|
|
|
|
|
|
|
|
|
while (_history.isNotEmpty() && _history.last.timestamp > timestamp) { |
|
|
|
while (_history.isNotEmpty() && _history.last.timestamp > timestamp) { |
|
|
|
val previous = _history.removeLast() |
|
|
|
val previous = _history.removeLast() |
|
|
|
if (previous is ActionHistoryEntry) { |
|
|
|
if (previous is HistoryEntry.Action) { |
|
|
|
_pendingActions.add(previous.action) |
|
|
|
_pendingActions.add(previous.action) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
@ -153,7 +189,9 @@ class Battle(initializer: LoadAction = LoadAction.ZERO) { |
|
|
|
|
|
|
|
|
|
|
|
private fun applyEvent(event: TimedEvent) { |
|
|
|
private fun applyEvent(event: TimedEvent) { |
|
|
|
advanceTimeTo(event.timestamp) |
|
|
|
advanceTimeTo(event.timestamp) |
|
|
|
with(event) { execute() } |
|
|
|
with(event) { with(eventAPI) { |
|
|
|
|
|
|
|
execute() |
|
|
|
|
|
|
|
} } |
|
|
|
saveHistoryEntry(event) |
|
|
|
saveHistoryEntry(event) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -161,17 +199,12 @@ class Battle(initializer: LoadAction = LoadAction.ZERO) { |
|
|
|
val archivedBattlers = _battlers.map { it.asSaved } |
|
|
|
val archivedBattlers = _battlers.map { it.asSaved } |
|
|
|
val archivedEffects = _pendingEffects.toList() |
|
|
|
val archivedEffects = _pendingEffects.toList() |
|
|
|
_history.add(when (entry) { |
|
|
|
_history.add(when (entry) { |
|
|
|
is LoadAction -> ActionHistoryEntry( |
|
|
|
is BattleAction -> HistoryEntry.Action( |
|
|
|
action = entry, |
|
|
|
|
|
|
|
battlers = entry.battlers, |
|
|
|
|
|
|
|
pendingEffects = entry.pendingEffects, |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
is BattleAction -> ActionHistoryEntry( |
|
|
|
|
|
|
|
action = entry, |
|
|
|
action = entry, |
|
|
|
battlers = archivedBattlers, |
|
|
|
battlers = archivedBattlers, |
|
|
|
pendingEffects = archivedEffects, |
|
|
|
pendingEffects = archivedEffects, |
|
|
|
) |
|
|
|
) |
|
|
|
is BattleEffect -> EffectHistoryEntry( |
|
|
|
is BattleEffect -> HistoryEntry.Effect( |
|
|
|
effect = entry, |
|
|
|
effect = entry, |
|
|
|
battlers = archivedBattlers, |
|
|
|
battlers = archivedBattlers, |
|
|
|
pendingEffects = archivedEffects, |
|
|
|
pendingEffects = archivedEffects, |
|
|
@ -183,7 +216,39 @@ class Battle(initializer: LoadAction = LoadAction.ZERO) { |
|
|
|
currentTime = entry.timestamp |
|
|
|
currentTime = entry.timestamp |
|
|
|
_pendingEffects.clear() |
|
|
|
_pendingEffects.clear() |
|
|
|
_pendingEffects.addAll(entry.pendingEffects) |
|
|
|
_pendingEffects.addAll(entry.pendingEffects) |
|
|
|
_battlers.clear() |
|
|
|
_battlers.retainAll { live -> entry.battlers.any { saved -> saved.id == live.id } } |
|
|
|
_battlers.addAll(entry.battlers.map { Battler(it, entry.timestamp) }) |
|
|
|
_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>, |
|
|
|
|
|
|
|
) |
|
|
|
} |
|
|
|
} |
|
|
|