style(client): gdformat all 52 GDScript files — zero format warnings
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -86,15 +86,23 @@ func _try_extract_message() -> PackedByteArray:
|
||||
if _pending_length < 0:
|
||||
if _read_buffer.size() < 4:
|
||||
return PackedByteArray()
|
||||
_pending_length = (_read_buffer[0] << 24) | (_read_buffer[1] << 16) | \
|
||||
(_read_buffer[2] << 8) | _read_buffer[3]
|
||||
_pending_length = (
|
||||
(_read_buffer[0] << 24)
|
||||
| (_read_buffer[1] << 16)
|
||||
| (_read_buffer[2] << 8)
|
||||
| _read_buffer[3]
|
||||
)
|
||||
_read_buffer = _read_buffer.slice(4)
|
||||
|
||||
if _pending_length > MAX_MESSAGE_SIZE:
|
||||
# Stream is corrupt — we can't find the next valid frame boundary.
|
||||
# Disconnect rather than silently discarding valid buffered data.
|
||||
push_error(
|
||||
"LocalBridge: incoming message too large: %d bytes (max %d) — disconnecting" % [_pending_length, MAX_MESSAGE_SIZE])
|
||||
(
|
||||
"LocalBridge: incoming message too large: %d bytes (max %d) — disconnecting"
|
||||
% [_pending_length, MAX_MESSAGE_SIZE]
|
||||
)
|
||||
)
|
||||
_corrupt = true
|
||||
_pending_length = -1
|
||||
_read_buffer.clear()
|
||||
@@ -129,6 +137,7 @@ func reset() -> void:
|
||||
# These exist for unit tests that verify framing logic without a live TCP
|
||||
# connection. Not used in production code paths.
|
||||
|
||||
|
||||
## Encode a payload into a framed byte array: [4-byte BE length][payload].
|
||||
static func frame_encode(payload: PackedByteArray) -> PackedByteArray:
|
||||
var len := payload.size()
|
||||
|
||||
@@ -14,9 +14,9 @@ extends Node
|
||||
## v20: adds settings_response field to ObserverSnapshot (#627, D-138).
|
||||
const PROTOCOL_VERSION: int = 20
|
||||
|
||||
|
||||
# -- Decode: bytes from server → GDScript types --------------------------------
|
||||
|
||||
|
||||
## Decode an ObserverSnapshot from MessagePack bytes.
|
||||
## Returns decoded snapshot Dictionary or null on error.
|
||||
## v2 fields (version, game_time, player_facing, visible_tiles) default to null/empty
|
||||
@@ -36,7 +36,11 @@ static func decode_snapshot(bytes: PackedByteArray) -> Variant:
|
||||
var version: Variant = raw.get("version")
|
||||
if version != PROTOCOL_VERSION:
|
||||
push_error(
|
||||
"Protocol: version mismatch (got %s, expected %s). Server and client are out of sync." % [version, PROTOCOL_VERSION])
|
||||
(
|
||||
"Protocol: version mismatch (got %s, expected %s). Server and client are out of sync."
|
||||
% [version, PROTOCOL_VERSION]
|
||||
)
|
||||
)
|
||||
return null
|
||||
|
||||
var entities: Array[Dictionary] = []
|
||||
@@ -51,7 +55,11 @@ static func decode_snapshot(bytes: PackedByteArray) -> Variant:
|
||||
|
||||
if dropped > 0:
|
||||
push_error(
|
||||
"Protocol: %d/%d entities failed to decode (D-010 information boundary violation)" % [dropped, raw_entities.size()])
|
||||
(
|
||||
"Protocol: %d/%d entities failed to decode (D-010 information boundary violation)"
|
||||
% [dropped, raw_entities.size()]
|
||||
)
|
||||
)
|
||||
|
||||
# GDScript int is signed 64-bit. Rust tick is u64 but will not exceed 2^63
|
||||
# in any realistic scenario (would require ~29 billion years at 10 ticks/game-minute per D-031).
|
||||
@@ -71,7 +79,12 @@ static func decode_snapshot(bytes: PackedByteArray) -> Variant:
|
||||
var raw_vtiles: Variant = raw.get("visible_tiles")
|
||||
if raw_vtiles is Array:
|
||||
for raw_tile in raw_vtiles:
|
||||
if raw_tile is Dictionary and raw_tile.has("x") and raw_tile.has("y") and raw_tile.has("z"):
|
||||
if (
|
||||
raw_tile is Dictionary
|
||||
and raw_tile.has("x")
|
||||
and raw_tile.has("y")
|
||||
and raw_tile.has("z")
|
||||
):
|
||||
var tile_entry := {
|
||||
"x": int(raw_tile["x"]),
|
||||
"y": int(raw_tile["y"]),
|
||||
@@ -119,11 +132,16 @@ static func decode_snapshot(bytes: PackedByteArray) -> Variant:
|
||||
if raw_inventory is Array:
|
||||
for raw_item in raw_inventory:
|
||||
if raw_item is Dictionary and raw_item.has("item_id") and raw_item.has("name"):
|
||||
player_inventory.append({
|
||||
"item_id": int(raw_item["item_id"]),
|
||||
"name": str(raw_item["name"]),
|
||||
"slot": int(raw_item.get("slot", 0)),
|
||||
})
|
||||
(
|
||||
player_inventory
|
||||
. append(
|
||||
{
|
||||
"item_id": int(raw_item["item_id"]),
|
||||
"name": str(raw_item["name"]),
|
||||
"slot": int(raw_item.get("slot", 0)),
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
# v7: pending_recognitions (#431, D-059/D-060) — cognitive delay fog entities
|
||||
# Bounded: server sends at most ~50 pending recognitions per snapshot (practical limit
|
||||
@@ -135,17 +153,32 @@ static func decode_snapshot(bytes: PackedByteArray) -> Variant:
|
||||
var count := 0
|
||||
for raw_pr in raw_recognitions:
|
||||
if count >= MAX_PENDING_RECOGNITIONS:
|
||||
push_warning("Protocol: pending_recognitions truncated at %d entries" % MAX_PENDING_RECOGNITIONS)
|
||||
push_warning(
|
||||
(
|
||||
"Protocol: pending_recognitions truncated at %d entries"
|
||||
% MAX_PENDING_RECOGNITIONS
|
||||
)
|
||||
)
|
||||
break
|
||||
if raw_pr is Dictionary and raw_pr.has("entity_id") and raw_pr.has("x") and raw_pr.has("y"):
|
||||
pending_recognitions.append({
|
||||
"entity_id": int(raw_pr["entity_id"]),
|
||||
"x": float(raw_pr["x"]),
|
||||
"y": float(raw_pr["y"]),
|
||||
"z": int(raw_pr.get("z", 0)),
|
||||
"remaining_ticks": int(raw_pr.get("remaining_ticks", 0)),
|
||||
"total_delay_ticks": int(raw_pr.get("total_delay_ticks", 1)),
|
||||
})
|
||||
if (
|
||||
raw_pr is Dictionary
|
||||
and raw_pr.has("entity_id")
|
||||
and raw_pr.has("x")
|
||||
and raw_pr.has("y")
|
||||
):
|
||||
(
|
||||
pending_recognitions
|
||||
. append(
|
||||
{
|
||||
"entity_id": int(raw_pr["entity_id"]),
|
||||
"x": float(raw_pr["x"]),
|
||||
"y": float(raw_pr["y"]),
|
||||
"z": int(raw_pr.get("z", 0)),
|
||||
"remaining_ticks": int(raw_pr.get("remaining_ticks", 0)),
|
||||
"total_delay_ticks": int(raw_pr.get("total_delay_ticks", 1)),
|
||||
}
|
||||
)
|
||||
)
|
||||
count += 1
|
||||
|
||||
# v7: current_dialogue (#435, D-061/D-062) — NPC speech + player response options
|
||||
@@ -159,12 +192,17 @@ static func decode_snapshot(bytes: PackedByteArray) -> Variant:
|
||||
if raw_options is Array:
|
||||
for raw_opt in raw_options:
|
||||
if raw_opt is Dictionary and raw_opt.has("text"):
|
||||
dialogue_options.append({
|
||||
"text": str(raw_opt["text"]),
|
||||
"response_id": str(raw_opt.get("response_id", "")),
|
||||
"priority": int(raw_opt.get("priority", 0)),
|
||||
"confrontation": bool(raw_opt.get("confrontation", false)),
|
||||
})
|
||||
(
|
||||
dialogue_options
|
||||
. append(
|
||||
{
|
||||
"text": str(raw_opt["text"]),
|
||||
"response_id": str(raw_opt.get("response_id", "")),
|
||||
"priority": int(raw_opt.get("priority", 0)),
|
||||
"confrontation": bool(raw_opt.get("confrontation", false)),
|
||||
}
|
||||
)
|
||||
)
|
||||
current_dialogue = {
|
||||
"npc_name": str(raw_dialogue.get("npc_name", "")),
|
||||
"npc_entity_id": int(raw_dialogue.get("npc_entity_id", -1)),
|
||||
@@ -190,13 +228,18 @@ static func decode_snapshot(bytes: PackedByteArray) -> Variant:
|
||||
if raw_conv_events is Array:
|
||||
for raw_ce in raw_conv_events:
|
||||
if raw_ce is Dictionary and raw_ce.has("occluded_line"):
|
||||
conversation_events.append({
|
||||
"speaker_id": int(raw_ce.get("speaker_id", 0)),
|
||||
"target_id": int(raw_ce.get("target_id", 0)),
|
||||
"speaker_name": str(raw_ce.get("speaker_name", "")),
|
||||
"target_name": str(raw_ce.get("target_name", "")),
|
||||
"occluded_line": str(raw_ce["occluded_line"]),
|
||||
})
|
||||
(
|
||||
conversation_events
|
||||
. append(
|
||||
{
|
||||
"speaker_id": int(raw_ce.get("speaker_id", 0)),
|
||||
"target_id": int(raw_ce.get("target_id", 0)),
|
||||
"speaker_name": str(raw_ce.get("speaker_name", "")),
|
||||
"target_name": str(raw_ce.get("target_name", "")),
|
||||
"occluded_line": str(raw_ce["occluded_line"]),
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
# v9: conversation_ended (#535, D-078) — pairs whose conversation ended this tick.
|
||||
var conversation_ended: Array = []
|
||||
@@ -204,10 +247,15 @@ static func decode_snapshot(bytes: PackedByteArray) -> Variant:
|
||||
if raw_conv_ended is Array:
|
||||
for raw_end in raw_conv_ended:
|
||||
if raw_end is Dictionary:
|
||||
conversation_ended.append({
|
||||
"speaker_id": int(raw_end.get("speaker_id", 0)),
|
||||
"target_id": int(raw_end.get("target_id", 0)),
|
||||
})
|
||||
(
|
||||
conversation_ended
|
||||
. append(
|
||||
{
|
||||
"speaker_id": int(raw_end.get("speaker_id", 0)),
|
||||
"target_id": int(raw_end.get("target_id", 0)),
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
# v14: poi_list (#151) — discovered POIs for minimap rendering.
|
||||
# Each entry: {poi_id, name, x, y, z, poi_category}. Positions in sim tile coords.
|
||||
@@ -215,15 +263,26 @@ static func decode_snapshot(bytes: PackedByteArray) -> Variant:
|
||||
var raw_pois: Variant = raw.get("poi_list")
|
||||
if raw_pois is Array:
|
||||
for raw_poi in raw_pois:
|
||||
if raw_poi is Dictionary and raw_poi.has("poi_id") and raw_poi.has("x") and raw_poi.has("y"):
|
||||
poi_list.append({
|
||||
"poi_id": str(raw_poi["poi_id"]),
|
||||
"name": str(raw_poi.get("name", "")),
|
||||
"x": int(raw_poi["x"]),
|
||||
"y": int(raw_poi["y"]),
|
||||
"z": int(raw_poi.get("z", 0)),
|
||||
"poi_category": str(raw_poi.get("poi_category", raw_poi.get("category", "Location"))),
|
||||
})
|
||||
if (
|
||||
raw_poi is Dictionary
|
||||
and raw_poi.has("poi_id")
|
||||
and raw_poi.has("x")
|
||||
and raw_poi.has("y")
|
||||
):
|
||||
(
|
||||
poi_list
|
||||
. append(
|
||||
{
|
||||
"poi_id": str(raw_poi["poi_id"]),
|
||||
"name": str(raw_poi.get("name", "")),
|
||||
"x": int(raw_poi["x"]),
|
||||
"y": int(raw_poi["y"]),
|
||||
"z": int(raw_poi.get("z", 0)),
|
||||
"poi_category":
|
||||
str(raw_poi.get("poi_category", raw_poi.get("category", "Location"))),
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
# v14: examine_result (#174, #242) — character-filtered observation text.
|
||||
# {entity_id, text, confidence} or null. Auto-dismisses on client after 4-6 seconds.
|
||||
@@ -268,9 +327,14 @@ static func decode_snapshot(bytes: PackedByteArray) -> Variant:
|
||||
if raw_tce is Array:
|
||||
for raw_ev in raw_tce:
|
||||
if raw_ev is Dictionary and raw_ev.has("triangle_id"):
|
||||
triangle_crisis_events.append({
|
||||
"triangle_id": int(raw_ev["triangle_id"]),
|
||||
})
|
||||
(
|
||||
triangle_crisis_events
|
||||
. append(
|
||||
{
|
||||
"triangle_id": int(raw_ev["triangle_id"]),
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
# v20: settings_response (#627, D-138) — server ack after ChangeSettings / full settings dump
|
||||
# after RequestAllSettings. kind = "full"|"ack". "full": settings array [{key, value}].
|
||||
@@ -285,10 +349,15 @@ static func decode_snapshot(bytes: PackedByteArray) -> Variant:
|
||||
if raw_sr_settings is Array:
|
||||
for raw_s in raw_sr_settings:
|
||||
if raw_s is Dictionary and raw_s.has("key"):
|
||||
sr_settings.append({
|
||||
"key": str(raw_s["key"]),
|
||||
"value": raw_s.get("value"),
|
||||
})
|
||||
(
|
||||
sr_settings
|
||||
. append(
|
||||
{
|
||||
"key": str(raw_s["key"]),
|
||||
"value": raw_s.get("value"),
|
||||
}
|
||||
)
|
||||
)
|
||||
settings_response = {"kind": "full", "settings": sr_settings}
|
||||
elif sr_kind == "ack":
|
||||
settings_response = {
|
||||
@@ -338,27 +407,37 @@ static func decode_snapshot(bytes: PackedByteArray) -> Variant:
|
||||
if raw_kg_entities is Array:
|
||||
for raw_ke in raw_kg_entities:
|
||||
if raw_ke is Dictionary and raw_ke.has("entity_id"):
|
||||
kg_entities.append({
|
||||
"entity_id": int(raw_ke["entity_id"]),
|
||||
"name": str(raw_ke.get("name", "Unknown")),
|
||||
"confidence": str(raw_ke.get("confidence", "Suspects")),
|
||||
"source": str(raw_ke.get("source", "")),
|
||||
"state": str(raw_ke.get("state", "Active")),
|
||||
"relationship": str(raw_ke.get("relationship", "Unknown")),
|
||||
"last_observed_tick": int(raw_ke.get("last_observed_tick", 0)),
|
||||
})
|
||||
(
|
||||
kg_entities
|
||||
. append(
|
||||
{
|
||||
"entity_id": int(raw_ke["entity_id"]),
|
||||
"name": str(raw_ke.get("name", "Unknown")),
|
||||
"confidence": str(raw_ke.get("confidence", "Suspects")),
|
||||
"source": str(raw_ke.get("source", "")),
|
||||
"state": str(raw_ke.get("state", "Active")),
|
||||
"relationship": str(raw_ke.get("relationship", "Unknown")),
|
||||
"last_observed_tick": int(raw_ke.get("last_observed_tick", 0)),
|
||||
}
|
||||
)
|
||||
)
|
||||
var kg_facts: Array = []
|
||||
var raw_kg_facts: Variant = raw_pk.get("facts")
|
||||
if raw_kg_facts is Array:
|
||||
for raw_kf in raw_kg_facts:
|
||||
if raw_kf is Dictionary and raw_kf.has("fact_id"):
|
||||
kg_facts.append({
|
||||
"fact_id": str(raw_kf["fact_id"]),
|
||||
"confidence": str(raw_kf.get("confidence", "Suspects")),
|
||||
"source": str(raw_kf.get("source", "")),
|
||||
"state": str(raw_kf.get("state", "Active")),
|
||||
"acquired_tick": int(raw_kf.get("acquired_tick", 0)),
|
||||
})
|
||||
(
|
||||
kg_facts
|
||||
. append(
|
||||
{
|
||||
"fact_id": str(raw_kf["fact_id"]),
|
||||
"confidence": str(raw_kf.get("confidence", "Suspects")),
|
||||
"source": str(raw_kf.get("source", "")),
|
||||
"state": str(raw_kf.get("state", "Active")),
|
||||
"acquired_tick": int(raw_kf.get("acquired_tick", 0)),
|
||||
}
|
||||
)
|
||||
)
|
||||
player_knowledge = {
|
||||
"entities": kg_entities,
|
||||
"facts": kg_facts,
|
||||
@@ -396,8 +475,13 @@ static func decode_snapshot(bytes: PackedByteArray) -> Variant:
|
||||
|
||||
## Decode a single VisibleEntity from a raw msgpack map.
|
||||
static func _decode_entity(raw: Dictionary) -> Variant:
|
||||
if not raw.has("entity_id") or not raw.has("x") or not raw.has("y") \
|
||||
or not raw.has("z") or not raw.has("kind"):
|
||||
if (
|
||||
not raw.has("entity_id")
|
||||
or not raw.has("x")
|
||||
or not raw.has("y")
|
||||
or not raw.has("z")
|
||||
or not raw.has("kind")
|
||||
):
|
||||
push_warning("Protocol: entity missing required fields: %s" % str(raw.keys()))
|
||||
return null
|
||||
|
||||
@@ -485,23 +569,25 @@ static func _decode_verb_option(raw) -> Variant:
|
||||
## Returns { "variant": String, "data": Variant } in both cases.
|
||||
static func _decode_enum_variant(raw) -> Dictionary:
|
||||
if raw is String:
|
||||
return { "variant": raw, "data": null }
|
||||
return {"variant": raw, "data": null}
|
||||
if raw is Dictionary and raw.size() == 1:
|
||||
var variant_name: String = raw.keys()[0]
|
||||
return { "variant": variant_name, "data": raw[variant_name] }
|
||||
return {"variant": variant_name, "data": raw[variant_name]}
|
||||
push_warning("Protocol: unexpected enum encoding: %s" % str(raw))
|
||||
return { "variant": "Unknown", "data": raw }
|
||||
return {"variant": "Unknown", "data": raw}
|
||||
|
||||
|
||||
# -- Encode: GDScript types → bytes to server ----------------------------------
|
||||
|
||||
|
||||
## Encode a StartupMessage to MessagePack bytes (#175, #588, #718).
|
||||
## Sent by the client immediately after handshake validation.
|
||||
## Server reads this to initialize SimRng (D-010, D-029) and select monologue pool (D-032).
|
||||
## character_archetype: "detective" → "Detective", "smuggler" → "Smuggler" (server enum variant).
|
||||
## character_visual: optional CharacterVisualDescriptor — included as "character_visual_descriptor" dict.
|
||||
static func encode_startup_message(
|
||||
world_seed: int, character_archetype: String = "detective", character_visual: Variant = null) -> PackedByteArray:
|
||||
world_seed: int, character_archetype: String = "detective", character_visual: Variant = null
|
||||
) -> PackedByteArray:
|
||||
# Map client lowercase archetype string to server PascalCase enum variant.
|
||||
# Explicit match prevents unknown strings silently reaching the server as
|
||||
# garbage enum values — fail loudly and fall back to "Detective".
|
||||
@@ -512,7 +598,12 @@ static func encode_startup_message(
|
||||
"smuggler":
|
||||
archetype_variant = "Smuggler"
|
||||
_:
|
||||
push_error("Protocol: unknown character_archetype '%s' — defaulting to 'Detective'" % character_archetype)
|
||||
push_error(
|
||||
(
|
||||
"Protocol: unknown character_archetype '%s' — defaulting to 'Detective'"
|
||||
% character_archetype
|
||||
)
|
||||
)
|
||||
archetype_variant = "Detective"
|
||||
var msg := {
|
||||
"world_seed": world_seed,
|
||||
@@ -531,7 +622,9 @@ static func encode_startup_message(
|
||||
## action_name: one of "MoveNorth", "MoveSouth", "MoveEast", "MoveWest",
|
||||
## "Interact", "UsePerceptionMode", "Pause", "Unpause"
|
||||
## action_data: null for unit variants, String for UsePerceptionMode
|
||||
static func encode_player_input(tick: int, action_name: String, action_data: Variant = null) -> PackedByteArray:
|
||||
static func encode_player_input(
|
||||
tick: int, action_name: String, action_data: Variant = null
|
||||
) -> PackedByteArray:
|
||||
var action_value: Variant = _encode_action(action_name, action_data)
|
||||
|
||||
var input := {
|
||||
@@ -555,10 +648,15 @@ static func encode_player_inputs(inputs: Array) -> PackedByteArray:
|
||||
for input in inputs:
|
||||
var action_name: String = input["action_name"]
|
||||
var action_data: Variant = input.get("action_data")
|
||||
wire_inputs.append({
|
||||
"tick": input["tick"],
|
||||
"action": _encode_action(action_name, action_data),
|
||||
})
|
||||
(
|
||||
wire_inputs
|
||||
. append(
|
||||
{
|
||||
"tick": input["tick"],
|
||||
"action": _encode_action(action_name, action_data),
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
var result = Messagepack.encode(wire_inputs)
|
||||
if result.status != null:
|
||||
@@ -573,10 +671,10 @@ static func encode_player_inputs(inputs: Array) -> PackedByteArray:
|
||||
## Unit variants (MoveNorth, Pause, etc.) encode as bare strings.
|
||||
static func _encode_action(action_name: String, action_data: Variant) -> Variant:
|
||||
if action_data != null:
|
||||
return { action_name: action_data }
|
||||
return {action_name: action_data}
|
||||
# Interact is a struct variant — server expects named fields, not a bare string
|
||||
if action_name == "Interact":
|
||||
return { "Interact": { "target_entity_id": null, "verb": null } }
|
||||
return {"Interact": {"target_entity_id": null, "verb": null}}
|
||||
return action_name
|
||||
|
||||
|
||||
@@ -586,11 +684,13 @@ static func _encode_action(action_name: String, action_data: Variant) -> Variant
|
||||
## queuing via SimBridge._outbound_buffer.
|
||||
## The server receives this as a PlayerAction::ChangeSettings variant.
|
||||
static func encode_change_settings(enabled: bool) -> PackedByteArray:
|
||||
var entries: Array = [{
|
||||
"tick": 0,
|
||||
"action_name": "ChangeSettings",
|
||||
"action_data": {"ai_enhanced_dialogue": enabled},
|
||||
}]
|
||||
var entries: Array = [
|
||||
{
|
||||
"tick": 0,
|
||||
"action_name": "ChangeSettings",
|
||||
"action_data": {"ai_enhanced_dialogue": enabled},
|
||||
}
|
||||
]
|
||||
var result = Messagepack.encode(entries)
|
||||
if result.status != null:
|
||||
push_error("Protocol: encode_change_settings failed: %s" % result.status)
|
||||
|
||||
@@ -7,14 +7,34 @@ extends RefCounted
|
||||
|
||||
const _WALLS: Array = [
|
||||
# Room walls (8x8 room from (7,7) to (14,14))
|
||||
Vector2i(7,7), Vector2i(8,7), Vector2i(9,7), Vector2i(10,7),
|
||||
Vector2i(11,7), Vector2i(12,7), Vector2i(13,7), Vector2i(14,7),
|
||||
Vector2i(7,14), Vector2i(8,14), Vector2i(9,14), Vector2i(10,14),
|
||||
Vector2i(11,14), Vector2i(12,14), Vector2i(13,14), Vector2i(14,14),
|
||||
Vector2i(7,8), Vector2i(7,9), Vector2i(7,10), Vector2i(7,11),
|
||||
Vector2i(7,12), Vector2i(7,13),
|
||||
Vector2i(14,8), Vector2i(14,9), Vector2i(14,10), Vector2i(14,11),
|
||||
Vector2i(14,12), Vector2i(14,13),
|
||||
Vector2i(7, 7),
|
||||
Vector2i(8, 7),
|
||||
Vector2i(9, 7),
|
||||
Vector2i(10, 7),
|
||||
Vector2i(11, 7),
|
||||
Vector2i(12, 7),
|
||||
Vector2i(13, 7),
|
||||
Vector2i(14, 7),
|
||||
Vector2i(7, 14),
|
||||
Vector2i(8, 14),
|
||||
Vector2i(9, 14),
|
||||
Vector2i(10, 14),
|
||||
Vector2i(11, 14),
|
||||
Vector2i(12, 14),
|
||||
Vector2i(13, 14),
|
||||
Vector2i(14, 14),
|
||||
Vector2i(7, 8),
|
||||
Vector2i(7, 9),
|
||||
Vector2i(7, 10),
|
||||
Vector2i(7, 11),
|
||||
Vector2i(7, 12),
|
||||
Vector2i(7, 13),
|
||||
Vector2i(14, 8),
|
||||
Vector2i(14, 9),
|
||||
Vector2i(14, 10),
|
||||
Vector2i(14, 11),
|
||||
Vector2i(14, 12),
|
||||
Vector2i(14, 13),
|
||||
# Interior wall blocking NPC
|
||||
Vector2i(12, 10),
|
||||
]
|
||||
@@ -48,6 +68,7 @@ func process_facing(new_facing: String) -> void:
|
||||
|
||||
# -- Snapshot generation -------------------------------------------------------
|
||||
|
||||
|
||||
func snapshot() -> Dictionary:
|
||||
tick += 1
|
||||
|
||||
@@ -76,42 +97,60 @@ func snapshot() -> Dictionary:
|
||||
var py := player_pos.y
|
||||
|
||||
# Build entities — player always visible
|
||||
var entities: Array = [{
|
||||
"entity_id": 1,
|
||||
"x": float(px),
|
||||
"y": float(py),
|
||||
"z": 0,
|
||||
"kind": { "variant": "Player", "data": null },
|
||||
"visibility": "Forward",
|
||||
}]
|
||||
var entities: Array = [
|
||||
{
|
||||
"entity_id": 1,
|
||||
"x": float(px),
|
||||
"y": float(py),
|
||||
"z": 0,
|
||||
"kind": {"variant": "Player", "data": null},
|
||||
"visibility": "Forward",
|
||||
}
|
||||
]
|
||||
|
||||
# NPC at (12, 9) — visible if within range and not blocked by wall at (12, 10)
|
||||
var npc_pos := Vector2i(12, 9)
|
||||
var npc_dist := absi(px - npc_pos.x) + absi(py - npc_pos.y)
|
||||
if npc_dist <= 4 and has_los(Vector2i(px, py), npc_pos):
|
||||
var sector: String = "Forward" if npc_pos.y <= py else "Peripheral"
|
||||
entities.append({
|
||||
"entity_id": 2,
|
||||
"x": float(npc_pos.x),
|
||||
"y": float(npc_pos.y),
|
||||
"z": 0,
|
||||
"kind": { "variant": "Npc", "data": null },
|
||||
"visibility": sector,
|
||||
"relationship": npc_relationship,
|
||||
})
|
||||
(
|
||||
entities
|
||||
. append(
|
||||
{
|
||||
"entity_id": 2,
|
||||
"x": float(npc_pos.x),
|
||||
"y": float(npc_pos.y),
|
||||
"z": 0,
|
||||
"kind": {"variant": "Npc", "data": null},
|
||||
"visibility": sector,
|
||||
"relationship": npc_relationship,
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
# v4: nearby_interactions when NPC is nearby and visible (#404/#405)
|
||||
var nearby: Array = []
|
||||
if npc_dist <= 2 and has_los(Vector2i(px, py), npc_pos):
|
||||
nearby.append({
|
||||
"entity_id": 2,
|
||||
"entity_type": "Npc",
|
||||
"distance": npc_dist,
|
||||
"verbs": [
|
||||
{"kind": "Talk", "label": "Talk", "priority": 1, "available": true},
|
||||
{"kind": "ExamineNpc", "label": "Observe", "priority": 2, "available": true},
|
||||
],
|
||||
})
|
||||
(
|
||||
nearby
|
||||
. append(
|
||||
{
|
||||
"entity_id": 2,
|
||||
"entity_type": "Npc",
|
||||
"distance": npc_dist,
|
||||
"verbs":
|
||||
[
|
||||
{"kind": "Talk", "label": "Talk", "priority": 1, "available": true},
|
||||
{
|
||||
"kind": "ExamineNpc",
|
||||
"label": "Observe",
|
||||
"priority": 2,
|
||||
"available": true
|
||||
},
|
||||
],
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
# v5: monologue on first tick (#414)
|
||||
var monologue: Variant = null
|
||||
@@ -128,11 +167,28 @@ func snapshot() -> Dictionary:
|
||||
dialogue = {
|
||||
"npc_name": "Kael",
|
||||
"npc_entity_id": 2,
|
||||
"speech": "Haven't seen you around the transit hub before. You new to Sova, or just passing through?",
|
||||
"options": [
|
||||
{"text": "Just arrived. Still getting my bearings.", "response_id": "kael_greet_01", "priority": 1, "confrontation": false}, # gdlint:ignore = max-line-length
|
||||
{"text": "Passing through. Know where I can find work?", "response_id": "kael_greet_02", "priority": 2, "confrontation": false}, # gdlint:ignore = max-line-length
|
||||
{"text": "I saw you near the cargo bay last night.", "response_id": "kael_confront_01", "priority": 3, "confrontation": true}, # gdlint:ignore = max-line-length
|
||||
"speech":
|
||||
"Haven't seen you around the transit hub before. You new to Sova, or just passing through?",
|
||||
"options":
|
||||
[
|
||||
{
|
||||
"text": "Just arrived. Still getting my bearings.",
|
||||
"response_id": "kael_greet_01",
|
||||
"priority": 1,
|
||||
"confrontation": false
|
||||
}, # gdlint:ignore = max-line-length
|
||||
{
|
||||
"text": "Passing through. Know where I can find work?",
|
||||
"response_id": "kael_greet_02",
|
||||
"priority": 2,
|
||||
"confrontation": false
|
||||
}, # gdlint:ignore = max-line-length
|
||||
{
|
||||
"text": "I saw you near the cargo bay last night.",
|
||||
"response_id": "kael_confront_01",
|
||||
"priority": 3,
|
||||
"confrontation": true
|
||||
}, # gdlint:ignore = max-line-length
|
||||
],
|
||||
}
|
||||
|
||||
@@ -142,26 +198,55 @@ func snapshot() -> Dictionary:
|
||||
if cycle_pos < 6:
|
||||
var total_delay := 6
|
||||
var remaining := total_delay - cycle_pos
|
||||
pending_recs.append({
|
||||
"entity_id": 100,
|
||||
"x": 13.5,
|
||||
"y": 12.5,
|
||||
"z": 0,
|
||||
"remaining_ticks": remaining,
|
||||
"total_delay_ticks": total_delay,
|
||||
})
|
||||
(
|
||||
pending_recs
|
||||
. append(
|
||||
{
|
||||
"entity_id": 100,
|
||||
"x": 13.5,
|
||||
"y": 12.5,
|
||||
"z": 0,
|
||||
"remaining_ticks": remaining,
|
||||
"total_delay_ticks": total_delay,
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
# #535: Mock overheard NPC-NPC conversation (D-078)
|
||||
var conv_events: Array = []
|
||||
var conv_ended: Array = []
|
||||
var conv_start := 3
|
||||
var conv_lines := [
|
||||
{"speaker": "Mira", "target": "Soren", "line": "The cargo manifests don't add up. Three containers unaccounted for."},
|
||||
{"speaker": "Soren", "target": "Mira", "line": "Could be a logging error. Happens every... cycle."},
|
||||
{"speaker": "Mira", "target": "Soren", "line": "Not like this. Someone moved them after... check."},
|
||||
{"speaker": "Soren", "target": "Mira", "line": "You're reading too much into it. The docks are... these days."},
|
||||
{"speaker": "Mira", "target": "Soren", "line": "Then explain the weight discrepancy. Two hundred kilos... just gone."}, # gdlint:ignore = max-line-length
|
||||
{"speaker": "Soren", "target": "Mira", "line": "Fine. I'll pull the bay... tonight. But keep this between us."},
|
||||
{
|
||||
"speaker": "Mira",
|
||||
"target": "Soren",
|
||||
"line": "The cargo manifests don't add up. Three containers unaccounted for."
|
||||
},
|
||||
{
|
||||
"speaker": "Soren",
|
||||
"target": "Mira",
|
||||
"line": "Could be a logging error. Happens every... cycle."
|
||||
},
|
||||
{
|
||||
"speaker": "Mira",
|
||||
"target": "Soren",
|
||||
"line": "Not like this. Someone moved them after... check."
|
||||
},
|
||||
{
|
||||
"speaker": "Soren",
|
||||
"target": "Mira",
|
||||
"line": "You're reading too much into it. The docks are... these days."
|
||||
},
|
||||
{
|
||||
"speaker": "Mira",
|
||||
"target": "Soren",
|
||||
"line": "Then explain the weight discrepancy. Two hundred kilos... just gone."
|
||||
}, # gdlint:ignore = max-line-length
|
||||
{
|
||||
"speaker": "Soren",
|
||||
"target": "Mira",
|
||||
"line": "Fine. I'll pull the bay... tonight. But keep this between us."
|
||||
},
|
||||
]
|
||||
var conv_tick_interval := 5
|
||||
var conv_total_ticks := conv_lines.size() * conv_tick_interval
|
||||
@@ -170,20 +255,26 @@ func snapshot() -> Dictionary:
|
||||
var within_tick := (tick - conv_start) % conv_tick_interval
|
||||
if within_tick == 0 and conv_index < conv_lines.size():
|
||||
var cl: Dictionary = conv_lines[conv_index]
|
||||
conv_events.append({
|
||||
"speaker_id": 10,
|
||||
"target_id": 11,
|
||||
"speaker_name": cl.speaker,
|
||||
"target_name": cl.target,
|
||||
"occluded_line": cl.line,
|
||||
})
|
||||
(
|
||||
conv_events
|
||||
. append(
|
||||
{
|
||||
"speaker_id": 10,
|
||||
"target_id": 11,
|
||||
"speaker_name": cl.speaker,
|
||||
"target_name": cl.target,
|
||||
"occluded_line": cl.line,
|
||||
}
|
||||
)
|
||||
)
|
||||
elif tick == conv_start + conv_total_ticks:
|
||||
conv_ended.append({"speaker_id": 10, "target_id": 11})
|
||||
|
||||
return {
|
||||
"tick": tick,
|
||||
"version": Protocol.PROTOCOL_VERSION,
|
||||
"game_time": {
|
||||
"game_time":
|
||||
{
|
||||
"day": 0,
|
||||
"time_of_day": tick * 10,
|
||||
"day_phase": "Morning",
|
||||
@@ -207,6 +298,7 @@ func snapshot() -> Dictionary:
|
||||
|
||||
# -- Map generation ------------------------------------------------------------
|
||||
|
||||
|
||||
func _tiles() -> Array:
|
||||
var tiles: Array = []
|
||||
var room_x := 7
|
||||
@@ -216,8 +308,9 @@ func _tiles() -> Array:
|
||||
|
||||
for x in range(room_x, room_x + room_w):
|
||||
for y in range(room_y, room_y + room_h):
|
||||
var is_edge := (x == room_x or x == room_x + room_w - 1
|
||||
or y == room_y or y == room_y + room_h - 1)
|
||||
var is_edge := (
|
||||
x == room_x or x == room_x + room_w - 1 or y == room_y or y == room_y + room_h - 1
|
||||
)
|
||||
var tile_type: String
|
||||
if is_edge:
|
||||
if y == room_y + room_h - 1 and x == room_x + room_w / 2:
|
||||
@@ -253,7 +346,9 @@ func _visible_tiles() -> Array:
|
||||
if dist <= radius:
|
||||
if x >= room_x and x < room_x + room_w and y >= room_y and y < room_y + room_h:
|
||||
var sector: String = "Forward" if y <= py else "Peripheral"
|
||||
vtiles.append({"x": x, "y": y, "z": 0, "visibility": sector, "type": _get_tile_type(x, y)})
|
||||
vtiles.append(
|
||||
{"x": x, "y": y, "z": 0, "visibility": sector, "type": _get_tile_type(x, y)}
|
||||
)
|
||||
return vtiles
|
||||
|
||||
|
||||
@@ -262,8 +357,9 @@ func _get_tile_type(x: int, y: int) -> String:
|
||||
var room_y := 7
|
||||
var room_w := 8
|
||||
var room_h := 8
|
||||
var is_edge := (x == room_x or x == room_x + room_w - 1
|
||||
or y == room_y or y == room_y + room_h - 1)
|
||||
var is_edge := (
|
||||
x == room_x or x == room_x + room_w - 1 or y == room_y or y == room_y + room_h - 1
|
||||
)
|
||||
if is_edge:
|
||||
if y == room_y + room_h - 1 and x == room_x + room_w / 2:
|
||||
return "door"
|
||||
@@ -273,6 +369,7 @@ func _get_tile_type(x: int, y: int) -> String:
|
||||
|
||||
# -- Spatial helpers -----------------------------------------------------------
|
||||
|
||||
|
||||
func _is_walkable(pos: Vector2i) -> bool:
|
||||
return not _WALLS.has(pos)
|
||||
|
||||
@@ -302,25 +399,43 @@ func has_los(from: Vector2i, to: Vector2i) -> bool:
|
||||
|
||||
static func action_to_delta(action_name: String) -> Vector2i:
|
||||
match action_name:
|
||||
"MoveNorth": return Vector2i(0, -1)
|
||||
"MoveNortheast": return Vector2i(1, -1)
|
||||
"MoveEast": return Vector2i(1, 0)
|
||||
"MoveSoutheast": return Vector2i(1, 1)
|
||||
"MoveSouth": return Vector2i(0, 1)
|
||||
"MoveSouthwest": return Vector2i(-1, 1)
|
||||
"MoveWest": return Vector2i(-1, 0)
|
||||
"MoveNorthwest": return Vector2i(-1, -1)
|
||||
_: return Vector2i.ZERO
|
||||
"MoveNorth":
|
||||
return Vector2i(0, -1)
|
||||
"MoveNortheast":
|
||||
return Vector2i(1, -1)
|
||||
"MoveEast":
|
||||
return Vector2i(1, 0)
|
||||
"MoveSoutheast":
|
||||
return Vector2i(1, 1)
|
||||
"MoveSouth":
|
||||
return Vector2i(0, 1)
|
||||
"MoveSouthwest":
|
||||
return Vector2i(-1, 1)
|
||||
"MoveWest":
|
||||
return Vector2i(-1, 0)
|
||||
"MoveNorthwest":
|
||||
return Vector2i(-1, -1)
|
||||
_:
|
||||
return Vector2i.ZERO
|
||||
|
||||
|
||||
static func delta_to_facing(delta: Vector2i) -> String:
|
||||
match delta:
|
||||
Vector2i(0, -1): return "North"
|
||||
Vector2i(1, -1): return "Northeast"
|
||||
Vector2i(1, 0): return "East"
|
||||
Vector2i(1, 1): return "Southeast"
|
||||
Vector2i(0, 1): return "South"
|
||||
Vector2i(-1, 1): return "Southwest"
|
||||
Vector2i(-1, 0): return "West"
|
||||
Vector2i(-1, -1): return "Northwest"
|
||||
_: return "North"
|
||||
Vector2i(0, -1):
|
||||
return "North"
|
||||
Vector2i(1, -1):
|
||||
return "Northeast"
|
||||
Vector2i(1, 0):
|
||||
return "East"
|
||||
Vector2i(1, 1):
|
||||
return "Southeast"
|
||||
Vector2i(0, 1):
|
||||
return "South"
|
||||
Vector2i(-1, 1):
|
||||
return "Southwest"
|
||||
Vector2i(-1, 0):
|
||||
return "West"
|
||||
Vector2i(-1, -1):
|
||||
return "Northwest"
|
||||
_:
|
||||
return "North"
|
||||
|
||||
Reference in New Issue
Block a user