Files
aetherbound-guild/runtime/p9_8/boss_state.gd
T

178 lines
5.6 KiB
GDScript

class_name P98BossState
extends RefCounted
var revision := 0
var tick := 0
var boss_id := ""
var plan := ""
var terminal := false
var outcome := ""
var cause := ""
var boss_max_hp := 0
var boss_hp := 0
var pane_index := 0
var pane := "projectile"
var phase_two := false
var open_window_until := 0
var next_rotation_tick := 30
var next_boss_action_tick := 12
var sweep_count := 0
var rotation_count := 0
var member_ids: Array[String] = []
var member_professions: Dictionary = {}
var member_max_hp: Dictionary = {}
var member_hp: Dictionary = {}
var member_positions: Dictionary = {}
var member_next_action: Dictionary = {}
var member_action_counts: Dictionary = {}
var event_log: Array[Dictionary] = []
func reset() -> void:
revision = 0
tick = 0
boss_id = ""
plan = ""
terminal = false
outcome = ""
cause = ""
boss_max_hp = 0
boss_hp = 0
pane_index = 0
pane = "projectile"
phase_two = false
open_window_until = 0
next_rotation_tick = 30
next_boss_action_tick = 12
sweep_count = 0
rotation_count = 0
member_ids.clear()
member_professions.clear()
member_max_hp.clear()
member_hp.clear()
member_positions.clear()
member_next_action.clear()
member_action_counts.clear()
event_log.clear()
func snapshot() -> Dictionary:
return {
"revision": revision, "tick": tick, "boss_id": boss_id, "plan": plan,
"terminal": terminal, "outcome": outcome, "cause": cause,
"boss_max_hp": boss_max_hp, "boss_hp": boss_hp,
"pane_index": pane_index, "pane": pane, "phase_two": phase_two,
"open_window_until": open_window_until,
"next_rotation_tick": next_rotation_tick,
"next_boss_action_tick": next_boss_action_tick,
"sweep_count": sweep_count, "rotation_count": rotation_count,
"member_ids": member_ids.duplicate(),
"member_professions": member_professions.duplicate(true),
"member_max_hp": member_max_hp.duplicate(true),
"member_hp": member_hp.duplicate(true),
"member_positions": member_positions.duplicate(true),
"member_next_action": member_next_action.duplicate(true),
"member_action_counts": member_action_counts.duplicate(true),
"event_log": event_log.duplicate(true),
}.duplicate(true)
func restore_snapshot(raw: Dictionary) -> bool:
var required := [
"revision", "tick", "boss_id", "plan", "terminal", "outcome", "cause",
"boss_max_hp", "boss_hp", "pane_index", "pane", "phase_two",
"open_window_until", "next_rotation_tick", "next_boss_action_tick",
"sweep_count", "rotation_count", "member_ids", "member_professions",
"member_max_hp", "member_hp", "member_positions", "member_next_action",
"member_action_counts", "event_log",
]
for key in required:
if not raw.has(key):
return false
for key in ["member_ids", "event_log"]:
if not raw[key] is Array:
return false
for key in ["member_professions", "member_max_hp", "member_hp", "member_positions", "member_next_action", "member_action_counts"]:
if not raw[key] is Dictionary:
return false
var before := snapshot()
_assign(raw)
if not invariants_hold():
_assign(before)
return false
return true
func living_member_ids() -> Array[String]:
var result: Array[String] = []
for member_id in member_ids:
if int(member_hp.get(member_id, 0)) > 0:
result.append(member_id)
return result
func invariants_hold() -> bool:
if revision < 0 or tick < 0 or boss_id != "BO-R1-01" or plan not in ["rotate", "brace"]:
return false
if boss_max_hp <= 0 or boss_hp < 0 or boss_hp > boss_max_hp:
return false
if pane_index < 0 or pane_index > 2 or pane not in ["projectile", "spell", "forced_movement"]:
return false
if member_ids.is_empty() or not _unique(member_ids):
return false
for member_id in member_ids:
if not member_professions.has(member_id) or not member_max_hp.has(member_id) or not member_positions.has(member_id) or not member_next_action.has(member_id) or not member_action_counts.has(member_id):
return false
var hp := int(member_hp.get(member_id, -1))
if hp < 0 or hp > int(member_max_hp[member_id]):
return false
if terminal and outcome not in ["victory", "defeat", "timeout"]:
return false
if not terminal and (not outcome.is_empty() or not cause.is_empty()):
return false
if outcome == "victory" and boss_hp != 0:
return false
if outcome == "defeat" and not living_member_ids().is_empty():
return false
for index in event_log.size():
if not event_log[index] is Dictionary or int(event_log[index].get("index", -1)) != index:
return false
return true
func _assign(raw: Dictionary) -> void:
revision = int(raw.revision)
tick = int(raw.tick)
boss_id = String(raw.boss_id)
plan = String(raw.plan)
terminal = bool(raw.terminal)
outcome = String(raw.outcome)
cause = String(raw.cause)
boss_max_hp = int(raw.boss_max_hp)
boss_hp = int(raw.boss_hp)
pane_index = int(raw.pane_index)
pane = String(raw.pane)
phase_two = bool(raw.phase_two)
open_window_until = int(raw.open_window_until)
next_rotation_tick = int(raw.next_rotation_tick)
next_boss_action_tick = int(raw.next_boss_action_tick)
sweep_count = int(raw.sweep_count)
rotation_count = int(raw.rotation_count)
member_ids.assign(raw.member_ids)
member_professions = Dictionary(raw.member_professions).duplicate(true)
member_max_hp = Dictionary(raw.member_max_hp).duplicate(true)
member_hp = Dictionary(raw.member_hp).duplicate(true)
member_positions = Dictionary(raw.member_positions).duplicate(true)
member_next_action = Dictionary(raw.member_next_action).duplicate(true)
member_action_counts = Dictionary(raw.member_action_counts).duplicate(true)
event_log.assign(raw.event_log)
func _unique(values: Array) -> bool:
var seen := {}
for value in values:
if seen.has(value):
return false
seen[value] = true
return true