feat: author emergent situations and dialogue
This commit is contained in:
@@ -2,6 +2,15 @@ class_name ContentCatalog
|
|||||||
extends RefCounted
|
extends RefCounted
|
||||||
|
|
||||||
const CORE_PACK_PATH := "res://simulation/definitions/packs/core.tres"
|
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"
|
||||||
|
]
|
||||||
|
|
||||||
var _valid := false
|
var _valid := false
|
||||||
var _errors: Array[String] = []
|
var _errors: Array[String] = []
|
||||||
@@ -14,6 +23,9 @@ var _resources: Array[ResourceDefinition] = []
|
|||||||
var _storages: Array[StorageDefinition] = []
|
var _storages: Array[StorageDefinition] = []
|
||||||
var _enemies: Array[EnemyDefinition] = []
|
var _enemies: Array[EnemyDefinition] = []
|
||||||
var _animals: Array[AnimalDefinition] = []
|
var _animals: Array[AnimalDefinition] = []
|
||||||
|
var _situations: Array[SituationDefinition] = []
|
||||||
|
var _dialogue_intents: Array[ConversationIntentDefinition] = []
|
||||||
|
var _dialogue_template_catalogs: Array[ConversationTemplateCatalog] = []
|
||||||
var _packs_by_id: Dictionary = {}
|
var _packs_by_id: Dictionary = {}
|
||||||
var _actions_by_id: Dictionary = {}
|
var _actions_by_id: Dictionary = {}
|
||||||
var _professions_by_id: Dictionary = {}
|
var _professions_by_id: Dictionary = {}
|
||||||
@@ -23,11 +35,17 @@ var _resources_by_id: Dictionary = {}
|
|||||||
var _storages_by_id: Dictionary = {}
|
var _storages_by_id: Dictionary = {}
|
||||||
var _enemies_by_id: Dictionary = {}
|
var _enemies_by_id: Dictionary = {}
|
||||||
var _animals_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 = {}
|
||||||
|
|
||||||
|
|
||||||
static func create_core(
|
static func create_core(
|
||||||
supported_handler_ids: Array[StringName] = [],
|
supported_handler_ids: Array[StringName] = [],
|
||||||
supported_presentation_cue_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:
|
) -> ContentCatalog:
|
||||||
var catalog := ContentCatalog.new()
|
var catalog := ContentCatalog.new()
|
||||||
var pack := load(CORE_PACK_PATH) as SimulationContentPack
|
var pack := load(CORE_PACK_PATH) as SimulationContentPack
|
||||||
@@ -35,14 +53,24 @@ static func create_core(
|
|||||||
catalog._errors = ["Core content pack failed to load from '%s'" % CORE_PACK_PATH]
|
catalog._errors = ["Core content pack failed to load from '%s'" % CORE_PACK_PATH]
|
||||||
return catalog
|
return catalog
|
||||||
var packs: Array[SimulationContentPack] = [pack]
|
var packs: Array[SimulationContentPack] = [pack]
|
||||||
catalog.rebuild(packs, supported_handler_ids, supported_presentation_cue_ids)
|
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
|
return catalog
|
||||||
|
|
||||||
|
|
||||||
func rebuild(
|
func rebuild(
|
||||||
content_packs: Array[SimulationContentPack],
|
content_packs: Array[SimulationContentPack],
|
||||||
supported_handler_ids: Array[StringName] = [],
|
supported_handler_ids: Array[StringName] = [],
|
||||||
supported_presentation_cue_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]:
|
) -> Array[String]:
|
||||||
_clear_published_content()
|
_clear_published_content()
|
||||||
var errors: Array[String] = []
|
var errors: Array[String] = []
|
||||||
@@ -69,6 +97,24 @@ func rebuild(
|
|||||||
errors.append("Duplicate supported presentation cue '%s'" % cue_id)
|
errors.append("Duplicate supported presentation cue '%s'" % cue_id)
|
||||||
else:
|
else:
|
||||||
supported_cues[cue_id] = true
|
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 packs_by_id := {}
|
||||||
var actions_by_id := {}
|
var actions_by_id := {}
|
||||||
@@ -79,6 +125,9 @@ func rebuild(
|
|||||||
var storages_by_id := {}
|
var storages_by_id := {}
|
||||||
var enemies_by_id := {}
|
var enemies_by_id := {}
|
||||||
var animals_by_id := {}
|
var animals_by_id := {}
|
||||||
|
var situations_by_id := {}
|
||||||
|
var dialogue_intents_by_id := {}
|
||||||
|
var dialogue_template_catalogs_by_id := {}
|
||||||
var actions: Array[ActionDefinition] = []
|
var actions: Array[ActionDefinition] = []
|
||||||
var professions: Array[ProfessionDefinition] = []
|
var professions: Array[ProfessionDefinition] = []
|
||||||
var capability_tags: Array[CapabilityTagDefinition] = []
|
var capability_tags: Array[CapabilityTagDefinition] = []
|
||||||
@@ -87,6 +136,9 @@ func rebuild(
|
|||||||
var storages: Array[StorageDefinition] = []
|
var storages: Array[StorageDefinition] = []
|
||||||
var enemies: Array[EnemyDefinition] = []
|
var enemies: Array[EnemyDefinition] = []
|
||||||
var animals: Array[AnimalDefinition] = []
|
var animals: Array[AnimalDefinition] = []
|
||||||
|
var situations: Array[SituationDefinition] = []
|
||||||
|
var dialogue_intents: Array[ConversationIntentDefinition] = []
|
||||||
|
var dialogue_template_catalogs: Array[ConversationTemplateCatalog] = []
|
||||||
|
|
||||||
for pack in pack_list:
|
for pack in pack_list:
|
||||||
if pack == null:
|
if pack == null:
|
||||||
@@ -106,6 +158,11 @@ func rebuild(
|
|||||||
_collect_storages(pack, storages, storages_by_id, errors)
|
_collect_storages(pack, storages, storages_by_id, errors)
|
||||||
_collect_enemies(pack, enemies, enemies_by_id, errors)
|
_collect_enemies(pack, enemies, enemies_by_id, errors)
|
||||||
_collect_animals(pack, animals, animals_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
|
||||||
|
)
|
||||||
|
|
||||||
for pack in pack_list:
|
for pack in pack_list:
|
||||||
if pack == null:
|
if pack == null:
|
||||||
@@ -149,6 +206,19 @@ func rebuild(
|
|||||||
)
|
)
|
||||||
_validate_enemy_references(enemies, items_by_id, errors)
|
_validate_enemy_references(enemies, items_by_id, errors)
|
||||||
_validate_animal_references(animals, actions_by_id, supported_cues, 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,
|
||||||
|
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
|
||||||
|
)
|
||||||
errors.sort()
|
errors.sort()
|
||||||
_errors = errors
|
_errors = errors
|
||||||
if not errors.is_empty():
|
if not errors.is_empty():
|
||||||
@@ -162,6 +232,9 @@ func rebuild(
|
|||||||
storages.sort_custom(_sort_storages)
|
storages.sort_custom(_sort_storages)
|
||||||
enemies.sort_custom(_sort_enemies)
|
enemies.sort_custom(_sort_enemies)
|
||||||
animals.sort_custom(_sort_animals)
|
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)
|
||||||
_packs = pack_list
|
_packs = pack_list
|
||||||
_actions = actions
|
_actions = actions
|
||||||
_professions = professions
|
_professions = professions
|
||||||
@@ -171,6 +244,9 @@ func rebuild(
|
|||||||
_storages = storages
|
_storages = storages
|
||||||
_enemies = enemies
|
_enemies = enemies
|
||||||
_animals = animals
|
_animals = animals
|
||||||
|
_situations = situations
|
||||||
|
_dialogue_intents = dialogue_intents
|
||||||
|
_dialogue_template_catalogs = dialogue_template_catalogs
|
||||||
_packs_by_id = packs_by_id
|
_packs_by_id = packs_by_id
|
||||||
_actions_by_id = actions_by_id
|
_actions_by_id = actions_by_id
|
||||||
_professions_by_id = professions_by_id
|
_professions_by_id = professions_by_id
|
||||||
@@ -180,6 +256,9 @@ func rebuild(
|
|||||||
_storages_by_id = storages_by_id
|
_storages_by_id = storages_by_id
|
||||||
_enemies_by_id = enemies_by_id
|
_enemies_by_id = enemies_by_id
|
||||||
_animals_by_id = animals_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
|
||||||
_valid = true
|
_valid = true
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@@ -228,6 +307,18 @@ func get_animals() -> Array[AnimalDefinition]:
|
|||||||
return _animals.duplicate()
|
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_pack(pack_id: StringName) -> SimulationContentPack:
|
func get_pack(pack_id: StringName) -> SimulationContentPack:
|
||||||
return _packs_by_id.get(pack_id) as SimulationContentPack
|
return _packs_by_id.get(pack_id) as SimulationContentPack
|
||||||
|
|
||||||
@@ -264,6 +355,18 @@ func get_animal(animal_definition_id: StringName) -> AnimalDefinition:
|
|||||||
return _animals_by_id.get(animal_definition_id) as 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_authoring_report() -> Dictionary:
|
func get_authoring_report() -> Dictionary:
|
||||||
return {
|
return {
|
||||||
"valid": _valid,
|
"valid": _valid,
|
||||||
@@ -277,6 +380,9 @@ func get_authoring_report() -> Dictionary:
|
|||||||
"storages": _ids_for(_storages, &"storage_id"),
|
"storages": _ids_for(_storages, &"storage_id"),
|
||||||
"enemies": _ids_for(_enemies, &"enemy_id"),
|
"enemies": _ids_for(_enemies, &"enemy_id"),
|
||||||
"animals": _ids_for(_animals, &"animal_definition_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"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -292,6 +398,9 @@ func _clear_published_content() -> void:
|
|||||||
_storages.clear()
|
_storages.clear()
|
||||||
_enemies.clear()
|
_enemies.clear()
|
||||||
_animals.clear()
|
_animals.clear()
|
||||||
|
_situations.clear()
|
||||||
|
_dialogue_intents.clear()
|
||||||
|
_dialogue_template_catalogs.clear()
|
||||||
_packs_by_id.clear()
|
_packs_by_id.clear()
|
||||||
_actions_by_id.clear()
|
_actions_by_id.clear()
|
||||||
_professions_by_id.clear()
|
_professions_by_id.clear()
|
||||||
@@ -301,6 +410,9 @@ func _clear_published_content() -> void:
|
|||||||
_storages_by_id.clear()
|
_storages_by_id.clear()
|
||||||
_enemies_by_id.clear()
|
_enemies_by_id.clear()
|
||||||
_animals_by_id.clear()
|
_animals_by_id.clear()
|
||||||
|
_situations_by_id.clear()
|
||||||
|
_dialogue_intents_by_id.clear()
|
||||||
|
_dialogue_template_catalogs_by_id.clear()
|
||||||
|
|
||||||
|
|
||||||
static func _collect_actions(
|
static func _collect_actions(
|
||||||
@@ -431,6 +543,56 @@ static func _collect_animals(
|
|||||||
definitions.append(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 _validate_action_references(
|
static func _validate_action_references(
|
||||||
actions: Array[ActionDefinition],
|
actions: Array[ActionDefinition],
|
||||||
actions_by_id: Dictionary,
|
actions_by_id: Dictionary,
|
||||||
@@ -730,6 +892,125 @@ static func _validate_animal_references(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
static func _validate_situation_references(
|
||||||
|
situations: Array[SituationDefinition],
|
||||||
|
actions_by_id: Dictionary,
|
||||||
|
items_by_id: Dictionary,
|
||||||
|
dialogue_intents_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 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 _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:
|
static func _sort_packs(first: SimulationContentPack, second: SimulationContentPack) -> bool:
|
||||||
if first == null:
|
if first == null:
|
||||||
return second != null
|
return second != null
|
||||||
@@ -772,6 +1053,22 @@ static func _sort_animals(first: AnimalDefinition, second: AnimalDefinition) ->
|
|||||||
return String(first.animal_definition_id) < String(second.animal_definition_id)
|
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 _ids_for(definitions: Array, id_property: StringName) -> Array[StringName]:
|
static func _ids_for(definitions: Array, id_property: StringName) -> Array[StringName]:
|
||||||
var ids: Array[StringName] = []
|
var ids: Array[StringName] = []
|
||||||
for definition in definitions:
|
for definition in definitions:
|
||||||
|
|||||||
@@ -13,6 +13,9 @@ extends Resource
|
|||||||
@export var storages: Array[StorageDefinition] = []
|
@export var storages: Array[StorageDefinition] = []
|
||||||
@export var enemies: Array[EnemyDefinition] = []
|
@export var enemies: Array[EnemyDefinition] = []
|
||||||
@export var animals: Array[AnimalDefinition] = []
|
@export var animals: Array[AnimalDefinition] = []
|
||||||
|
@export var situations: Array[SituationDefinition] = []
|
||||||
|
@export var dialogue_intents: Array[ConversationIntentDefinition] = []
|
||||||
|
@export var dialogue_template_catalogs: Array[ConversationTemplateCatalog] = []
|
||||||
|
|
||||||
|
|
||||||
func validate() -> Array[String]:
|
func validate() -> Array[String]:
|
||||||
@@ -33,6 +36,9 @@ func validate() -> Array[String]:
|
|||||||
_validate_storages(errors)
|
_validate_storages(errors)
|
||||||
_validate_enemies(errors)
|
_validate_enemies(errors)
|
||||||
_validate_animals(errors)
|
_validate_animals(errors)
|
||||||
|
_validate_situations(errors)
|
||||||
|
_validate_dialogue_intents(errors)
|
||||||
|
_validate_dialogue_template_catalogs(errors)
|
||||||
return errors
|
return errors
|
||||||
|
|
||||||
|
|
||||||
@@ -108,6 +114,32 @@ func _validate_animals(errors: Array[String]) -> void:
|
|||||||
errors.append("AnimalDefinition: " + error)
|
errors.append("AnimalDefinition: " + error)
|
||||||
|
|
||||||
|
|
||||||
|
func _validate_situations(errors: Array[String]) -> void:
|
||||||
|
for definition in situations:
|
||||||
|
if definition == null:
|
||||||
|
errors.append("situations contains a null definition")
|
||||||
|
continue
|
||||||
|
for error in definition.validate():
|
||||||
|
errors.append("SituationDefinition: " + error)
|
||||||
|
|
||||||
|
|
||||||
|
func _validate_dialogue_intents(errors: Array[String]) -> void:
|
||||||
|
for definition in dialogue_intents:
|
||||||
|
if definition == null:
|
||||||
|
errors.append("dialogue_intents contains a null definition")
|
||||||
|
continue
|
||||||
|
for error in definition.validate():
|
||||||
|
errors.append("ConversationIntentDefinition: " + error)
|
||||||
|
|
||||||
|
|
||||||
|
func _validate_dialogue_template_catalogs(errors: Array[String]) -> void:
|
||||||
|
for catalog in dialogue_template_catalogs:
|
||||||
|
if catalog == null:
|
||||||
|
errors.append("dialogue_template_catalogs contains a null catalog")
|
||||||
|
elif catalog.catalog_id.is_empty():
|
||||||
|
errors.append("ConversationTemplateCatalog: catalog_id is empty")
|
||||||
|
|
||||||
|
|
||||||
static func _validate_dependency_ids(
|
static func _validate_dependency_ids(
|
||||||
ids: Array[StringName], label: String, errors: Array[String]
|
ids: Array[StringName], label: String, errors: Array[String]
|
||||||
) -> void:
|
) -> void:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
[gd_resource type="Resource" script_class="SimulationContentPack" load_steps=42 format=3]
|
[gd_resource type="Resource" script_class="SimulationContentPack" load_steps=60 format=3]
|
||||||
|
|
||||||
[ext_resource type="Script" path="res://simulation/definitions/SimulationContentPack.gd" id="1_pack"]
|
[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"]
|
[ext_resource type="Resource" path="res://simulation/definitions/actions/defend.tres" id="2_defend"]
|
||||||
@@ -40,6 +40,24 @@
|
|||||||
[ext_resource type="Resource" path="res://simulation/definitions/storages/woodpile.tres" id="38_woodpile"]
|
[ext_resource type="Resource" path="res://simulation/definitions/storages/woodpile.tres" id="38_woodpile"]
|
||||||
[ext_resource type="Resource" path="res://simulation/animals/definitions/domestic_goat.tres" id="39_goat"]
|
[ext_resource type="Resource" path="res://simulation/animals/definitions/domestic_goat.tres" id="39_goat"]
|
||||||
[ext_resource type="Resource" path="res://simulation/animals/definitions/domestic_sheep.tres" id="40_sheep"]
|
[ext_resource type="Resource" path="res://simulation/animals/definitions/domestic_sheep.tres" id="40_sheep"]
|
||||||
|
[ext_resource type="Resource" path="res://simulation/situations/resources/pantry_shortage.tres" id="41_pantry_situation"]
|
||||||
|
[ext_resource type="Resource" path="res://simulation/dialogue/resources/intents/accept.tres" id="42_accept"]
|
||||||
|
[ext_resource type="Resource" path="res://simulation/dialogue/resources/intents/acknowledge_supersession.tres" id="43_acknowledge"]
|
||||||
|
[ext_resource type="Resource" path="res://simulation/dialogue/resources/intents/ask_wellbeing.tres" id="44_wellbeing"]
|
||||||
|
[ext_resource type="Resource" path="res://simulation/dialogue/resources/intents/ask_what_happened.tres" id="45_happened"]
|
||||||
|
[ext_resource type="Resource" path="res://simulation/dialogue/resources/intents/ask_who_else.tres" id="46_who"]
|
||||||
|
[ext_resource type="Resource" path="res://simulation/dialogue/resources/intents/ask_work.tres" id="47_work"]
|
||||||
|
[ext_resource type="Resource" path="res://simulation/dialogue/resources/intents/decline.tres" id="48_decline"]
|
||||||
|
[ext_resource type="Resource" path="res://simulation/dialogue/resources/intents/describe_need.tres" id="49_need"]
|
||||||
|
[ext_resource type="Resource" path="res://simulation/dialogue/resources/intents/goodbye.tres" id="50_goodbye"]
|
||||||
|
[ext_resource type="Resource" path="res://simulation/dialogue/resources/intents/greet.tres" id="51_greet"]
|
||||||
|
[ext_resource type="Resource" path="res://simulation/dialogue/resources/intents/offer_help.tres" id="52_offer"]
|
||||||
|
[ext_resource type="Resource" path="res://simulation/dialogue/resources/intents/renegotiate.tres" id="53_renegotiate"]
|
||||||
|
[ext_resource type="Resource" path="res://simulation/dialogue/resources/intents/report_progress.tres" id="54_progress"]
|
||||||
|
[ext_resource type="Resource" path="res://simulation/dialogue/resources/intents/reproach.tres" id="55_reproach"]
|
||||||
|
[ext_resource type="Resource" path="res://simulation/dialogue/resources/intents/share_fact.tres" id="56_share"]
|
||||||
|
[ext_resource type="Resource" path="res://simulation/dialogue/resources/intents/thank.tres" id="57_thank"]
|
||||||
|
[ext_resource type="Resource" path="res://simulation/dialogue/resources/jajce_conversation_templates.tres" id="58_templates"]
|
||||||
|
|
||||||
[resource]
|
[resource]
|
||||||
script = ExtResource("1_pack")
|
script = ExtResource("1_pack")
|
||||||
@@ -53,3 +71,6 @@ resources = [ExtResource("33_berry_patch"), ExtResource("34_herb_patch"), ExtRes
|
|||||||
storages = [ExtResource("36_apothecary"), ExtResource("37_pantry"), ExtResource("38_woodpile")]
|
storages = [ExtResource("36_apothecary"), ExtResource("37_pantry"), ExtResource("38_woodpile")]
|
||||||
enemies = [ExtResource("24_raider"), ExtResource("25_wolf"), ExtResource("26_boar")]
|
enemies = [ExtResource("24_raider"), ExtResource("25_wolf"), ExtResource("26_boar")]
|
||||||
animals = [ExtResource("39_goat"), ExtResource("40_sheep")]
|
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")]
|
||||||
|
|||||||
@@ -3,12 +3,15 @@ extends Resource
|
|||||||
|
|
||||||
@export var intent_id: StringName
|
@export var intent_id: StringName
|
||||||
@export var response_intent_ids: Array[StringName] = []
|
@export var response_intent_ids: Array[StringName] = []
|
||||||
|
@export var template_catalog_id: StringName
|
||||||
|
|
||||||
|
|
||||||
func validate() -> Array[String]:
|
func validate() -> Array[String]:
|
||||||
var errors: Array[String] = []
|
var errors: Array[String] = []
|
||||||
if not ConversationSemantic.is_valid_id(intent_id):
|
if not ConversationSemantic.is_valid_id(intent_id):
|
||||||
errors.append("intent_id is invalid")
|
errors.append("intent_id is invalid")
|
||||||
|
if template_catalog_id.is_empty():
|
||||||
|
errors.append("template_catalog_id is empty for '%s'" % intent_id)
|
||||||
var seen: Dictionary = {}
|
var seen: Dictionary = {}
|
||||||
for response_intent_id in response_intent_ids:
|
for response_intent_id in response_intent_ids:
|
||||||
if not ConversationSemantic.is_valid_id(response_intent_id):
|
if not ConversationSemantic.is_valid_id(response_intent_id):
|
||||||
@@ -20,3 +23,11 @@ func validate() -> Array[String]:
|
|||||||
else:
|
else:
|
||||||
seen[response_intent_id] = true
|
seen[response_intent_id] = true
|
||||||
return errors
|
return errors
|
||||||
|
|
||||||
|
|
||||||
|
func duplicate_definition() -> ConversationIntentDefinition:
|
||||||
|
var copy := ConversationIntentDefinition.new()
|
||||||
|
copy.intent_id = intent_id
|
||||||
|
copy.response_intent_ids = response_intent_ids.duplicate()
|
||||||
|
copy.template_catalog_id = template_catalog_id
|
||||||
|
return copy
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
class_name ConversationTemplateCatalog
|
class_name ConversationTemplateCatalog
|
||||||
extends Resource
|
extends Resource
|
||||||
|
|
||||||
|
@export var catalog_id: StringName
|
||||||
@export var line_templates: Dictionary = {}
|
@export var line_templates: Dictionary = {}
|
||||||
@export var option_templates: Dictionary = {}
|
@export var option_templates: Dictionary = {}
|
||||||
|
|
||||||
|
|
||||||
func validate(intent_ids: Array[StringName] = ConversationIntentIds.ALL) -> Array[String]:
|
func validate(intent_ids: Array[StringName] = ConversationIntentIds.ALL) -> Array[String]:
|
||||||
var errors: Array[String] = []
|
var errors: Array[String] = []
|
||||||
|
if catalog_id.is_empty():
|
||||||
|
errors.append("catalog_id is empty")
|
||||||
_validate_templates(line_templates, "line", intent_ids, errors)
|
_validate_templates(line_templates, "line", intent_ids, errors)
|
||||||
_validate_templates(option_templates, "option", intent_ids, errors)
|
_validate_templates(option_templates, "option", intent_ids, errors)
|
||||||
errors.sort()
|
errors.sort()
|
||||||
@@ -21,6 +24,14 @@ func get_option_templates(intent_id: StringName) -> Array[String]:
|
|||||||
return _get_templates(option_templates, intent_id)
|
return _get_templates(option_templates, intent_id)
|
||||||
|
|
||||||
|
|
||||||
|
func get_line_template_ids() -> Array[StringName]:
|
||||||
|
return _template_ids(line_templates)
|
||||||
|
|
||||||
|
|
||||||
|
func get_option_template_ids() -> Array[StringName]:
|
||||||
|
return _template_ids(option_templates)
|
||||||
|
|
||||||
|
|
||||||
func _get_templates(source: Dictionary, intent_id: StringName) -> Array[String]:
|
func _get_templates(source: Dictionary, intent_id: StringName) -> Array[String]:
|
||||||
var result: Array[String] = []
|
var result: Array[String] = []
|
||||||
var values: Variant = source.get(String(intent_id), [])
|
var values: Variant = source.get(String(intent_id), [])
|
||||||
@@ -34,6 +45,23 @@ func _get_templates(source: Dictionary, intent_id: StringName) -> Array[String]:
|
|||||||
func _validate_templates(
|
func _validate_templates(
|
||||||
source: Dictionary, kind: String, intent_ids: Array[StringName], errors: Array[String]
|
source: Dictionary, kind: String, intent_ids: Array[StringName], errors: Array[String]
|
||||||
) -> void:
|
) -> void:
|
||||||
|
var allowed_ids := {}
|
||||||
|
for intent_id in intent_ids:
|
||||||
|
allowed_ids[intent_id] = true
|
||||||
|
for raw_intent_id in source:
|
||||||
|
if not raw_intent_id is String and not raw_intent_id is StringName:
|
||||||
|
errors.append("Non-string %s template intent ID" % kind)
|
||||||
|
continue
|
||||||
|
var source_intent_id := StringName(raw_intent_id)
|
||||||
|
if not allowed_ids.has(source_intent_id):
|
||||||
|
errors.append("Unknown %s template intent '%s'" % [kind, source_intent_id])
|
||||||
|
var raw_templates: Variant = source[raw_intent_id]
|
||||||
|
if not raw_templates is Array and not raw_templates is PackedStringArray:
|
||||||
|
errors.append("Invalid %s templates for '%s'" % [kind, source_intent_id])
|
||||||
|
continue
|
||||||
|
for raw_template in raw_templates:
|
||||||
|
if not raw_template is String:
|
||||||
|
errors.append("Non-string %s template for '%s'" % [kind, source_intent_id])
|
||||||
for intent_id in intent_ids:
|
for intent_id in intent_ids:
|
||||||
var templates := _get_templates(source, intent_id)
|
var templates := _get_templates(source, intent_id)
|
||||||
if templates.is_empty():
|
if templates.is_empty():
|
||||||
@@ -42,3 +70,14 @@ func _validate_templates(
|
|||||||
for template in templates:
|
for template in templates:
|
||||||
if template.strip_edges().is_empty():
|
if template.strip_edges().is_empty():
|
||||||
errors.append("Empty %s template for '%s'" % [kind, intent_id])
|
errors.append("Empty %s template for '%s'" % [kind, intent_id])
|
||||||
|
|
||||||
|
|
||||||
|
func _template_ids(source: Dictionary) -> Array[StringName]:
|
||||||
|
var result: Array[StringName] = []
|
||||||
|
for raw_id in source:
|
||||||
|
if raw_id is String or raw_id is StringName:
|
||||||
|
result.append(StringName(raw_id))
|
||||||
|
result.sort_custom(
|
||||||
|
func(left: StringName, right: StringName) -> bool: return String(left) < String(right)
|
||||||
|
)
|
||||||
|
return result
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
[gd_resource type="Resource" script_class="ConversationIntentDefinition" load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://simulation/dialogue/ConversationIntentDefinition.gd" id="1"]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("1")
|
||||||
|
intent_id = &"accept"
|
||||||
|
response_intent_ids = Array[StringName]([&"thank", &"report_progress", &"goodbye"])
|
||||||
|
template_catalog_id = &"jajce_conversation_templates"
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
[gd_resource type="Resource" script_class="ConversationIntentDefinition" load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://simulation/dialogue/ConversationIntentDefinition.gd" id="1"]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("1")
|
||||||
|
intent_id = &"acknowledge_supersession"
|
||||||
|
response_intent_ids = Array[StringName]([&"thank", &"goodbye"])
|
||||||
|
template_catalog_id = &"jajce_conversation_templates"
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
[gd_resource type="Resource" script_class="ConversationIntentDefinition" load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://simulation/dialogue/ConversationIntentDefinition.gd" id="1"]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("1")
|
||||||
|
intent_id = &"ask_wellbeing"
|
||||||
|
response_intent_ids = Array[StringName]([&"describe_need", &"share_fact", &"offer_help", &"goodbye"])
|
||||||
|
template_catalog_id = &"jajce_conversation_templates"
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
[gd_resource type="Resource" script_class="ConversationIntentDefinition" load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://simulation/dialogue/ConversationIntentDefinition.gd" id="1"]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("1")
|
||||||
|
intent_id = &"ask_what_happened"
|
||||||
|
response_intent_ids = Array[StringName]([&"share_fact", &"ask_who_else", &"offer_help", &"goodbye"])
|
||||||
|
template_catalog_id = &"jajce_conversation_templates"
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
[gd_resource type="Resource" script_class="ConversationIntentDefinition" load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://simulation/dialogue/ConversationIntentDefinition.gd" id="1"]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("1")
|
||||||
|
intent_id = &"ask_who_else"
|
||||||
|
response_intent_ids = Array[StringName]([&"share_fact", &"offer_help", &"goodbye"])
|
||||||
|
template_catalog_id = &"jajce_conversation_templates"
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
[gd_resource type="Resource" script_class="ConversationIntentDefinition" load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://simulation/dialogue/ConversationIntentDefinition.gd" id="1"]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("1")
|
||||||
|
intent_id = &"ask_work"
|
||||||
|
response_intent_ids = Array[StringName]([&"describe_need", &"report_progress", &"share_fact", &"ask_who_else", &"goodbye"])
|
||||||
|
template_catalog_id = &"jajce_conversation_templates"
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
[gd_resource type="Resource" script_class="ConversationIntentDefinition" load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://simulation/dialogue/ConversationIntentDefinition.gd" id="1"]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("1")
|
||||||
|
intent_id = &"decline"
|
||||||
|
response_intent_ids = Array[StringName]([&"reproach", &"renegotiate", &"goodbye"])
|
||||||
|
template_catalog_id = &"jajce_conversation_templates"
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
[gd_resource type="Resource" script_class="ConversationIntentDefinition" load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://simulation/dialogue/ConversationIntentDefinition.gd" id="1"]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("1")
|
||||||
|
intent_id = &"describe_need"
|
||||||
|
response_intent_ids = Array[StringName]([&"offer_help", &"decline", &"ask_who_else", &"goodbye"])
|
||||||
|
template_catalog_id = &"jajce_conversation_templates"
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
[gd_resource type="Resource" script_class="ConversationIntentDefinition" load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://simulation/dialogue/ConversationIntentDefinition.gd" id="1"]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("1")
|
||||||
|
intent_id = &"goodbye"
|
||||||
|
template_catalog_id = &"jajce_conversation_templates"
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
[gd_resource type="Resource" script_class="ConversationIntentDefinition" load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://simulation/dialogue/ConversationIntentDefinition.gd" id="1"]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("1")
|
||||||
|
intent_id = &"greet"
|
||||||
|
response_intent_ids = Array[StringName]([&"ask_work", &"ask_wellbeing", &"ask_what_happened", &"goodbye"])
|
||||||
|
template_catalog_id = &"jajce_conversation_templates"
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
[gd_resource type="Resource" script_class="ConversationIntentDefinition" load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://simulation/dialogue/ConversationIntentDefinition.gd" id="1"]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("1")
|
||||||
|
intent_id = &"offer_help"
|
||||||
|
response_intent_ids = Array[StringName]([&"accept", &"decline", &"renegotiate"])
|
||||||
|
template_catalog_id = &"jajce_conversation_templates"
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
[gd_resource type="Resource" script_class="ConversationIntentDefinition" load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://simulation/dialogue/ConversationIntentDefinition.gd" id="1"]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("1")
|
||||||
|
intent_id = &"renegotiate"
|
||||||
|
response_intent_ids = Array[StringName]([&"accept", &"decline", &"goodbye"])
|
||||||
|
template_catalog_id = &"jajce_conversation_templates"
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
[gd_resource type="Resource" script_class="ConversationIntentDefinition" load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://simulation/dialogue/ConversationIntentDefinition.gd" id="1"]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("1")
|
||||||
|
intent_id = &"report_progress"
|
||||||
|
response_intent_ids = Array[StringName]([&"thank", &"renegotiate", &"acknowledge_supersession", &"goodbye"])
|
||||||
|
template_catalog_id = &"jajce_conversation_templates"
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
[gd_resource type="Resource" script_class="ConversationIntentDefinition" load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://simulation/dialogue/ConversationIntentDefinition.gd" id="1"]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("1")
|
||||||
|
intent_id = &"reproach"
|
||||||
|
response_intent_ids = Array[StringName]([&"acknowledge_supersession", &"renegotiate", &"goodbye"])
|
||||||
|
template_catalog_id = &"jajce_conversation_templates"
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
[gd_resource type="Resource" script_class="ConversationIntentDefinition" load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://simulation/dialogue/ConversationIntentDefinition.gd" id="1"]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("1")
|
||||||
|
intent_id = &"share_fact"
|
||||||
|
response_intent_ids = Array[StringName]([&"ask_what_happened", &"ask_who_else", &"acknowledge_supersession", &"thank", &"goodbye"])
|
||||||
|
template_catalog_id = &"jajce_conversation_templates"
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
[gd_resource type="Resource" script_class="ConversationIntentDefinition" load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://simulation/dialogue/ConversationIntentDefinition.gd" id="1"]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("1")
|
||||||
|
intent_id = &"thank"
|
||||||
|
response_intent_ids = Array[StringName]([&"goodbye"])
|
||||||
|
template_catalog_id = &"jajce_conversation_templates"
|
||||||
@@ -1,87 +1,23 @@
|
|||||||
[gd_resource type="Resource" script_class="ConversationIntentCatalog" load_steps=18 format=3]
|
[gd_resource type="Resource" script_class="ConversationIntentCatalog" load_steps=18 format=3]
|
||||||
|
|
||||||
[ext_resource type="Script" path="res://simulation/dialogue/ConversationIntentCatalog.gd" id="1_catalog"]
|
[ext_resource type="Script" path="res://simulation/dialogue/ConversationIntentCatalog.gd" id="1_catalog"]
|
||||||
[ext_resource type="Script" path="res://simulation/dialogue/ConversationIntentDefinition.gd" id="2_intent"]
|
[ext_resource type="Resource" path="res://simulation/dialogue/resources/intents/accept.tres" id="2_accept"]
|
||||||
|
[ext_resource type="Resource" path="res://simulation/dialogue/resources/intents/acknowledge_supersession.tres" id="3_acknowledge"]
|
||||||
[sub_resource type="Resource" id="Intent_greet"]
|
[ext_resource type="Resource" path="res://simulation/dialogue/resources/intents/ask_wellbeing.tres" id="4_wellbeing"]
|
||||||
script = ExtResource("2_intent")
|
[ext_resource type="Resource" path="res://simulation/dialogue/resources/intents/ask_what_happened.tres" id="5_happened"]
|
||||||
intent_id = &"greet"
|
[ext_resource type="Resource" path="res://simulation/dialogue/resources/intents/ask_who_else.tres" id="6_who"]
|
||||||
response_intent_ids = Array[StringName]([&"ask_work", &"ask_wellbeing", &"ask_what_happened", &"goodbye"])
|
[ext_resource type="Resource" path="res://simulation/dialogue/resources/intents/ask_work.tres" id="7_work"]
|
||||||
|
[ext_resource type="Resource" path="res://simulation/dialogue/resources/intents/decline.tres" id="8_decline"]
|
||||||
[sub_resource type="Resource" id="Intent_ask_work"]
|
[ext_resource type="Resource" path="res://simulation/dialogue/resources/intents/describe_need.tres" id="9_need"]
|
||||||
script = ExtResource("2_intent")
|
[ext_resource type="Resource" path="res://simulation/dialogue/resources/intents/goodbye.tres" id="10_goodbye"]
|
||||||
intent_id = &"ask_work"
|
[ext_resource type="Resource" path="res://simulation/dialogue/resources/intents/greet.tres" id="11_greet"]
|
||||||
response_intent_ids = Array[StringName]([&"describe_need", &"report_progress", &"share_fact", &"ask_who_else", &"goodbye"])
|
[ext_resource type="Resource" path="res://simulation/dialogue/resources/intents/offer_help.tres" id="12_offer"]
|
||||||
|
[ext_resource type="Resource" path="res://simulation/dialogue/resources/intents/renegotiate.tres" id="13_renegotiate"]
|
||||||
[sub_resource type="Resource" id="Intent_ask_wellbeing"]
|
[ext_resource type="Resource" path="res://simulation/dialogue/resources/intents/report_progress.tres" id="14_progress"]
|
||||||
script = ExtResource("2_intent")
|
[ext_resource type="Resource" path="res://simulation/dialogue/resources/intents/reproach.tres" id="15_reproach"]
|
||||||
intent_id = &"ask_wellbeing"
|
[ext_resource type="Resource" path="res://simulation/dialogue/resources/intents/share_fact.tres" id="16_share"]
|
||||||
response_intent_ids = Array[StringName]([&"describe_need", &"share_fact", &"offer_help", &"goodbye"])
|
[ext_resource type="Resource" path="res://simulation/dialogue/resources/intents/thank.tres" id="17_thank"]
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Intent_ask_what_happened"]
|
|
||||||
script = ExtResource("2_intent")
|
|
||||||
intent_id = &"ask_what_happened"
|
|
||||||
response_intent_ids = Array[StringName]([&"share_fact", &"ask_who_else", &"offer_help", &"goodbye"])
|
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Intent_share_fact"]
|
|
||||||
script = ExtResource("2_intent")
|
|
||||||
intent_id = &"share_fact"
|
|
||||||
response_intent_ids = Array[StringName]([&"ask_what_happened", &"ask_who_else", &"acknowledge_supersession", &"thank", &"goodbye"])
|
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Intent_describe_need"]
|
|
||||||
script = ExtResource("2_intent")
|
|
||||||
intent_id = &"describe_need"
|
|
||||||
response_intent_ids = Array[StringName]([&"offer_help", &"decline", &"ask_who_else", &"goodbye"])
|
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Intent_ask_who_else"]
|
|
||||||
script = ExtResource("2_intent")
|
|
||||||
intent_id = &"ask_who_else"
|
|
||||||
response_intent_ids = Array[StringName]([&"share_fact", &"offer_help", &"goodbye"])
|
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Intent_offer_help"]
|
|
||||||
script = ExtResource("2_intent")
|
|
||||||
intent_id = &"offer_help"
|
|
||||||
response_intent_ids = Array[StringName]([&"accept", &"decline", &"renegotiate"])
|
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Intent_accept"]
|
|
||||||
script = ExtResource("2_intent")
|
|
||||||
intent_id = &"accept"
|
|
||||||
response_intent_ids = Array[StringName]([&"thank", &"report_progress", &"goodbye"])
|
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Intent_decline"]
|
|
||||||
script = ExtResource("2_intent")
|
|
||||||
intent_id = &"decline"
|
|
||||||
response_intent_ids = Array[StringName]([&"reproach", &"renegotiate", &"goodbye"])
|
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Intent_report_progress"]
|
|
||||||
script = ExtResource("2_intent")
|
|
||||||
intent_id = &"report_progress"
|
|
||||||
response_intent_ids = Array[StringName]([&"thank", &"renegotiate", &"acknowledge_supersession", &"goodbye"])
|
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Intent_renegotiate"]
|
|
||||||
script = ExtResource("2_intent")
|
|
||||||
intent_id = &"renegotiate"
|
|
||||||
response_intent_ids = Array[StringName]([&"accept", &"decline", &"goodbye"])
|
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Intent_thank"]
|
|
||||||
script = ExtResource("2_intent")
|
|
||||||
intent_id = &"thank"
|
|
||||||
response_intent_ids = Array[StringName]([&"goodbye"])
|
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Intent_reproach"]
|
|
||||||
script = ExtResource("2_intent")
|
|
||||||
intent_id = &"reproach"
|
|
||||||
response_intent_ids = Array[StringName]([&"acknowledge_supersession", &"renegotiate", &"goodbye"])
|
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Intent_acknowledge_supersession"]
|
|
||||||
script = ExtResource("2_intent")
|
|
||||||
intent_id = &"acknowledge_supersession"
|
|
||||||
response_intent_ids = Array[StringName]([&"thank", &"goodbye"])
|
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Intent_goodbye"]
|
|
||||||
script = ExtResource("2_intent")
|
|
||||||
intent_id = &"goodbye"
|
|
||||||
|
|
||||||
[resource]
|
[resource]
|
||||||
script = ExtResource("1_catalog")
|
script = ExtResource("1_catalog")
|
||||||
intents = Array[ExtResource("2_intent")]([SubResource("Intent_greet"), SubResource("Intent_ask_work"), SubResource("Intent_ask_wellbeing"), SubResource("Intent_ask_what_happened"), SubResource("Intent_share_fact"), SubResource("Intent_describe_need"), SubResource("Intent_ask_who_else"), SubResource("Intent_offer_help"), SubResource("Intent_accept"), SubResource("Intent_decline"), SubResource("Intent_report_progress"), SubResource("Intent_renegotiate"), SubResource("Intent_thank"), SubResource("Intent_reproach"), SubResource("Intent_acknowledge_supersession"), SubResource("Intent_goodbye")])
|
intents = [ExtResource("11_greet"), ExtResource("7_work"), ExtResource("4_wellbeing"), ExtResource("5_happened"), ExtResource("16_share"), ExtResource("9_need"), ExtResource("6_who"), ExtResource("12_offer"), ExtResource("2_accept"), ExtResource("8_decline"), ExtResource("14_progress"), ExtResource("13_renegotiate"), ExtResource("17_thank"), ExtResource("15_reproach"), ExtResource("3_acknowledge"), ExtResource("10_goodbye")]
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
[resource]
|
[resource]
|
||||||
script = ExtResource("1_catalog")
|
script = ExtResource("1_catalog")
|
||||||
|
catalog_id = &"jajce_conversation_templates"
|
||||||
line_templates = {
|
line_templates = {
|
||||||
"accept": ["That works for me.", "Yes, let us do that."],
|
"accept": ["That works for me.", "Yes, let us do that."],
|
||||||
"acknowledge_supersession": ["Things have changed since then; I understand.", "That news has overtaken our earlier plan."],
|
"acknowledge_supersession": ["Things have changed since then; I understand.", "That news has overtaken our earlier plan."],
|
||||||
|
|||||||
@@ -17,12 +17,27 @@ func validate() -> Array[String]:
|
|||||||
errors.append("display_name is empty for alternative '%s'" % alternative_id)
|
errors.append("display_name is empty for alternative '%s'" % alternative_id)
|
||||||
if resolution_predicate_ids.is_empty():
|
if resolution_predicate_ids.is_empty():
|
||||||
errors.append("alternative '%s' has no resolution predicates" % alternative_id)
|
errors.append("alternative '%s' has no resolution predicates" % alternative_id)
|
||||||
|
var seen_predicate_ids := {}
|
||||||
for predicate_id in resolution_predicate_ids:
|
for predicate_id in resolution_predicate_ids:
|
||||||
if predicate_id.is_empty():
|
if predicate_id.is_empty():
|
||||||
errors.append("alternative '%s' has an empty resolution predicate ID" % alternative_id)
|
errors.append("alternative '%s' has an empty resolution predicate ID" % alternative_id)
|
||||||
|
elif seen_predicate_ids.has(predicate_id):
|
||||||
|
errors.append(
|
||||||
|
(
|
||||||
|
"alternative '%s' has duplicate resolution predicate '%s'"
|
||||||
|
% [alternative_id, predicate_id]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
seen_predicate_ids[predicate_id] = true
|
||||||
|
var seen_topic_ids := {}
|
||||||
for topic_id in dialogue_topic_ids:
|
for topic_id in dialogue_topic_ids:
|
||||||
if topic_id.is_empty():
|
if topic_id.is_empty():
|
||||||
errors.append("alternative '%s' has an empty dialogue topic ID" % alternative_id)
|
errors.append("alternative '%s' has an empty dialogue topic ID" % alternative_id)
|
||||||
|
elif seen_topic_ids.has(topic_id):
|
||||||
|
errors.append(
|
||||||
|
"alternative '%s' has duplicate dialogue topic '%s'" % [alternative_id, topic_id]
|
||||||
|
)
|
||||||
|
seen_topic_ids[topic_id] = true
|
||||||
for term_key in commitment_terms:
|
for term_key in commitment_terms:
|
||||||
if not term_key is String and not term_key is StringName:
|
if not term_key is String and not term_key is StringName:
|
||||||
errors.append("alternative '%s' has a non-string commitment term key" % alternative_id)
|
errors.append("alternative '%s' has a non-string commitment term key" % alternative_id)
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ extends Resource
|
|||||||
@export_range(0, 1000000, 1) var expiry_ticks := 0
|
@export_range(0, 1000000, 1) var expiry_ticks := 0
|
||||||
@export_range(0, 1000000, 1) var cooldown_ticks := 0
|
@export_range(0, 1000000, 1) var cooldown_ticks := 0
|
||||||
@export var dialogue_topic_ids: Array[StringName] = []
|
@export var dialogue_topic_ids: Array[StringName] = []
|
||||||
|
@export var dialogue_intent_ids: Array[StringName] = []
|
||||||
@export var alternatives: Array[SituationAlternativeDefinition] = []
|
@export var alternatives: Array[SituationAlternativeDefinition] = []
|
||||||
|
|
||||||
|
|
||||||
@@ -63,6 +64,19 @@ func validate(
|
|||||||
)
|
)
|
||||||
_validate_unique_names(dedupe_key_fields, "dedupe key", errors)
|
_validate_unique_names(dedupe_key_fields, "dedupe key", errors)
|
||||||
_validate_unique_names(dialogue_topic_ids, "dialogue topic", errors)
|
_validate_unique_names(dialogue_topic_ids, "dialogue topic", errors)
|
||||||
|
_validate_unique_names(dialogue_intent_ids, "dialogue intent", errors)
|
||||||
|
var predicate_ids := {}
|
||||||
|
for predicate in trigger_predicates + resolution_predicates:
|
||||||
|
if predicate == null:
|
||||||
|
continue
|
||||||
|
if predicate_ids.has(predicate.predicate_id):
|
||||||
|
errors.append(
|
||||||
|
(
|
||||||
|
"'%s' has duplicate predicate '%s' across trigger/resolution predicates"
|
||||||
|
% [situation_definition_id, predicate.predicate_id]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
predicate_ids[predicate.predicate_id] = true
|
||||||
var alternatives_by_id: Dictionary = {}
|
var alternatives_by_id: Dictionary = {}
|
||||||
for alternative in alternatives:
|
for alternative in alternatives:
|
||||||
if alternative == null:
|
if alternative == null:
|
||||||
@@ -89,6 +103,34 @@ func validate(
|
|||||||
return errors
|
return errors
|
||||||
|
|
||||||
|
|
||||||
|
func duplicate_definition() -> SituationDefinition:
|
||||||
|
var copy := SituationDefinition.new()
|
||||||
|
copy.situation_definition_id = situation_definition_id
|
||||||
|
copy.display_name = display_name
|
||||||
|
copy.description = description
|
||||||
|
copy.severity = severity
|
||||||
|
copy.priority = priority
|
||||||
|
for predicate in trigger_predicates:
|
||||||
|
copy.trigger_predicates.append(
|
||||||
|
predicate.duplicate_predicate() if predicate != null else null
|
||||||
|
)
|
||||||
|
for predicate in resolution_predicates:
|
||||||
|
copy.resolution_predicates.append(
|
||||||
|
predicate.duplicate_predicate() if predicate != null else null
|
||||||
|
)
|
||||||
|
copy.expiry_predicate_ids = expiry_predicate_ids.duplicate()
|
||||||
|
copy.dedupe_key_fields = dedupe_key_fields.duplicate()
|
||||||
|
copy.expiry_ticks = expiry_ticks
|
||||||
|
copy.cooldown_ticks = cooldown_ticks
|
||||||
|
copy.dialogue_topic_ids = dialogue_topic_ids.duplicate()
|
||||||
|
copy.dialogue_intent_ids = dialogue_intent_ids.duplicate()
|
||||||
|
for alternative in alternatives:
|
||||||
|
copy.alternatives.append(
|
||||||
|
alternative.duplicate_alternative() if alternative != null else null
|
||||||
|
)
|
||||||
|
return copy
|
||||||
|
|
||||||
|
|
||||||
func get_predicate(predicate_id: StringName) -> SituationPredicate:
|
func get_predicate(predicate_id: StringName) -> SituationPredicate:
|
||||||
for predicate in trigger_predicates:
|
for predicate in trigger_predicates:
|
||||||
if predicate != null and predicate.predicate_id == predicate_id:
|
if predicate != null and predicate.predicate_id == predicate_id:
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ class_name SituationSystem
|
|||||||
extends RefCounted
|
extends RefCounted
|
||||||
|
|
||||||
const MAX_CONCURRENT_SITUATIONS := 3
|
const MAX_CONCURRENT_SITUATIONS := 3
|
||||||
|
const PANTRY_SHORTAGE_DEFINITION_PATH := "res://simulation/situations/resources/pantry_shortage.tres"
|
||||||
const DEFINITION_PANTRY_SHORTAGE := &"situation_pantry_shortage"
|
const DEFINITION_PANTRY_SHORTAGE := &"situation_pantry_shortage"
|
||||||
const PREDICATE_EVENT_TYPE := &"event_type"
|
const PREDICATE_EVENT_TYPE := &"event_type"
|
||||||
const PREDICATE_EVENT_PAYLOAD_EQUALS := &"event_payload_equals"
|
const PREDICATE_EVENT_PAYLOAD_EQUALS := &"event_payload_equals"
|
||||||
@@ -26,68 +27,18 @@ var _next_situation_id := 0
|
|||||||
|
|
||||||
static func create_default() -> SituationSystem:
|
static func create_default() -> SituationSystem:
|
||||||
var system := SituationSystem.new()
|
var system := SituationSystem.new()
|
||||||
var definitions: Array[SituationDefinition] = [create_pantry_shortage_definition()]
|
var definitions: Array[SituationDefinition] = []
|
||||||
|
var catalog := ContentCatalog.create_core()
|
||||||
|
if catalog.is_valid():
|
||||||
|
for definition in catalog.get_situations():
|
||||||
|
definitions.append(definition.duplicate_definition())
|
||||||
system.configure(definitions)
|
system.configure(definitions)
|
||||||
return system
|
return system
|
||||||
|
|
||||||
|
|
||||||
static func create_pantry_shortage_definition() -> SituationDefinition:
|
static func create_pantry_shortage_definition() -> SituationDefinition:
|
||||||
var definition := SituationDefinition.new()
|
var authored := load(PANTRY_SHORTAGE_DEFINITION_PATH) as SituationDefinition
|
||||||
definition.situation_definition_id = DEFINITION_PANTRY_SHORTAGE
|
return authored.duplicate_definition() if authored != null else null
|
||||||
definition.display_name = "The Pantry Is Empty"
|
|
||||||
definition.description = "The village pantry has run out of food."
|
|
||||||
definition.severity = 70
|
|
||||||
definition.priority = 80
|
|
||||||
definition.trigger_predicates = [
|
|
||||||
_event_predicate(
|
|
||||||
&"pantry_withdrawal", PREDICATE_EVENT_TYPE, {"event_type": "storage_withdrawn"}
|
|
||||||
),
|
|
||||||
_event_predicate(
|
|
||||||
&"withdrawal_from_pantry",
|
|
||||||
PREDICATE_EVENT_PARTICIPANT_ID,
|
|
||||||
{"role": "source", "entity_id": "village_pantry"}
|
|
||||||
),
|
|
||||||
_event_predicate(
|
|
||||||
&"withdrawal_of_food",
|
|
||||||
PREDICATE_EVENT_PAYLOAD_EQUALS,
|
|
||||||
{"key": "item_id", "value": "food"}
|
|
||||||
),
|
|
||||||
_state_predicate(
|
|
||||||
&"pantry_empty", PREDICATE_STATE_NUMBER_LTE, {"fact_key": "pantry.food", "value": 0.0}
|
|
||||||
),
|
|
||||||
]
|
|
||||||
definition.resolution_predicates = [
|
|
||||||
_event_predicate(
|
|
||||||
&"pantry_restocked",
|
|
||||||
PREDICATE_EVENT_PANTRY_SUPPLIED,
|
|
||||||
{"target_id": "village_pantry", "item_id": "food", "amount": 1.0}
|
|
||||||
)
|
|
||||||
]
|
|
||||||
definition.expiry_predicate_ids = [EXPIRY_AGE_REACHED]
|
|
||||||
definition.dedupe_key_fields = [&"world_id", &"source_id"]
|
|
||||||
definition.expiry_ticks = 200
|
|
||||||
definition.cooldown_ticks = 50
|
|
||||||
definition.dialogue_topic_ids = [DIALOGUE_TOPIC_PANTRY_SHORTAGE]
|
|
||||||
var restock := SituationAlternativeDefinition.new()
|
|
||||||
restock.alternative_id = ALTERNATIVE_RESTOCK
|
|
||||||
restock.display_name = "Restock the pantry"
|
|
||||||
restock.description = "Bring at least one food item to the village pantry."
|
|
||||||
restock.resolution_predicate_ids = [&"pantry_restocked"]
|
|
||||||
restock.dialogue_topic_ids = [DIALOGUE_TOPIC_PANTRY_SHORTAGE]
|
|
||||||
restock.commitment_terms = {
|
|
||||||
"action_id": "deposit_food",
|
|
||||||
"target_id": "village_pantry",
|
|
||||||
"item_id": "food",
|
|
||||||
"amount": 1.0,
|
|
||||||
}
|
|
||||||
var endure := SituationAlternativeDefinition.new()
|
|
||||||
endure.alternative_id = ALTERNATIVE_ENDURE
|
|
||||||
endure.display_name = "Endure the shortage"
|
|
||||||
endure.description = "Make no promise and let the immediate concern pass."
|
|
||||||
endure.resolution_predicate_ids = [&"pantry_restocked"]
|
|
||||||
endure.dialogue_topic_ids = [DIALOGUE_TOPIC_PANTRY_SHORTAGE]
|
|
||||||
definition.alternatives = [restock, endure]
|
|
||||||
return definition
|
|
||||||
|
|
||||||
|
|
||||||
func configure(definitions: Array[SituationDefinition]) -> Array[String]:
|
func configure(definitions: Array[SituationDefinition]) -> Array[String]:
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
[gd_resource type="Resource" script_class="SituationDefinition" load_steps=10 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://simulation/situations/SituationDefinition.gd" id="1_definition"]
|
||||||
|
[ext_resource type="Script" path="res://simulation/situations/SituationPredicate.gd" id="2_predicate"]
|
||||||
|
[ext_resource type="Script" path="res://simulation/situations/SituationAlternativeDefinition.gd" id="3_alternative"]
|
||||||
|
|
||||||
|
[sub_resource type="Resource" id="Predicate_pantry_withdrawal"]
|
||||||
|
script = ExtResource("2_predicate")
|
||||||
|
predicate_id = &"pantry_withdrawal"
|
||||||
|
evaluator_id = &"event_type"
|
||||||
|
parameters = {"event_type": "storage_withdrawn"}
|
||||||
|
|
||||||
|
[sub_resource type="Resource" id="Predicate_withdrawal_from_pantry"]
|
||||||
|
script = ExtResource("2_predicate")
|
||||||
|
predicate_id = &"withdrawal_from_pantry"
|
||||||
|
evaluator_id = &"event_participant_id"
|
||||||
|
parameters = {"entity_id": "village_pantry", "role": "source"}
|
||||||
|
|
||||||
|
[sub_resource type="Resource" id="Predicate_withdrawal_of_food"]
|
||||||
|
script = ExtResource("2_predicate")
|
||||||
|
predicate_id = &"withdrawal_of_food"
|
||||||
|
evaluator_id = &"event_payload_equals"
|
||||||
|
parameters = {"key": "item_id", "value": "food"}
|
||||||
|
|
||||||
|
[sub_resource type="Resource" id="Predicate_pantry_empty"]
|
||||||
|
script = ExtResource("2_predicate")
|
||||||
|
predicate_id = &"pantry_empty"
|
||||||
|
evaluator_id = &"state_number_lte"
|
||||||
|
source = "state"
|
||||||
|
parameters = {"fact_key": "pantry.food", "value": 0.0}
|
||||||
|
|
||||||
|
[sub_resource type="Resource" id="Predicate_pantry_restocked"]
|
||||||
|
script = ExtResource("2_predicate")
|
||||||
|
predicate_id = &"pantry_restocked"
|
||||||
|
evaluator_id = &"event_pantry_supplied"
|
||||||
|
parameters = {"amount": 1.0, "item_id": "food", "target_id": "village_pantry"}
|
||||||
|
|
||||||
|
[sub_resource type="Resource" id="Alternative_restock"]
|
||||||
|
script = ExtResource("3_alternative")
|
||||||
|
alternative_id = &"restock_pantry"
|
||||||
|
display_name = "Restock the pantry"
|
||||||
|
description = "Bring at least one food item to the village pantry."
|
||||||
|
resolution_predicate_ids = Array[StringName]([&"pantry_restocked"])
|
||||||
|
dialogue_topic_ids = Array[StringName]([&"topic_pantry_shortage"])
|
||||||
|
commitment_terms = {"action_id": "deposit_food", "amount": 1.0, "item_id": "food", "target_id": "village_pantry"}
|
||||||
|
|
||||||
|
[sub_resource type="Resource" id="Alternative_endure"]
|
||||||
|
script = ExtResource("3_alternative")
|
||||||
|
alternative_id = &"endure_shortage"
|
||||||
|
display_name = "Endure the shortage"
|
||||||
|
description = "Make no promise and let the immediate concern pass."
|
||||||
|
resolution_predicate_ids = Array[StringName]([&"pantry_restocked"])
|
||||||
|
dialogue_topic_ids = Array[StringName]([&"topic_pantry_shortage"])
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("1_definition")
|
||||||
|
situation_definition_id = &"situation_pantry_shortage"
|
||||||
|
display_name = "The Pantry Is Empty"
|
||||||
|
description = "The village pantry has run out of food."
|
||||||
|
severity = 70
|
||||||
|
priority = 80
|
||||||
|
trigger_predicates = [SubResource("Predicate_pantry_withdrawal"), SubResource("Predicate_withdrawal_from_pantry"), SubResource("Predicate_withdrawal_of_food"), SubResource("Predicate_pantry_empty")]
|
||||||
|
resolution_predicates = [SubResource("Predicate_pantry_restocked")]
|
||||||
|
expiry_predicate_ids = Array[StringName]([&"age_reached"])
|
||||||
|
dedupe_key_fields = Array[StringName]([&"world_id", &"source_id"])
|
||||||
|
expiry_ticks = 200
|
||||||
|
cooldown_ticks = 50
|
||||||
|
dialogue_topic_ids = Array[StringName]([&"topic_pantry_shortage"])
|
||||||
|
dialogue_intent_ids = Array[StringName]([&"accept", &"decline", &"describe_need", &"offer_help", &"report_progress"])
|
||||||
|
alternatives = [SubResource("Alternative_restock"), SubResource("Alternative_endure")]
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
extends GutTest
|
||||||
|
|
||||||
|
|
||||||
|
func test_core_pack_owns_sorted_situation_intent_and_template_content() -> void:
|
||||||
|
var catalog := ContentCatalog.create_core()
|
||||||
|
assert_true(catalog.is_valid(), "%s" % [catalog.get_errors()])
|
||||||
|
assert_eq(_situation_ids(catalog.get_situations()), [&"situation_pantry_shortage"])
|
||||||
|
assert_eq(_intent_ids(catalog.get_dialogue_intents()), _sorted_ids(ConversationIntentIds.ALL))
|
||||||
|
assert_eq(
|
||||||
|
_template_catalog_ids(catalog.get_dialogue_template_catalogs()),
|
||||||
|
[&"jajce_conversation_templates"]
|
||||||
|
)
|
||||||
|
|
||||||
|
var authored := catalog.get_situation(SituationSystem.DEFINITION_PANTRY_SHORTAGE)
|
||||||
|
var compatibility_copy := SituationSystem.create_pantry_shortage_definition()
|
||||||
|
assert_not_null(authored)
|
||||||
|
assert_not_same(authored, compatibility_copy)
|
||||||
|
assert_eq(authored.display_name, compatibility_copy.display_name)
|
||||||
|
assert_eq(authored.priority, compatibility_copy.priority)
|
||||||
|
assert_eq(authored.dialogue_intent_ids, compatibility_copy.dialogue_intent_ids)
|
||||||
|
compatibility_copy.priority = -100
|
||||||
|
assert_eq(authored.priority, 80, "Compatibility callers cannot mutate authored content")
|
||||||
|
|
||||||
|
var legacy_catalog := (
|
||||||
|
load("res://simulation/dialogue/resources/jajce_conversation_intents.tres")
|
||||||
|
as ConversationIntentCatalog
|
||||||
|
)
|
||||||
|
assert_not_null(legacy_catalog)
|
||||||
|
assert_true(legacy_catalog.rebuild().is_empty(), "%s" % [legacy_catalog.get_errors()])
|
||||||
|
assert_eq(legacy_catalog.get_intent_ids(), _intent_ids(catalog.get_dialogue_intents()))
|
||||||
|
var templates := catalog.get_dialogue_template_catalog(&"jajce_conversation_templates")
|
||||||
|
assert_not_null(templates)
|
||||||
|
assert_true(templates.validate(legacy_catalog.get_intent_ids()).is_empty())
|
||||||
|
|
||||||
|
|
||||||
|
func test_invalid_cross_references_fail_without_partial_publication() -> void:
|
||||||
|
var core := load(ContentCatalog.CORE_PACK_PATH) as SimulationContentPack
|
||||||
|
var addon := SimulationContentPack.new()
|
||||||
|
addon.pack_id = &"broken_authored_content"
|
||||||
|
addon.display_name = "Broken authored content"
|
||||||
|
addon.required_pack_ids = [&"core"]
|
||||||
|
|
||||||
|
var broken_situation := _broken_situation()
|
||||||
|
assert_eq(broken_situation.alternatives.size(), 2)
|
||||||
|
assert_eq(
|
||||||
|
(
|
||||||
|
broken_situation
|
||||||
|
. validate()
|
||||||
|
. filter(func(error: String) -> bool: return "alternative" in error.to_lower())
|
||||||
|
. size()
|
||||||
|
),
|
||||||
|
3
|
||||||
|
)
|
||||||
|
addon.situations = [core.situations[0], broken_situation]
|
||||||
|
var broken_intent := ConversationIntentDefinition.new()
|
||||||
|
broken_intent.intent_id = &"broken_intent"
|
||||||
|
broken_intent.response_intent_ids = [&"missing_response"]
|
||||||
|
broken_intent.template_catalog_id = &"missing_template_catalog"
|
||||||
|
addon.dialogue_intents = [core.dialogue_intents[0], broken_intent]
|
||||||
|
var rogue_templates := ConversationTemplateCatalog.new()
|
||||||
|
rogue_templates.catalog_id = &"rogue_templates"
|
||||||
|
rogue_templates.line_templates = {"ghost_intent": ["Ghost line"]}
|
||||||
|
rogue_templates.option_templates = {"ghost_intent": ["Ghost option"]}
|
||||||
|
addon.dialogue_template_catalogs = [core.dialogue_template_catalogs[0], rogue_templates]
|
||||||
|
|
||||||
|
var catalog := ContentCatalog.new()
|
||||||
|
var packs: Array[SimulationContentPack] = [addon, core]
|
||||||
|
var errors := catalog.rebuild(packs)
|
||||||
|
for expected in [
|
||||||
|
"Duplicate situation_definition_id 'situation_pantry_shortage'",
|
||||||
|
"duplicate predicate 'shared_predicate' across trigger/resolution predicates",
|
||||||
|
"unsupported event predicate 'unknown_predicate_handler'",
|
||||||
|
"duplicate alternative 'duplicate_alternative'",
|
||||||
|
"references unknown resolution predicate 'missing_predicate'",
|
||||||
|
"references unknown dialogue intent 'missing_intent'",
|
||||||
|
"Duplicate dialogue intent_id 'accept'",
|
||||||
|
"references unknown response intent 'missing_response'",
|
||||||
|
"references unknown template catalog 'missing_template_catalog'",
|
||||||
|
"Duplicate dialogue template catalog_id 'jajce_conversation_templates'",
|
||||||
|
"Unknown line template intent 'ghost_intent'",
|
||||||
|
"Unknown option template intent 'ghost_intent'",
|
||||||
|
]:
|
||||||
|
assert_true(_has_error(errors, expected), "%s missing from %s" % [expected, errors])
|
||||||
|
|
||||||
|
assert_false(catalog.is_valid())
|
||||||
|
assert_true(catalog.get_situations().is_empty())
|
||||||
|
assert_true(catalog.get_dialogue_intents().is_empty())
|
||||||
|
assert_true(catalog.get_dialogue_template_catalogs().is_empty())
|
||||||
|
assert_null(catalog.get_situation(&"broken_situation"))
|
||||||
|
assert_null(catalog.get_dialogue_intent(&"broken_intent"))
|
||||||
|
|
||||||
|
|
||||||
|
func _broken_situation() -> SituationDefinition:
|
||||||
|
var trigger := SituationPredicate.new()
|
||||||
|
trigger.predicate_id = &"shared_predicate"
|
||||||
|
trigger.evaluator_id = &"unknown_predicate_handler"
|
||||||
|
trigger.source = String(SituationPredicate.SOURCE_EVENT)
|
||||||
|
var resolution := SituationPredicate.new()
|
||||||
|
resolution.predicate_id = &"shared_predicate"
|
||||||
|
resolution.evaluator_id = &"event_type"
|
||||||
|
resolution.source = String(SituationPredicate.SOURCE_EVENT)
|
||||||
|
|
||||||
|
var first_alternative := SituationAlternativeDefinition.new()
|
||||||
|
first_alternative.alternative_id = &"duplicate_alternative"
|
||||||
|
first_alternative.display_name = "First alternative"
|
||||||
|
first_alternative.resolution_predicate_ids = [&"missing_predicate"]
|
||||||
|
var second_alternative := first_alternative.duplicate_alternative()
|
||||||
|
|
||||||
|
var definition := SituationDefinition.new()
|
||||||
|
definition.situation_definition_id = &"broken_situation"
|
||||||
|
definition.display_name = "Broken situation"
|
||||||
|
definition.trigger_predicates = [trigger]
|
||||||
|
definition.resolution_predicates = [resolution]
|
||||||
|
definition.dialogue_intent_ids = [&"missing_intent"]
|
||||||
|
definition.alternatives = [first_alternative, second_alternative]
|
||||||
|
return definition
|
||||||
|
|
||||||
|
|
||||||
|
func _situation_ids(definitions: Array[SituationDefinition]) -> Array[StringName]:
|
||||||
|
var ids: Array[StringName] = []
|
||||||
|
for definition in definitions:
|
||||||
|
ids.append(definition.situation_definition_id)
|
||||||
|
return ids
|
||||||
|
|
||||||
|
|
||||||
|
func _intent_ids(definitions: Array[ConversationIntentDefinition]) -> Array[StringName]:
|
||||||
|
var ids: Array[StringName] = []
|
||||||
|
for definition in definitions:
|
||||||
|
ids.append(definition.intent_id)
|
||||||
|
return ids
|
||||||
|
|
||||||
|
|
||||||
|
func _template_catalog_ids(
|
||||||
|
definitions: Array[ConversationTemplateCatalog],
|
||||||
|
) -> Array[StringName]:
|
||||||
|
var ids: Array[StringName] = []
|
||||||
|
for definition in definitions:
|
||||||
|
ids.append(definition.catalog_id)
|
||||||
|
return ids
|
||||||
|
|
||||||
|
|
||||||
|
func _sorted_ids(source: Array[StringName]) -> Array[StringName]:
|
||||||
|
var ids := source.duplicate()
|
||||||
|
ids.sort_custom(
|
||||||
|
func(left: StringName, right: StringName) -> bool: return String(left) < String(right)
|
||||||
|
)
|
||||||
|
return ids
|
||||||
|
|
||||||
|
|
||||||
|
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://bupkjb7j38smr
|
||||||
Reference in New Issue
Block a user