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