merge: integrate remote player quest slices

This commit is contained in:
Rijad Zuzo
2026-08-16 16:53:30 +02:00
55 changed files with 3640 additions and 54 deletions
+151 -6
View File
@@ -249,6 +249,7 @@ func get_nearby_villager_inspection() -> VillagerInspectionResult:
if decision != null and decision.action_id == npc.current_task:
reason = decision.reason
var need := _get_open_need_for_npc(npc)
need["opportunity_note"] = _build_opportunity_note(npc)
return VillagerInspectionResult.new(
npc.id,
npc.npc_name,
@@ -263,6 +264,57 @@ func get_nearby_villager_inspection() -> VillagerInspectionResult:
)
func _build_opportunity_note(npc: SimNPC) -> VillagerOpportunityNote:
if (
simulation_manager == null
or not simulation_manager.has_method("get_active_opportunity")
or not simulation_manager.has_method("get_active_opportunity_player_response")
or not simulation_manager.has_method("get_active_opportunity_helper")
):
return null
var opportunity: OpportunityStateRecord = simulation_manager.get_active_opportunity()
if (
opportunity == null
or opportunity.get_interested_npc_id() != npc.id
or (
opportunity.get_target_id()
not in [SimulationIds.STORAGE_VILLAGE_PANTRY, SimulationIds.STORAGE_VILLAGE_WOODPILE]
)
):
return null
var player_response: OpportunityPlayerResponseResult = (
simulation_manager.get_active_opportunity_player_response()
)
var helper: OpportunityHelperResult = simulation_manager.get_active_opportunity_helper()
var helper_name := ""
if helper != null:
var helper_npc := _find_npc(helper.helper_npc_id)
if helper_npc != null:
helper_name = helper_npc.npc_name
return VillagerOpportunityNote.derive(
opportunity,
player_response,
helper,
helper_name,
_get_opportunity_storage(opportunity),
_get_target_display_name(opportunity.get_target_id()),
_display_id(opportunity.get_resource_id()).to_lower()
)
func _get_opportunity_storage(opportunity: OpportunityStateRecord) -> StorageStateRecord:
if simulation_manager == null:
return null
if opportunity.get_target_id() == SimulationIds.STORAGE_VILLAGE_PANTRY:
return simulation_manager.get_pantry() as StorageStateRecord
if (
opportunity.get_target_id() == SimulationIds.STORAGE_VILLAGE_WOODPILE
and simulation_manager.has_method("get_woodpile")
):
return simulation_manager.get_woodpile() as StorageStateRecord
return null
func _get_open_need_for_npc(npc: SimNPC) -> Dictionary:
if not simulation_manager.has_method("get_latest_opportunity_for_npc"):
return {
@@ -439,14 +491,24 @@ func _build_resource_context(node: ResourceNode) -> PlayerInteractionResult:
definition.display_name if definition != null else String(node.action_id).capitalize()
)
var display_name := String(node.node_id).replace("_", " ").capitalize()
var carried := _get_player_carried(node.resource_id)
var detail := (
"%s · %.1f remaining · carrying %.1f" % [display_name, node.get_amount_remaining(), carried]
)
if _get_available_carry() <= 0.0:
detail = "Hands full — deposit before gathering"
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
detail,
node,
{
"blocked_reason":
"" if _get_available_carry() > 0.0 else "You cannot carry more right now."
}
)
@@ -526,13 +588,19 @@ func _execute_animal_interaction(context: PlayerInteractionResult) -> void:
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")
if node == null or not simulation_manager.has_method("player_gather"):
push_error("Player: SimulationManager cannot gather the resolved ResourceNode")
return
var extracted: float = simulation_manager.harvest_resource_node(node)
var extracted: float = simulation_manager.player_gather(node)
if extracted <= 0.0:
interaction_feedback.emit(
"%s is unavailable" % context.display_name, "Nothing could be gathered.", false
"%s is unavailable" % context.display_name,
(
context.blocked_reason
if not context.blocked_reason.is_empty()
else "Nothing could be gathered."
),
false
)
return
interaction_feedback.emit(
@@ -582,6 +650,38 @@ func _execute_activity_interaction(context: PlayerInteractionResult) -> void:
)
func _execute_talk_interaction(context: PlayerInteractionResult) -> void:
var npc_id := _talk_npc_id_from_target(context.target_id)
if npc_id < 0:
return
var quest: PlayerQuestRecord = simulation_manager.accept_villager_request(npc_id)
if quest == null:
interaction_feedback.emit(
"%s declines" % context.display_name,
"They have no request you can accept right now.",
false
)
return
interaction_feedback.emit(
"Request accepted",
(
"%s asked you for help · +%.0f Standing on completion"
% [context.display_name, quest.get_standing_reward()]
),
true
)
func _talk_npc_id_from_target(target_id: StringName) -> int:
var prefix := "npc_"
var suffix := "_inventory"
var raw := String(target_id)
if not raw.begins_with(prefix) or not raw.ends_with(suffix):
return -1
var id_text := raw.trim_prefix(prefix).trim_suffix(suffix)
return id_text.to_int()
func _execute_pantry_interaction(context: PlayerInteractionResult) -> void:
if not context.is_available():
interaction_feedback.emit("The pantry is empty", context.blocked_reason, false)
@@ -624,6 +724,37 @@ func _storage_name_for(resource_id: StringName) -> String:
return "village pantry" if resource_id == SimulationIds.RESOURCE_FOOD else "village woodpile"
func _get_player_hunger() -> float:
if simulation_manager == null or not simulation_manager.has_method("get_player_state"):
return 0.0
var state: PlayerStateRecord = simulation_manager.get_player_state()
return state.get_hunger() if state != null else 0.0
func is_starving() -> bool:
if simulation_manager == null or not simulation_manager.has_method("get_player_state"):
return false
var state: PlayerStateRecord = simulation_manager.get_player_state()
return state.is_starving() if state != null else false
func _needs_speed_factor() -> float:
if simulation_manager == null or not simulation_manager.has_method("get_player_state"):
return 1.0
var state: PlayerStateRecord = simulation_manager.get_player_state()
if state == null or state.is_dead():
return 0.0
var factor := 1.0
if state.is_starving():
factor *= 0.5
var energy := state.get_energy()
if energy < 20.0:
factor *= 0.6
elif energy < 45.0:
factor *= 0.8
return factor
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
@@ -648,6 +779,20 @@ func _get_carried_wood() -> float:
return simulation_manager.get_player_state().get_inventory_amount(SimulationIds.RESOURCE_WOOD)
func _get_player_carried(resource_id: StringName) -> float:
if simulation_manager == null or not simulation_manager.has_method("get_player_state"):
return 0.0
var state: PlayerStateRecord = simulation_manager.get_player_state()
return state.get_inventory_amount(resource_id) if state != null else 0.0
func _get_available_carry() -> float:
if simulation_manager == null or not simulation_manager.has_method("get_player_state"):
return 0.0
var state: PlayerStateRecord = simulation_manager.get_player_state()
return state.get_available_carry() if state != null else 0.0
func is_near_storage(storage_node: StorageNode) -> bool:
if storage_node == null:
return false