Server-driven interaction prompt that displays "E - Talk" when near an interactable NPC. Decodes v4 nearby_interactions from snapshot, stores in GameState, renders via InteractionPrompt UI with fade animation. Extensible interface (get_interaction_target/get_selected_verb) for future radial verb menu (v0.2). - Protocol: decode nearby_interactions array with nested VerbOption structs, entity relationship/observation fields, tick_rate in GameTime - GameState: store/clear nearby_interactions per snapshot - SimBridge: test mode generates v4 format with structured verbs - InteractionPrompt: PanelContainer with fade in/out, polls GameState - Tests: 19 new test cases covering protocol, state, sim bridge, UI, encoding - Fixture assertions updated for v4 protocol version Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
81 lines
3.0 KiB
GDScript
81 lines
3.0 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, paused} 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}]}]
|
|
|
|
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 player entity
|
|
var found_player := false
|
|
for entity in visible_entities:
|
|
if entity.has("entity_id") and entity.entity_id == player_entity_id:
|
|
player_position = Vector2(entity.x, entity.y)
|
|
found_player = true
|
|
break
|
|
if not found_player and visible_entities.size() > 0:
|
|
push_warning("GameState: player entity_id %d not found in %d entities" % [
|
|
player_entity_id, visible_entities.size()])
|
|
|
|
if snapshot.has("tiles"):
|
|
visible_tiles = snapshot.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 = []
|
|
|
|
# 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
|