294 lines
9.8 KiB
GDScript
294 lines
9.8 KiB
GDScript
class_name VillageWhisperHud
|
|
extends Control
|
|
|
|
const DISPLAY_SECONDS := 4.2
|
|
const ENTER_OFFSET := 8.0
|
|
const FEEDBACK_PLAYER_RESPONSE := &"player_response"
|
|
|
|
@export var simulation_manager: Node
|
|
|
|
@onready var accent: ColorRect = $Accent
|
|
@onready var kicker: Label = $Copy/Kicker
|
|
@onready var message: Label = $Copy/Message
|
|
|
|
var feedback_tween: Tween
|
|
var resting_position: Vector2
|
|
var feedback_version := 0
|
|
var active_trigger_event_id := OpportunityStateRecord.NO_EVENT_ID
|
|
var informed_listener_id := -1
|
|
var feedback_kind := &""
|
|
var player_response_available := false
|
|
|
|
|
|
func _ready() -> void:
|
|
resting_position = position
|
|
_reset_feedback()
|
|
if simulation_manager == null:
|
|
push_error("VillageWhisperHud: simulation_manager missing")
|
|
return
|
|
simulation_manager.opportunity_opened.connect(_on_opportunity_opened)
|
|
simulation_manager.opportunity_resolved.connect(_on_opportunity_resolved)
|
|
simulation_manager.opportunity_invalidated.connect(_on_opportunity_invalidated)
|
|
simulation_manager.event_knowledge_transferred.connect(_on_event_knowledge_transferred)
|
|
simulation_manager.npc_decision_recorded.connect(_on_npc_decision_recorded)
|
|
simulation_manager.event_knowledge_changed.connect(_on_helper_facts_changed)
|
|
simulation_manager.event_knowledge_forgotten.connect(_on_helper_fact_forgotten)
|
|
simulation_manager.relationship_changed.connect(_on_helper_relationship_changed)
|
|
simulation_manager.relationship_changed.connect(_on_player_trust_changed)
|
|
simulation_manager.npc_inventory_changed.connect(_on_helper_inventory_changed)
|
|
simulation_manager.village_changed.connect(_on_village_changed)
|
|
simulation_manager.state_restored.connect(_on_state_restored)
|
|
player_response_available = simulation_manager.get_active_opportunity_player_response() != null
|
|
|
|
|
|
func _on_opportunity_opened(opportunity: OpportunityStateRecord) -> void:
|
|
active_trigger_event_id = opportunity.get_trigger_event_id()
|
|
informed_listener_id = -1
|
|
var player_response: OpportunityPlayerResponseResult = (
|
|
simulation_manager.get_active_opportunity_player_response()
|
|
)
|
|
player_response_available = player_response != null
|
|
if player_response != null:
|
|
_show_feedback(
|
|
"You can help",
|
|
_build_player_response_message(player_response),
|
|
FEEDBACK_PLAYER_RESPONSE
|
|
)
|
|
return
|
|
_show_feedback("Village need", _build_need_message(opportunity))
|
|
|
|
|
|
func _on_event_knowledge_transferred(
|
|
speaker_id: int, listener_id: int, event: EconomicEventRecord
|
|
) -> void:
|
|
var opportunity: OpportunityStateRecord = simulation_manager.get_active_opportunity()
|
|
if opportunity == null or int(event.data["event_id"]) != opportunity.get_trigger_event_id():
|
|
return
|
|
active_trigger_event_id = opportunity.get_trigger_event_id()
|
|
informed_listener_id = listener_id
|
|
_show_feedback(
|
|
"News travels",
|
|
(
|
|
"%s shares the %s with %s."
|
|
% [
|
|
_get_npc_name(speaker_id),
|
|
_get_need_name(opportunity),
|
|
_get_npc_name(listener_id),
|
|
]
|
|
)
|
|
)
|
|
|
|
|
|
func _on_npc_decision_recorded(npc: SimNPC, decision: ActionSelectionResult) -> void:
|
|
if npc.id != informed_listener_id:
|
|
return
|
|
var helper: OpportunityHelperResult = simulation_manager.get_active_opportunity_helper()
|
|
if (
|
|
helper == null
|
|
or helper.trigger_event_id != active_trigger_event_id
|
|
or helper.helper_npc_id != npc.id
|
|
or helper.action_id != decision.action_id
|
|
):
|
|
return
|
|
_show_feedback(
|
|
"Help is on the way",
|
|
(
|
|
"%s heard the need and chose to %s."
|
|
% [npc.npc_name, _get_action_phrase(decision.action_id)]
|
|
)
|
|
)
|
|
|
|
|
|
func _on_helper_facts_changed(_knower_id: int, _event: EconomicEventRecord) -> void:
|
|
_refresh_player_response()
|
|
|
|
|
|
func _on_helper_fact_forgotten(_knower_id: int, _event: EconomicEventRecord) -> void:
|
|
_refresh_player_response()
|
|
|
|
|
|
func _on_helper_relationship_changed(
|
|
_relationship: RelationshipStateRecord, _cause_event: EconomicEventRecord
|
|
) -> void:
|
|
_refresh_player_response()
|
|
|
|
|
|
func _on_player_trust_changed(
|
|
relationship: RelationshipStateRecord, cause_event: EconomicEventRecord
|
|
) -> void:
|
|
if relationship == null or cause_event == null:
|
|
return
|
|
if relationship.get_subject_id() != SimulationIds.PLAYER_ACTOR_ID:
|
|
return
|
|
if _is_active_need_supply(cause_event):
|
|
return
|
|
var observer_name := _get_npc_name(relationship.get_observer_id())
|
|
_show_feedback(
|
|
"Trust grows", "%s trusts you — you stocked the pantry." % observer_name, &"player_trust"
|
|
)
|
|
|
|
|
|
func _is_active_need_supply(cause_event: EconomicEventRecord) -> bool:
|
|
var opportunity: OpportunityStateRecord = simulation_manager.get_active_opportunity()
|
|
if opportunity == null:
|
|
return false
|
|
return (
|
|
opportunity.get_target_id() == StringName(cause_event.data["destination_id"])
|
|
and opportunity.get_resource_id() == StringName(cause_event.data["item_id"])
|
|
)
|
|
|
|
|
|
func _on_helper_inventory_changed(_npc: SimNPC, _item_id: StringName, _amount: float) -> void:
|
|
_refresh_player_response()
|
|
|
|
|
|
func _on_village_changed(_village: SimVillage) -> void:
|
|
_refresh_player_response()
|
|
|
|
|
|
func _refresh_player_response() -> void:
|
|
var response: OpportunityPlayerResponseResult = (
|
|
simulation_manager.get_active_opportunity_player_response()
|
|
)
|
|
var is_available := response != null
|
|
if is_available == player_response_available:
|
|
return
|
|
player_response_available = is_available
|
|
if response != null:
|
|
_show_feedback(
|
|
"You can help", _build_player_response_message(response), FEEDBACK_PLAYER_RESPONSE
|
|
)
|
|
elif feedback_kind == FEEDBACK_PLAYER_RESPONSE:
|
|
_reset_feedback()
|
|
|
|
|
|
func _on_opportunity_resolved(
|
|
opportunity: OpportunityStateRecord, resolution_event: EconomicEventRecord
|
|
) -> void:
|
|
if opportunity.get_trigger_event_id() != active_trigger_event_id:
|
|
return
|
|
var actor_id := int(resolution_event.data["actor_id"])
|
|
var actor_name := "The player" if actor_id < 0 else _get_npc_name(actor_id)
|
|
var outcome := (
|
|
"restocked the pantry"
|
|
if opportunity.get_resource_id() == SimulationIds.RESOURCE_FOOD
|
|
else "supplied the woodpile"
|
|
)
|
|
_show_feedback("Need met", "%s %s." % [actor_name, outcome])
|
|
informed_listener_id = -1
|
|
player_response_available = false
|
|
|
|
|
|
func _on_opportunity_invalidated(opportunity: OpportunityStateRecord) -> void:
|
|
if opportunity.get_trigger_event_id() != active_trigger_event_id:
|
|
return
|
|
_show_feedback("Need closed", "The village could no longer act on this report.")
|
|
informed_listener_id = -1
|
|
player_response_available = false
|
|
|
|
|
|
func _on_state_restored() -> void:
|
|
_reset_feedback()
|
|
var opportunity: OpportunityStateRecord = simulation_manager.get_active_opportunity()
|
|
active_trigger_event_id = (
|
|
opportunity.get_trigger_event_id()
|
|
if opportunity != null
|
|
else OpportunityStateRecord.NO_EVENT_ID
|
|
)
|
|
informed_listener_id = -1
|
|
player_response_available = simulation_manager.get_active_opportunity_player_response() != null
|
|
|
|
|
|
func _build_need_message(opportunity: OpportunityStateRecord) -> String:
|
|
var interested_name := _get_npc_name(opportunity.get_interested_npc_id())
|
|
if opportunity.get_resource_id() == SimulationIds.RESOURCE_FOOD:
|
|
return "%s worries over the empty pantry." % interested_name
|
|
var trigger: EconomicEventRecord = simulation_manager.get_opportunity_trigger_event(opportunity)
|
|
var action_name := "work"
|
|
if trigger != null:
|
|
action_name = _get_action_phrase(StringName(trigger.data.get("action_id", &"")))
|
|
return "%s needs wood to finish %s." % [interested_name, action_name]
|
|
|
|
|
|
func _build_player_response_message(response: OpportunityPlayerResponseResult) -> String:
|
|
var destination := _get_destination_name(response.target_id)
|
|
if response.resource_id == SimulationIds.RESOURCE_FOOD:
|
|
return "Forage food; it goes straight to the %s." % destination
|
|
return "Harvest a tree; wood goes straight to the %s." % destination
|
|
|
|
|
|
func _get_destination_name(target_id: StringName) -> String:
|
|
match target_id:
|
|
SimulationIds.STORAGE_VILLAGE_PANTRY:
|
|
return "village pantry"
|
|
SimulationIds.STORAGE_VILLAGE_WOODPILE:
|
|
return "village woodpile"
|
|
return String(target_id).replace("_", " ")
|
|
|
|
|
|
func _get_need_name(opportunity: OpportunityStateRecord) -> String:
|
|
return (
|
|
"empty-pantry worry"
|
|
if opportunity.get_resource_id() == SimulationIds.RESOURCE_FOOD
|
|
else "missing-wood report"
|
|
)
|
|
|
|
|
|
func _get_action_phrase(action_id: StringName) -> String:
|
|
var definition := SimulationDefinitions.get_action(action_id)
|
|
return definition.display_name.to_lower() if definition != null else String(action_id)
|
|
|
|
|
|
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"
|
|
|
|
|
|
func _show_feedback(heading: String, body: String, kind: StringName = &"") -> void:
|
|
feedback_version += 1
|
|
var version := feedback_version
|
|
if feedback_tween != null:
|
|
feedback_tween.kill()
|
|
kicker.text = heading.to_upper()
|
|
message.text = body
|
|
feedback_kind = kind
|
|
visible = true
|
|
position = resting_position + Vector2(0.0, ENTER_OFFSET)
|
|
modulate.a = 0.0
|
|
accent.pivot_offset = accent.size * 0.5
|
|
accent.scale.x = 0.15
|
|
feedback_tween = create_tween().set_parallel(true)
|
|
feedback_tween.set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT)
|
|
feedback_tween.tween_property(self, "position", resting_position, 0.32)
|
|
feedback_tween.tween_property(self, "modulate:a", 1.0, 0.24)
|
|
feedback_tween.tween_property(accent, "scale:x", 1.0, 0.38)
|
|
_fade_after_delay(version)
|
|
|
|
|
|
func _fade_after_delay(version: int) -> void:
|
|
await get_tree().create_timer(DISPLAY_SECONDS).timeout
|
|
if version != feedback_version or not is_inside_tree():
|
|
return
|
|
feedback_tween = create_tween().set_parallel(true)
|
|
feedback_tween.set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN)
|
|
feedback_tween.tween_property(self, "modulate:a", 0.0, 0.45)
|
|
feedback_tween.tween_property(self, "position:y", resting_position.y - 4.0, 0.45)
|
|
await feedback_tween.finished
|
|
if version == feedback_version:
|
|
visible = false
|
|
feedback_kind = &""
|
|
|
|
|
|
func _reset_feedback() -> void:
|
|
feedback_version += 1
|
|
if feedback_tween != null:
|
|
feedback_tween.kill()
|
|
visible = false
|
|
feedback_kind = &""
|
|
position = resting_position
|
|
modulate.a = 0.0
|
|
if accent != null:
|
|
accent.scale.x = 1.0
|