Startup crash — preload loop referenced STANCE_ICONS but the constant was accidentally dropped when rewriting the icon cache logic. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
103 lines
3.1 KiB
GDScript
103 lines
3.1 KiB
GDScript
extends Control
|
||
|
||
## D-053: Stance indicator — implant-styled badge (D-169).
|
||
## Sprint/Walk/Careful/Crouch. Icon (D-086) + color-coded label.
|
||
|
||
const STANCE_COLORS := {
|
||
"Sprint": Color("#d45d5d"), # Red — fast, loud, dangerous
|
||
"Walk": Color("#c8d0e0"), # Default — neutral white-blue
|
||
"Careful": Color("#6bc9a6"), # Green — quiet, observant
|
||
"Crouch": Color("#e8c547"), # Amber — very quiet, slow
|
||
}
|
||
|
||
const STANCE_ICONS := {
|
||
"Sprint": "res://assets/icons/icon_stance_sprint.svg",
|
||
"Walk": "res://assets/icons/icon_stance_walk.svg",
|
||
"Careful": "res://assets/icons/icon_stance_careful.svg",
|
||
"Crouch": "res://assets/icons/icon_stance_crouch.svg",
|
||
}
|
||
|
||
var _current_stance: String = "Walk"
|
||
var _panel: ImplantPanel
|
||
var _icon: TextureRect
|
||
var _label: Label
|
||
var _icon_mat: ShaderMaterial
|
||
var _icon_cache: Dictionary = {} # stance name → Texture2D
|
||
|
||
|
||
func _ready() -> void:
|
||
mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||
var theme_res := load("res://ui/implant/default_implant.tres") as ImplantTheme
|
||
var icon_shader := load("res://shaders/icon_tint.gdshader") as Shader
|
||
if not theme_res:
|
||
push_error("StanceIndicator: failed to load implant theme")
|
||
return
|
||
if not icon_shader:
|
||
push_error("StanceIndicator: failed to load icon_tint shader")
|
||
return
|
||
|
||
# Preload all stance icons at startup
|
||
for stance_name in STANCE_ICONS:
|
||
var tex := load(STANCE_ICONS[stance_name]) as Texture2D
|
||
if tex:
|
||
_icon_cache[stance_name] = tex
|
||
else:
|
||
push_error("StanceIndicator: failed to load icon for %s" % stance_name)
|
||
|
||
_panel = ImplantPanel.new()
|
||
_panel.name = "StancePanel"
|
||
_panel.theme_resource = theme_res
|
||
_panel.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||
add_child(_panel)
|
||
|
||
var hbox := HBoxContainer.new()
|
||
hbox.add_theme_constant_override("separation", 4)
|
||
hbox.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||
|
||
_icon = TextureRect.new()
|
||
_icon.expand_mode = TextureRect.EXPAND_KEEP_SIZE
|
||
_icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
||
_icon.custom_minimum_size = Vector2(20.0, 20.0) # D-086: 20×20 viewBox
|
||
_icon.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||
_icon_mat = ShaderMaterial.new()
|
||
_icon_mat.shader = icon_shader
|
||
_icon_mat.set_shader_parameter("tint_color", STANCE_COLORS["Walk"])
|
||
_icon.material = _icon_mat
|
||
hbox.add_child(_icon)
|
||
|
||
_label = Label.new()
|
||
_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||
_label.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||
_label.add_theme_font_size_override("font_size", theme_res.font_body)
|
||
hbox.add_child(_label)
|
||
|
||
_panel.add_component(hbox)
|
||
_apply_stance("Walk")
|
||
|
||
|
||
func _apply_stance(stance: String) -> void:
|
||
_current_stance = stance
|
||
var color: Color = STANCE_COLORS.get(stance, Color("#c8d0e0"))
|
||
|
||
if _icon:
|
||
var tex: Texture2D = _icon_cache.get(stance)
|
||
if tex:
|
||
_icon.texture = tex
|
||
if _icon_mat:
|
||
_icon_mat.set_shader_parameter("tint_color", color)
|
||
|
||
if _label:
|
||
_label.text = stance
|
||
_label.add_theme_color_override("font_color", color)
|
||
|
||
|
||
func update_from_state() -> void:
|
||
var stance: String = GameState.player_stance
|
||
if stance == _current_stance:
|
||
return
|
||
_apply_stance(stance)
|
||
|
||
|
||
func get_current_stance() -> String:
|
||
return _current_stance
|