class_name ClientView extends RefCounted ## The client-side mirror of server state. Presentation code (HUD, party ## frames, boss bar) reads from this; it never simulates rules itself. ## The transport layer feeds every incoming server message to apply_message(). var fight_phase: int = -1 var end_reason: String = "" var fight_time: float = 0.0 var boss_hp: float = 0.0 var boss_max_hp: float = 0.0 var boss_phase: int = 1 var enrage_timer: float = -1.0 # <= 0 means no hard enrage (soft-only fights) var soft_enrage_active: bool = false ## id -> latest snapshot entry for that player. var players: Dictionary = {} ## The most recent non-snapshot message (events). Presentation consumes this ## to trigger VFX/audio/telegraphs, then calls clear_last_event(). var last_event: Dictionary = {} func apply_message(msg: Dictionary) -> void: var msg_type := str(msg.get("type", "")) if msg_type == "snapshot": _apply_snapshot(msg) elif msg_type == "fight_ended": fight_phase = RaidState.FightPhase.WIPED if str(msg.get("result")) == "wiped" \ else RaidState.FightPhase.COMPLETE end_reason = str(msg.get("reason", "")) else: last_event = msg func clear_last_event() -> void: last_event = {} func _apply_snapshot(msg: Dictionary) -> void: var f: Dictionary = msg.get("fight", {}) fight_phase = int(f.get("phase", -1)) fight_time = float(f.get("time", 0.0)) boss_hp = float(f.get("boss_hp", 0.0)) boss_max_hp = float(f.get("boss_max_hp", 0.0)) boss_phase = int(f.get("boss_phase", 1)) enrage_timer = float(f.get("enrage_timer", -1.0)) soft_enrage_active = bool(f.get("soft_enrage", false)) players.clear() for entry in msg.get("players", []): if typeof(entry) == TYPE_DICTIONARY: players[int((entry as Dictionary).get("id", -1))] = entry ## Returns the latest snapshot data for a player, or {} if unknown. func get_player(player_id: int) -> Dictionary: var entry = players.get(player_id, {}) return entry if typeof(entry) == TYPE_DICTIONARY else {}