feat: surface player standing and quest journal in the village HUD
This commit is contained in:
@@ -15,6 +15,7 @@
|
|||||||
[ext_resource type="Script" uid="uid://dwwxux24jc6yq" path="res://world/ui/village_whisper_hud.gd" id="15_whisper"]
|
[ext_resource type="Script" uid="uid://dwwxux24jc6yq" path="res://world/ui/village_whisper_hud.gd" id="15_whisper"]
|
||||||
[ext_resource type="Script" path="res://world/ui/player_interaction_hud.gd" id="16_interaction"]
|
[ext_resource type="Script" path="res://world/ui/player_interaction_hud.gd" id="16_interaction"]
|
||||||
[ext_resource type="Script" path="res://world/ui/villager_field_note_hud.gd" id="17_villager_note"]
|
[ext_resource type="Script" path="res://world/ui/villager_field_note_hud.gd" id="17_villager_note"]
|
||||||
|
[ext_resource type="Script" path="res://world/ui/quest_journal_hud.gd" id="18_quest_journal"]
|
||||||
|
|
||||||
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_o5qli"]
|
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_o5qli"]
|
||||||
radius = 0.4
|
radius = 0.4
|
||||||
@@ -390,6 +391,86 @@ text = "The pantry is empty."
|
|||||||
horizontal_alignment = 1
|
horizontal_alignment = 1
|
||||||
vertical_alignment = 1
|
vertical_alignment = 1
|
||||||
|
|
||||||
|
[node name="QuestJournalLayer" type="CanvasLayer" parent="."]
|
||||||
|
layer = 4
|
||||||
|
|
||||||
|
[node name="QuestJournal" type="Control" parent="QuestJournalLayer" node_paths=PackedStringArray("simulation_manager")]
|
||||||
|
visible = false
|
||||||
|
anchors_preset = 3
|
||||||
|
anchor_left = 1.0
|
||||||
|
anchor_top = 1.0
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
offset_left = -360.0
|
||||||
|
offset_top = -150.0
|
||||||
|
offset_right = -16.0
|
||||||
|
offset_bottom = -20.0
|
||||||
|
grow_horizontal = 0
|
||||||
|
grow_vertical = 0
|
||||||
|
mouse_filter = 2
|
||||||
|
script = ExtResource("18_quest_journal")
|
||||||
|
simulation_manager = NodePath("../../SimulationManager")
|
||||||
|
|
||||||
|
[node name="Accent" type="ColorRect" parent="QuestJournalLayer/QuestJournal"]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 9
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
offset_right = 2.0
|
||||||
|
grow_vertical = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
color = Color(0.55, 0.42, 0.2, 0.9)
|
||||||
|
|
||||||
|
[node name="Copy" type="VBoxContainer" parent="QuestJournalLayer/QuestJournal"]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
offset_left = 12.0
|
||||||
|
offset_top = 8.0
|
||||||
|
mouse_filter = 2
|
||||||
|
theme_override_constants/separation = 2
|
||||||
|
alignment = 2
|
||||||
|
|
||||||
|
[node name="Standing" type="Label" parent="QuestJournalLayer/QuestJournal/Copy"]
|
||||||
|
layout_mode = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
theme_override_colors/font_color = Color(0.98, 0.92, 0.79, 1)
|
||||||
|
theme_override_colors/font_outline_color = Color(0.07, 0.045, 0.025, 0.94)
|
||||||
|
theme_override_constants/outline_size = 6
|
||||||
|
theme_override_font_sizes/font_size = 18
|
||||||
|
text = "Standing · Stranger · 0/100"
|
||||||
|
|
||||||
|
[node name="QuestKicker" type="Label" parent="QuestJournalLayer/QuestJournal/Copy"]
|
||||||
|
layout_mode = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
theme_override_colors/font_color = Color(0.93, 0.7, 0.36, 1)
|
||||||
|
theme_override_colors/font_outline_color = Color(0.07, 0.045, 0.025, 0.9)
|
||||||
|
theme_override_constants/outline_size = 5
|
||||||
|
theme_override_font_sizes/font_size = 11
|
||||||
|
text = "ACTIVE QUEST"
|
||||||
|
|
||||||
|
[node name="Quest" type="Label" parent="QuestJournalLayer/QuestJournal/Copy"]
|
||||||
|
layout_mode = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
theme_override_colors/font_color = Color(0.9, 0.84, 0.7, 1)
|
||||||
|
theme_override_colors/font_outline_color = Color(0.07, 0.045, 0.025, 0.9)
|
||||||
|
theme_override_constants/outline_size = 4
|
||||||
|
theme_override_font_sizes/font_size = 13
|
||||||
|
text = "Amina · bring 1 food to the village pantry"
|
||||||
|
autowrap_mode = 2
|
||||||
|
|
||||||
|
[node name="Thanks" type="Label" parent="QuestJournalLayer/QuestJournal/Copy"]
|
||||||
|
visible = false
|
||||||
|
layout_mode = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
theme_override_colors/font_color = Color(0.95, 0.76, 0.42, 1)
|
||||||
|
theme_override_colors/font_outline_color = Color(0.07, 0.045, 0.025, 0.94)
|
||||||
|
theme_override_constants/outline_size = 5
|
||||||
|
theme_override_font_sizes/font_size = 13
|
||||||
|
text = "Gratitude"
|
||||||
|
horizontal_alignment = 1
|
||||||
|
autowrap_mode = 2
|
||||||
|
|
||||||
[node name="DemoController" type="Node" parent="." unique_id=644713371 node_paths=PackedStringArray("ui", "active_npcs_parent", "simulation_manager", "world_view_manager", "camera_rig", "player", "pantry_storage", "crisis_caption")]
|
[node name="DemoController" type="Node" parent="." unique_id=644713371 node_paths=PackedStringArray("ui", "active_npcs_parent", "simulation_manager", "world_view_manager", "camera_rig", "player", "pantry_storage", "crisis_caption")]
|
||||||
script = ExtResource("13_demo")
|
script = ExtResource("13_demo")
|
||||||
ui = NodePath("../UI")
|
ui = NodePath("../UI")
|
||||||
|
|||||||
@@ -0,0 +1,173 @@
|
|||||||
|
extends SceneTree
|
||||||
|
|
||||||
|
var failures: Array[String] = []
|
||||||
|
|
||||||
|
|
||||||
|
func _initialize() -> void:
|
||||||
|
call_deferred("_run")
|
||||||
|
|
||||||
|
|
||||||
|
func _run() -> void:
|
||||||
|
var main_scene: Node = load("res://main.tscn").instantiate()
|
||||||
|
root.add_child(main_scene)
|
||||||
|
await process_frame
|
||||||
|
for _frame in 10:
|
||||||
|
await physics_frame
|
||||||
|
|
||||||
|
var manager: Node = main_scene.get_node("SimulationManager")
|
||||||
|
var view: Node = main_scene.get_node("WorldViewManager")
|
||||||
|
var player: CharacterBody3D = main_scene.get_node("Player")
|
||||||
|
var journal := main_scene.get_node("QuestJournalLayer/QuestJournal")
|
||||||
|
manager.set_process(false)
|
||||||
|
player.set_physics_process(false)
|
||||||
|
_freeze_visuals(view)
|
||||||
|
|
||||||
|
var actor: SimNPC = manager.npcs[0]
|
||||||
|
var interested: SimNPC = manager.npcs[2]
|
||||||
|
_set_pantry_amount(manager, 1.0)
|
||||||
|
for npc in manager.npcs:
|
||||||
|
_set_loaded_position(manager, view, npc, Vector3(40.0 + npc.id * 10.0, 0.0, 30.0))
|
||||||
|
actor.position = Vector3.ZERO
|
||||||
|
interested.position = Vector3(4.0, 0.0, 0.0)
|
||||||
|
actor.hunger = 60.0
|
||||||
|
interested.hunger = 90.0
|
||||||
|
manager.economy.withdraw_to_inventory(actor, SimulationIds.RESOURCE_FOOD, 1.0)
|
||||||
|
var quest: PlayerQuestRecord = manager.get_active_player_quest()
|
||||||
|
_check(quest != null, "An unassisted known need should generate a player quest")
|
||||||
|
if quest == null:
|
||||||
|
manager.set_process(true)
|
||||||
|
_finish()
|
||||||
|
return
|
||||||
|
|
||||||
|
_check(
|
||||||
|
(
|
||||||
|
quest.get_requester_npc_id() == interested.id
|
||||||
|
and journal.visible
|
||||||
|
and "Standing" in (journal.get_node("Copy/Standing") as Label).text
|
||||||
|
and "Stranger" in (journal.get_node("Copy/Standing") as Label).text
|
||||||
|
and interested.npc_name in (journal.get_node("Copy/Quest") as Label).text
|
||||||
|
),
|
||||||
|
"The quest journal should surface the standing tier and the named need",
|
||||||
|
)
|
||||||
|
|
||||||
|
var food_node: ResourceNode = _find_food_node(manager)
|
||||||
|
_check(food_node != null, "The runtime proof needs a stocked player-usable food source")
|
||||||
|
if food_node == null:
|
||||||
|
_finish()
|
||||||
|
return
|
||||||
|
var checksum_before: String = manager.get_state_checksum()
|
||||||
|
_check(
|
||||||
|
manager.harvest_resource_node(food_node) > 0.0,
|
||||||
|
"The player should resolve the quest through the ordinary finite harvest"
|
||||||
|
)
|
||||||
|
_check(
|
||||||
|
(
|
||||||
|
manager.get_active_player_quest() == null
|
||||||
|
and is_equal_approx(
|
||||||
|
manager.get_player_standing().get_standing(), quest.get_standing_reward()
|
||||||
|
)
|
||||||
|
and is_equal_approx(manager.get_player_standing().get_gratitude(interested.id), 0.1)
|
||||||
|
and manager.get_state_checksum() != checksum_before
|
||||||
|
),
|
||||||
|
"Resolving the need should complete the quest and grant standing + gratitude",
|
||||||
|
)
|
||||||
|
|
||||||
|
var saved_json: String = manager.serialize_state()
|
||||||
|
var restored := _create_restored_manager(saved_json)
|
||||||
|
_check(
|
||||||
|
(
|
||||||
|
restored != null
|
||||||
|
and restored.get_state_checksum() == manager.get_state_checksum()
|
||||||
|
and restored.get_player_standing().get_resolved_needs() == 1
|
||||||
|
and is_equal_approx(
|
||||||
|
restored.get_player_standing().get_standing(), quest.get_standing_reward()
|
||||||
|
)
|
||||||
|
),
|
||||||
|
"Completed quest standing should restore deterministically through the schema",
|
||||||
|
)
|
||||||
|
manager.set_process(true)
|
||||||
|
_finish()
|
||||||
|
|
||||||
|
|
||||||
|
func _create_restored_manager(saved_json: String) -> Node:
|
||||||
|
var restored: Node = load("res://simulation/SimulationManager.gd").new()
|
||||||
|
restored.simulation_seed = 909
|
||||||
|
restored.debug_logs = false
|
||||||
|
var home_positions: Array[Vector3] = [
|
||||||
|
Vector3(0.0, 0.0, 0.0),
|
||||||
|
Vector3(2.0, 0.0, 0.0),
|
||||||
|
Vector3(10.0, 0.0, 0.0),
|
||||||
|
Vector3(12.0, 0.0, 0.0),
|
||||||
|
Vector3(30.0, 0.0, 30.0),
|
||||||
|
Vector3(40.0, 0.0, 40.0),
|
||||||
|
]
|
||||||
|
restored.home_positions = home_positions
|
||||||
|
root.add_child(restored)
|
||||||
|
restored.set_process(false)
|
||||||
|
if not restored.restore_state_from_json(saved_json):
|
||||||
|
return null
|
||||||
|
return restored
|
||||||
|
|
||||||
|
|
||||||
|
func _find_food_node(manager: Node) -> ResourceNode:
|
||||||
|
for node in ResourceNode.get_all():
|
||||||
|
var state: ResourceStateRecord = (
|
||||||
|
manager.get_resource_state(node.node_id) as ResourceStateRecord
|
||||||
|
)
|
||||||
|
if (
|
||||||
|
node.resource_id == SimulationIds.RESOURCE_FOOD
|
||||||
|
and state != null
|
||||||
|
and state.can_player_use_resource()
|
||||||
|
and state.can_extract()
|
||||||
|
and state.get_amount_remaining() >= 1.0
|
||||||
|
):
|
||||||
|
return node
|
||||||
|
return null
|
||||||
|
|
||||||
|
|
||||||
|
func _prepare_shared_patrol(first: SimNPC, second: SimNPC, position: Vector3) -> void:
|
||||||
|
for npc in [first, second]:
|
||||||
|
npc.set_task(SimulationIds.ACTION_PATROL)
|
||||||
|
npc.target_id = &"guard_post"
|
||||||
|
npc.start_working()
|
||||||
|
first.position = position
|
||||||
|
second.position = position + Vector3(1.0, 0.0, 0.0)
|
||||||
|
|
||||||
|
|
||||||
|
func _set_pantry_amount(manager: Node, amount: float) -> void:
|
||||||
|
var pantry: StorageStateRecord = manager.get_pantry()
|
||||||
|
pantry.withdraw(SimulationIds.RESOURCE_FOOD, pantry.get_amount(SimulationIds.RESOURCE_FOOD))
|
||||||
|
pantry.deposit(SimulationIds.RESOURCE_FOOD, amount)
|
||||||
|
manager.economy.sync_resource(SimulationIds.RESOURCE_FOOD)
|
||||||
|
|
||||||
|
|
||||||
|
func _set_loaded_position(manager: Node, view: Node, npc: SimNPC, position: Vector3) -> void:
|
||||||
|
manager.synchronize_npc_position(npc.id, position)
|
||||||
|
var visual := view.active_npc_visuals.get(npc.id) as Node3D
|
||||||
|
if visual != null:
|
||||||
|
visual.set_physics_process(false)
|
||||||
|
if visual.has_method("stop_travel"):
|
||||||
|
visual.stop_travel()
|
||||||
|
visual.global_position = position
|
||||||
|
|
||||||
|
|
||||||
|
func _freeze_visuals(view: Node) -> void:
|
||||||
|
for visual in view.active_npc_visuals.values():
|
||||||
|
visual.set_physics_process(false)
|
||||||
|
if visual.has_method("stop_travel"):
|
||||||
|
visual.stop_travel()
|
||||||
|
|
||||||
|
|
||||||
|
func _check(condition: bool, message: String) -> void:
|
||||||
|
if not condition:
|
||||||
|
failures.append(message)
|
||||||
|
|
||||||
|
|
||||||
|
func _finish() -> void:
|
||||||
|
if failures.is_empty():
|
||||||
|
print("[TEST] Quest journal runtime surfacing passed")
|
||||||
|
quit(0)
|
||||||
|
return
|
||||||
|
for failure in failures:
|
||||||
|
push_error("[TEST] " + failure)
|
||||||
|
quit(1)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://bl3m6a3k78cxe
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
class_name QuestJournalHud
|
||||||
|
extends Control
|
||||||
|
|
||||||
|
const ENTER_OFFSET := 8.0
|
||||||
|
const THANKS_SECONDS := 3.4
|
||||||
|
|
||||||
|
@export var simulation_manager: Node
|
||||||
|
|
||||||
|
@onready var accent: ColorRect = $Accent
|
||||||
|
@onready var copy: VBoxContainer = $Copy
|
||||||
|
@onready var standing_label: Label = $Copy/Standing
|
||||||
|
@onready var quest_label: Label = $Copy/Quest
|
||||||
|
@onready var quest_kicker: Label = $Copy/QuestKicker
|
||||||
|
@onready var thanks_label: Label = $Copy/Thanks
|
||||||
|
|
||||||
|
var resting_position: Vector2
|
||||||
|
var feedback_version := 0
|
||||||
|
var feedback_tween: Tween
|
||||||
|
var journal_tween: Tween
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
resting_position = position
|
||||||
|
visible = false
|
||||||
|
modulate.a = 0.0
|
||||||
|
if simulation_manager == null:
|
||||||
|
push_error("QuestJournalHud: simulation_manager missing")
|
||||||
|
return
|
||||||
|
if simulation_manager.has_signal("player_quest_opened"):
|
||||||
|
simulation_manager.player_quest_opened.connect(_on_quest_opened)
|
||||||
|
if simulation_manager.has_signal("player_quest_completed"):
|
||||||
|
simulation_manager.player_quest_completed.connect(_on_quest_completed)
|
||||||
|
if simulation_manager.has_signal("player_quest_expired"):
|
||||||
|
simulation_manager.player_quest_expired.connect(_on_quest_expired)
|
||||||
|
if simulation_manager.has_signal("player_standing_changed"):
|
||||||
|
simulation_manager.player_standing_changed.connect(_on_standing_changed)
|
||||||
|
if simulation_manager.has_signal("opportunity_resolved"):
|
||||||
|
simulation_manager.opportunity_resolved.connect(_on_opportunity_resolved)
|
||||||
|
if simulation_manager.has_signal("state_restored"):
|
||||||
|
simulation_manager.state_restored.connect(_on_state_restored)
|
||||||
|
_refresh()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_quest_opened(_quest: PlayerQuestRecord) -> void:
|
||||||
|
_refresh()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_quest_expired(_quest: PlayerQuestRecord) -> void:
|
||||||
|
_refresh()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_standing_changed(_standing: PlayerStandingRecord) -> void:
|
||||||
|
_refresh()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_opportunity_resolved(
|
||||||
|
_opportunity: OpportunityStateRecord, _cause_event: EconomicEventRecord
|
||||||
|
) -> void:
|
||||||
|
_refresh()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_state_restored() -> void:
|
||||||
|
_refresh()
|
||||||
|
|
||||||
|
|
||||||
|
func _refresh() -> void:
|
||||||
|
if simulation_manager == null:
|
||||||
|
return
|
||||||
|
var standing: PlayerStandingRecord = simulation_manager.get_player_standing()
|
||||||
|
if standing == null:
|
||||||
|
return
|
||||||
|
var standing_text := (
|
||||||
|
"Standing · %s · %.0f/100" % [standing.get_tier_name(), standing.get_standing()]
|
||||||
|
)
|
||||||
|
standing_label.text = standing_text
|
||||||
|
var quest: PlayerQuestRecord = simulation_manager.get_active_player_quest()
|
||||||
|
if quest == null:
|
||||||
|
quest_kicker.text = ""
|
||||||
|
quest_label.text = ""
|
||||||
|
else:
|
||||||
|
var requester_name := _get_npc_name(quest.get_requester_npc_id())
|
||||||
|
quest_kicker.text = "ACTIVE QUEST"
|
||||||
|
quest_label.text = (
|
||||||
|
"%s · bring %.0f %s to the %s"
|
||||||
|
% [
|
||||||
|
requester_name,
|
||||||
|
quest.get_target_amount(),
|
||||||
|
String(quest.get_resource_id()).replace("_", " "),
|
||||||
|
String(quest.get_target_id()).replace("_", " "),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
var has_content := not quest_kicker.text.is_empty() or not standing_text.is_empty()
|
||||||
|
if not has_content:
|
||||||
|
return
|
||||||
|
if not visible:
|
||||||
|
visible = true
|
||||||
|
modulate.a = 0.0
|
||||||
|
position = resting_position + Vector2(0.0, ENTER_OFFSET)
|
||||||
|
if journal_tween != null and journal_tween.is_valid():
|
||||||
|
journal_tween.kill()
|
||||||
|
journal_tween = create_tween().set_parallel(true)
|
||||||
|
journal_tween.set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT)
|
||||||
|
journal_tween.tween_property(self, "position", resting_position, 0.3)
|
||||||
|
journal_tween.tween_property(self, "modulate:a", 1.0, 0.22)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_quest_completed(quest: PlayerQuestRecord) -> void:
|
||||||
|
_refresh()
|
||||||
|
var requester_name := _get_npc_name(quest.get_requester_npc_id())
|
||||||
|
_show_thanks(
|
||||||
|
"Gratitude",
|
||||||
|
(
|
||||||
|
"%s thanks you for your help. +%.0f Standing"
|
||||||
|
% [requester_name, quest.get_standing_reward()]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _show_thanks(heading: String, message: String) -> void:
|
||||||
|
feedback_version += 1
|
||||||
|
var version := feedback_version
|
||||||
|
if feedback_tween != null and feedback_tween.is_valid():
|
||||||
|
feedback_tween.kill()
|
||||||
|
thanks_label.text = "%s\n%s" % [heading, message]
|
||||||
|
thanks_label.visible = true
|
||||||
|
thanks_label.modulate.a = 0.0
|
||||||
|
feedback_tween = create_tween().set_parallel(true)
|
||||||
|
feedback_tween.set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT)
|
||||||
|
feedback_tween.tween_property(thanks_label, "modulate:a", 1.0, 0.3)
|
||||||
|
feedback_tween.chain().tween_interval(THANKS_SECONDS)
|
||||||
|
feedback_tween.chain().tween_property(thanks_label, "modulate:a", 0.0, 0.5)
|
||||||
|
feedback_tween.chain().tween_callback(
|
||||||
|
func() -> void:
|
||||||
|
if version == feedback_version:
|
||||||
|
thanks_label.visible = false
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _get_npc_name(npc_id: int) -> String:
|
||||||
|
for npc in simulation_manager.npcs:
|
||||||
|
if npc.id == npc_id:
|
||||||
|
return npc.npc_name
|
||||||
|
return "Someone"
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://bj0u6l60v2o5f
|
||||||
+20
-1
@@ -86,6 +86,7 @@ func _on_village_changed(village: SimVillage) -> void:
|
|||||||
if rates["wood_per_day"] > 0.01:
|
if rates["wood_per_day"] > 0.01:
|
||||||
wood_rate = " (−%.0f/d)" % rates["wood_per_day"]
|
wood_rate = " (−%.0f/d)" % rates["wood_per_day"]
|
||||||
var opportunity_text := _build_active_opportunity_display()
|
var opportunity_text := _build_active_opportunity_display()
|
||||||
|
var standing_text := _build_standing_display()
|
||||||
village_stats_label.text = (
|
village_stats_label.text = (
|
||||||
"""
|
"""
|
||||||
Village [%s]
|
Village [%s]
|
||||||
@@ -99,7 +100,7 @@ func _on_village_changed(village: SimVillage) -> void:
|
|||||||
Food Mod: %.2f
|
Food Mod: %.2f
|
||||||
Safety Mod: %.2f
|
Safety Mod: %.2f
|
||||||
Knowledge Mod: %.2f
|
Knowledge Mod: %.2f
|
||||||
%s%s
|
%s%s%s
|
||||||
"""
|
"""
|
||||||
% [
|
% [
|
||||||
speed_text,
|
speed_text,
|
||||||
@@ -113,12 +114,30 @@ func _on_village_changed(village: SimVillage) -> void:
|
|||||||
village.food_modifier,
|
village.food_modifier,
|
||||||
village.safety_modifier,
|
village.safety_modifier,
|
||||||
village.knowledge_modifier,
|
village.knowledge_modifier,
|
||||||
|
standing_text,
|
||||||
opportunity_text,
|
opportunity_text,
|
||||||
event_text,
|
event_text,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _build_standing_display() -> String:
|
||||||
|
if not simulation_manager.has_method("get_player_standing"):
|
||||||
|
return ""
|
||||||
|
var standing: PlayerStandingRecord = simulation_manager.get_player_standing()
|
||||||
|
if standing == null:
|
||||||
|
return ""
|
||||||
|
return (
|
||||||
|
"\n\nStanding\n◆ %s · %.0f/100 · %d need%s met"
|
||||||
|
% [
|
||||||
|
standing.get_tier_name(),
|
||||||
|
standing.get_standing(),
|
||||||
|
standing.get_resolved_needs(),
|
||||||
|
"" if standing.get_resolved_needs() == 1 else "s",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
func _on_npc_decision_recorded(_npc: SimNPC, _decision: ActionSelectionResult) -> void:
|
func _on_npc_decision_recorded(_npc: SimNPC, _decision: ActionSelectionResult) -> void:
|
||||||
_refresh_npc_inspector()
|
_refresh_npc_inspector()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user