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] = [ &"event_type", &"event_payload_equals", &"event_participant_id", &"event_pantry_supplied" ] const DEFAULT_SITUATION_STATE_PREDICATE_IDS: Array[StringName] = [ &"state_number_lte", &"state_number_gte" ] 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] = [] var _packs: Array[SimulationContentPack] = [] var _actions: Array[ActionDefinition] = [] var _professions: Array[ProfessionDefinition] = [] var _capability_tags: Array[CapabilityTagDefinition] = [] var _items: Array[ItemDefinition] = [] var _resources: Array[ResourceDefinition] = [] var _storages: Array[StorageDefinition] = [] var _enemies: Array[EnemyDefinition] = [] var _animals: Array[AnimalDefinition] = [] var _situations: Array[SituationDefinition] = [] var _dialogue_intents: Array[ConversationIntentDefinition] = [] var _dialogue_template_catalogs: Array[ConversationTemplateCatalog] = [] var _event_types: Array[EventTypeDefinition] = [] var _social_consequences: Array[SocialConsequenceDefinition] = [] var _packs_by_id: Dictionary = {} var _actions_by_id: Dictionary = {} var _professions_by_id: Dictionary = {} var _capability_tags_by_id: Dictionary = {} var _items_by_id: Dictionary = {} var _resources_by_id: Dictionary = {} var _storages_by_id: Dictionary = {} var _enemies_by_id: Dictionary = {} var _animals_by_id: Dictionary = {} var _situations_by_id: Dictionary = {} var _dialogue_intents_by_id: Dictionary = {} var _dialogue_template_catalogs_by_id: Dictionary = {} var _event_types_by_id: Dictionary = {} var _social_consequences_by_id: Dictionary = {} static func create_core( supported_handler_ids: Array[StringName] = [], supported_presentation_cue_ids: Array[StringName] = [], supported_situation_event_predicate_ids: Array[StringName] = [], supported_situation_state_predicate_ids: Array[StringName] = [], supported_situation_expiry_predicate_ids: Array[StringName] = [] ) -> ContentCatalog: var catalog := ContentCatalog.new() var pack := load(CORE_PACK_PATH) as SimulationContentPack if pack == null: catalog._errors = ["Core content pack failed to load from '%s'" % CORE_PACK_PATH] return catalog var packs: Array[SimulationContentPack] = [pack] catalog.rebuild( packs, supported_handler_ids, supported_presentation_cue_ids, supported_situation_event_predicate_ids, supported_situation_state_predicate_ids, supported_situation_expiry_predicate_ids ) return catalog func rebuild( content_packs: Array[SimulationContentPack], supported_handler_ids: Array[StringName] = [], supported_presentation_cue_ids: Array[StringName] = [], supported_situation_event_predicate_ids: Array[StringName] = [], supported_situation_state_predicate_ids: Array[StringName] = [], supported_situation_expiry_predicate_ids: Array[StringName] = [] ) -> Array[String]: _clear_published_content() var errors: Array[String] = [] 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): 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 := ( supported_presentation_cue_ids if not supported_presentation_cue_ids.is_empty() else PresentationCueIds.get_all() ) var supported_cues := {} for cue_id in cue_ids: if cue_id.is_empty(): errors.append("Supported presentation cue ID is empty") elif supported_cues.has(cue_id): errors.append("Duplicate supported presentation cue '%s'" % cue_id) else: supported_cues[cue_id] = true var supported_event_predicates := _validated_id_set( supported_situation_event_predicate_ids, DEFAULT_SITUATION_EVENT_PREDICATE_IDS, "situation event predicate", errors ) var supported_state_predicates := _validated_id_set( supported_situation_state_predicate_ids, DEFAULT_SITUATION_STATE_PREDICATE_IDS, "situation state predicate", errors ) var supported_expiry_predicates := _validated_id_set( supported_situation_expiry_predicate_ids, DEFAULT_SITUATION_EXPIRY_PREDICATE_IDS, "situation expiry predicate", errors ) var packs_by_id := {} var actions_by_id := {} var professions_by_id := {} var capability_tags_by_id := {} var items_by_id := {} var resources_by_id := {} var storages_by_id := {} var enemies_by_id := {} var animals_by_id := {} var situations_by_id := {} var dialogue_intents_by_id := {} var dialogue_template_catalogs_by_id := {} var event_types_by_id := {} var social_consequences_by_id := {} var actions: Array[ActionDefinition] = [] var professions: Array[ProfessionDefinition] = [] var capability_tags: Array[CapabilityTagDefinition] = [] var items: Array[ItemDefinition] = [] var resources: Array[ResourceDefinition] = [] var storages: Array[StorageDefinition] = [] var enemies: Array[EnemyDefinition] = [] var animals: Array[AnimalDefinition] = [] var situations: Array[SituationDefinition] = [] var dialogue_intents: Array[ConversationIntentDefinition] = [] var dialogue_template_catalogs: Array[ConversationTemplateCatalog] = [] var event_types: Array[EventTypeDefinition] = [] var social_consequences: Array[SocialConsequenceDefinition] = [] for pack in pack_list: if pack == null: errors.append("Content pack list contains a null pack") continue for error in pack.validate(): errors.append("Pack '%s': %s" % [pack.pack_id, error]) if packs_by_id.has(pack.pack_id): errors.append("Duplicate pack_id '%s'" % pack.pack_id) else: packs_by_id[pack.pack_id] = pack _collect_actions(pack, actions, actions_by_id, errors) _collect_professions(pack, professions, professions_by_id, errors) _collect_capability_tags(pack, capability_tags, capability_tags_by_id, errors) _collect_items(pack, items, items_by_id, errors) _collect_resources(pack, resources, resources_by_id, errors) _collect_storages(pack, storages, storages_by_id, errors) _collect_enemies(pack, enemies, enemies_by_id, errors) _collect_animals(pack, animals, animals_by_id, errors) _collect_situations(pack, situations, situations_by_id, errors) _collect_dialogue_intents(pack, dialogue_intents, dialogue_intents_by_id, errors) _collect_dialogue_template_catalogs( pack, dialogue_template_catalogs, dialogue_template_catalogs_by_id, errors ) _collect_event_types(pack, event_types, event_types_by_id, errors) _collect_social_consequences(pack, social_consequences, social_consequences_by_id, errors) for pack in pack_list: if pack == null: continue for required_pack_id in pack.required_pack_ids: if not required_pack_id.is_empty() and not packs_by_id.has(required_pack_id): errors.append( ( "Pack '%s' references unknown required pack '%s'" % [pack.pack_id, required_pack_id] ) ) for required_handler_id in pack.required_handler_ids: if ( not required_handler_id.is_empty() and not supported_handlers.has(required_handler_id) ): errors.append( ( "Pack '%s' references unknown required handler '%s'" % [pack.pack_id, required_handler_id] ) ) _validate_action_references( actions, 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 ) _validate_item_references(items, capability_tags_by_id, supported_cues, errors) _validate_resource_references( resources, items_by_id, actions_by_id, capability_tags_by_id, supported_cues, errors ) _validate_storage_references( storages, items_by_id, actions_by_id, capability_tags_by_id, supported_cues, errors ) _validate_enemy_references(enemies, items_by_id, errors) _validate_animal_references(animals, actions_by_id, supported_cues, errors) _validate_situation_references( situations, actions_by_id, items_by_id, dialogue_intents_by_id, event_types_by_id, supported_event_predicates, supported_state_predicates, supported_expiry_predicates, errors ) _validate_dialogue_references( dialogue_intents, dialogue_intents_by_id, dialogue_template_catalogs_by_id, errors ) _validate_social_consequence_references(social_consequences, event_types_by_id, errors) errors.sort() _errors = errors if not errors.is_empty(): return get_errors() actions.sort_custom(_sort_actions) professions.sort_custom(_sort_professions) capability_tags.sort_custom(_sort_capability_tags) items.sort_custom(_sort_items) resources.sort_custom(_sort_resources) storages.sort_custom(_sort_storages) enemies.sort_custom(_sort_enemies) animals.sort_custom(_sort_animals) situations.sort_custom(_sort_situations) dialogue_intents.sort_custom(_sort_dialogue_intents) dialogue_template_catalogs.sort_custom(_sort_dialogue_template_catalogs) event_types.sort_custom(_sort_event_types) social_consequences.sort_custom(_sort_social_consequences) _packs = pack_list _actions = actions _professions = professions _capability_tags = capability_tags _items = items _resources = resources _storages = storages _enemies = enemies _animals = animals _situations = situations _dialogue_intents = dialogue_intents _dialogue_template_catalogs = dialogue_template_catalogs _event_types = event_types _social_consequences = social_consequences _packs_by_id = packs_by_id _actions_by_id = actions_by_id _professions_by_id = professions_by_id _capability_tags_by_id = capability_tags_by_id _items_by_id = items_by_id _resources_by_id = resources_by_id _storages_by_id = storages_by_id _enemies_by_id = enemies_by_id _animals_by_id = animals_by_id _situations_by_id = situations_by_id _dialogue_intents_by_id = dialogue_intents_by_id _dialogue_template_catalogs_by_id = dialogue_template_catalogs_by_id _event_types_by_id = event_types_by_id _social_consequences_by_id = social_consequences_by_id _valid = true return [] func is_valid() -> bool: return _valid func get_errors() -> Array[String]: return _errors.duplicate() func get_packs() -> Array[SimulationContentPack]: return _packs.duplicate() func get_actions() -> Array[ActionDefinition]: return _actions.duplicate() func get_professions() -> Array[ProfessionDefinition]: return _professions.duplicate() func get_capability_tags() -> Array[CapabilityTagDefinition]: return _capability_tags.duplicate() func get_items() -> Array[ItemDefinition]: return _items.duplicate() func get_resources() -> Array[ResourceDefinition]: return _resources.duplicate() func get_storages() -> Array[StorageDefinition]: return _storages.duplicate() func get_enemies() -> Array[EnemyDefinition]: return _enemies.duplicate() func get_animals() -> Array[AnimalDefinition]: return _animals.duplicate() func get_situations() -> Array[SituationDefinition]: return _situations.duplicate() func get_dialogue_intents() -> Array[ConversationIntentDefinition]: return _dialogue_intents.duplicate() func get_dialogue_template_catalogs() -> Array[ConversationTemplateCatalog]: return _dialogue_template_catalogs.duplicate() func get_event_types() -> Array[EventTypeDefinition]: return _event_types.duplicate() func get_social_consequences() -> Array[SocialConsequenceDefinition]: return _social_consequences.duplicate() func get_pack(pack_id: StringName) -> SimulationContentPack: return _packs_by_id.get(pack_id) as SimulationContentPack func get_action(action_id: StringName) -> ActionDefinition: return _actions_by_id.get(action_id) as ActionDefinition func get_profession(profession_id: StringName) -> ProfessionDefinition: return _professions_by_id.get(profession_id) as ProfessionDefinition func get_capability_tag(tag_id: StringName) -> CapabilityTagDefinition: return _capability_tags_by_id.get(tag_id) as CapabilityTagDefinition func get_item(item_id: StringName) -> ItemDefinition: return _items_by_id.get(item_id) as ItemDefinition func get_resource(resource_id: StringName) -> ResourceDefinition: return _resources_by_id.get(resource_id) as ResourceDefinition func get_storage(storage_id: StringName) -> StorageDefinition: return _storages_by_id.get(storage_id) as StorageDefinition func get_enemy(enemy_id: StringName) -> EnemyDefinition: return _enemies_by_id.get(enemy_id) as EnemyDefinition func get_animal(animal_definition_id: StringName) -> AnimalDefinition: return _animals_by_id.get(animal_definition_id) as AnimalDefinition func get_situation(situation_definition_id: StringName) -> SituationDefinition: return _situations_by_id.get(situation_definition_id) as SituationDefinition func get_dialogue_intent(intent_id: StringName) -> ConversationIntentDefinition: return _dialogue_intents_by_id.get(intent_id) as ConversationIntentDefinition func get_dialogue_template_catalog(catalog_id: StringName) -> ConversationTemplateCatalog: return _dialogue_template_catalogs_by_id.get(catalog_id) as ConversationTemplateCatalog func get_event_type(event_type_id: StringName) -> EventTypeDefinition: return _event_types_by_id.get(event_type_id) as EventTypeDefinition func get_social_consequence(consequence_id: StringName) -> SocialConsequenceDefinition: return _social_consequences_by_id.get(consequence_id) as SocialConsequenceDefinition func get_authoring_report() -> Dictionary: return { "valid": _valid, "errors": get_errors(), "packs": _ids_for(_packs, &"pack_id"), "actions": _ids_for(_actions, &"action_id"), "professions": _ids_for(_professions, &"profession_id"), "capability_tags": _ids_for(_capability_tags, &"tag_id"), "items": _ids_for(_items, &"item_id"), "resources": _ids_for(_resources, &"resource_id"), "storages": _ids_for(_storages, &"storage_id"), "enemies": _ids_for(_enemies, &"enemy_id"), "animals": _ids_for(_animals, &"animal_definition_id"), "situations": _ids_for(_situations, &"situation_definition_id"), "dialogue_intents": _ids_for(_dialogue_intents, &"intent_id"), "dialogue_template_catalogs": _ids_for(_dialogue_template_catalogs, &"catalog_id"), "event_types": _ids_for(_event_types, &"event_type_id"), "social_consequences": _ids_for(_social_consequences, &"consequence_id"), } func _clear_published_content() -> void: _valid = false _errors.clear() _packs.clear() _actions.clear() _professions.clear() _capability_tags.clear() _items.clear() _resources.clear() _storages.clear() _enemies.clear() _animals.clear() _situations.clear() _dialogue_intents.clear() _dialogue_template_catalogs.clear() _event_types.clear() _social_consequences.clear() _packs_by_id.clear() _actions_by_id.clear() _professions_by_id.clear() _capability_tags_by_id.clear() _items_by_id.clear() _resources_by_id.clear() _storages_by_id.clear() _enemies_by_id.clear() _animals_by_id.clear() _situations_by_id.clear() _dialogue_intents_by_id.clear() _dialogue_template_catalogs_by_id.clear() _event_types_by_id.clear() _social_consequences_by_id.clear() static func _collect_actions( pack: SimulationContentPack, definitions: Array[ActionDefinition], definitions_by_id: Dictionary, errors: Array[String] ) -> void: for definition in pack.actions: if definition == null: continue if definitions_by_id.has(definition.action_id): errors.append("Duplicate action_id '%s'" % definition.action_id) continue definitions_by_id[definition.action_id] = definition definitions.append(definition) static func _collect_professions( pack: SimulationContentPack, definitions: Array[ProfessionDefinition], definitions_by_id: Dictionary, errors: Array[String] ) -> void: for definition in pack.professions: if definition == null: continue if definitions_by_id.has(definition.profession_id): errors.append("Duplicate profession_id '%s'" % definition.profession_id) continue definitions_by_id[definition.profession_id] = definition definitions.append(definition) static func _collect_items( pack: SimulationContentPack, definitions: Array[ItemDefinition], definitions_by_id: Dictionary, errors: Array[String] ) -> void: for definition in pack.items: if definition == null: continue if definitions_by_id.has(definition.item_id): errors.append("Duplicate item_id '%s'" % definition.item_id) continue definitions_by_id[definition.item_id] = definition definitions.append(definition) static func _collect_capability_tags( pack: SimulationContentPack, definitions: Array[CapabilityTagDefinition], definitions_by_id: Dictionary, errors: Array[String] ) -> void: for definition in pack.capability_tags: if definition == null: continue if definitions_by_id.has(definition.tag_id): errors.append("Duplicate tag_id '%s'" % definition.tag_id) continue definitions_by_id[definition.tag_id] = definition definitions.append(definition) static func _collect_resources( pack: SimulationContentPack, definitions: Array[ResourceDefinition], definitions_by_id: Dictionary, errors: Array[String] ) -> void: for definition in pack.resources: if definition == null: continue if definitions_by_id.has(definition.resource_id): errors.append("Duplicate resource_id '%s'" % definition.resource_id) continue definitions_by_id[definition.resource_id] = definition definitions.append(definition) static func _collect_storages( pack: SimulationContentPack, definitions: Array[StorageDefinition], definitions_by_id: Dictionary, errors: Array[String] ) -> void: for definition in pack.storages: if definition == null: continue if definitions_by_id.has(definition.storage_id): errors.append("Duplicate storage_id '%s'" % definition.storage_id) continue definitions_by_id[definition.storage_id] = definition definitions.append(definition) static func _collect_enemies( pack: SimulationContentPack, definitions: Array[EnemyDefinition], definitions_by_id: Dictionary, errors: Array[String] ) -> void: for definition in pack.enemies: if definition == null: continue if definitions_by_id.has(definition.enemy_id): errors.append("Duplicate enemy_id '%s'" % definition.enemy_id) continue definitions_by_id[definition.enemy_id] = definition definitions.append(definition) static func _collect_animals( pack: SimulationContentPack, definitions: Array[AnimalDefinition], definitions_by_id: Dictionary, errors: Array[String] ) -> void: for definition in pack.animals: if definition == null: continue if definitions_by_id.has(definition.animal_definition_id): errors.append("Duplicate animal_definition_id '%s'" % definition.animal_definition_id) continue definitions_by_id[definition.animal_definition_id] = definition definitions.append(definition) static func _collect_situations( pack: SimulationContentPack, definitions: Array[SituationDefinition], definitions_by_id: Dictionary, errors: Array[String] ) -> void: for definition in pack.situations: if definition == null: continue if definitions_by_id.has(definition.situation_definition_id): errors.append( "Duplicate situation_definition_id '%s'" % definition.situation_definition_id ) continue definitions_by_id[definition.situation_definition_id] = definition definitions.append(definition) static func _collect_dialogue_intents( pack: SimulationContentPack, definitions: Array[ConversationIntentDefinition], definitions_by_id: Dictionary, errors: Array[String] ) -> void: for definition in pack.dialogue_intents: if definition == null: continue if definitions_by_id.has(definition.intent_id): errors.append("Duplicate dialogue intent_id '%s'" % definition.intent_id) continue definitions_by_id[definition.intent_id] = definition definitions.append(definition) static func _collect_dialogue_template_catalogs( pack: SimulationContentPack, definitions: Array[ConversationTemplateCatalog], definitions_by_id: Dictionary, errors: Array[String] ) -> void: for definition in pack.dialogue_template_catalogs: if definition == null: continue if definitions_by_id.has(definition.catalog_id): errors.append("Duplicate dialogue template catalog_id '%s'" % definition.catalog_id) continue definitions_by_id[definition.catalog_id] = definition definitions.append(definition) static func _collect_event_types( pack: SimulationContentPack, definitions: Array[EventTypeDefinition], definitions_by_id: Dictionary, errors: Array[String] ) -> void: for definition in pack.event_types: if definition == null: continue if definitions_by_id.has(definition.event_type_id): errors.append("Duplicate event_type_id '%s'" % definition.event_type_id) continue definitions_by_id[definition.event_type_id] = definition definitions.append(definition) static func _collect_social_consequences( pack: SimulationContentPack, definitions: Array[SocialConsequenceDefinition], definitions_by_id: Dictionary, errors: Array[String] ) -> void: for definition in pack.social_consequences: if definition == null: continue if definitions_by_id.has(definition.consequence_id): errors.append("Duplicate social consequence_id '%s'" % definition.consequence_id) continue definitions_by_id[definition.consequence_id] = definition definitions.append(definition) static func _validate_action_references( actions: Array[ActionDefinition], 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] ) -> void: for definition in actions: _validate_tag_ids( "Action '%s' actor" % definition.action_id, definition.actor_capability_tags, CapabilityTagDefinition.DOMAIN_ACTOR, capability_tags_by_id, errors ) _validate_tag_ids( "Action '%s' target" % definition.action_id, definition.target_capability_tags, CapabilityTagDefinition.DOMAIN_TARGET, capability_tags_by_id, errors ) _validate_presentation_cue( "Action '%s'" % definition.action_id, definition.presentation_cue_id, 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], ["completion handler", definition.completion_handler_id], ]: var handler_id: StringName = handler_field[1] if not handler_id.is_empty() and not supported_handlers.has(handler_id): errors.append( ( "Action '%s' references unknown %s '%s'" % [definition.action_id, handler_field[0], handler_id] ) ) if ( not definition.preferred_profession_id.is_empty() and not professions_by_id.has(definition.preferred_profession_id) ): errors.append( ( "Action '%s' references unknown profession '%s'" % [definition.action_id, definition.preferred_profession_id] ) ) if ( definition.target_type == SimulationIds.TARGET_RESOURCE and not actions_by_id.has(definition.resource_action_id) ): errors.append( ( "Action '%s' references unknown resource action '%s'" % [definition.action_id, definition.resource_action_id] ) ) if ( not definition.completion_cost_resource_id.is_empty() and not items_by_id.has(definition.completion_cost_resource_id) ): errors.append( ( "Action '%s' references unknown completion cost item '%s'" % [definition.action_id, definition.completion_cost_resource_id] ) ) elif not definition.completion_cost_resource_id.is_empty(): var cost_item := ( items_by_id.get(definition.completion_cost_resource_id) as ItemDefinition ) if cost_item != null and not cost_item.is_resource(): errors.append( ( "Action '%s' references non-resource completion cost item '%s'" % [definition.action_id, definition.completion_cost_resource_id] ) ) for effect in definition.effects: if effect == null: continue var effect_item_id: StringName = effect.get("item_id") if not effect_item_id.is_empty() and not items_by_id.has(effect_item_id): errors.append( ( "Action '%s' effect references unknown item '%s'" % [definition.action_id, effect_item_id] ) ) var scheduled_action_id: StringName = effect.get("scheduled_action_id") if not scheduled_action_id.is_empty() and not actions_by_id.has(scheduled_action_id): errors.append( ( "Action '%s' effect references unknown action '%s'" % [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( items: Array[ItemDefinition], capability_tags_by_id: Dictionary, supported_cues: Dictionary, errors: Array[String] ) -> void: for definition in items: _validate_tag_ids( "Item '%s'" % definition.item_id, definition.capability_tag_ids, CapabilityTagDefinition.DOMAIN_ITEM, capability_tags_by_id, errors ) _validate_presentation_cue( "Item '%s'" % definition.item_id, definition.presentation_cue_id, supported_cues, errors ) static func _validate_resource_references( resources: Array[ResourceDefinition], items_by_id: Dictionary, actions_by_id: Dictionary, capability_tags_by_id: Dictionary, supported_cues: Dictionary, errors: Array[String] ) -> void: for definition in resources: var item := items_by_id.get(definition.item_id) as ItemDefinition if item == null: errors.append( ( "Resource '%s' references unknown item '%s'" % [definition.resource_id, definition.item_id] ) ) elif not item.is_resource(): errors.append( ( "Resource '%s' references non-resource item '%s'" % [definition.resource_id, definition.item_id] ) ) var action := actions_by_id.get(definition.extraction_action_id) as ActionDefinition if action == null: errors.append( ( "Resource '%s' references unknown action '%s'" % [definition.resource_id, definition.extraction_action_id] ) ) elif action.target_type != SimulationIds.TARGET_RESOURCE: errors.append( ( "Resource '%s' references non-resource-target action '%s'" % [definition.resource_id, definition.extraction_action_id] ) ) _validate_tag_ids( "Resource '%s'" % definition.resource_id, definition.capability_tag_ids, CapabilityTagDefinition.DOMAIN_RESOURCE, capability_tags_by_id, errors ) _validate_presentation_cue( "Resource '%s'" % definition.resource_id, definition.presentation_cue_id, supported_cues, errors ) static func _validate_storage_references( storages: Array[StorageDefinition], items_by_id: Dictionary, actions_by_id: Dictionary, capability_tags_by_id: Dictionary, supported_cues: Dictionary, errors: Array[String] ) -> void: for definition in storages: for item_id in definition.accepted_item_ids: var item := items_by_id.get(item_id) as ItemDefinition if item == null: errors.append( "Storage '%s' references unknown item '%s'" % [definition.storage_id, item_id] ) elif not item.is_resource(): errors.append( "Storage '%s' accepts non-resource item '%s'" % [definition.storage_id, item_id] ) _validate_tag_ids( "Storage '%s' accepted item" % definition.storage_id, definition.accepted_item_tag_ids, CapabilityTagDefinition.DOMAIN_ITEM, capability_tags_by_id, errors ) _validate_tag_ids( "Storage '%s'" % definition.storage_id, definition.capability_tag_ids, CapabilityTagDefinition.DOMAIN_STORAGE, capability_tags_by_id, errors ) for action_id in [definition.deposit_action_id, definition.withdrawal_action_id]: if not action_id.is_empty() and not actions_by_id.has(action_id): errors.append( ( "Storage '%s' references unknown action '%s'" % [definition.storage_id, action_id] ) ) _validate_presentation_cue( "Storage '%s'" % definition.storage_id, definition.presentation_cue_id, supported_cues, errors ) static func _validate_tag_ids( owner: String, tag_ids: Array[StringName], expected_domain: StringName, capability_tags_by_id: Dictionary, errors: Array[String] ) -> void: for tag_id in tag_ids: var tag := capability_tags_by_id.get(tag_id) as CapabilityTagDefinition if tag == null: errors.append("%s references unknown tag '%s'" % [owner, tag_id]) elif tag.domain != expected_domain: errors.append( ( "%s references '%s' tag '%s' where '%s' is required" % [owner, tag.domain, tag_id, expected_domain] ) ) static func _validate_presentation_cue( owner: String, cue_id: StringName, supported_cues: Dictionary, errors: Array[String] ) -> void: if not cue_id.is_empty() and not supported_cues.has(cue_id): errors.append("%s references unknown presentation cue '%s'" % [owner, cue_id]) static func _validate_enemy_references( enemies: Array[EnemyDefinition], items_by_id: Dictionary, errors: Array[String] ) -> void: for definition in enemies: var weapon := items_by_id.get(definition.weapon_id) as ItemDefinition if weapon == null: errors.append( ( "Enemy '%s' references unknown weapon '%s'" % [definition.enemy_id, definition.weapon_id] ) ) elif not weapon.is_weapon(): errors.append( ( "Enemy '%s' references non-weapon item '%s'" % [definition.enemy_id, definition.weapon_id] ) ) static func _validate_animal_references( animals: Array[AnimalDefinition], actions_by_id: Dictionary, supported_cues: Dictionary, errors: Array[String] ) -> void: for definition in animals: _validate_presentation_cue( "Animal '%s'" % definition.animal_definition_id, definition.presentation_cue_id, supported_cues, errors ) for action_id in definition.supported_action_ids: if not actions_by_id.has(action_id): errors.append( ( "Animal '%s' references unknown action '%s'" % [definition.animal_definition_id, action_id] ) ) static func _validate_situation_references( situations: Array[SituationDefinition], actions_by_id: Dictionary, items_by_id: Dictionary, dialogue_intents_by_id: Dictionary, event_types_by_id: Dictionary, supported_event_predicates: Dictionary, supported_state_predicates: Dictionary, supported_expiry_predicates: Dictionary, errors: Array[String] ) -> void: var event_predicate_ids := _sorted_string_name_keys(supported_event_predicates) var state_predicate_ids := _sorted_string_name_keys(supported_state_predicates) var expiry_predicate_ids := _sorted_string_name_keys(supported_expiry_predicates) for definition in situations: for error in definition.validate( event_predicate_ids, state_predicate_ids, expiry_predicate_ids ): errors.append("Situation '%s': %s" % [definition.situation_definition_id, error]) for intent_id in definition.dialogue_intent_ids: if not intent_id.is_empty() and not dialogue_intents_by_id.has(intent_id): errors.append( ( "Situation '%s' references unknown dialogue intent '%s'" % [definition.situation_definition_id, intent_id] ) ) for predicate in definition.trigger_predicates + definition.resolution_predicates: if predicate == null or predicate.evaluator_id != &"event_type": continue var event_type_id := StringName(predicate.parameters.get("event_type", "")) if not event_types_by_id.has(event_type_id): errors.append( ( "Situation predicate '%s' references unknown event type '%s'" % [predicate.predicate_id, event_type_id] ) ) for alternative in definition.alternatives: if alternative == null: continue var action_id := StringName(alternative.commitment_terms.get("action_id", "")) if not action_id.is_empty() and not actions_by_id.has(action_id): errors.append( ( "Situation alternative '%s' references unknown action '%s'" % [alternative.alternative_id, action_id] ) ) var item_id := StringName(alternative.commitment_terms.get("item_id", "")) if not item_id.is_empty() and not items_by_id.has(item_id): errors.append( ( "Situation alternative '%s' references unknown item '%s'" % [alternative.alternative_id, item_id] ) ) static func _validate_dialogue_references( intents: Array[ConversationIntentDefinition], intents_by_id: Dictionary, template_catalogs_by_id: Dictionary, errors: Array[String] ) -> void: var intent_ids_by_template_catalog := {} for definition in intents: for response_intent_id in definition.response_intent_ids: if not intents_by_id.has(response_intent_id): errors.append( ( "Dialogue intent '%s' references unknown response intent '%s'" % [definition.intent_id, response_intent_id] ) ) if not template_catalogs_by_id.has(definition.template_catalog_id): errors.append( ( "Dialogue intent '%s' references unknown template catalog '%s'" % [definition.intent_id, definition.template_catalog_id] ) ) continue var catalog_intent_ids: Array[StringName] = [] for existing_intent_id in intent_ids_by_template_catalog.get( definition.template_catalog_id, [] ): catalog_intent_ids.append(StringName(existing_intent_id)) catalog_intent_ids.append(definition.intent_id) intent_ids_by_template_catalog[definition.template_catalog_id] = catalog_intent_ids for raw_catalog_id in template_catalogs_by_id: var catalog_id := StringName(raw_catalog_id) var catalog := template_catalogs_by_id.get(catalog_id) as ConversationTemplateCatalog var expected_intent_ids: Array[StringName] = [] for expected_intent_id in intent_ids_by_template_catalog.get(catalog_id, []): expected_intent_ids.append(StringName(expected_intent_id)) expected_intent_ids.sort_custom( func(left: StringName, right: StringName) -> bool: return String(left) < String(right) ) for error in catalog.validate(expected_intent_ids): errors.append("Dialogue template catalog '%s': %s" % [catalog_id, error]) static func _validate_social_consequence_references( definitions: Array[SocialConsequenceDefinition], event_types_by_id: Dictionary, errors: Array[String] ) -> void: for definition in definitions: if not event_types_by_id.has(definition.event_type_id): errors.append( ( "Social consequence '%s' references unknown event type '%s'" % [definition.consequence_id, definition.event_type_id] ) ) static func _validated_id_set( provided_ids: Array[StringName], default_ids: Array[StringName], label: String, errors: Array[String] ) -> Dictionary: var result := {} var ids := provided_ids if not provided_ids.is_empty() else default_ids for id in ids: if id.is_empty(): errors.append("Supported %s ID is empty" % label) elif result.has(id): errors.append("Duplicate supported %s '%s'" % [label, id]) else: result[id] = true return result static func _sorted_string_name_keys(source: Dictionary) -> Array[StringName]: var result: Array[StringName] = [] for key in source: result.append(StringName(key)) result.sort_custom( func(left: StringName, right: StringName) -> bool: return String(left) < String(right) ) return result static func _sort_packs(first: SimulationContentPack, second: SimulationContentPack) -> bool: if first == null: return second != null if second == null: return false return String(first.pack_id) < String(second.pack_id) static func _sort_actions(first: ActionDefinition, second: ActionDefinition) -> bool: return String(first.action_id) < String(second.action_id) static func _sort_professions(first: ProfessionDefinition, second: ProfessionDefinition) -> bool: return String(first.profession_id) < String(second.profession_id) static func _sort_capability_tags( first: CapabilityTagDefinition, second: CapabilityTagDefinition ) -> bool: return String(first.tag_id) < String(second.tag_id) static func _sort_items(first: ItemDefinition, second: ItemDefinition) -> bool: return String(first.item_id) < String(second.item_id) static func _sort_resources(first: ResourceDefinition, second: ResourceDefinition) -> bool: return String(first.resource_id) < String(second.resource_id) static func _sort_storages(first: StorageDefinition, second: StorageDefinition) -> bool: return String(first.storage_id) < String(second.storage_id) static func _sort_enemies(first: EnemyDefinition, second: EnemyDefinition) -> bool: return String(first.enemy_id) < String(second.enemy_id) static func _sort_animals(first: AnimalDefinition, second: AnimalDefinition) -> bool: return String(first.animal_definition_id) < String(second.animal_definition_id) static func _sort_situations(first: SituationDefinition, second: SituationDefinition) -> bool: return String(first.situation_definition_id) < String(second.situation_definition_id) static func _sort_dialogue_intents( first: ConversationIntentDefinition, second: ConversationIntentDefinition ) -> bool: return String(first.intent_id) < String(second.intent_id) static func _sort_dialogue_template_catalogs( first: ConversationTemplateCatalog, second: ConversationTemplateCatalog ) -> bool: return String(first.catalog_id) < String(second.catalog_id) static func _sort_event_types(first: EventTypeDefinition, second: EventTypeDefinition) -> bool: return String(first.event_type_id) < String(second.event_type_id) static func _sort_social_consequences( first: SocialConsequenceDefinition, second: SocialConsequenceDefinition ) -> bool: return String(first.consequence_id) < String(second.consequence_id) static func _ids_for(definitions: Array, id_property: StringName) -> Array[StringName]: var ids: Array[StringName] = [] for definition in definitions: ids.append(StringName(definition.get(id_property))) return ids