178 lines
5.6 KiB
GDScript
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
|