Files
settled-reach/client/scripts/autoloads/game_state.gd
T
jpmschweitzerandClaude Opus 4.6 bfc699c15d feat(ui): implement dialogue pipeline — selection, walk-away, confrontation
Protocol: decode current_dialogue with structured options {text,
response_id, priority, confrontation} and npc_entity_id (#435).
Dialogue box: priority sort, max 3 visible, RichTextLabel for BBCode
italic confrontation options (D-063), 1.5s monologue beat with audio
dip before confrontation send. Walk-away: WASD triggers WalkAway
input, 300ms fade, dialogue_active flag gates movement (D-064).
Implements #435, #437, #436.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 16:34:55 +01:00

136 lines
5.3 KiB
GDScript

extends Node
# Updated each frame from ObserverSnapshot data (Protocol format: {tick, entities, tiles}).
# Entities use Protocol decoded format: {entity_id, x, y, z, kind: {variant, data}}.
# Tiles use format: [{x, y, z, type}].
var current_snapshot: Dictionary = {}
var current_tick: int = 0
var player_position: Vector2 = Vector2.ZERO
var visible_entities: Array = []
var visible_tiles: Array = []
var visible_positions: Dictionary = {} # Vector2i -> true, for fast fog lookups
# v2 fields (D-015, D-031)
var game_time: Dictionary = {} # {day, time_of_day, day_phase, tick_rate} or empty
var player_facing: String = "North" # 8-directional facing direction
var visibility_sectors: Dictionary = {} # Vector2i -> "Forward"/"Peripheral"
# Player entity ID — the first entity is assumed to be the player (will be
# refined when the server assigns explicit player entity IDs).
var player_entity_id: int = 1
# v4 fields (#404/#405)
var nearby_interactions: Array = [] # [{entity_id, entity_type, distance, verbs: [{kind, label, priority, available}]}]
# v5 fields (#414)
var current_monologue: Variant = null # {id, text, duration_seconds} or null
# v6 fields (#449, D-053, D-065)
var player_stance: String = "Walk" # Sprint/Walk/Careful/Crouch
var player_inventory: Array = [] # [{item_id, name, slot}]
# v7 fields (#435, D-061/D-062)
var current_dialogue: Variant = null # {npc_name, npc_entity_id, speech, options: [{text, response_id, priority}]} or null
# D-064: true while dialogue box is visible or fading out (300ms).
# InputMapper suppresses movement when this is true.
var dialogue_active: bool = false
# v7 fields (#431, D-059/D-060)
var pending_recognitions: Array = [] # [{entity_id, x, y, z, remaining_ticks, total_delay_ticks}]
func apply_snapshot(snapshot: Dictionary) -> void:
current_snapshot = snapshot
if snapshot.has("tick"):
current_tick = snapshot.tick
if snapshot.has("entities"):
visible_entities = snapshot.entities
# Derive player position from the entity with kind.variant == "Player"
var found_player := false
for entity in visible_entities:
if entity.has("kind") and entity.kind is Dictionary and entity.kind.get("variant") == "Player":
player_position = Vector2(entity.x, entity.y)
if entity.has("entity_id"):
player_entity_id = entity.entity_id
found_player = true
break
if not found_player and visible_entities.size() > 0:
push_warning("GameState: no Player entity found in %d entities" % [
visible_entities.size()])
# Tiles for rendering: test mode sends "tiles", live server sends tile data in "visible_tiles"
if snapshot.has("tiles"):
visible_tiles = snapshot.tiles
elif snapshot.has("visible_tiles") and snapshot.visible_tiles is Array and snapshot.visible_tiles.size() > 0:
# Live server: visible_tiles now includes type from tile_kind field
var has_type := false
if snapshot.visible_tiles.size() > 0 and snapshot.visible_tiles[0] is Dictionary:
has_type = snapshot.visible_tiles[0].has("type")
if has_type:
visible_tiles = snapshot.visible_tiles
if snapshot.has("visible_positions"):
visible_positions.clear()
for pos in snapshot.visible_positions:
visible_positions[Vector2i(pos.x, pos.y)] = true
# v2: game_time (D-031)
if snapshot.has("game_time") and snapshot.game_time is Dictionary:
game_time = snapshot.game_time
# v2: player_facing (D-015)
if snapshot.has("player_facing") and snapshot.player_facing is String:
player_facing = snapshot.player_facing
# v4: nearby_interactions (#404/#405)
if snapshot.has("nearby_interactions") and snapshot.nearby_interactions is Array:
nearby_interactions = snapshot.nearby_interactions
else:
nearby_interactions = []
# v5: current_monologue (#414)
if snapshot.has("current_monologue") and snapshot.current_monologue is Dictionary:
current_monologue = snapshot.current_monologue
else:
current_monologue = null
# v6: player_stance (#449, D-053)
if snapshot.has("player_stance") and snapshot.player_stance is String:
player_stance = snapshot.player_stance
# v6: player_inventory (#449, D-065)
if snapshot.has("player_inventory") and snapshot.player_inventory is Array:
player_inventory = snapshot.player_inventory
else:
player_inventory = []
# v7: current_dialogue (#434, D-061)
if snapshot.has("current_dialogue") and snapshot.current_dialogue is Dictionary:
current_dialogue = snapshot.current_dialogue
else:
current_dialogue = null
# v7: pending_recognitions (#431, D-059/D-060)
if snapshot.has("pending_recognitions") and snapshot.pending_recognitions is Array:
pending_recognitions = snapshot.pending_recognitions
else:
pending_recognitions = []
# v2: visible_tiles with visibility sectors
# Derives visible_positions when not explicitly provided (real server mode)
if snapshot.has("visible_tiles") and snapshot.visible_tiles is Array and snapshot.visible_tiles.size() > 0:
visibility_sectors.clear()
var has_explicit_positions := snapshot.has("visible_positions")
if not has_explicit_positions:
visible_positions.clear()
for vtile in snapshot.visible_tiles:
if not vtile is Dictionary or not vtile.has("x") or not vtile.has("y"):
continue
var pos := Vector2i(vtile.x, vtile.y)
if vtile.has("visibility"):
visibility_sectors[pos] = vtile.visibility
if not has_explicit_positions:
visible_positions[pos] = true