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:
2026-04-05 11:09:10 +02:00
co-authored by Claude Opus 4.6
parent 572e66f026
commit 88c7407cb6
52 changed files with 2323 additions and 1061 deletions
+54 -16
View File
@@ -40,12 +40,25 @@ const OUTLINE_SHADER_PATH := BASE_PATH + "shaders/outline.gdshader"
## All body segment names in assembly order. D-160: 14 base + 2 swappable torso + 2 face = 18.
## torso_upper is loaded and hidden by default; clothing coverage reveals it.
const ALL_SEGMENTS: Array[String] = [
"head", "neck", "torso_upper", "torso", "hips",
"arm_upper_l", "arm_upper_r", "arm_lower_l", "arm_lower_r",
"hand_l", "hand_r",
"leg_upper_l", "leg_upper_r", "leg_lower_l", "leg_lower_r",
"foot_l", "foot_r",
"eyes", "eyebrows",
"head",
"neck",
"torso_upper",
"torso",
"hips",
"arm_upper_l",
"arm_upper_r",
"arm_lower_l",
"arm_lower_r",
"hand_l",
"hand_r",
"leg_upper_l",
"leg_upper_r",
"leg_lower_l",
"leg_lower_r",
"foot_l",
"foot_r",
"eyes",
"eyebrows",
]
## Segments that do NOT receive the skin tone shader — preserve original embedded material.
@@ -107,14 +120,14 @@ var _overhead_attachment: BoneAttachment3D = null
# Inspectable state for tests
var _active_torso_variant: String = "full"
var _active_coverages: Dictionary = {} # item_id -> coverage dict
var _loaded_slots: Array[String] = [] # "hair", "facial_hair", etc.
var _active_coverages: Dictionary = {} # item_id -> coverage dict
var _loaded_slots: Array[String] = [] # "hair", "facial_hair", etc.
var _toon_shader: Shader = null
var _toon_masked_shader: Shader = null
var _outline_shader: Shader = null
var _white_mask: ImageTexture = null # 1x1 white pixel — forces full-body tinting
var _iris_mask: Texture2D = null # iris-only mask from T_Eye_Split.png
var _iris_mask: Texture2D = null # iris-only mask from T_Eye_Split.png
func _ready() -> void:
@@ -140,6 +153,7 @@ func _load_shaders() -> void:
# Public API
# =============================================================================
## Rebuild the full character from a CharacterVisualDescriptor.
func load_descriptor(descriptor: CharacterVisualDescriptor) -> void:
_clear()
@@ -168,8 +182,14 @@ func set_facing(direction: Variant) -> void:
rotation.y = -atan2((direction as Vector2).x, (direction as Vector2).y)
elif direction is String:
var angles: Dictionary = {
"south": 0.0, "southwest": 45.0, "west": 90.0, "northwest": 135.0,
"north": 180.0, "northeast": -135.0, "east": -90.0, "southeast": -45.0,
"south": 0.0,
"southwest": 45.0,
"west": 90.0,
"northwest": 135.0,
"north": 180.0,
"northeast": -135.0,
"east": -90.0,
"southeast": -45.0,
}
rotation_degrees.y = angles.get((direction as String).to_lower(), 0.0)
@@ -232,6 +252,7 @@ func get_overhead_anchor() -> Marker3D:
# Internal — teardown
# =============================================================================
func _clear() -> void:
# Outline nodes are duplicates of body/clothing meshes — must be freed FIRST,
# before the source meshes are removed. _rebuild_outlines() is not called here
@@ -287,6 +308,7 @@ func _clear() -> void:
# Internal — skeleton
# =============================================================================
func _load_skeleton() -> void:
if not ResourceLoader.exists(SKELETON_PATH):
push_error("CharacterVisual: skeleton not found at %s" % SKELETON_PATH)
@@ -317,7 +339,12 @@ func _validate_slot_bones() -> void:
for slot: String in SLOT_TO_BONE:
var bone_name: String = SLOT_TO_BONE[slot]
if _skeleton.find_bone(bone_name) == -1:
push_warning("CharacterVisual: SLOT_TO_BONE['%s'] = '%s' — bone not found in skeleton" % [slot, bone_name])
push_warning(
(
"CharacterVisual: SLOT_TO_BONE['%s'] = '%s' — bone not found in skeleton"
% [slot, bone_name]
)
)
## #712: Create a Marker3D anchored ~0.3m above the Head bone via BoneAttachment3D.
@@ -344,6 +371,7 @@ func _create_overhead_anchor() -> void:
# Internal — body segments (D-160)
# =============================================================================
func _load_body_segments(desc: CharacterVisualDescriptor) -> void:
var body_dir := BASE_PATH + "bodies/%s/" % desc.body_type_key()
var tone := SKIN_TONES[clampi(desc.skin_tone, 0, SKIN_TONES.size() - 1)]
@@ -420,6 +448,7 @@ func _apply_body_shader(mi: MeshInstance3D, seg_name: String, tone: Dictionary)
# Internal — head, hair, facial hair, eyebrows (BoneAttachment3D) (D-161)
# =============================================================================
func _load_head(desc: CharacterVisualDescriptor) -> void:
if desc.head_id.is_empty():
return
@@ -452,7 +481,8 @@ func _load_eyebrows(_desc: CharacterVisualDescriptor) -> void:
func _attach_to_bone(
path: String, bone_name: String, tint: Color = Color.WHITE, _render_priority: int = 0) -> BoneAttachment3D:
path: String, bone_name: String, tint: Color = Color.WHITE, _render_priority: int = 0
) -> BoneAttachment3D:
if _skeleton == null:
return null
if not ResourceLoader.exists(path):
@@ -517,6 +547,7 @@ func _attach_to_bone(
# Internal — clothing (D-162): coverage.json → segment hiding + torso variant
# =============================================================================
func _load_clothing(desc: CharacterVisualDescriptor) -> void:
if _skeleton == null or desc.clothing_slots.is_empty():
return
@@ -566,7 +597,6 @@ func _load_clothing(desc: CharacterVisualDescriptor) -> void:
# Hiding is reserved for amputation/prosthetics via the debug tab or game logic.
func _read_coverage(path: String) -> Dictionary:
if not FileAccess.file_exists(path):
return {}
@@ -608,6 +638,7 @@ func _apply_clothing_shader(mi: MeshInstance3D, tints: Array, mask_tex: Texture2
# Internal — accessories (BoneAttachment3D per slot→bone)
# =============================================================================
func _load_accessories(desc: CharacterVisualDescriptor) -> void:
for slot: String in desc.accessory_slots:
var item_id: String = desc.accessory_slots[slot]
@@ -629,8 +660,10 @@ func _load_accessories(desc: CharacterVisualDescriptor) -> void:
# Internal — tinting (hair, accessories, bone-attached assets with tint)
# =============================================================================
func _apply_tinted_shader(
mi: MeshInstance3D, tint: Color, mask_tex: Texture2D = null, _render_priority: int = 0) -> void:
mi: MeshInstance3D, tint: Color, mask_tex: Texture2D = null, _render_priority: int = 0
) -> void:
if mi.mesh == null:
return
for surf in range(mi.mesh.get_surface_count()):
@@ -657,6 +690,7 @@ func _apply_tinted_shader(
# Internal — outline pass (inverted hull, cull_front)
# =============================================================================
func _rebuild_outlines() -> void:
for node in _outline_nodes:
if is_instance_valid(node):
@@ -693,6 +727,7 @@ func _rebuild_outlines() -> void:
# Animation
# =============================================================================
func _load_animations() -> void:
if _skeleton == null:
return
@@ -764,6 +799,7 @@ func stop_animation() -> void:
# Static helpers
# =============================================================================
static func _find_skeleton(root: Node) -> Skeleton3D:
if root is Skeleton3D:
return root as Skeleton3D
@@ -791,7 +827,9 @@ static func _get_albedo_texture(mat: Material) -> Texture2D:
if mat is BaseMaterial3D:
return (mat as BaseMaterial3D).albedo_texture
if mat is ShaderMaterial:
for p: String in ["albedo_tex", "albedo_texture", "texture_albedo", "Hair_Texture", "BaseColor"]:
for p: String in [
"albedo_tex", "albedo_texture", "texture_albedo", "Hair_Texture", "BaseColor"
]:
var val: Variant = (mat as ShaderMaterial).get_shader_parameter(p)
if val is Texture2D:
return val as Texture2D
@@ -166,6 +166,7 @@ func to_dict() -> Dictionary:
# -- Color serialization helpers --
# Wire format: [r, g, b, a] float array (rmp_serde serializes Color as tuple).
static func _decode_color(value: Variant, fallback: Color) -> Color:
if value is Array and value.size() >= 3:
return Color(value[0], value[1], value[2], value[3] if value.size() >= 4 else 1.0)
@@ -198,5 +199,3 @@ static func _encode_tint_map(data: Dictionary) -> Dictionary:
encoded.append(_encode_color(c))
result[key] = encoded
return result
+81 -22
View File
@@ -84,6 +84,7 @@ func _process(delta: float) -> void:
# --- Hover detection (automatic, from GameState.visible_entities) ---
func _detect_hover() -> void:
var prev_id := hovered_entity_id
@@ -115,7 +116,7 @@ func _find_nearest_entity() -> Dictionary:
var mouse_world: Vector2 = xform.affine_inverse() * mouse_screen
var best_dist := INF
var result := { id = -1, kind = "", color = COLOR_DEFAULT, offset = Vector2.ZERO }
var result := {id = -1, kind = "", color = COLOR_DEFAULT, offset = Vector2.ZERO}
for entity in GameState.visible_entities:
if not entity.has("entity_id") or not entity.has("x") or not entity.has("y"):
@@ -138,6 +139,7 @@ func _find_nearest_entity() -> Dictionary:
# --- State transitions ---
func _set_target(new_state: State) -> void:
if new_state == _target:
return
@@ -154,29 +156,75 @@ func _set_target(new_state: State) -> void:
func _snapshot() -> Dictionary:
return { gap = _gap, len = _len, rot = _rot, thick = _thick,
color = _color, bloom = _bloom, bracket_a = _bracket_a, alpha = _alpha }
return {
gap = _gap,
len = _len,
rot = _rot,
thick = _thick,
color = _color,
bloom = _bloom,
bracket_a = _bracket_a,
alpha = _alpha
}
func _params_for(state: State) -> Dictionary:
match state:
State.ENTITY_HOVER:
return { gap = 10.0, len = 6.0, rot = 0.0, thick = 1.0,
color = _hover_color, bloom = 0.5, bracket_a = 1.0, alpha = 1.0 }
return {
gap = 10.0,
len = 6.0,
rot = 0.0,
thick = 1.0,
color = _hover_color,
bloom = 0.5,
bracket_a = 1.0,
alpha = 1.0
}
State.OBJECT_HOVER:
return { gap = 4.0, len = 6.0, rot = PI / 4.0, thick = 1.0,
color = _hover_color, bloom = 0.3, bracket_a = 0.0, alpha = 0.8 }
return {
gap = 4.0,
len = 6.0,
rot = PI / 4.0,
thick = 1.0,
color = _hover_color,
bloom = 0.3,
bracket_a = 0.0,
alpha = 0.8
}
State.WEAPON_AIM:
return { gap = 12.0, len = 9.0, rot = 0.0, thick = 2.0,
color = COLOR_WEAPON, bloom = 0.0, bracket_a = 0.0, alpha = 1.0 }
return {
gap = 12.0,
len = 9.0,
rot = 0.0,
thick = 2.0,
color = COLOR_WEAPON,
bloom = 0.0,
bracket_a = 0.0,
alpha = 1.0
}
_:
return { gap = 4.0, len = 6.0, rot = 0.0, thick = 1.0,
color = COLOR_DEFAULT, bloom = 0.4, bracket_a = 0.0, alpha = 0.45 }
return {
gap = 4.0,
len = 6.0,
rot = 0.0,
thick = 1.0,
color = COLOR_DEFAULT,
bloom = 0.4,
bracket_a = 0.0,
alpha = 0.45
}
func _apply_params(p: Dictionary) -> void:
_gap = p.gap; _len = p.len; _rot = p.rot; _thick = p.thick
_color = p.color; _bloom = p.bloom; _bracket_a = p.bracket_a; _alpha = p.alpha
_gap = p.gap
_len = p.len
_rot = p.rot
_thick = p.thick
_color = p.color
_bloom = p.bloom
_bracket_a = p.bracket_a
_alpha = p.alpha
func _interpolate() -> void:
@@ -193,6 +241,7 @@ func _interpolate() -> void:
# --- Drawing ---
func _draw() -> void:
# _hover_offset is computed in _detect_hover() during _process(), not recalculated
# here. This is safe because Camera2D (tree sibling, earlier in scene order) applies
@@ -233,13 +282,19 @@ func _draw_brackets(offset: Vector2, color: Color) -> void:
# --- Test-friendly API (expected by test_cursor_states.gd) ---
func get_state() -> String:
match current_state:
State.DEFAULT: return "Default"
State.ENTITY_HOVER: return "EntityHover"
State.OBJECT_HOVER: return "ObjectHover"
State.WEAPON_AIM: return "WeaponAim"
_: return "Default"
State.DEFAULT:
return "Default"
State.ENTITY_HOVER:
return "EntityHover"
State.OBJECT_HOVER:
return "ObjectHover"
State.WEAPON_AIM:
return "WeaponAim"
_:
return "Default"
func set_hover_target(data: Dictionary) -> void:
@@ -252,10 +307,14 @@ func set_hover_target(data: Dictionary) -> void:
if kind == "Npc":
var rel: String = data.get("relationship", "Unknown")
match rel:
"Friendly": _hover_color = Constants.ENTITY_COLOR_FRIENDLY
"PersonOfInterest": _hover_color = Constants.ENTITY_COLOR_POI
"Hostile": _hover_color = Constants.ENTITY_COLOR_HOSTILE
_: _hover_color = Constants.ENTITY_COLOR_UNKNOWN
"Friendly":
_hover_color = Constants.ENTITY_COLOR_FRIENDLY
"PersonOfInterest":
_hover_color = Constants.ENTITY_COLOR_POI
"Hostile":
_hover_color = Constants.ENTITY_COLOR_HOSTILE
_:
_hover_color = Constants.ENTITY_COLOR_UNKNOWN
_target = State.ENTITY_HOVER
_t = 1.0
_apply_params(_params_for(State.ENTITY_HOVER))
+24 -13
View File
@@ -18,7 +18,7 @@ const TILE_SIZE: int = Constants.TILE_SIZE
# D-044: 24x32 entity footprint within 32x32 visual tile (64x64 source at 0.5 scale = 32px runtime)
const ENTITY_WIDTH: int = 24
const ENTITY_HEIGHT: int = 32
const ENTITY_OFFSET_X: float = 0.0 # sprite fills tile width at 0.5 scale
const ENTITY_OFFSET_X: float = 0.0 # sprite fills tile width at 0.5 scale
const ENTITY_OFFSET_Y: float = TILE_SIZE - ENTITY_HEIGHT # feet-anchored for correct y-sort with D-019 tilt
# Lerp speed — framerate-independent exponential smoothing.
@@ -28,12 +28,13 @@ const LERP_SPEED: float = 12.0
# #521: Color transition duration in seconds (D-033: "0.5s fade")
const COLOR_FADE_DURATION: float = 0.5
var entity_nodes: Dictionary = {} # entity_id -> Node2D
var _entity_targets: Dictionary = {} # entity_id -> Vector2 (target pixel position)
var entity_nodes: Dictionary = {} # entity_id -> Node2D
var _entity_targets: Dictionary = {} # entity_id -> Vector2 (target pixel position)
var _entity_relationships: Dictionary = {} # #521: entity_id -> String (last relationship)
var _entity_tweens: Dictionary = {} # #521: entity_id -> {from: Color, target: Color, elapsed: float}
var _entity_facing: Dictionary = {} # #540: entity_id -> String ("north"/"east"/"south"/"west")
func _ready() -> void:
print("EntityRenderer: Initialized")
@@ -111,7 +112,11 @@ func _create_entity_node(entity_id: int, entity_data: Dictionary) -> void:
var tex := _load_sprite_texture(direction)
if tex == null:
push_error(
"EntityRenderer: no texture for entity %d direction '%s' — entity will be invisible" % [entity_id, direction])
(
"EntityRenderer: no texture for entity %d direction '%s' — entity will be invisible"
% [entity_id, direction]
)
)
entity_node.texture = tex
# D-033: self_modulate for relationship tinting; modulate.a is reserved for D-015 dimming.
@@ -220,10 +225,14 @@ func _entity_direction(entity_id: int, _entity_data: Dictionary) -> String:
# N/NW → north, NE/E → east, SE/S → south, SW/W → west
static func _octant_to_direction(octant: String) -> String:
match octant:
"North", "Northwest": return "north"
"Northeast", "East": return "east"
"Southeast", "South": return "south"
"Southwest", "West": return "west"
"North", "Northwest":
return "north"
"Northeast", "East":
return "east"
"Southeast", "South":
return "south"
"Southwest", "West":
return "west"
_:
push_warning("EntityRenderer: unrecognised octant '%s' — defaulting to south" % octant)
return "south"
@@ -247,11 +256,13 @@ func _add_facing_indicator(parent_node: Node2D) -> void:
var s := Constants.FACING_INDICATOR_SIZE
var offset := Constants.FACING_INDICATOR_OFFSET
# Triangle pointing up (North), offset from center. Rotates around (0,0).
indicator.polygon = PackedVector2Array([
Vector2(0, -offset - s),
Vector2(-s * 0.6, -offset + s * 0.4),
Vector2(s * 0.6, -offset + s * 0.4),
])
indicator.polygon = PackedVector2Array(
[
Vector2(0, -offset - s),
Vector2(-s * 0.6, -offset + s * 0.4),
Vector2(s * 0.6, -offset + s * 0.4),
]
)
indicator.color = Constants.ENTITY_COLOR_PLAYER
# Sprite2D local space: 64px texture at scale 0.5 → center of visible sprite at (32,32).
# Indicator rotates around this point to track player facing direction.
+19 -21
View File
@@ -17,25 +17,25 @@ extends Node2D
const TILE_SIZE: int = Constants.TILE_SIZE
# D-059: Fog entity colors
const COLOR_UNRECOGNIZED := Color("#555566") # Neutral grey blob
const COLOR_UNRECOGNIZED := Color("#555566") # Neutral grey blob
# Intentionally matches Constants.INSERT_COLOR_TEXT / cursor default (#c8d0e0).
# Sonar pings are insert-generated — same visual language as cursor and HUD overlays.
const COLOR_PING := Color("#c8d0e0") # Insert white-blue (D-048/D-056)
const COLOR_PING := Color("#c8d0e0") # Insert white-blue (D-048/D-056)
# Animation timing
const PULSE_PERIOD: float = 0.8 # Breathing pulse cycle (seconds)
const PULSE_MIN_ALPHA: float = 0.4 # Min alpha during breathing
const PULSE_MAX_ALPHA: float = 0.8 # Max alpha during breathing
const PING_DURATION: float = 1.5 # Sound ping expand + fade (seconds)
const PING_MAX_RADIUS: float = 24.0 # Max ring expand radius (pixels)
const PING_RING_COUNT: int = 3 # Concentric rings per ping
const PING_RING_WIDTH: float = 1.5 # Ring line width (pixels)
const DRIFT_RANGE: float = 0.5 # ±0.5 tile position drift (D-059)
const DRIFT_PERIOD: float = 2.0 # Seconds per drift wander
const PULSE_PERIOD: float = 0.8 # Breathing pulse cycle (seconds)
const PULSE_MIN_ALPHA: float = 0.4 # Min alpha during breathing
const PULSE_MAX_ALPHA: float = 0.8 # Max alpha during breathing
const PING_DURATION: float = 1.5 # Sound ping expand + fade (seconds)
const PING_MAX_RADIUS: float = 24.0 # Max ring expand radius (pixels)
const PING_RING_COUNT: int = 3 # Concentric rings per ping
const PING_RING_WIDTH: float = 1.5 # Ring line width (pixels)
const DRIFT_RANGE: float = 0.5 # ±0.5 tile position drift (D-059)
const DRIFT_PERIOD: float = 2.0 # Seconds per drift wander
# Entity blob rendering
const BLOB_RADIUS: float = 10.0
const SILHOUETTE_SCALE: float = 1.3 # Silhouette slightly larger than blob
const SILHOUETTE_SCALE: float = 1.3 # Silhouette slightly larger than blob
# Transition thresholds: recognition progress mapped from remaining_ticks/total_delay_ticks.
# Color transition starts at 50% to compress the visual change into ~0.3s (D-060).
const COLOR_TRANSITION_START: float = 0.5
@@ -45,8 +45,8 @@ const SILHOUETTE_APPEAR_THRESHOLD: float = 0.3
const SILHOUETTE_SIZE := Vector2(6.0, 10.0)
# Internal state — no child nodes, pure data + _draw()
var _entities: Dictionary = {} # entity_id -> {pos, drift_offset, drift_target, drift_timer, progress}
var _pings: Array = [] # [{pos: Vector2, elapsed: float}]
var _entities: Dictionary = {} # entity_id -> {pos, drift_offset, drift_target, drift_timer, progress}
var _pings: Array = [] # [{pos: Vector2, elapsed: float}]
var _time: float = 0.0
@@ -121,8 +121,7 @@ func update_from_state() -> void:
func _draw() -> void:
# Breathing pulse — shared across all entities
var pulse_t := fmod(_time, PULSE_PERIOD) / PULSE_PERIOD
var pulse_alpha := lerpf(PULSE_MIN_ALPHA, PULSE_MAX_ALPHA,
0.5 + 0.5 * sin(pulse_t * TAU))
var pulse_alpha := lerpf(PULSE_MIN_ALPHA, PULSE_MAX_ALPHA, 0.5 + 0.5 * sin(pulse_t * TAU))
# Draw entity blobs
for eid in _entities.keys():
@@ -132,7 +131,9 @@ func _draw() -> void:
# Color transition: grey → D-033 teal based on recognition progress
# Transition begins at 50% progress (D-060: ~0.3s visual transition within delay window)
var progress: float = e.progress
var color_t: float = clampf((progress - COLOR_TRANSITION_START) / (1.0 - COLOR_TRANSITION_START), 0.0, 1.0)
var color_t: float = clampf(
(progress - COLOR_TRANSITION_START) / (1.0 - COLOR_TRANSITION_START), 0.0, 1.0
)
var blob_color: Color = COLOR_UNRECOGNIZED.lerp(Constants.ENTITY_COLOR_UNKNOWN, color_t)
blob_color.a = pulse_alpha
@@ -172,7 +173,4 @@ func _draw() -> void:
func _random_drift() -> Vector2:
return Vector2(
randf_range(-DRIFT_RANGE, DRIFT_RANGE),
randf_range(-DRIFT_RANGE, DRIFT_RANGE)
)
return Vector2(randf_range(-DRIFT_RANGE, DRIFT_RANGE), randf_range(-DRIFT_RANGE, DRIFT_RANGE))
+8 -2
View File
@@ -41,7 +41,11 @@ func _ready() -> void:
noise_tex.width = 256
noise_tex.height = 256
noise_tex.seamless = true
noise_tex.changed.connect(func(): _noise_ready = true; fog_noise_ready.emit())
noise_tex.changed.connect(
func():
_noise_ready = true
fog_noise_ready.emit()
)
_shader_mat.set_shader_parameter("noise_tex", noise_tex)
_shader_mat.set_shader_parameter("tile_size", TILE_SIZE)
@@ -74,6 +78,8 @@ func update_fog() -> void:
_shader_mat.set_shader_parameter("rect_sz", _fog_rect.size)
_shader_mat.set_shader_parameter("map_offset", Vector2(FogState.map_bounds.position))
_shader_mat.set_shader_parameter("map_size", Vector2(FogState.map_bounds.size))
var t: float = FogState.override_time if FogState.override_time >= 0.0 else Time.get_ticks_msec() / 1000.0
var t: float = (
FogState.override_time if FogState.override_time >= 0.0 else Time.get_ticks_msec() / 1000.0
)
_shader_mat.set_shader_parameter("time", t)
_shader_mat.set_shader_parameter("debug_exploration", FogState.debug_exploration)
@@ -20,20 +20,21 @@ extends Node2D
const TILE_SIZE: int = Constants.TILE_SIZE
# Visual parameters
const INDICATOR_LIFETIME: float = 3.5 # Total seconds visible
const FADE_DURATION: float = 0.6 # Fade-out window at end
const EDGE_INSET: float = 20.0 # Pixels inward from viewport edge
const ARROW_HALF: float = 7.0 # Half-width of arrowhead base
const ARROW_LEN: float = 12.0 # Length from tip to base
const INDICATOR_LIFETIME: float = 3.5 # Total seconds visible
const FADE_DURATION: float = 0.6 # Fade-out window at end
const EDGE_INSET: float = 20.0 # Pixels inward from viewport edge
const ARROW_HALF: float = 7.0 # Half-width of arrowhead base
const ARROW_LEN: float = 12.0 # Length from tip to base
# D-018/D-069 colors — sourced from Constants to prevent palette drift
const COLOR_NEUTRAL: Color = Constants.INSERT_COLOR_TEXT # Generic / footstep
const COLOR_VOICE: Color = Constants.ENTITY_COLOR_POI # Speech / conversation
const COLOR_NEUTRAL: Color = Constants.INSERT_COLOR_TEXT # Generic / footstep
const COLOR_VOICE: Color = Constants.ENTITY_COLOR_POI # Speech / conversation
const COLOR_DANGER: Color = Constants.ENTITY_COLOR_HOSTILE # Alert / threat / gunshot
# Indicators: [{x, y, event_type, elapsed}]
var _indicators: Array = []
func _process(delta: float) -> void:
if _indicators.is_empty():
return
@@ -68,12 +69,17 @@ func update_sound_events(events: Array) -> void:
found = true
break
if not found:
_indicators.append({
"x": ex,
"y": ey,
"event_type": evt.get("event_type", ""),
"elapsed": 0.0,
})
(
_indicators
. append(
{
"x": ex,
"y": ey,
"event_type": evt.get("event_type", ""),
"elapsed": 0.0,
}
)
)
if not _indicators.is_empty():
queue_redraw()
@@ -144,9 +150,19 @@ func _draw_arrow(pos: Vector2, dir: Vector2, color: Color) -> void:
## Map event type string → D-018 color category.
func color_for_type(event_type: String) -> Color:
var et := event_type.to_lower()
if et.contains("voice") or et.contains("speech") or et.contains("convers") or et.contains("talk"):
if (
et.contains("voice")
or et.contains("speech")
or et.contains("convers")
or et.contains("talk")
):
return COLOR_VOICE
if et.contains("danger") or et.contains("gunshot") or et.contains("explosion") \
or et.contains("alert") or et.contains("threat"):
if (
et.contains("danger")
or et.contains("gunshot")
or et.contains("explosion")
or et.contains("alert")
or et.contains("threat")
):
return COLOR_DANGER
return COLOR_NEUTRAL
+11 -2
View File
@@ -27,11 +27,13 @@ const TILE_TYPE_MAP: Dictionary = {
var _initialized: bool = false
func _ready() -> void:
_setup_tileset()
_initialized = true
print("TileRenderer: Initialized")
# Build a programmatic TileSet with colored placeholder tiles
func _setup_tileset() -> void:
var ts := TileSet.new()
@@ -63,6 +65,7 @@ func _setup_tileset() -> void:
var source_id := ts.add_source(source)
tile_set = ts
# Update tiles from snapshot data
# tiles: Array of {x: int, y: int, z: int, type: String, visibility: String (optional)}
# z here is the server-side FLOOR LEVEL (0 = ground, 1 = first floor, etc.),
@@ -93,19 +96,25 @@ func update_tiles(tiles: Array) -> void:
var tile_type_str: String = tile_data.type
if not TILE_TYPE_MAP.has(tile_type_str):
push_warning("TileRenderer: unknown tile type '%s' at (%d, %d)" % [
tile_type_str, tile_data.x, tile_data.y])
push_warning(
(
"TileRenderer: unknown tile type '%s' at (%d, %d)"
% [tile_type_str, tile_data.x, tile_data.y]
)
)
continue
var atlas_x: int = TILE_TYPE_MAP[tile_type_str]
var coords := Vector2i(tile_data.x, tile_data.y)
set_cell(coords, 0, Vector2i(atlas_x, 0))
# Fill a tile region with a solid color
func _fill_tile(img: Image, tile_index: int, color: Color) -> void:
var rect := Rect2i(tile_index * TILE_SIZE, 0, TILE_SIZE, TILE_SIZE)
img.fill_rect(rect, color)
# Fill a tile region with a color and a 1px border
func _fill_tile_with_border(img: Image, tile_index: int, fill: Color, border: Color) -> void:
var x_offset := tile_index * TILE_SIZE
@@ -20,9 +20,11 @@ var _last_tick: int = -1
@onready var entity_renderer = $FogGroup/YSortGroup/Entities
@onready var sound_indicator_renderer = $SoundIndicators # #126 D-018 medium-range indicators
func _ready() -> void:
print("WorldRenderer: Initialized (D-049 z-stack)")
# Called each frame to update visuals from game state.
# Uses tick-based invalidation — re-renders all layers when a new snapshot arrives.
func update_from_state() -> void: