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

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