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.
189 lines
6.4 KiB
189 lines
6.4 KiB
package model
|
|
|
|
import korlibs.datastructure.*
|
|
import korlibs.time.*
|
|
|
|
class Battle(initializer: LoadAction = LoadAction.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()
|
|
/** 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> = _battlers.map { it.asReadonly }
|
|
val pendingEffects: List<BattleEffect> = _pendingEffects.toList()
|
|
val history: List<HistoryEntry> = _history.toList()
|
|
val pendingActions: List<BattleAction> = _pendingActions.toList()
|
|
val pastHistory: List<BattleAction> = _pastHistory.toList()
|
|
|
|
init {
|
|
saveHistoryEntry(initializer)
|
|
}
|
|
|
|
constructor(actions: List<BattleAction>, timestamp: TimeSpan? = null): this(
|
|
actions.firstOrNull().run {
|
|
when (this) {
|
|
is LoadAction -> this
|
|
else -> LoadAction.ZERO
|
|
}
|
|
}
|
|
) {
|
|
if (actions.firstOrNull() is LoadAction) {
|
|
injectActions(actions.drop(1))
|
|
} else {
|
|
injectActions(actions)
|
|
}
|
|
if (timestamp != null) {
|
|
setTimeTo(timestamp)
|
|
}
|
|
}
|
|
|
|
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 ActionHistoryEntry) {
|
|
_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 ActionHistoryEntry) {
|
|
_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) { execute() }
|
|
saveHistoryEntry(event)
|
|
}
|
|
|
|
private fun saveHistoryEntry(entry: TimedEvent) {
|
|
val archivedBattlers = _battlers.map { it.asSaved }
|
|
val archivedEffects = _pendingEffects.toList()
|
|
_history.add(when (entry) {
|
|
is LoadAction -> ActionHistoryEntry(
|
|
action = entry,
|
|
battlers = entry.battlers,
|
|
pendingEffects = entry.pendingEffects,
|
|
)
|
|
is BattleAction -> ActionHistoryEntry(
|
|
action = entry,
|
|
battlers = archivedBattlers,
|
|
pendingEffects = archivedEffects,
|
|
)
|
|
is BattleEffect -> EffectHistoryEntry(
|
|
effect = entry,
|
|
battlers = archivedBattlers,
|
|
pendingEffects = archivedEffects,
|
|
)
|
|
})
|
|
}
|
|
|
|
private fun applyHistoryEntry(entry: HistoryEntry) {
|
|
currentTime = entry.timestamp
|
|
_pendingEffects.clear()
|
|
_pendingEffects.addAll(entry.pendingEffects)
|
|
_battlers.clear()
|
|
_battlers.addAll(entry.battlers.map { Battler(it, entry.timestamp) })
|
|
}
|
|
}
|
|
|