merge: integrate remote player quest slices
This commit is contained in:
@@ -7,6 +7,10 @@ const KIND_PANTRY := &"pantry"
|
||||
const KIND_STORAGE_DEPOSIT := &"storage_deposit"
|
||||
const KIND_ACTIVITY := &"activity"
|
||||
const KIND_DIALOGUE := &"dialogue"
|
||||
const KIND_DEPOSIT := &"deposit"
|
||||
const KIND_TALK := &"talk"
|
||||
const KIND_GUARD := &"guard"
|
||||
const KIND_STUDY := &"study"
|
||||
|
||||
var kind: StringName
|
||||
var action_id: StringName
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
class_name PlayerTalkResult
|
||||
extends RefCounted
|
||||
|
||||
var npc_id: int
|
||||
var npc_name: String
|
||||
var greeting: String
|
||||
var has_need: bool
|
||||
var need_text: String
|
||||
var quest_available: bool
|
||||
var quest_reward: float
|
||||
var can_accept: bool
|
||||
var can_decline: bool
|
||||
|
||||
|
||||
func _init(
|
||||
talk_npc_id: int,
|
||||
talk_npc_name: String,
|
||||
talk_greeting: String,
|
||||
talk_has_need: bool,
|
||||
talk_need_text: String,
|
||||
talk_quest_available: bool,
|
||||
talk_quest_reward: float,
|
||||
talk_can_accept: bool,
|
||||
talk_can_decline: bool
|
||||
) -> void:
|
||||
npc_id = talk_npc_id
|
||||
npc_name = talk_npc_name
|
||||
greeting = talk_greeting
|
||||
has_need = talk_has_need
|
||||
need_text = talk_need_text
|
||||
quest_available = talk_quest_available
|
||||
quest_reward = talk_quest_reward
|
||||
can_accept = talk_can_accept
|
||||
can_decline = talk_can_decline
|
||||
|
||||
|
||||
func cache_key() -> String:
|
||||
return (
|
||||
"|"
|
||||
. join(
|
||||
[
|
||||
str(npc_id),
|
||||
npc_name,
|
||||
greeting,
|
||||
str(has_need),
|
||||
need_text,
|
||||
str(quest_available),
|
||||
"%.2f" % quest_reward,
|
||||
str(can_accept),
|
||||
str(can_decline),
|
||||
]
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
uid://i7pjb5605cgr
|
||||
@@ -13,6 +13,7 @@ var target_name: String
|
||||
var carried_amounts: Dictionary
|
||||
var decision_reason: String
|
||||
var _has_decision_reason: bool
|
||||
var opportunity_note: VillagerOpportunityNote
|
||||
|
||||
var has_open_need := false
|
||||
var need_type: StringName
|
||||
@@ -46,6 +47,7 @@ func _init(
|
||||
need_type = StringName(inspection_need.get("need_type", &""))
|
||||
need_progress = String(inspection_need.get("progress", ""))
|
||||
need_response = String(inspection_need.get("response", ""))
|
||||
opportunity_note = inspection_need.get("opportunity_note") as VillagerOpportunityNote
|
||||
|
||||
|
||||
func has_decision_reason() -> bool:
|
||||
@@ -77,6 +79,7 @@ func cache_key() -> String:
|
||||
var carried_parts: Array[String] = []
|
||||
for item_id in carried_keys:
|
||||
carried_parts.append("%s=%.3f" % [String(item_id), float(carried_amounts[item_id])])
|
||||
var note_part := opportunity_note.cache_key() if opportunity_note != null else "-"
|
||||
return (
|
||||
"|"
|
||||
. join(
|
||||
@@ -94,6 +97,7 @@ func cache_key() -> String:
|
||||
String(need_type),
|
||||
need_progress,
|
||||
need_response,
|
||||
note_part,
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
class_name VillagerOpportunityNote
|
||||
extends RefCounted
|
||||
|
||||
const HELP_PLAYER_ROUTE := &"player_route"
|
||||
const HELP_HELPER := &"helper"
|
||||
const HELP_UNAVAILABLE := &"unavailable"
|
||||
|
||||
var has_need := false
|
||||
var need_text := ""
|
||||
var help_kind: StringName = &""
|
||||
var help_text := ""
|
||||
|
||||
|
||||
func _init(
|
||||
note_has_need := false,
|
||||
note_need_text := "",
|
||||
note_help_kind: StringName = &"",
|
||||
note_help_text := ""
|
||||
) -> void:
|
||||
has_need = note_has_need
|
||||
need_text = note_need_text
|
||||
help_kind = note_help_kind
|
||||
help_text = note_help_text
|
||||
|
||||
|
||||
func cache_key() -> String:
|
||||
return "%s|%s|%s|%s" % [has_need, need_text, String(help_kind), help_text]
|
||||
|
||||
|
||||
static func derive(
|
||||
opportunity: OpportunityStateRecord,
|
||||
player_response: OpportunityPlayerResponseResult,
|
||||
helper: OpportunityHelperResult,
|
||||
helper_npc_name: String,
|
||||
storage: StorageStateRecord,
|
||||
target_display_name: String,
|
||||
resource_display_name: String
|
||||
) -> VillagerOpportunityNote:
|
||||
if opportunity == null:
|
||||
return VillagerOpportunityNote.new()
|
||||
var need_text := "The %s needs %s" % [target_display_name, resource_display_name]
|
||||
if player_response != null:
|
||||
return (
|
||||
VillagerOpportunityNote
|
||||
. new(
|
||||
true,
|
||||
need_text,
|
||||
HELP_PLAYER_ROUTE,
|
||||
"You can help · forage %s to the %s" % [resource_display_name, target_display_name],
|
||||
)
|
||||
)
|
||||
if helper != null:
|
||||
return VillagerOpportunityNote.new(
|
||||
true,
|
||||
need_text,
|
||||
HELP_HELPER,
|
||||
"%s is bringing %s" % [helper_npc_name, resource_display_name]
|
||||
)
|
||||
if storage == null:
|
||||
return VillagerOpportunityNote.new(
|
||||
true, need_text, HELP_UNAVAILABLE, "No storage is within reach right now."
|
||||
)
|
||||
var remaining := maxf(
|
||||
opportunity.get_target_amount() - storage.get_amount(opportunity.get_resource_id()), 0.0
|
||||
)
|
||||
if remaining <= 0.0:
|
||||
return VillagerOpportunityNote.new(
|
||||
true,
|
||||
need_text,
|
||||
HELP_UNAVAILABLE,
|
||||
"The %s already has enough %s." % [target_display_name, resource_display_name]
|
||||
)
|
||||
if storage.get_available_capacity() < remaining:
|
||||
return VillagerOpportunityNote.new(
|
||||
true, need_text, HELP_UNAVAILABLE, "The %s has no room right now." % target_display_name
|
||||
)
|
||||
return VillagerOpportunityNote.new(
|
||||
true, need_text, HELP_UNAVAILABLE, "No stocked source is in reach right now."
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
uid://ds7o7atkwyfon
|
||||
+151
-6
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user