extends GutTest const RelationshipSystemScript := preload("res://simulation/relationships/RelationshipSystem.gd") const CORE_EVENT_TYPE_IDS: Array[StringName] = [ SimulationIds.EVENT_RESOURCE_EXTRACTED, SimulationIds.EVENT_STORAGE_DEPOSITED, SimulationIds.EVENT_STORAGE_WITHDRAWN, SimulationIds.EVENT_ITEM_CONSUMED, SimulationIds.EVENT_NPC_SLEPT, SimulationIds.EVENT_NPC_DIED, SimulationIds.EVENT_TASK_STARTED, SimulationIds.EVENT_TASK_BLOCKED, SimulationIds.EVENT_RESOURCE_DEPLETED, SimulationIds.EVENT_ANIMAL_FED, SimulationIds.EVENT_VILLAGER_WEAK, SimulationIds.EVENT_HOME_DAMAGED, SimulationIds.EVENT_COMMITMENT_ACCEPTED, SimulationIds.EVENT_COMMITMENT_FULFILLED, SimulationIds.EVENT_COMMITMENT_BROKEN, SimulationIds.EVENT_COMMITMENT_RELEASED, SimulationIds.EVENT_COMMITMENT_SUPERSEDED, SimulationIds.EVENT_COMBATANT_HURT, SimulationIds.EVENT_COMBATANT_KILLED, SimulationIds.EVENT_RAID_STARTED, SimulationIds.EVENT_WAR_RESOLVED, SimulationIds.EVENT_WAR_ABORTED, SimulationIds.EVENT_WOLF_HUNT, ] const CORE_SOCIAL_CONSEQUENCE_IDS: Array[StringName] = [ &"commitment_accepted_obligation", &"commitment_broken_hostility", &"commitment_broken_obligation", &"commitment_broken_trust", &"commitment_fulfilled_obligation", &"commitment_fulfilled_trust", &"commitment_released_obligation", &"commitment_superseded_obligation", &"player_food_aid_trust", &"storage_food_aid_trust", ] func test_core_catalog_authors_every_canonical_event_and_existing_social_effect() -> void: var catalog := ContentCatalog.create_core() assert_true(catalog.is_valid(), "%s" % [catalog.get_errors()]) assert_eq(_event_ids(catalog.get_event_types()), _sorted(CORE_EVENT_TYPE_IDS)) assert_eq( _consequence_ids(catalog.get_social_consequences()), _sorted(CORE_SOCIAL_CONSEQUENCE_IDS) ) for consequence in catalog.get_social_consequences(): assert_not_null(catalog.get_event_type(consequence.event_type_id)) assert_has(RelationshipStateRecord.DIMENSIONS, consequence.relationship_dimension_id) assert_true(consequence.validate().is_empty(), "%s" % [consequence.validate()]) assert_eq( float(catalog.get_social_consequence(&"storage_food_aid_trust").parameters["delta"]), RelationshipSystemScript.FOOD_AID_TRUST_GAIN ) assert_eq( float(catalog.get_social_consequence(&"player_food_aid_trust").parameters["delta"]), RelationshipSystemScript.FOOD_AID_TRUST_GAIN ) assert_eq( float( ( catalog . get_social_consequence(&"commitment_accepted_obligation") . parameters["fallback_delta"] ) ), CommitmentLifecycleService.ACCEPTED_OBLIGATION ) assert_eq( float(catalog.get_social_consequence(&"commitment_fulfilled_trust").parameters["delta"]), CommitmentLifecycleService.FULFILLED_TRUST ) assert_eq( float(catalog.get_social_consequence(&"commitment_broken_trust").parameters["delta"]), CommitmentLifecycleService.BROKEN_TRUST ) assert_eq( float(catalog.get_social_consequence(&"commitment_broken_hostility").parameters["delta"]), CommitmentLifecycleService.BROKEN_HOSTILITY ) var report := catalog.get_authoring_report() assert_eq(report["event_types"], _event_ids(catalog.get_event_types())) assert_eq(report["social_consequences"], _consequence_ids(catalog.get_social_consequences())) func test_addon_event_and_consequence_are_data_only_and_order_independent() -> void: var core := load(ContentCatalog.CORE_PACK_PATH) as SimulationContentPack var addon := _pack(&"festival_addon") addon.required_pack_ids = [&"core"] var event := _event(&"festival_shared_meal", "Festival Shared Meal") event.category = String(EventTypeDefinition.CATEGORY_SOCIAL) event.parameters = {"public": true, "salience": 0.5, "topic": &"festival"} var consequence := _consequence( &"festival_shared_meal_affection", event.event_type_id, RelationshipStateRecord.DIMENSION_AFFECTION ) consequence.parameters = {"delta": 0.05, "observer_role": "guest"} addon.event_types = [event] addon.social_consequences = [consequence] var first := ContentCatalog.new() var first_packs: Array[SimulationContentPack] = [addon, core] assert_true(first.rebuild(first_packs).is_empty(), "%s" % [first.get_errors()]) assert_same(first.get_event_type(event.event_type_id), event) assert_same(first.get_social_consequence(consequence.consequence_id), consequence) var second := ContentCatalog.new() var second_packs: Array[SimulationContentPack] = [core, addon] assert_true(second.rebuild(second_packs).is_empty(), "%s" % [second.get_errors()]) assert_eq(first.get_authoring_report(), second.get_authoring_report()) assert_eq(_event_ids(first.get_event_types()), _sorted(_event_ids(first.get_event_types()))) assert_eq( _consequence_ids(first.get_social_consequences()), _sorted(_consequence_ids(first.get_social_consequences())) ) func test_invalid_event_and_consequence_pack_publishes_nothing() -> void: var core := load(ContentCatalog.CORE_PACK_PATH) as SimulationContentPack var broken := _pack(&"broken_event_content") broken.required_pack_ids = [&"core"] var invalid_event := _event(&"invalid_event", "Invalid Event") invalid_event.parameters = {"nested": {"not": "primitive"}} broken.event_types = [core.event_types[0], invalid_event] var predicate := SituationPredicate.new() predicate.predicate_id = &"missing_event_predicate" predicate.evaluator_id = &"event_type" predicate.parameters = {"event_type": "missing_situation_event"} var situation := SituationDefinition.new() situation.situation_definition_id = &"missing_event_situation" situation.display_name = "Missing event situation" situation.trigger_predicates = [predicate] broken.situations = [situation] var invalid_consequence := _consequence(&"invalid_consequence", &"missing_event", &"rumour") invalid_consequence.parameters = {"weights": [0.1, 0.2]} broken.social_consequences = [core.social_consequences[0], invalid_consequence] var catalog := ContentCatalog.create_core() assert_true(catalog.is_valid()) var packs: Array[SimulationContentPack] = [broken, core] var errors := catalog.rebuild(packs) for expected in [ "Duplicate event_type_id", "Duplicate social consequence_id", "references unknown relationship dimension 'rumour'", "references unknown event type 'missing_event'", "references unknown event type 'missing_situation_event'", "parameter 'nested' must be primitive", "parameter 'weights' must be primitive", ]: assert_true(_has_error(errors, expected), "%s missing from %s" % [expected, errors]) assert_false(catalog.is_valid()) assert_true(catalog.get_packs().is_empty()) assert_true(catalog.get_actions().is_empty()) assert_true(catalog.get_event_types().is_empty()) assert_true(catalog.get_social_consequences().is_empty()) assert_null(catalog.get_event_type(&"invalid_event")) assert_null(catalog.get_social_consequence(&"invalid_consequence")) func _pack(pack_id: StringName) -> SimulationContentPack: var pack := SimulationContentPack.new() pack.pack_id = pack_id pack.display_name = String(pack_id).capitalize() return pack func _event(event_type_id: StringName, display_name: String) -> EventTypeDefinition: var definition := EventTypeDefinition.new() definition.event_type_id = event_type_id definition.display_name = display_name return definition func _consequence( consequence_id: StringName, event_type_id: StringName, dimension_id: StringName ) -> SocialConsequenceDefinition: var definition := SocialConsequenceDefinition.new() definition.consequence_id = consequence_id definition.display_name = String(consequence_id).capitalize() definition.event_type_id = event_type_id definition.relationship_dimension_id = dimension_id return definition func _event_ids(definitions: Array[EventTypeDefinition]) -> Array[StringName]: var ids: Array[StringName] = [] for definition in definitions: ids.append(definition.event_type_id) return ids func _consequence_ids( definitions: Array[SocialConsequenceDefinition], ) -> Array[StringName]: var ids: Array[StringName] = [] for definition in definitions: ids.append(definition.consequence_id) return ids func _sorted(ids: Array[StringName]) -> Array[StringName]: var result := ids.duplicate() result.sort_custom( func(left: StringName, right: StringName) -> bool: return String(left) < String(right) ) return result func _has_error(errors: Array[String], fragment: String) -> bool: for error in errors: if fragment in error: return true return false