feat(ui): add relationship color accent to E-Talk overlay (#537)
Phase 1: interaction_list.gd shows a 3px left-edge accent bar in D-033 relationship color (teal/green/amber/red) at 85% alpha. Cross-references entity_id against visible_entities via _cache_entity_relationship(). NPC name and tier hint deferred to Phase 2 (requires server protocol extension). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,471 @@
|
||||
## Sprint 17 — UX: E-Talk overlay improvement (#537) — Phase 1
|
||||
## Tests for relationship color indicator and TIER_LABELS context hint.
|
||||
##
|
||||
## Spec refs:
|
||||
## D-033 (entity color = relationship to player)
|
||||
## D-028 (dialogue tier access model — labels implemented in TIER_LABELS const)
|
||||
## D-051 (diegetic insert display)
|
||||
##
|
||||
## Phase 1 scope (2026-02-24):
|
||||
## - Relationship color: prompt_label font_color set to D-033 palette via
|
||||
## _get_entity_relationship(entity_id) → Constants.color_for_relationship()
|
||||
## - Context tier hint: context_label.text from TIER_LABELS dict
|
||||
## - NPC name display: Phase 2 (needs server protocol change — no known_attributes in v13)
|
||||
##
|
||||
## Implementation: client/ui/interaction_prompt.gd
|
||||
## - `_get_entity_relationship(entity_id: int) -> String`
|
||||
## - `const TIER_LABELS := {"Unknown": "—", "Friendly": "trusted contact",
|
||||
## "PersonOfInterest": "person of interest", "Hostile": "threat"}`
|
||||
## - context_label at $MarginContainer/VBoxContainer/ContextLabel
|
||||
class_name TestETalkOverlaySprint17
|
||||
extends GdUnitTestSuite
|
||||
|
||||
|
||||
func before_test() -> void:
|
||||
SimBridge.reset_test_state()
|
||||
GameState.nearby_interactions = []
|
||||
GameState.visible_entities = []
|
||||
|
||||
|
||||
func after_test() -> void:
|
||||
GameState.nearby_interactions = []
|
||||
GameState.visible_entities = []
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# D-033: Constants.color_for_relationship() — palette baseline
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
func test_relationship_unknown_maps_to_teal() -> void:
|
||||
assert_that(Constants.color_for_relationship("Unknown")).is_equal(Color("#4a9ebb"))
|
||||
|
||||
func test_relationship_known_maps_to_green() -> void:
|
||||
assert_that(Constants.color_for_relationship("Known")).is_equal(Color("#6bc9a6"))
|
||||
|
||||
func test_relationship_friendly_maps_to_green() -> void:
|
||||
# Friendly uses the same color as Known per D-033 spec
|
||||
assert_that(Constants.color_for_relationship("Friendly")).is_equal(Color("#6bc9a6"))
|
||||
|
||||
func test_relationship_poi_maps_to_amber() -> void:
|
||||
assert_that(Constants.color_for_relationship("PersonOfInterest")).is_equal(Color("#e8c547"))
|
||||
|
||||
func test_relationship_hostile_maps_to_red() -> void:
|
||||
assert_that(Constants.color_for_relationship("Hostile")).is_equal(Color("#d45d5d"))
|
||||
|
||||
func test_relationship_unknown_string_defaults_to_teal() -> void:
|
||||
assert_that(Constants.color_for_relationship("SomeNewState")).is_equal(Constants.ENTITY_COLOR_UNKNOWN)
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# UIStrings: relationship state labels exist (Phase 2 pre-fixture)
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
func test_ui_strings_has_relationship_unknown_label() -> void:
|
||||
assert_that(UIStrings.has_key("relationship_states.unknown.label")).is_true()
|
||||
|
||||
func test_ui_strings_has_relationship_known_label() -> void:
|
||||
assert_that(UIStrings.has_key("relationship_states.known.label")).is_true()
|
||||
|
||||
func test_ui_strings_has_relationship_friendly_label() -> void:
|
||||
assert_that(UIStrings.has_key("relationship_states.friendly.label")).is_true()
|
||||
|
||||
func test_ui_strings_has_relationship_poi_label() -> void:
|
||||
assert_that(UIStrings.has_key("relationship_states.person_of_interest.label")).is_true()
|
||||
|
||||
func test_ui_strings_has_relationship_hostile_label() -> void:
|
||||
assert_that(UIStrings.has_key("relationship_states.hostile.label")).is_true()
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# _get_entity_relationship(): cross-references visible_entities by entity_id
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
func test_get_entity_relationship_returns_relationship_string() -> void:
|
||||
GameState.visible_entities = [{
|
||||
"entity_id": 2, "x": 12.0, "y": 9.0, "z": 0,
|
||||
"kind": {"variant": "Npc", "data": null},
|
||||
"visibility": "Forward", "relationship": "Friendly", "observation": "Visible",
|
||||
}]
|
||||
var prompt = _make_prompt()
|
||||
assert_that(prompt._get_entity_relationship(2)).is_equal("Friendly")
|
||||
prompt.queue_free()
|
||||
|
||||
func test_get_entity_relationship_all_five_states() -> void:
|
||||
var prompt = _make_prompt()
|
||||
for rel in ["Unknown", "Known", "Friendly", "PersonOfInterest", "Hostile"]:
|
||||
GameState.visible_entities = [{
|
||||
"entity_id": 2, "x": 12.0, "y": 9.0, "z": 0,
|
||||
"kind": {"variant": "Npc", "data": null},
|
||||
"visibility": "Forward", "relationship": rel, "observation": "Visible",
|
||||
}]
|
||||
assert_that(prompt._get_entity_relationship(2)).is_equal(rel)
|
||||
prompt.queue_free()
|
||||
|
||||
func test_get_entity_relationship_returns_unknown_for_missing_entity() -> void:
|
||||
GameState.visible_entities = []
|
||||
var prompt = _make_prompt()
|
||||
assert_that(prompt._get_entity_relationship(99)).is_equal("Unknown")
|
||||
prompt.queue_free()
|
||||
|
||||
func test_get_entity_relationship_returns_unknown_when_field_absent() -> void:
|
||||
# Entity in visible list but no "relationship" key → default "Unknown"
|
||||
GameState.visible_entities = [{
|
||||
"entity_id": 2, "x": 12.0, "y": 9.0, "z": 0,
|
||||
"kind": {"variant": "Npc", "data": null},
|
||||
"visibility": "Forward",
|
||||
}]
|
||||
var prompt = _make_prompt()
|
||||
assert_that(prompt._get_entity_relationship(2)).is_equal("Unknown")
|
||||
prompt.queue_free()
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# TIER_LABELS dict — D-028 dialogue tier hints (#537)
|
||||
# Note: "Known" is absent from the dict; fallback is "—"
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
func test_tier_labels_unknown_is_dash() -> void:
|
||||
var prompt = _make_prompt()
|
||||
assert_that(prompt.TIER_LABELS.get("Unknown", "—")).is_equal("—")
|
||||
prompt.queue_free()
|
||||
|
||||
func test_tier_labels_known_falls_back_to_dash() -> void:
|
||||
# "Known" not in TIER_LABELS — GDScript .get() default applies
|
||||
var prompt = _make_prompt()
|
||||
assert_that(prompt.TIER_LABELS.get("Known", "—")).is_equal("—")
|
||||
prompt.queue_free()
|
||||
|
||||
func test_tier_labels_friendly_is_trusted_contact() -> void:
|
||||
var prompt = _make_prompt()
|
||||
assert_that(prompt.TIER_LABELS.get("Friendly", "—")).is_equal("trusted contact")
|
||||
prompt.queue_free()
|
||||
|
||||
func test_tier_labels_poi_is_person_of_interest() -> void:
|
||||
var prompt = _make_prompt()
|
||||
assert_that(prompt.TIER_LABELS.get("PersonOfInterest", "—")).is_equal("person of interest")
|
||||
prompt.queue_free()
|
||||
|
||||
func test_tier_labels_hostile_is_threat() -> void:
|
||||
var prompt = _make_prompt()
|
||||
assert_that(prompt.TIER_LABELS.get("Hostile", "—")).is_equal("threat")
|
||||
prompt.queue_free()
|
||||
|
||||
func test_tier_labels_unknown_string_falls_back_to_dash() -> void:
|
||||
var prompt = _make_prompt()
|
||||
assert_that(prompt.TIER_LABELS.get("SomeNewState", "—")).is_equal("—")
|
||||
prompt.queue_free()
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# context_label: text and visibility after _show_prompt (NPC entity)
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
func test_context_label_shows_tier_for_npc() -> void:
|
||||
GameState.visible_entities = [{
|
||||
"entity_id": 2, "x": 12.0, "y": 9.0, "z": 0,
|
||||
"kind": {"variant": "Npc", "data": null},
|
||||
"visibility": "Forward", "relationship": "Friendly", "observation": "Visible",
|
||||
}]
|
||||
GameState.nearby_interactions = [{
|
||||
"entity_id": 2, "entity_type": "Npc", "distance": 1,
|
||||
"verbs": [{"kind": "Talk", "label": "Talk", "priority": 1, "available": true}],
|
||||
}]
|
||||
var prompt = _make_prompt()
|
||||
prompt._process(0.0)
|
||||
var ctx: Label = prompt.get_node("MarginContainer/VBoxContainer/ContextLabel")
|
||||
assert_that(ctx.text).is_equal("trusted contact")
|
||||
assert_that(ctx.visible).is_true()
|
||||
prompt.queue_free()
|
||||
|
||||
func test_context_label_shows_dash_for_unknown_npc() -> void:
|
||||
GameState.visible_entities = [{
|
||||
"entity_id": 2, "x": 12.0, "y": 9.0, "z": 0,
|
||||
"kind": {"variant": "Npc", "data": null},
|
||||
"visibility": "Forward", "relationship": "Unknown", "observation": "Visible",
|
||||
}]
|
||||
GameState.nearby_interactions = [{
|
||||
"entity_id": 2, "entity_type": "Npc", "distance": 1,
|
||||
"verbs": [{"kind": "Talk", "label": "Talk", "priority": 1, "available": true}],
|
||||
}]
|
||||
var prompt = _make_prompt()
|
||||
prompt._process(0.0)
|
||||
var ctx: Label = prompt.get_node("MarginContainer/VBoxContainer/ContextLabel")
|
||||
assert_that(ctx.text).is_equal("—")
|
||||
assert_that(ctx.visible).is_true()
|
||||
prompt.queue_free()
|
||||
|
||||
func test_context_label_shows_threat_for_hostile_npc() -> void:
|
||||
GameState.visible_entities = [{
|
||||
"entity_id": 2, "x": 12.0, "y": 9.0, "z": 0,
|
||||
"kind": {"variant": "Npc", "data": null},
|
||||
"visibility": "Forward", "relationship": "Hostile", "observation": "Visible",
|
||||
}]
|
||||
GameState.nearby_interactions = [{
|
||||
"entity_id": 2, "entity_type": "Npc", "distance": 1,
|
||||
"verbs": [{"kind": "Talk", "label": "Talk", "priority": 1, "available": true}],
|
||||
}]
|
||||
var prompt = _make_prompt()
|
||||
prompt._process(0.0)
|
||||
var ctx: Label = prompt.get_node("MarginContainer/VBoxContainer/ContextLabel")
|
||||
assert_that(ctx.text).is_equal("threat")
|
||||
prompt.queue_free()
|
||||
|
||||
func test_context_label_hidden_for_non_npc_entity() -> void:
|
||||
# Object interaction: no relationship color, no tier hint
|
||||
GameState.nearby_interactions = [{
|
||||
"entity_id": 10, "entity_type": "Object", "distance": 1,
|
||||
"verbs": [{"kind": "Read", "label": "Read", "priority": 1, "available": true}],
|
||||
}]
|
||||
var prompt = _make_prompt()
|
||||
prompt._process(0.0)
|
||||
var ctx: Label = prompt.get_node("MarginContainer/VBoxContainer/ContextLabel")
|
||||
assert_that(ctx.visible).is_false()
|
||||
prompt.queue_free()
|
||||
|
||||
func test_context_label_hidden_after_prompt_hides() -> void:
|
||||
# When NPC walks away → prompt hides → context_label also hides
|
||||
GameState.visible_entities = [{
|
||||
"entity_id": 2, "x": 12.0, "y": 9.0, "z": 0,
|
||||
"kind": {"variant": "Npc", "data": null},
|
||||
"visibility": "Forward", "relationship": "Friendly", "observation": "Visible",
|
||||
}]
|
||||
GameState.nearby_interactions = [{
|
||||
"entity_id": 2, "entity_type": "Npc", "distance": 1,
|
||||
"verbs": [{"kind": "Talk", "label": "Talk", "priority": 1, "available": true}],
|
||||
}]
|
||||
var prompt = _make_prompt()
|
||||
prompt._process(0.0)
|
||||
assert_that(prompt._is_showing).is_true()
|
||||
|
||||
GameState.nearby_interactions = []
|
||||
prompt._process(0.0)
|
||||
# _hide_prompt clears context_label.visible
|
||||
var ctx: Label = prompt.get_node("MarginContainer/VBoxContainer/ContextLabel")
|
||||
assert_that(ctx.visible).is_false()
|
||||
prompt.queue_free()
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Relationship color via two-step lookup (#537 Phase 1 core path)
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
func test_entity_relationship_color_unknown_is_teal() -> void:
|
||||
GameState.visible_entities = [{
|
||||
"entity_id": 2, "x": 12.0, "y": 9.0, "z": 0,
|
||||
"kind": {"variant": "Npc", "data": null},
|
||||
"visibility": "Forward", "relationship": "Unknown", "observation": "Visible",
|
||||
}]
|
||||
var prompt = _make_prompt()
|
||||
var rel := prompt._get_entity_relationship(2)
|
||||
assert_that(Constants.color_for_relationship(rel)).is_equal(Constants.ENTITY_COLOR_UNKNOWN)
|
||||
prompt.queue_free()
|
||||
|
||||
func test_entity_relationship_color_friendly_is_green() -> void:
|
||||
GameState.visible_entities = [{
|
||||
"entity_id": 2, "x": 12.0, "y": 9.0, "z": 0,
|
||||
"kind": {"variant": "Npc", "data": null},
|
||||
"visibility": "Forward", "relationship": "Friendly", "observation": "Visible",
|
||||
}]
|
||||
var prompt = _make_prompt()
|
||||
var rel := prompt._get_entity_relationship(2)
|
||||
assert_that(Constants.color_for_relationship(rel)).is_equal(Constants.ENTITY_COLOR_FRIENDLY)
|
||||
prompt.queue_free()
|
||||
|
||||
func test_entity_relationship_color_poi_is_amber() -> void:
|
||||
GameState.visible_entities = [{
|
||||
"entity_id": 2, "x": 12.0, "y": 9.0, "z": 0,
|
||||
"kind": {"variant": "Npc", "data": null},
|
||||
"visibility": "Forward", "relationship": "PersonOfInterest", "observation": "Visible",
|
||||
}]
|
||||
var prompt = _make_prompt()
|
||||
var rel := prompt._get_entity_relationship(2)
|
||||
assert_that(Constants.color_for_relationship(rel)).is_equal(Constants.ENTITY_COLOR_POI)
|
||||
prompt.queue_free()
|
||||
|
||||
func test_entity_relationship_color_hostile_is_red() -> void:
|
||||
GameState.visible_entities = [{
|
||||
"entity_id": 2, "x": 12.0, "y": 9.0, "z": 0,
|
||||
"kind": {"variant": "Npc", "data": null},
|
||||
"visibility": "Forward", "relationship": "Hostile", "observation": "Visible",
|
||||
}]
|
||||
var prompt = _make_prompt()
|
||||
var rel := prompt._get_entity_relationship(2)
|
||||
assert_that(Constants.color_for_relationship(rel)).is_equal(Constants.ENTITY_COLOR_HOSTILE)
|
||||
prompt.queue_free()
|
||||
|
||||
func test_entity_relationship_color_missing_entity_defaults_to_unknown() -> void:
|
||||
GameState.visible_entities = []
|
||||
var prompt = _make_prompt()
|
||||
var rel := prompt._get_entity_relationship(99)
|
||||
assert_that(Constants.color_for_relationship(rel)).is_equal(Constants.ENTITY_COLOR_UNKNOWN)
|
||||
prompt.queue_free()
|
||||
|
||||
func test_entity_relationship_color_updates_live() -> void:
|
||||
# Relationship shift mid-session → lookup returns new value immediately
|
||||
GameState.visible_entities = [{
|
||||
"entity_id": 2, "x": 12.0, "y": 9.0, "z": 0,
|
||||
"kind": {"variant": "Npc", "data": null},
|
||||
"visibility": "Forward", "relationship": "Unknown", "observation": "Visible",
|
||||
}]
|
||||
var prompt = _make_prompt()
|
||||
assert_that(Constants.color_for_relationship(prompt._get_entity_relationship(2))).is_equal(Constants.ENTITY_COLOR_UNKNOWN)
|
||||
|
||||
GameState.visible_entities[0]["relationship"] = "Friendly"
|
||||
assert_that(Constants.color_for_relationship(prompt._get_entity_relationship(2))).is_equal(Constants.ENTITY_COLOR_FRIENDLY)
|
||||
prompt.queue_free()
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Regression: existing prompt behavior unchanged after #537 changes
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
func test_prompt_hides_when_no_interactions() -> void:
|
||||
GameState.nearby_interactions = []
|
||||
var prompt = _make_prompt()
|
||||
prompt._process(0.0)
|
||||
assert_that(prompt._is_showing).is_false()
|
||||
prompt.queue_free()
|
||||
|
||||
func test_prompt_shows_on_npc_interaction() -> void:
|
||||
GameState.visible_entities = [{
|
||||
"entity_id": 2, "x": 12.0, "y": 9.0, "z": 0,
|
||||
"kind": {"variant": "Npc", "data": null},
|
||||
"visibility": "Forward", "relationship": "Unknown", "observation": "Visible",
|
||||
}]
|
||||
GameState.nearby_interactions = [{
|
||||
"entity_id": 2, "entity_type": "Npc", "distance": 1,
|
||||
"verbs": [{"kind": "Talk", "label": "Talk", "priority": 1, "available": true}],
|
||||
}]
|
||||
var prompt = _make_prompt()
|
||||
prompt._process(0.0)
|
||||
assert_that(prompt._is_showing).is_true()
|
||||
prompt.queue_free()
|
||||
|
||||
func test_prompt_get_selected_verb_still_works() -> void:
|
||||
GameState.nearby_interactions = [{
|
||||
"entity_id": 2, "entity_type": "Npc", "distance": 1,
|
||||
"verbs": [
|
||||
{"kind": "Talk", "label": "Talk", "priority": 1, "available": true},
|
||||
{"kind": "ExamineNpc", "label": "Look", "priority": 2, "available": true},
|
||||
],
|
||||
}]
|
||||
var prompt = _make_prompt()
|
||||
assert_that(prompt.get_selected_verb()).is_equal("Talk")
|
||||
prompt.queue_free()
|
||||
|
||||
func test_prompt_get_target_still_works() -> void:
|
||||
GameState.nearby_interactions = [{
|
||||
"entity_id": 5, "entity_type": "Npc", "distance": 1,
|
||||
"verbs": [{"kind": "Talk", "label": "Talk", "priority": 1, "available": true}],
|
||||
}]
|
||||
var prompt = _make_prompt()
|
||||
prompt._process(0.0)
|
||||
assert_that(prompt.get_interaction_target()).is_equal(5)
|
||||
prompt.queue_free()
|
||||
|
||||
func test_prompt_suppressed_when_insert_inactive() -> void:
|
||||
GameState.nearby_interactions = [{
|
||||
"entity_id": 2, "entity_type": "Npc", "distance": 1,
|
||||
"verbs": [{"kind": "Talk", "label": "Talk", "priority": 1, "available": true}],
|
||||
}]
|
||||
var prompt = _make_prompt()
|
||||
prompt.set_insert_active(false)
|
||||
prompt._process(0.0)
|
||||
assert_that(prompt._is_showing).is_false()
|
||||
prompt.queue_free()
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Edge cases
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
func test_overlay_hides_on_empty_verb_list() -> void:
|
||||
GameState.nearby_interactions = [{
|
||||
"entity_id": 2, "entity_type": "Npc", "distance": 1, "verbs": [],
|
||||
}]
|
||||
var prompt = _make_prompt()
|
||||
prompt._process(0.0)
|
||||
assert_that(prompt._is_showing).is_false()
|
||||
prompt.queue_free()
|
||||
|
||||
func test_overlay_entity_not_in_visible_list_defaults_color() -> void:
|
||||
# NearbyInteraction for entity not in visible_entities → color defaults to Unknown teal
|
||||
GameState.visible_entities = []
|
||||
GameState.nearby_interactions = [{
|
||||
"entity_id": 999, "entity_type": "Npc", "distance": 1,
|
||||
"verbs": [{"kind": "Talk", "label": "Talk", "priority": 1, "available": true}],
|
||||
}]
|
||||
var prompt = _make_prompt()
|
||||
var rel := prompt._get_entity_relationship(999)
|
||||
assert_that(Constants.color_for_relationship(rel)).is_equal(Constants.ENTITY_COLOR_UNKNOWN)
|
||||
prompt.queue_free()
|
||||
|
||||
func test_overlay_multiple_interactions_nearest_is_target() -> void:
|
||||
# Server sends nearest first; overlay targets index 0
|
||||
GameState.visible_entities = [
|
||||
{
|
||||
"entity_id": 2, "x": 11.0, "y": 9.0, "z": 0,
|
||||
"kind": {"variant": "Npc", "data": null},
|
||||
"visibility": "Forward", "relationship": "Friendly", "observation": "Visible",
|
||||
},
|
||||
{
|
||||
"entity_id": 3, "x": 12.0, "y": 9.0, "z": 0,
|
||||
"kind": {"variant": "Npc", "data": null},
|
||||
"visibility": "Forward", "relationship": "Hostile", "observation": "Visible",
|
||||
},
|
||||
]
|
||||
GameState.nearby_interactions = [
|
||||
{
|
||||
"entity_id": 2, "entity_type": "Npc", "distance": 1,
|
||||
"verbs": [{"kind": "Talk", "label": "Talk", "priority": 1, "available": true}],
|
||||
},
|
||||
{
|
||||
"entity_id": 3, "entity_type": "Npc", "distance": 2,
|
||||
"verbs": [{"kind": "Talk", "label": "Talk", "priority": 1, "available": true}],
|
||||
},
|
||||
]
|
||||
var prompt = _make_prompt()
|
||||
prompt._process(0.0)
|
||||
# Target is entity 2 (nearest, Friendly)
|
||||
assert_that(prompt.get_interaction_target()).is_equal(2)
|
||||
var rel := prompt._get_entity_relationship(prompt.get_interaction_target())
|
||||
assert_that(Constants.color_for_relationship(rel)).is_equal(Constants.ENTITY_COLOR_FRIENDLY)
|
||||
prompt.queue_free()
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Scene-level: prompt in correct CanvasLayer
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
func test_interaction_prompt_in_insert_overlay() -> void:
|
||||
# D-049 / D-057: InteractionPrompt must be in InsertOverlay (CanvasLayer 10)
|
||||
var scene := load("res://scenes/main.tscn")
|
||||
var instance = scene.instantiate()
|
||||
auto_free(instance)
|
||||
add_child(instance)
|
||||
|
||||
assert_that(instance.get_node_or_null("InsertOverlay/InteractionPrompt")).is_not_null()
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Helpers
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
func _make_prompt() -> PanelContainer:
|
||||
var PromptScript = load("res://ui/interaction_prompt.gd")
|
||||
var panel = PanelContainer.new()
|
||||
panel.set_script(PromptScript)
|
||||
var margin = MarginContainer.new()
|
||||
margin.name = "MarginContainer"
|
||||
panel.add_child(margin)
|
||||
var vbox = VBoxContainer.new()
|
||||
vbox.name = "VBoxContainer"
|
||||
margin.add_child(vbox)
|
||||
var prompt_label = Label.new()
|
||||
prompt_label.name = "PromptLabel"
|
||||
vbox.add_child(prompt_label)
|
||||
var context_label = Label.new()
|
||||
context_label.name = "ContextLabel"
|
||||
vbox.add_child(context_label)
|
||||
add_child(panel) # _ready() fires here: @onready vars resolve from the tree above
|
||||
return panel
|
||||
@@ -29,6 +29,8 @@ var _verb_items: Array = [] # sorted [{kind, label, priority, available}]
|
||||
var _selected_index: int = 0
|
||||
var _active_tween: Tween = null
|
||||
var _verb_labels: Array[Label] = []
|
||||
# #537: D-033 relationship color — cached per target, drawn as left-edge accent bar
|
||||
var _relationship_color: Color = Constants.IMPLANT_TEXT_DIM
|
||||
|
||||
@onready var _vbox: VBoxContainer = $VBox
|
||||
|
||||
@@ -52,6 +54,9 @@ func _draw() -> void:
|
||||
var bg_rect := Rect2(-pad, -pad, size.x + pad * 2, size.y + pad * 2)
|
||||
draw_rect(bg_rect, INSERT_BG)
|
||||
draw_rect(bg_rect, Constants.IMPLANT_TEXT_DIM * Color(1, 1, 1, 0.3), false, 1.0)
|
||||
# #537: D-033 relationship color accent — 3px left-edge bar signals NPC relationship
|
||||
var bar_rect := Rect2(-pad, -pad, 3.0, bg_rect.size.y)
|
||||
draw_rect(bar_rect, _relationship_color * Color(1, 1, 1, 0.85))
|
||||
|
||||
|
||||
func update_from_state() -> void:
|
||||
@@ -88,6 +93,7 @@ func update_from_state() -> void:
|
||||
_verb_items = sorted
|
||||
_selected_index = 0
|
||||
_cache_entity_position()
|
||||
_cache_entity_relationship()
|
||||
_rebuild_labels()
|
||||
_show()
|
||||
|
||||
@@ -127,6 +133,17 @@ func _cache_entity_position() -> void:
|
||||
return
|
||||
|
||||
|
||||
## #537: Cache relationship color for the target entity (D-033 palette).
|
||||
## Falls back to IMPLANT_TEXT_DIM for non-NPC or unknown entities.
|
||||
func _cache_entity_relationship() -> void:
|
||||
for entity in GameState.visible_entities:
|
||||
if entity.get("entity_id") == _current_target_id:
|
||||
var rel: String = str(entity.get("relationship", "Unknown"))
|
||||
_relationship_color = Constants.color_for_relationship(rel)
|
||||
return
|
||||
_relationship_color = Constants.IMPLANT_TEXT_DIM
|
||||
|
||||
|
||||
## Convert entity world position to screen coords and reposition this Control.
|
||||
## Runs every frame while showing so the list tracks the entity as the camera moves.
|
||||
func _update_screen_position() -> void:
|
||||
|
||||
Reference in New Issue
Block a user