feat: author world events and social effects
This commit is contained in:
@@ -0,0 +1,220 @@
|
||||
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
|
||||
@@ -0,0 +1 @@
|
||||
uid://w24xte28x0k7
|
||||
Reference in New Issue
Block a user