665 lines
22 KiB
GDScript
665 lines
22 KiB
GDScript
extends CharacterBody3D
|
|
|
|
signal interaction_feedback(heading: String, message: String, succeeded: bool)
|
|
|
|
const DIALOGUE_MODE_CONTROLLER_SCRIPT := preload("res://world/dialogue/DialogueModeController.gd")
|
|
|
|
@export var move_speed := 7.0
|
|
@export var acceleration := 18.0
|
|
@export var rotation_speed := 12.0
|
|
@export var camera_rig: Node3D
|
|
|
|
@export var simulation_manager: Node
|
|
@export var world_view_manager: Node
|
|
|
|
@export var pantry_storage: StorageNode
|
|
@export var woodpile_storage: StorageNode
|
|
@export var guard_site: ActivitySite
|
|
@export var study_site: ActivitySite
|
|
|
|
@export var stomach_capacity_for_food := 5
|
|
@export var interaction_range := 3.0
|
|
@export var villager_inspection_range := 3.0
|
|
|
|
@onready var combat_controller: Node = get_node_or_null("PlayerCombatController")
|
|
|
|
var dialogue_mode_controller: DialogueModeController
|
|
|
|
|
|
func _ready() -> void:
|
|
add_to_group("grass_interactors")
|
|
_install_dialogue_mode_controller()
|
|
|
|
|
|
func _physics_process(delta: float) -> void:
|
|
if is_dialogue_input_locked():
|
|
velocity.x = move_toward(velocity.x, 0.0, acceleration * delta)
|
|
velocity.z = move_toward(velocity.z, 0.0, acceleration * delta)
|
|
if not is_on_floor():
|
|
velocity.y -= 30.0 * delta
|
|
else:
|
|
velocity.y = 0.0
|
|
move_and_slide()
|
|
return
|
|
if _is_player_downed():
|
|
velocity.x = move_toward(velocity.x, 0.0, acceleration * delta)
|
|
velocity.z = move_toward(velocity.z, 0.0, acceleration * delta)
|
|
move_and_slide()
|
|
return
|
|
var input := Input.get_vector("move_left", "move_right", "move_forward", "move_backward")
|
|
|
|
var forward := -camera_rig.global_transform.basis.z
|
|
var right := camera_rig.global_transform.basis.x
|
|
|
|
forward.y = 0
|
|
right.y = 0
|
|
|
|
forward = forward.normalized()
|
|
right = right.normalized()
|
|
|
|
var direction := (right * input.x + forward * -input.y).normalized()
|
|
|
|
var target_velocity := direction * move_speed
|
|
var dash_velocity := Vector3.ZERO
|
|
if combat_controller != null and combat_controller.has_method("get_dash_velocity"):
|
|
dash_velocity = combat_controller.get_dash_velocity()
|
|
if not dash_velocity.is_zero_approx():
|
|
target_velocity = dash_velocity
|
|
direction = dash_velocity.normalized()
|
|
velocity.x = target_velocity.x
|
|
velocity.z = target_velocity.z
|
|
else:
|
|
velocity.x = move_toward(velocity.x, target_velocity.x, acceleration * delta)
|
|
velocity.z = move_toward(velocity.z, target_velocity.z, acceleration * delta)
|
|
|
|
if not is_on_floor():
|
|
velocity.y -= 30.0 * delta
|
|
else:
|
|
velocity.y = 0.0
|
|
|
|
move_and_slide()
|
|
|
|
if direction.length() > 0.01:
|
|
var target_angle := atan2(direction.x, direction.z)
|
|
rotation.y = lerp_angle(rotation.y, target_angle, rotation_speed * delta)
|
|
|
|
|
|
func _is_player_downed() -> bool:
|
|
return (
|
|
simulation_manager != null
|
|
and simulation_manager.has_method("player_is_downed")
|
|
and simulation_manager.player_is_downed()
|
|
)
|
|
|
|
|
|
func _unhandled_input(event: InputEvent) -> void:
|
|
if is_dialogue_input_locked():
|
|
return
|
|
if (
|
|
event.is_action_pressed("interact")
|
|
or (event.is_action_pressed("ui_accept") and _get_nearby_dialogue_npc_id() >= 0)
|
|
):
|
|
try_interact()
|
|
|
|
|
|
func try_interact() -> void:
|
|
if is_dialogue_input_locked():
|
|
return
|
|
var context := get_interaction_context()
|
|
if context == null:
|
|
return
|
|
|
|
match context.kind:
|
|
PlayerInteractionResult.KIND_DIALOGUE:
|
|
if dialogue_mode_controller != null:
|
|
dialogue_mode_controller.begin_conversation(int(String(context.target_id)))
|
|
PlayerInteractionResult.KIND_ANIMAL:
|
|
_execute_animal_interaction(context)
|
|
PlayerInteractionResult.KIND_RESOURCE:
|
|
_execute_resource_interaction(context)
|
|
PlayerInteractionResult.KIND_ACTIVITY:
|
|
_execute_activity_interaction(context)
|
|
PlayerInteractionResult.KIND_PANTRY:
|
|
_execute_pantry_interaction(context)
|
|
PlayerInteractionResult.KIND_STORAGE_DEPOSIT:
|
|
_execute_deposit_interaction(context)
|
|
|
|
|
|
func get_interaction_context() -> PlayerInteractionResult:
|
|
if simulation_manager == null or is_dialogue_input_locked():
|
|
return null
|
|
var villager := _find_npc(_get_nearby_dialogue_npc_id())
|
|
if villager != null:
|
|
return PlayerInteractionResult.new(
|
|
PlayerInteractionResult.KIND_DIALOGUE,
|
|
DialogueModeController.TALK_ACTION_ID,
|
|
StringName(str(villager.id)),
|
|
villager.npc_name,
|
|
"Talk with %s" % villager.npc_name,
|
|
"Ask what is happening nearby",
|
|
null
|
|
)
|
|
var animal := _find_feed_animal()
|
|
if animal != null:
|
|
return _build_animal_context(animal)
|
|
var resource := _find_resource_node()
|
|
if resource != null:
|
|
return _build_resource_context(resource)
|
|
if simulation_manager.has_method("get_player_activity_offer"):
|
|
var activity_offer: Dictionary = simulation_manager.call(
|
|
"get_player_activity_offer", global_position, interaction_range
|
|
)
|
|
if not activity_offer.is_empty():
|
|
var effect: Dictionary = activity_offer.get("effect", {})
|
|
var amount := float(effect.get("amount", 0.0))
|
|
var metric_name := String(effect.get("metric_id", "benefit")).capitalize()
|
|
return (
|
|
PlayerInteractionResult
|
|
. new(
|
|
PlayerInteractionResult.KIND_ACTIVITY,
|
|
StringName(activity_offer["action_id"]),
|
|
StringName(activity_offer["target_id"]),
|
|
String(activity_offer["display_name"]),
|
|
String(activity_offer["action_name"]),
|
|
"Work here · +%.1f %s" % [amount, metric_name.to_lower()],
|
|
null,
|
|
{
|
|
"blocked_reason": String(activity_offer.get("blocked_reason", "")),
|
|
"expected_state_revision": int(activity_offer.get("state_revision", -1)),
|
|
"target_generation": int(activity_offer.get("target_generation", -1)),
|
|
"target_context_id": String(activity_offer.get("target_context_id", "")),
|
|
"target_registry_instance_id":
|
|
int(activity_offer.get("target_registry_instance_id", 0)),
|
|
}
|
|
)
|
|
)
|
|
if is_near_storage(pantry_storage):
|
|
if _get_carried_food() > 0.0:
|
|
return _build_deposit_context(SimulationIds.RESOURCE_FOOD, pantry_storage)
|
|
return _build_pantry_context()
|
|
if woodpile_storage != null and is_near_storage(woodpile_storage):
|
|
if _get_carried_wood() > 0.0:
|
|
return _build_deposit_context(SimulationIds.RESOURCE_WOOD, woodpile_storage)
|
|
return null
|
|
|
|
|
|
func is_dialogue_input_locked() -> bool:
|
|
if (
|
|
simulation_manager != null
|
|
and simulation_manager.has_method("is_dialogue_active")
|
|
and bool(simulation_manager.call("is_dialogue_active"))
|
|
):
|
|
return true
|
|
return (
|
|
dialogue_mode_controller != null
|
|
and is_instance_valid(dialogue_mode_controller)
|
|
and dialogue_mode_controller.is_dialogue_active()
|
|
)
|
|
|
|
|
|
func _install_dialogue_mode_controller() -> void:
|
|
var existing := get_node_or_null("DialogueModeController") as DialogueModeController
|
|
if existing != null:
|
|
dialogue_mode_controller = existing
|
|
else:
|
|
dialogue_mode_controller = DIALOGUE_MODE_CONTROLLER_SCRIPT.new()
|
|
dialogue_mode_controller.name = "DialogueModeController"
|
|
dialogue_mode_controller.configure(self, simulation_manager, world_view_manager)
|
|
add_child(dialogue_mode_controller)
|
|
if existing != null:
|
|
dialogue_mode_controller.configure(self, simulation_manager, world_view_manager)
|
|
|
|
|
|
func _get_nearby_dialogue_npc_id() -> int:
|
|
if (
|
|
world_view_manager == null
|
|
or not world_view_manager.has_method("find_nearest_active_npc_id")
|
|
):
|
|
return -1
|
|
return int(
|
|
world_view_manager.call(
|
|
"find_nearest_active_npc_id", global_position, villager_inspection_range
|
|
)
|
|
)
|
|
|
|
|
|
func get_nearby_villager_inspection() -> VillagerInspectionResult:
|
|
if (
|
|
simulation_manager == null
|
|
or world_view_manager == null
|
|
or not world_view_manager.has_method("find_nearest_active_npc_id")
|
|
):
|
|
return null
|
|
var npc_id: int = world_view_manager.find_nearest_active_npc_id(
|
|
global_position, villager_inspection_range
|
|
)
|
|
var npc := _find_npc(npc_id)
|
|
if npc == null:
|
|
return null
|
|
var action_name := _get_action_display_name(npc.current_task)
|
|
var target_name := _get_target_display_name(npc.target_id)
|
|
var carried_amounts := {}
|
|
for raw_item_id in npc.inventory:
|
|
var amount := npc.get_inventory_amount(StringName(raw_item_id))
|
|
if amount > 0.0:
|
|
carried_amounts[String(raw_item_id)] = amount
|
|
var reason := ""
|
|
if simulation_manager.has_method("get_latest_decision"):
|
|
var decision: ActionSelectionResult = simulation_manager.get_latest_decision(npc.id)
|
|
if decision != null and decision.action_id == npc.current_task:
|
|
reason = decision.reason
|
|
var need := _get_open_need_for_npc(npc)
|
|
return VillagerInspectionResult.new(
|
|
npc.id,
|
|
npc.npc_name,
|
|
npc.current_task,
|
|
action_name,
|
|
npc.task_state,
|
|
npc.target_id,
|
|
target_name,
|
|
carried_amounts,
|
|
reason,
|
|
need
|
|
)
|
|
|
|
|
|
func _get_open_need_for_npc(npc: SimNPC) -> Dictionary:
|
|
if not simulation_manager.has_method("get_latest_opportunity_for_npc"):
|
|
return {
|
|
"has_open_need": false,
|
|
"need_type": &"",
|
|
"progress": "",
|
|
"response": "",
|
|
}
|
|
var opportunity: OpportunityStateRecord = simulation_manager.get_latest_opportunity_for_npc(
|
|
npc.id
|
|
)
|
|
if (
|
|
opportunity == null
|
|
or opportunity.get_status() != OpportunityStateRecord.STATUS_OPEN
|
|
or opportunity.get_interested_npc_id() != npc.id
|
|
):
|
|
return {
|
|
"has_open_need": false,
|
|
"need_type": &"",
|
|
"progress": "",
|
|
"response": "",
|
|
}
|
|
var resource_name := String(opportunity.get_resource_id()).capitalize()
|
|
var current_amount := _get_need_storage_amount(opportunity)
|
|
var progress := (
|
|
"%s %.0f / %.0f" % [resource_name, current_amount, opportunity.get_target_amount()]
|
|
)
|
|
var response := "No one is able to supply this right now."
|
|
if simulation_manager.has_method("get_active_opportunity_helper"):
|
|
var helper: OpportunityHelperResult = simulation_manager.get_active_opportunity_helper()
|
|
if helper != null and helper.opportunity_id == opportunity.get_opportunity_id():
|
|
var helper_name := _get_npc_name(helper.helper_npc_id)
|
|
var action_phrase := _get_action_phrase(helper.action_id)
|
|
response = "%s can %s." % [helper_name, action_phrase]
|
|
if response == "No one is able to supply this right now.":
|
|
if simulation_manager.has_method("get_active_opportunity_player_response"):
|
|
var player_route: OpportunityPlayerResponseResult = (
|
|
simulation_manager.get_active_opportunity_player_response()
|
|
)
|
|
if player_route != null:
|
|
var route_phrase := (
|
|
"forage food"
|
|
if opportunity.get_resource_id() == SimulationIds.RESOURCE_FOOD
|
|
else "harvest a tree"
|
|
)
|
|
response = (
|
|
"You can help — %s to the %s."
|
|
% [route_phrase, _get_need_target_name(opportunity)]
|
|
)
|
|
return {
|
|
"has_open_need": true,
|
|
"need_type": opportunity.get_opportunity_type(),
|
|
"progress": progress,
|
|
"response": response,
|
|
}
|
|
|
|
|
|
func _get_need_storage_amount(opportunity: OpportunityStateRecord) -> float:
|
|
if not simulation_manager.has_method("get_pantry"):
|
|
return 0.0
|
|
match opportunity.get_target_id():
|
|
SimulationIds.STORAGE_VILLAGE_PANTRY:
|
|
var pantry: StorageStateRecord = simulation_manager.get_pantry()
|
|
return pantry.get_amount(opportunity.get_resource_id()) if pantry != null else 0.0
|
|
SimulationIds.STORAGE_VILLAGE_WOODPILE:
|
|
if simulation_manager.has_method("get_woodpile"):
|
|
var woodpile: StorageStateRecord = simulation_manager.get_woodpile()
|
|
return (
|
|
woodpile.get_amount(opportunity.get_resource_id()) if woodpile != null else 0.0
|
|
)
|
|
return 0.0
|
|
|
|
|
|
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 _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_need_target_name(opportunity: OpportunityStateRecord) -> String:
|
|
match opportunity.get_target_id():
|
|
SimulationIds.STORAGE_VILLAGE_PANTRY:
|
|
return "village pantry"
|
|
SimulationIds.STORAGE_VILLAGE_WOODPILE:
|
|
return "village woodpile"
|
|
return String(opportunity.get_target_id()).replace("_", " ")
|
|
|
|
|
|
func _find_feed_animal() -> AnimalNode:
|
|
if not ("animal_care" in simulation_manager) or simulation_manager.animal_care == null:
|
|
push_error("Player: SimulationManager has no animal-care service")
|
|
return null
|
|
return simulation_manager.animal_care.find_node_for_player(global_position, interaction_range)
|
|
|
|
|
|
func _find_resource_node() -> ResourceNode:
|
|
if not simulation_manager.has_method("find_resource_node_for_player"):
|
|
push_error("Player: SimulationManager cannot find ResourceNodes")
|
|
return null
|
|
return simulation_manager.find_resource_node_for_player(global_position, interaction_range)
|
|
|
|
|
|
func _find_npc(npc_id: int) -> SimNPC:
|
|
if npc_id < 0:
|
|
return null
|
|
for npc in simulation_manager.npcs:
|
|
if npc.id == npc_id and not npc.is_dead:
|
|
return npc
|
|
return null
|
|
|
|
|
|
func _get_action_display_name(action_id: StringName) -> String:
|
|
if action_id == SimulationIds.ACTION_IDLE:
|
|
return "Idle"
|
|
if action_id == SimulationIds.ACTION_DEAD:
|
|
return "Dead"
|
|
var definition := SimulationDefinitions.get_action(action_id)
|
|
return definition.display_name if definition != null else _display_id(action_id)
|
|
|
|
|
|
func _get_target_display_name(target_id: StringName) -> String:
|
|
if target_id.is_empty():
|
|
return "None recorded"
|
|
if "animal_care" in simulation_manager and simulation_manager.animal_care != null:
|
|
var animal_state: AnimalStateRecord = simulation_manager.animal_care.get_state(target_id)
|
|
if animal_state != null:
|
|
return animal_state.get_display_name()
|
|
var storage := StorageNode.get_by_id(target_id) as StorageNode
|
|
if storage != null:
|
|
return storage.display_name
|
|
var activity := ActivitySite.get_by_id(target_id) as ActivitySite
|
|
if activity != null:
|
|
return activity.display_name
|
|
return _display_id(target_id)
|
|
|
|
|
|
static func _display_id(value: StringName) -> String:
|
|
return String(value).replace("_", " ").capitalize()
|
|
|
|
|
|
func _build_animal_context(node: AnimalNode) -> PlayerInteractionResult:
|
|
var cost := _get_animal_feed_cost()
|
|
var pantry_food := _get_pantry_food()
|
|
var blocked_reason := ""
|
|
var detail := "%.0f pantry food · %.0f available" % [cost, pantry_food]
|
|
if pantry_food < cost:
|
|
if pantry_food <= 0.0:
|
|
blocked_reason = "The village pantry is empty."
|
|
detail = "Pantry empty · needs %.0f food" % cost
|
|
else:
|
|
blocked_reason = "The village pantry does not have enough food."
|
|
detail = "Needs %.0f pantry food · %.1f available" % [cost, pantry_food]
|
|
return PlayerInteractionResult.new(
|
|
PlayerInteractionResult.KIND_ANIMAL,
|
|
SimulationIds.ACTION_FEED_ANIMAL,
|
|
node.animal_id,
|
|
node.display_name,
|
|
"Feed %s" % node.display_name,
|
|
detail,
|
|
node,
|
|
{"blocked_reason": blocked_reason}
|
|
)
|
|
|
|
|
|
func _build_resource_context(node: ResourceNode) -> PlayerInteractionResult:
|
|
var definition := SimulationDefinitions.get_action(node.action_id)
|
|
var prompt := (
|
|
definition.display_name if definition != null else String(node.action_id).capitalize()
|
|
)
|
|
var display_name := String(node.node_id).replace("_", " ").capitalize()
|
|
return PlayerInteractionResult.new(
|
|
PlayerInteractionResult.KIND_RESOURCE,
|
|
node.action_id,
|
|
node.node_id,
|
|
display_name,
|
|
prompt,
|
|
"%s · %.1f remaining" % [display_name, node.get_amount_remaining()],
|
|
node
|
|
)
|
|
|
|
|
|
func _build_pantry_context() -> PlayerInteractionResult:
|
|
var pantry_food := _get_pantry_food()
|
|
var carried_food := _get_carried_food()
|
|
var blocked_reason := ""
|
|
var detail := "%.0f food available" % pantry_food
|
|
if pantry_food <= 0.0 and carried_food < 1.0:
|
|
blocked_reason = "The village pantry is empty and you carry no food."
|
|
detail = "Pantry empty · carry nothing"
|
|
elif pantry_food <= 0.0:
|
|
blocked_reason = "The village pantry is empty."
|
|
detail = "Pantry empty · carry %.0f food" % carried_food
|
|
return PlayerInteractionResult.new(
|
|
PlayerInteractionResult.KIND_PANTRY,
|
|
SimulationIds.ACTION_EAT,
|
|
SimulationIds.STORAGE_VILLAGE_PANTRY,
|
|
"Village pantry",
|
|
"Eat from the pantry",
|
|
detail,
|
|
pantry_storage,
|
|
{"blocked_reason": blocked_reason}
|
|
)
|
|
|
|
|
|
func _build_deposit_context(
|
|
resource_id: StringName, storage_node: StorageNode
|
|
) -> PlayerInteractionResult:
|
|
var resource_name := String(resource_id).capitalize()
|
|
var carried := (
|
|
_get_carried_food() if resource_id == SimulationIds.RESOURCE_FOOD else _get_carried_wood()
|
|
)
|
|
var action_id := (
|
|
SimulationIds.ACTION_DEPOSIT_FOOD
|
|
if resource_id == SimulationIds.RESOURCE_FOOD
|
|
else SimulationIds.ACTION_DEPOSIT_WOOD
|
|
)
|
|
return PlayerInteractionResult.new(
|
|
PlayerInteractionResult.KIND_STORAGE_DEPOSIT,
|
|
action_id,
|
|
storage_node.storage_id,
|
|
storage_node.display_name,
|
|
"Deposit %s" % resource_name.to_lower(),
|
|
"%s · carrying %.0f" % [storage_node.display_name, carried],
|
|
storage_node
|
|
)
|
|
|
|
|
|
func _execute_animal_interaction(context: PlayerInteractionResult) -> void:
|
|
var node := context.target_node as AnimalNode
|
|
if node == null or not context.is_available():
|
|
interaction_feedback.emit(
|
|
"%s is still hungry" % context.display_name,
|
|
(
|
|
context.blocked_reason
|
|
if not context.blocked_reason.is_empty()
|
|
else "Animal care is unavailable."
|
|
),
|
|
false
|
|
)
|
|
return
|
|
if not simulation_manager.has_method("feed_animal"):
|
|
push_error("Player: SimulationManager cannot feed AnimalNodes")
|
|
return
|
|
if simulation_manager.feed_animal(context.target_id, -1):
|
|
interaction_feedback.emit(
|
|
"%s is fed" % context.display_name,
|
|
"%.0f food moved from the village pantry." % _get_animal_feed_cost(),
|
|
true
|
|
)
|
|
return
|
|
interaction_feedback.emit(
|
|
"%s is still hungry" % context.display_name, "Animal care could not be completed.", false
|
|
)
|
|
|
|
|
|
func _execute_resource_interaction(context: PlayerInteractionResult) -> void:
|
|
var node := context.target_node as ResourceNode
|
|
if node == null or not simulation_manager.has_method("harvest_resource_node"):
|
|
push_error("Player: SimulationManager cannot harvest the resolved ResourceNode")
|
|
return
|
|
var extracted: float = simulation_manager.harvest_resource_node(node)
|
|
if extracted <= 0.0:
|
|
interaction_feedback.emit(
|
|
"%s is unavailable" % context.display_name, "Nothing could be gathered.", false
|
|
)
|
|
return
|
|
interaction_feedback.emit(
|
|
"%s gathered" % String(node.resource_id).capitalize(),
|
|
(
|
|
"%.1f added to your pack — deposit it at the %s."
|
|
% [extracted, _storage_name_for(node.resource_id)]
|
|
),
|
|
true
|
|
)
|
|
|
|
|
|
func _execute_activity_interaction(context: PlayerInteractionResult) -> void:
|
|
if not context.is_available():
|
|
interaction_feedback.emit(
|
|
"%s unavailable" % context.display_name, context.blocked_reason, false
|
|
)
|
|
return
|
|
if not simulation_manager.has_method("execute_player_activity_action"):
|
|
push_error("Player: SimulationManager cannot execute authored activity actions")
|
|
return
|
|
if simulation_manager.has_method("update_player_combatant"):
|
|
simulation_manager.call("update_player_combatant", global_position)
|
|
var result := (
|
|
simulation_manager.call(
|
|
"execute_player_activity_action",
|
|
context.action_id,
|
|
context.target_id,
|
|
context.expected_state_revision,
|
|
context.target_generation,
|
|
context.target_context_id,
|
|
context.target_registry_instance_id
|
|
)
|
|
as ActionResult
|
|
)
|
|
if result == null or not result.did_succeed():
|
|
var reason := result.get_message() if result != null else "Activity service unavailable."
|
|
interaction_feedback.emit("%s unavailable" % context.display_name, reason, false)
|
|
return
|
|
var payload := result.get_payload()
|
|
var amount := float(payload.get("amount", 0.0))
|
|
var metric_name := String(payload.get("metric_id", "benefit")).capitalize()
|
|
interaction_feedback.emit(
|
|
String(context.action_id).capitalize(),
|
|
"%s increased by %.1f through authoritative work." % [metric_name, amount],
|
|
true
|
|
)
|
|
|
|
|
|
func _execute_pantry_interaction(context: PlayerInteractionResult) -> void:
|
|
if not context.is_available():
|
|
interaction_feedback.emit("The pantry is empty", context.blocked_reason, false)
|
|
return
|
|
if not simulation_manager.has_method("eat_player_food"):
|
|
push_error("Player: SimulationManager cannot consume player food")
|
|
return
|
|
if simulation_manager.eat_player_food():
|
|
interaction_feedback.emit("Ate", "Hunger eased from carried or pantry food.", true)
|
|
return
|
|
interaction_feedback.emit("Nothing to eat", "The village pantry is empty.", false)
|
|
|
|
|
|
func _execute_deposit_interaction(context: PlayerInteractionResult) -> void:
|
|
if not simulation_manager.has_method("deposit_player_inventory"):
|
|
push_error("Player: SimulationManager cannot deposit player inventory")
|
|
return
|
|
var resource_id := (
|
|
SimulationIds.RESOURCE_FOOD
|
|
if context.action_id == SimulationIds.ACTION_DEPOSIT_FOOD
|
|
else SimulationIds.RESOURCE_WOOD
|
|
)
|
|
var deposited: float = simulation_manager.deposit_player_inventory(resource_id)
|
|
var resource_name := String(resource_id).capitalize()
|
|
if deposited <= 0.0:
|
|
interaction_feedback.emit(
|
|
"%s not stored" % resource_name,
|
|
"The %s is full." % context.display_name.to_lower(),
|
|
false
|
|
)
|
|
return
|
|
interaction_feedback.emit(
|
|
"%s stored" % resource_name,
|
|
"%.0f moved to the %s." % [deposited, context.display_name.to_lower()],
|
|
true
|
|
)
|
|
|
|
|
|
func _storage_name_for(resource_id: StringName) -> String:
|
|
return "village pantry" if resource_id == SimulationIds.RESOURCE_FOOD else "village woodpile"
|
|
|
|
|
|
func _get_animal_feed_cost() -> float:
|
|
var definition := SimulationDefinitions.get_action(SimulationIds.ACTION_FEED_ANIMAL)
|
|
return definition.completion_cost_amount if definition != null else 1.0
|
|
|
|
|
|
func _get_pantry_food() -> float:
|
|
if not simulation_manager.has_method("get_pantry"):
|
|
return 0.0
|
|
var pantry: StorageStateRecord = simulation_manager.get_pantry()
|
|
return pantry.get_amount(SimulationIds.RESOURCE_FOOD) if pantry != null else 0.0
|
|
|
|
|
|
func _get_carried_food() -> float:
|
|
if not simulation_manager.has_method("get_player_state"):
|
|
return 0.0
|
|
return simulation_manager.get_player_state().get_inventory_amount(SimulationIds.RESOURCE_FOOD)
|
|
|
|
|
|
func _get_carried_wood() -> float:
|
|
if not simulation_manager.has_method("get_player_state"):
|
|
return 0.0
|
|
return simulation_manager.get_player_state().get_inventory_amount(SimulationIds.RESOURCE_WOOD)
|
|
|
|
|
|
func is_near_storage(storage_node: StorageNode) -> bool:
|
|
if storage_node == null:
|
|
return false
|
|
|
|
return global_position.distance_to(storage_node.get_interaction_position()) <= interaction_range
|
|
|
|
|
|
func is_near_activity_site(activity_site: ActivitySite) -> bool:
|
|
if activity_site == null:
|
|
return false
|
|
|
|
return (
|
|
global_position.distance_to(activity_site.get_interaction_position()) <= interaction_range
|
|
)
|