feat: unify authored activity commands

This commit is contained in:
Rijad Zuzo
2026-08-13 00:08:30 +02:00
parent e5aecee0de
commit b3b98c6456
36 changed files with 3013 additions and 96 deletions
+78 -1
View File
@@ -1,5 +1,6 @@
class_name ContentCatalog
extends RefCounted
# gdlint: disable=max-file-lines
const CORE_PACK_PATH := "res://simulation/definitions/packs/core.tres"
const DEFAULT_SITUATION_EVENT_PREDICATE_IDS: Array[StringName] = [
@@ -11,6 +12,7 @@ const DEFAULT_SITUATION_STATE_PREDICATE_IDS: Array[StringName] = [
const DEFAULT_SITUATION_EXPIRY_PREDICATE_IDS: Array[StringName] = [
&"state_number_gt", &"age_reached"
]
const BUILTIN_HANDLER_IDS: Array[StringName] = [SimulationIds.ACTION_HANDLER_ACTIVITY_METRIC_DELTA]
var _valid := false
var _errors: Array[String] = []
@@ -81,11 +83,14 @@ func rebuild(
var pack_list := content_packs.duplicate()
pack_list.sort_custom(_sort_packs)
var supported_handlers := {}
for handler_id in BUILTIN_HANDLER_IDS:
supported_handlers[handler_id] = true
for handler_id in supported_handler_ids:
if handler_id.is_empty():
errors.append("Supported handler ID is empty")
elif supported_handlers.has(handler_id):
errors.append("Duplicate supported handler '%s'" % handler_id)
if handler_id not in BUILTIN_HANDLER_IDS:
errors.append("Duplicate supported handler '%s'" % handler_id)
else:
supported_handlers[handler_id] = true
var cue_ids := (
@@ -202,7 +207,9 @@ func rebuild(
actions_by_id,
professions_by_id,
items_by_id,
storages_by_id,
capability_tags_by_id,
event_types_by_id,
supported_handlers,
supported_cues,
errors
@@ -670,7 +677,9 @@ static func _validate_action_references(
actions_by_id: Dictionary,
professions_by_id: Dictionary,
items_by_id: Dictionary,
storages_by_id: Dictionary,
capability_tags_by_id: Dictionary,
event_types_by_id: Dictionary,
supported_handlers: Dictionary,
supported_cues: Dictionary,
errors: Array[String]
@@ -696,6 +705,16 @@ static func _validate_action_references(
supported_cues,
errors
)
if (
not definition.event_type_id.is_empty()
and not event_types_by_id.has(definition.event_type_id)
):
errors.append(
(
"Action '%s' references unknown event type '%s'"
% [definition.action_id, definition.event_type_id]
)
)
for handler_field in [
["selection policy", definition.selection_policy_id],
["target policy", definition.target_policy_id],
@@ -769,6 +788,64 @@ static func _validate_action_references(
% [definition.action_id, scheduled_action_id]
)
)
_validate_builtin_action_handler_contract(definition, items_by_id, storages_by_id, errors)
static func _validate_builtin_action_handler_contract(
definition: ActionDefinition,
items_by_id: Dictionary,
storages_by_id: Dictionary,
errors: Array[String]
) -> void:
if definition.completion_handler_id != SimulationIds.ACTION_HANDLER_ACTIVITY_METRIC_DELTA:
return
var label := "Action '%s' activity_metric_delta" % definition.action_id
if definition.target_type != SimulationIds.TARGET_ACTIVITY:
errors.append("%s handler requires an activity target" % label)
if definition.effects.size() != 1:
errors.append("%s handler requires exactly one effect" % label)
return
var effect := definition.effects[0] as ActionEffect
if effect == null or effect.effect_kind != ActionEffect.KIND_METRIC_DELTA:
errors.append("%s handler requires one metric_delta effect" % label)
return
if effect.subject_key not in [&"safety", &"knowledge"]:
errors.append("%s metric must be safety or knowledge" % label)
if not is_finite(effect.value) or effect.value <= 0.0:
errors.append("%s effect value must be finite and positive" % label)
var allowed_parameters := {"player_multiplier": true, "npc_productivity": true}
for raw_key in effect.parameters:
var key := String(raw_key)
if not allowed_parameters.has(key):
errors.append("%s has unsupported effect parameter '%s'" % [label, key])
var player_multiplier: Variant = effect.parameters.get("player_multiplier", 1.0)
if player_multiplier is not int and player_multiplier is not float:
errors.append("%s player_multiplier must be numeric" % label)
elif not is_finite(float(player_multiplier)) or float(player_multiplier) <= 0.0:
errors.append("%s player_multiplier must be finite and positive" % label)
var npc_productivity: Variant = effect.parameters.get("npc_productivity", false)
if npc_productivity is not bool:
errors.append("%s npc_productivity must be a bool" % label)
if definition.event_type_id.is_empty():
errors.append("%s handler requires a nonempty event_type_id" % label)
if not definition.has_completion_cost():
return
var cost_item := items_by_id.get(definition.completion_cost_resource_id) as ItemDefinition
if cost_item == null:
return
var has_storage_route := false
for storage_value in storages_by_id.values():
var storage := storage_value as StorageDefinition
if storage != null and storage.accepts_item(cost_item):
has_storage_route = true
break
if not has_storage_route:
errors.append(
(
"%s completion cost item '%s' has no authored storage route"
% [label, definition.completion_cost_resource_id]
)
)
static func _validate_item_references(
@@ -28,6 +28,7 @@ const PROFESSION_PATHS := [
static var _action_list: Array[ActionDefinition] = []
static var _profession_list: Array[ProfessionDefinition] = []
static var _action_map: Dictionary = {}
static var _action_event_type_ids: Dictionary = {}
static var _profession_map: Dictionary = {}
static var _profession_id_list: Array[StringName] = []
static var _catalog_errors: Array[String] = []
@@ -40,6 +41,7 @@ static func _ensure_cache() -> void:
_action_list = []
_profession_list = []
_action_map = {}
_action_event_type_ids = {}
_profession_map = {}
_profession_id_list = []
_catalog_errors = []
@@ -52,6 +54,8 @@ static func _ensure_cache() -> void:
_profession_list = catalog.get_professions()
for definition in _action_list:
_action_map[definition.action_id] = definition
if not definition.event_type_id.is_empty():
_action_event_type_ids[definition.event_type_id] = true
for definition in _profession_list:
_profession_map[definition.profession_id] = definition
_profession_id_list.append(definition.profession_id)
@@ -78,6 +82,11 @@ static func get_action(action_id: StringName) -> ActionDefinition:
return _action_map.get(action_id)
static func has_action_event_type(event_type_id: StringName) -> bool:
_ensure_cache()
return _action_event_type_ids.has(event_type_id)
static func get_profession(profession_id: StringName) -> ProfessionDefinition:
_ensure_cache()
return _profession_map.get(profession_id)
+8
View File
@@ -6,6 +6,13 @@ const PLAYER_INVENTORY_ID := &"player_inventory"
const WORLD_CORE := &"world_core"
const LOCATION_JAJCE := &"settlement_jajce"
const REGIONAL_WORLD_BOSNIA := &"regional_bosnia"
const REGIONAL_LOCATION_JAJCE := &"location_jajce"
const ACTIVITY_GUARD_POST := &"guard_post"
const ACTIVITY_STUDY_DESK := &"study_desk"
const ACTION_HANDLER_ACTIVITY_METRIC_DELTA := &"activity_metric_delta"
const BLOCKED_CONTRACT_ACTION_COST := &"action_cost"
const BLOCKED_CONTRACT_LEGACY_UNSTRUCTURED := &"legacy_unstructured"
const ENTITY_PERSON := &"person"
const ENTITY_PLAYER := &"player"
@@ -72,6 +79,7 @@ const EVENT_COMMITMENT_FULFILLED := &"commitment_fulfilled"
const EVENT_COMMITMENT_BROKEN := &"commitment_broken"
const EVENT_COMMITMENT_RELEASED := &"commitment_released"
const EVENT_COMMITMENT_SUPERSEDED := &"commitment_superseded"
const EVENT_ACTIVITY_COMPLETED := &"activity_completed"
const OPPORTUNITY_RESTOCK_EMPTY_PANTRY := &"restock_empty_pantry"
const OPPORTUNITY_SUPPLY_MISSING_WOOD := &"supply_missing_wood"
+13 -1
View File
@@ -1,6 +1,15 @@
[gd_resource type="Resource" script_class="ActionDefinition" load_steps=2 format=3]
[gd_resource type="Resource" script_class="ActionDefinition" load_steps=3 format=3]
[ext_resource type="Script" path="res://simulation/definitions/ActionDefinition.gd" id="1"]
[ext_resource type="Script" path="res://simulation/commands/ActionEffect.gd" id="2"]
[sub_resource type="Resource" id="Effect_patrol_safety"]
script = ExtResource("2")
effect_id = &"patrol_safety"
effect_kind = &"metric_delta"
subject_key = &"safety"
value = 1.0
parameters = {"npc_productivity": true, "player_multiplier": 3.0}
[resource]
script = ExtResource("1")
@@ -11,3 +20,6 @@ preferred_profession_id = &"guard"
target_type = &"activity"
completion_cost_resource_id = &"wood"
completion_cost_amount = 1.0
completion_handler_id = &"activity_metric_delta"
effects = [SubResource("Effect_patrol_safety")]
event_type_id = &"activity_completed"
+13 -1
View File
@@ -1,6 +1,15 @@
[gd_resource type="Resource" script_class="ActionDefinition" load_steps=2 format=3]
[gd_resource type="Resource" script_class="ActionDefinition" load_steps=3 format=3]
[ext_resource type="Script" path="res://simulation/definitions/ActionDefinition.gd" id="1"]
[ext_resource type="Script" path="res://simulation/commands/ActionEffect.gd" id="2"]
[sub_resource type="Resource" id="Effect_study_knowledge"]
script = ExtResource("2")
effect_id = &"study_knowledge"
effect_kind = &"metric_delta"
subject_key = &"knowledge"
value = 1.0
parameters = {"npc_productivity": true, "player_multiplier": 2.0}
[resource]
script = ExtResource("1")
@@ -11,3 +20,6 @@ preferred_profession_id = &"scholar"
target_type = &"activity"
completion_cost_resource_id = &"wood"
completion_cost_amount = 1.0
completion_handler_id = &"activity_metric_delta"
effects = [SubResource("Effect_study_knowledge")]
event_type_id = &"activity_completed"
@@ -0,0 +1,10 @@
[gd_resource type="Resource" script_class="EventTypeDefinition" load_steps=2 format=3]
[ext_resource type="Script" path="res://simulation/definitions/EventTypeDefinition.gd" id="1"]
[resource]
script = ExtResource("1")
event_type_id = &"activity_completed"
display_name = "Activity Completed"
description = "An actor completed a world-backed activity and changed authoritative state."
category = "activity"
+4 -2
View File
@@ -1,4 +1,4 @@
[gd_resource type="Resource" script_class="SimulationContentPack" load_steps=93 format=3]
[gd_resource type="Resource" script_class="SimulationContentPack" load_steps=94 format=3]
[ext_resource type="Script" path="res://simulation/definitions/SimulationContentPack.gd" id="1_pack"]
[ext_resource type="Resource" path="res://simulation/definitions/actions/defend.tres" id="2_defend"]
@@ -81,6 +81,7 @@
[ext_resource type="Resource" path="res://simulation/definitions/event_types/war_resolved.tres" id="79_war_resolved"]
[ext_resource type="Resource" path="res://simulation/definitions/event_types/war_aborted.tres" id="80_war_aborted"]
[ext_resource type="Resource" path="res://simulation/definitions/event_types/wolf_hunt.tres" id="81_wolf_hunt"]
[ext_resource type="Resource" path="res://simulation/definitions/event_types/activity_completed.tres" id="92_activity_completed"]
[ext_resource type="Resource" path="res://simulation/definitions/social_consequences/storage_food_aid_trust.tres" id="82_storage_food_aid"]
[ext_resource type="Resource" path="res://simulation/definitions/social_consequences/player_food_aid_trust.tres" id="83_player_food_aid"]
[ext_resource type="Resource" path="res://simulation/definitions/social_consequences/commitment_accepted_obligation.tres" id="84_accepted_obligation"]
@@ -96,6 +97,7 @@
script = ExtResource("1_pack")
pack_id = &"core"
display_name = "The Steward Core"
required_handler_ids = [&"activity_metric_delta"]
actions = [ExtResource("2_defend"), ExtResource("3_deposit_food"), ExtResource("4_deposit_wood"), ExtResource("5_eat"), ExtResource("6_feed_animal"), ExtResource("7_gather_food"), ExtResource("8_gather_wood"), ExtResource("9_patrol"), ExtResource("10_rest"), ExtResource("11_sleep"), ExtResource("12_study"), ExtResource("13_wander"), ExtResource("14_withdraw_food")]
professions = [ExtResource("15_farmer"), ExtResource("16_guard"), ExtResource("17_scholar"), ExtResource("18_wanderer"), ExtResource("19_woodcutter")]
capability_tags = [ExtResource("28_item_food"), ExtResource("29_item_medicine"), ExtResource("30_item_wood"), ExtResource("31_resource_harvestable"), ExtResource("32_storage_village")]
@@ -107,5 +109,5 @@ animals = [ExtResource("39_goat"), ExtResource("40_sheep")]
situations = [ExtResource("41_pantry_situation")]
dialogue_intents = [ExtResource("42_accept"), ExtResource("43_acknowledge"), ExtResource("44_wellbeing"), ExtResource("45_happened"), ExtResource("46_who"), ExtResource("47_work"), ExtResource("48_decline"), ExtResource("49_need"), ExtResource("50_goodbye"), ExtResource("51_greet"), ExtResource("52_offer"), ExtResource("53_renegotiate"), ExtResource("54_progress"), ExtResource("55_reproach"), ExtResource("56_share"), ExtResource("57_thank")]
dialogue_template_catalogs = [ExtResource("58_templates")]
event_types = [ExtResource("59_resource_extracted"), ExtResource("60_storage_deposited"), ExtResource("61_storage_withdrawn"), ExtResource("62_item_consumed"), ExtResource("63_npc_slept"), ExtResource("64_npc_died"), ExtResource("65_task_started"), ExtResource("66_task_blocked"), ExtResource("67_resource_depleted"), ExtResource("68_animal_fed"), ExtResource("69_villager_weak"), ExtResource("70_home_damaged"), ExtResource("71_commitment_accepted"), ExtResource("72_commitment_fulfilled"), ExtResource("73_commitment_broken"), ExtResource("74_commitment_released"), ExtResource("75_commitment_superseded"), ExtResource("76_combatant_hurt"), ExtResource("77_combatant_killed"), ExtResource("78_raid_started"), ExtResource("79_war_resolved"), ExtResource("80_war_aborted"), ExtResource("81_wolf_hunt")]
event_types = [ExtResource("59_resource_extracted"), ExtResource("60_storage_deposited"), ExtResource("61_storage_withdrawn"), ExtResource("62_item_consumed"), ExtResource("63_npc_slept"), ExtResource("64_npc_died"), ExtResource("65_task_started"), ExtResource("66_task_blocked"), ExtResource("67_resource_depleted"), ExtResource("68_animal_fed"), ExtResource("69_villager_weak"), ExtResource("70_home_damaged"), ExtResource("71_commitment_accepted"), ExtResource("72_commitment_fulfilled"), ExtResource("73_commitment_broken"), ExtResource("74_commitment_released"), ExtResource("75_commitment_superseded"), ExtResource("76_combatant_hurt"), ExtResource("77_combatant_killed"), ExtResource("78_raid_started"), ExtResource("79_war_resolved"), ExtResource("80_war_aborted"), ExtResource("81_wolf_hunt"), ExtResource("92_activity_completed")]
social_consequences = [ExtResource("82_storage_food_aid"), ExtResource("83_player_food_aid"), ExtResource("84_accepted_obligation"), ExtResource("85_fulfilled_obligation"), ExtResource("86_fulfilled_trust"), ExtResource("87_broken_obligation"), ExtResource("88_broken_trust"), ExtResource("89_broken_hostility"), ExtResource("90_released_obligation"), ExtResource("91_superseded_obligation")]