feat: add exact player context actions
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
class_name PlayerInteractionResult
|
||||
extends RefCounted
|
||||
|
||||
const KIND_ANIMAL := &"animal"
|
||||
const KIND_RESOURCE := &"resource"
|
||||
const KIND_PANTRY := &"pantry"
|
||||
const KIND_GUARD := &"guard"
|
||||
const KIND_STUDY := &"study"
|
||||
|
||||
var kind: StringName
|
||||
var action_id: StringName
|
||||
var target_id: StringName
|
||||
var display_name: String
|
||||
var prompt_text: String
|
||||
var detail_text: String
|
||||
var blocked_reason: String
|
||||
var target_node: Node
|
||||
|
||||
|
||||
func _init(
|
||||
interaction_kind: StringName,
|
||||
interaction_action_id: StringName,
|
||||
interaction_target_id: StringName,
|
||||
interaction_display_name: String,
|
||||
interaction_prompt_text: String,
|
||||
interaction_detail_text: String,
|
||||
interaction_target_node: Node,
|
||||
interaction_blocked_reason := ""
|
||||
) -> void:
|
||||
kind = interaction_kind
|
||||
action_id = interaction_action_id
|
||||
target_id = interaction_target_id
|
||||
display_name = interaction_display_name
|
||||
prompt_text = interaction_prompt_text
|
||||
detail_text = interaction_detail_text
|
||||
target_node = interaction_target_node
|
||||
blocked_reason = interaction_blocked_reason
|
||||
|
||||
|
||||
func is_available() -> bool:
|
||||
return blocked_reason.is_empty()
|
||||
|
||||
|
||||
func cache_key() -> String:
|
||||
return (
|
||||
"|"
|
||||
. join(
|
||||
[
|
||||
String(kind),
|
||||
String(action_id),
|
||||
String(target_id),
|
||||
prompt_text,
|
||||
detail_text,
|
||||
blocked_reason,
|
||||
]
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
uid://bw2ng0q0wm030
|
||||
+185
-51
@@ -1,5 +1,7 @@
|
||||
extends CharacterBody3D
|
||||
|
||||
signal interaction_feedback(heading: String, message: String, succeeded: bool)
|
||||
|
||||
@export var move_speed := 7.0
|
||||
@export var acceleration := 18.0
|
||||
@export var rotation_speed := 12.0
|
||||
@@ -56,73 +58,205 @@ func _unhandled_input(event: InputEvent) -> void:
|
||||
|
||||
|
||||
func try_interact() -> void:
|
||||
var context := get_interaction_context()
|
||||
if context == null:
|
||||
return
|
||||
|
||||
match context.kind:
|
||||
PlayerInteractionResult.KIND_ANIMAL:
|
||||
_execute_animal_interaction(context)
|
||||
PlayerInteractionResult.KIND_RESOURCE:
|
||||
_execute_resource_interaction(context)
|
||||
PlayerInteractionResult.KIND_GUARD:
|
||||
simulation_manager.add_safety(3.0)
|
||||
interaction_feedback.emit(
|
||||
"Village guarded", "Safety increased through real guard-post work.", true
|
||||
)
|
||||
PlayerInteractionResult.KIND_STUDY:
|
||||
simulation_manager.add_knowledge(2.0)
|
||||
interaction_feedback.emit(
|
||||
"Knowledge shared", "Village knowledge increased at the study desk.", true
|
||||
)
|
||||
PlayerInteractionResult.KIND_PANTRY:
|
||||
_execute_pantry_interaction(context)
|
||||
|
||||
|
||||
func get_interaction_context() -> PlayerInteractionResult:
|
||||
if simulation_manager == null:
|
||||
return
|
||||
|
||||
if try_feed_animal():
|
||||
return
|
||||
|
||||
if try_harvest_resource_node():
|
||||
return
|
||||
|
||||
return 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 is_near_activity_site(guard_site):
|
||||
simulation_manager.add_safety(3.0)
|
||||
print("Player helped guard the village.")
|
||||
elif is_near_activity_site(study_site):
|
||||
simulation_manager.add_knowledge(2.0)
|
||||
print("Player shared knowledge.")
|
||||
elif is_near_storage(pantry_storage):
|
||||
simulation_manager.eat_food(stomach_capacity_for_food)
|
||||
print("Player ate food from the village supply.")
|
||||
return PlayerInteractionResult.new(
|
||||
PlayerInteractionResult.KIND_GUARD,
|
||||
SimulationIds.ACTION_PATROL,
|
||||
guard_site.site_id,
|
||||
guard_site.display_name,
|
||||
"Help guard the village",
|
||||
"Work here · +3 safety",
|
||||
guard_site
|
||||
)
|
||||
if is_near_activity_site(study_site):
|
||||
return PlayerInteractionResult.new(
|
||||
PlayerInteractionResult.KIND_STUDY,
|
||||
SimulationIds.ACTION_STUDY,
|
||||
study_site.site_id,
|
||||
study_site.display_name,
|
||||
"Share knowledge",
|
||||
"Work here · +2 knowledge",
|
||||
study_site
|
||||
)
|
||||
if is_near_storage(pantry_storage):
|
||||
return _build_pantry_context()
|
||||
return null
|
||||
|
||||
|
||||
func try_feed_animal() -> bool:
|
||||
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 false
|
||||
var node: AnimalNode = simulation_manager.animal_care.find_node_for_player(
|
||||
global_position, interaction_range
|
||||
)
|
||||
if node == null:
|
||||
return false
|
||||
if not simulation_manager.has_method("feed_animal"):
|
||||
push_error("Player: SimulationManager cannot feed AnimalNodes")
|
||||
return true
|
||||
if simulation_manager.feed_animal(node.animal_id, -1):
|
||||
print("Player fed %s one food from the village pantry." % node.display_name)
|
||||
else:
|
||||
print("%s is hungry, but the pantry has no food to spare." % node.display_name)
|
||||
return true
|
||||
return null
|
||||
return simulation_manager.animal_care.find_node_for_player(global_position, interaction_range)
|
||||
|
||||
|
||||
func try_harvest_resource_node() -> bool:
|
||||
func _find_resource_node() -> ResourceNode:
|
||||
if not simulation_manager.has_method("find_resource_node_for_player"):
|
||||
push_error("Player: SimulationManager cannot find ResourceNodes")
|
||||
return false
|
||||
var node: ResourceNode = simulation_manager.find_resource_node_for_player(
|
||||
global_position, interaction_range
|
||||
return null
|
||||
return simulation_manager.find_resource_node_for_player(global_position, interaction_range)
|
||||
|
||||
|
||||
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
|
||||
)
|
||||
if node == null:
|
||||
return false
|
||||
|
||||
if not node.is_enabled():
|
||||
print("%s is unavailable." % node.node_id)
|
||||
return true
|
||||
|
||||
if node.is_depleted():
|
||||
print("%s is depleted." % node.node_id)
|
||||
return true
|
||||
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
|
||||
)
|
||||
|
||||
if not simulation_manager.has_method("harvest_resource_node"):
|
||||
push_error("Player: SimulationManager cannot harvest ResourceNodes")
|
||||
return true
|
||||
|
||||
func _build_pantry_context() -> PlayerInteractionResult:
|
||||
var pantry_food := _get_pantry_food()
|
||||
var blocked_reason := "" if pantry_food > 0.0 else "The village pantry is empty."
|
||||
var detail := "%.0f food available" % pantry_food
|
||||
if not blocked_reason.is_empty():
|
||||
detail = "Pantry empty"
|
||||
return PlayerInteractionResult.new(
|
||||
PlayerInteractionResult.KIND_PANTRY,
|
||||
SimulationIds.ACTION_EAT,
|
||||
SimulationIds.STORAGE_VILLAGE_PANTRY,
|
||||
"Village pantry",
|
||||
"Eat from the pantry",
|
||||
detail,
|
||||
pantry_storage,
|
||||
blocked_reason
|
||||
)
|
||||
|
||||
|
||||
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:
|
||||
print("Player gathered %s %s from %s." % [extracted, node.resource_id, node.node_id])
|
||||
else:
|
||||
print("%s could not be harvested." % node.node_id)
|
||||
return true
|
||||
if extracted <= 0.0:
|
||||
interaction_feedback.emit(
|
||||
"%s is unavailable" % context.display_name, "Nothing could be gathered.", false
|
||||
)
|
||||
return
|
||||
var destination := "pantry" if node.resource_id == SimulationIds.RESOURCE_FOOD else "woodpile"
|
||||
interaction_feedback.emit(
|
||||
"%s gathered" % String(node.resource_id).capitalize(),
|
||||
"%.1f moved to the village %s." % [extracted, destination],
|
||||
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
|
||||
var food_before := _get_pantry_food()
|
||||
simulation_manager.eat_food(stomach_capacity_for_food)
|
||||
var consumed := food_before - _get_pantry_food()
|
||||
interaction_feedback.emit(
|
||||
"Ate from the pantry", "%.0f village food consumed." % consumed, consumed > 0.0
|
||||
)
|
||||
|
||||
|
||||
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 is_near_storage(storage_node: StorageNode) -> bool:
|
||||
|
||||
Reference in New Issue
Block a user