feat: route resources through typed content
This commit is contained in:
@@ -0,0 +1,30 @@
|
|||||||
|
class_name CapabilityTagDefinition
|
||||||
|
extends Resource
|
||||||
|
|
||||||
|
const DOMAIN_ACTOR := &"actor"
|
||||||
|
const DOMAIN_TARGET := &"target"
|
||||||
|
const DOMAIN_ITEM := &"item"
|
||||||
|
const DOMAIN_RESOURCE := &"resource"
|
||||||
|
const DOMAIN_STORAGE := &"storage"
|
||||||
|
const VALID_DOMAINS := [
|
||||||
|
DOMAIN_ACTOR,
|
||||||
|
DOMAIN_TARGET,
|
||||||
|
DOMAIN_ITEM,
|
||||||
|
DOMAIN_RESOURCE,
|
||||||
|
DOMAIN_STORAGE,
|
||||||
|
]
|
||||||
|
|
||||||
|
@export var tag_id: StringName
|
||||||
|
@export var display_name: String
|
||||||
|
@export var domain: StringName
|
||||||
|
|
||||||
|
|
||||||
|
func validate() -> Array[String]:
|
||||||
|
var errors: Array[String] = []
|
||||||
|
if tag_id.is_empty():
|
||||||
|
errors.append("tag_id is empty")
|
||||||
|
if display_name.is_empty():
|
||||||
|
errors.append("display_name is empty for '%s'" % tag_id)
|
||||||
|
if domain not in VALID_DOMAINS:
|
||||||
|
errors.append("domain '%s' is invalid for '%s'" % [domain, tag_id])
|
||||||
|
return errors
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://dhlqkrwuqdhri
|
||||||
@@ -8,28 +8,39 @@ var _errors: Array[String] = []
|
|||||||
var _packs: Array[SimulationContentPack] = []
|
var _packs: Array[SimulationContentPack] = []
|
||||||
var _actions: Array[ActionDefinition] = []
|
var _actions: Array[ActionDefinition] = []
|
||||||
var _professions: Array[ProfessionDefinition] = []
|
var _professions: Array[ProfessionDefinition] = []
|
||||||
|
var _capability_tags: Array[CapabilityTagDefinition] = []
|
||||||
var _items: Array[ItemDefinition] = []
|
var _items: Array[ItemDefinition] = []
|
||||||
|
var _resources: Array[ResourceDefinition] = []
|
||||||
|
var _storages: Array[StorageDefinition] = []
|
||||||
var _enemies: Array[EnemyDefinition] = []
|
var _enemies: Array[EnemyDefinition] = []
|
||||||
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 = {}
|
||||||
|
var _capability_tags_by_id: Dictionary = {}
|
||||||
var _items_by_id: Dictionary = {}
|
var _items_by_id: Dictionary = {}
|
||||||
|
var _resources_by_id: Dictionary = {}
|
||||||
|
var _storages_by_id: Dictionary = {}
|
||||||
var _enemies_by_id: Dictionary = {}
|
var _enemies_by_id: Dictionary = {}
|
||||||
|
|
||||||
|
|
||||||
static func create_core(supported_handler_ids: Array[StringName] = []) -> ContentCatalog:
|
static func create_core(
|
||||||
|
supported_handler_ids: Array[StringName] = [],
|
||||||
|
supported_presentation_cue_ids: Array[StringName] = []
|
||||||
|
) -> 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
|
||||||
if pack == null:
|
if pack == null:
|
||||||
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)
|
catalog.rebuild(packs, supported_handler_ids, supported_presentation_cue_ids)
|
||||||
return catalog
|
return catalog
|
||||||
|
|
||||||
|
|
||||||
func rebuild(
|
func rebuild(
|
||||||
content_packs: Array[SimulationContentPack], supported_handler_ids: Array[StringName] = []
|
content_packs: Array[SimulationContentPack],
|
||||||
|
supported_handler_ids: Array[StringName] = [],
|
||||||
|
supported_presentation_cue_ids: Array[StringName] = []
|
||||||
) -> Array[String]:
|
) -> Array[String]:
|
||||||
_clear_published_content()
|
_clear_published_content()
|
||||||
var errors: Array[String] = []
|
var errors: Array[String] = []
|
||||||
@@ -43,15 +54,34 @@ func rebuild(
|
|||||||
errors.append("Duplicate supported handler '%s'" % handler_id)
|
errors.append("Duplicate supported handler '%s'" % handler_id)
|
||||||
else:
|
else:
|
||||||
supported_handlers[handler_id] = true
|
supported_handlers[handler_id] = true
|
||||||
|
var cue_ids := (
|
||||||
|
supported_presentation_cue_ids
|
||||||
|
if not supported_presentation_cue_ids.is_empty()
|
||||||
|
else PresentationCueIds.get_all()
|
||||||
|
)
|
||||||
|
var supported_cues := {}
|
||||||
|
for cue_id in cue_ids:
|
||||||
|
if cue_id.is_empty():
|
||||||
|
errors.append("Supported presentation cue ID is empty")
|
||||||
|
elif supported_cues.has(cue_id):
|
||||||
|
errors.append("Duplicate supported presentation cue '%s'" % cue_id)
|
||||||
|
else:
|
||||||
|
supported_cues[cue_id] = true
|
||||||
|
|
||||||
var packs_by_id := {}
|
var packs_by_id := {}
|
||||||
var actions_by_id := {}
|
var actions_by_id := {}
|
||||||
var professions_by_id := {}
|
var professions_by_id := {}
|
||||||
|
var capability_tags_by_id := {}
|
||||||
var items_by_id := {}
|
var items_by_id := {}
|
||||||
|
var resources_by_id := {}
|
||||||
|
var storages_by_id := {}
|
||||||
var enemies_by_id := {}
|
var enemies_by_id := {}
|
||||||
var actions: Array[ActionDefinition] = []
|
var actions: Array[ActionDefinition] = []
|
||||||
var professions: Array[ProfessionDefinition] = []
|
var professions: Array[ProfessionDefinition] = []
|
||||||
|
var capability_tags: Array[CapabilityTagDefinition] = []
|
||||||
var items: Array[ItemDefinition] = []
|
var items: Array[ItemDefinition] = []
|
||||||
|
var resources: Array[ResourceDefinition] = []
|
||||||
|
var storages: Array[StorageDefinition] = []
|
||||||
var enemies: Array[EnemyDefinition] = []
|
var enemies: Array[EnemyDefinition] = []
|
||||||
|
|
||||||
for pack in pack_list:
|
for pack in pack_list:
|
||||||
@@ -66,7 +96,10 @@ func rebuild(
|
|||||||
packs_by_id[pack.pack_id] = pack
|
packs_by_id[pack.pack_id] = pack
|
||||||
_collect_actions(pack, actions, actions_by_id, errors)
|
_collect_actions(pack, actions, actions_by_id, errors)
|
||||||
_collect_professions(pack, professions, professions_by_id, errors)
|
_collect_professions(pack, professions, professions_by_id, errors)
|
||||||
|
_collect_capability_tags(pack, capability_tags, capability_tags_by_id, errors)
|
||||||
_collect_items(pack, items, items_by_id, errors)
|
_collect_items(pack, items, items_by_id, errors)
|
||||||
|
_collect_resources(pack, resources, resources_by_id, errors)
|
||||||
|
_collect_storages(pack, storages, storages_by_id, errors)
|
||||||
_collect_enemies(pack, enemies, enemies_by_id, errors)
|
_collect_enemies(pack, enemies, enemies_by_id, errors)
|
||||||
|
|
||||||
for pack in pack_list:
|
for pack in pack_list:
|
||||||
@@ -93,7 +126,21 @@ func rebuild(
|
|||||||
)
|
)
|
||||||
|
|
||||||
_validate_action_references(
|
_validate_action_references(
|
||||||
actions, actions_by_id, professions_by_id, items_by_id, supported_handlers, errors
|
actions,
|
||||||
|
actions_by_id,
|
||||||
|
professions_by_id,
|
||||||
|
items_by_id,
|
||||||
|
capability_tags_by_id,
|
||||||
|
supported_handlers,
|
||||||
|
supported_cues,
|
||||||
|
errors
|
||||||
|
)
|
||||||
|
_validate_item_references(items, capability_tags_by_id, supported_cues, errors)
|
||||||
|
_validate_resource_references(
|
||||||
|
resources, items_by_id, actions_by_id, capability_tags_by_id, supported_cues, errors
|
||||||
|
)
|
||||||
|
_validate_storage_references(
|
||||||
|
storages, items_by_id, actions_by_id, capability_tags_by_id, supported_cues, errors
|
||||||
)
|
)
|
||||||
_validate_enemy_references(enemies, items_by_id, errors)
|
_validate_enemy_references(enemies, items_by_id, errors)
|
||||||
errors.sort()
|
errors.sort()
|
||||||
@@ -103,17 +150,26 @@ func rebuild(
|
|||||||
|
|
||||||
actions.sort_custom(_sort_actions)
|
actions.sort_custom(_sort_actions)
|
||||||
professions.sort_custom(_sort_professions)
|
professions.sort_custom(_sort_professions)
|
||||||
|
capability_tags.sort_custom(_sort_capability_tags)
|
||||||
items.sort_custom(_sort_items)
|
items.sort_custom(_sort_items)
|
||||||
|
resources.sort_custom(_sort_resources)
|
||||||
|
storages.sort_custom(_sort_storages)
|
||||||
enemies.sort_custom(_sort_enemies)
|
enemies.sort_custom(_sort_enemies)
|
||||||
_packs = pack_list
|
_packs = pack_list
|
||||||
_actions = actions
|
_actions = actions
|
||||||
_professions = professions
|
_professions = professions
|
||||||
|
_capability_tags = capability_tags
|
||||||
_items = items
|
_items = items
|
||||||
|
_resources = resources
|
||||||
|
_storages = storages
|
||||||
_enemies = enemies
|
_enemies = enemies
|
||||||
_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
|
||||||
|
_capability_tags_by_id = capability_tags_by_id
|
||||||
_items_by_id = items_by_id
|
_items_by_id = items_by_id
|
||||||
|
_resources_by_id = resources_by_id
|
||||||
|
_storages_by_id = storages_by_id
|
||||||
_enemies_by_id = enemies_by_id
|
_enemies_by_id = enemies_by_id
|
||||||
_valid = true
|
_valid = true
|
||||||
return []
|
return []
|
||||||
@@ -139,10 +195,22 @@ func get_professions() -> Array[ProfessionDefinition]:
|
|||||||
return _professions.duplicate()
|
return _professions.duplicate()
|
||||||
|
|
||||||
|
|
||||||
|
func get_capability_tags() -> Array[CapabilityTagDefinition]:
|
||||||
|
return _capability_tags.duplicate()
|
||||||
|
|
||||||
|
|
||||||
func get_items() -> Array[ItemDefinition]:
|
func get_items() -> Array[ItemDefinition]:
|
||||||
return _items.duplicate()
|
return _items.duplicate()
|
||||||
|
|
||||||
|
|
||||||
|
func get_resources() -> Array[ResourceDefinition]:
|
||||||
|
return _resources.duplicate()
|
||||||
|
|
||||||
|
|
||||||
|
func get_storages() -> Array[StorageDefinition]:
|
||||||
|
return _storages.duplicate()
|
||||||
|
|
||||||
|
|
||||||
func get_enemies() -> Array[EnemyDefinition]:
|
func get_enemies() -> Array[EnemyDefinition]:
|
||||||
return _enemies.duplicate()
|
return _enemies.duplicate()
|
||||||
|
|
||||||
@@ -159,26 +227,59 @@ func get_profession(profession_id: StringName) -> ProfessionDefinition:
|
|||||||
return _professions_by_id.get(profession_id) as ProfessionDefinition
|
return _professions_by_id.get(profession_id) as ProfessionDefinition
|
||||||
|
|
||||||
|
|
||||||
|
func get_capability_tag(tag_id: StringName) -> CapabilityTagDefinition:
|
||||||
|
return _capability_tags_by_id.get(tag_id) as CapabilityTagDefinition
|
||||||
|
|
||||||
|
|
||||||
func get_item(item_id: StringName) -> ItemDefinition:
|
func get_item(item_id: StringName) -> ItemDefinition:
|
||||||
return _items_by_id.get(item_id) as ItemDefinition
|
return _items_by_id.get(item_id) as ItemDefinition
|
||||||
|
|
||||||
|
|
||||||
|
func get_resource(resource_id: StringName) -> ResourceDefinition:
|
||||||
|
return _resources_by_id.get(resource_id) as ResourceDefinition
|
||||||
|
|
||||||
|
|
||||||
|
func get_storage(storage_id: StringName) -> StorageDefinition:
|
||||||
|
return _storages_by_id.get(storage_id) as StorageDefinition
|
||||||
|
|
||||||
|
|
||||||
func get_enemy(enemy_id: StringName) -> EnemyDefinition:
|
func get_enemy(enemy_id: StringName) -> EnemyDefinition:
|
||||||
return _enemies_by_id.get(enemy_id) as EnemyDefinition
|
return _enemies_by_id.get(enemy_id) as EnemyDefinition
|
||||||
|
|
||||||
|
|
||||||
|
func get_authoring_report() -> Dictionary:
|
||||||
|
return {
|
||||||
|
"valid": _valid,
|
||||||
|
"errors": get_errors(),
|
||||||
|
"packs": _ids_for(_packs, &"pack_id"),
|
||||||
|
"actions": _ids_for(_actions, &"action_id"),
|
||||||
|
"professions": _ids_for(_professions, &"profession_id"),
|
||||||
|
"capability_tags": _ids_for(_capability_tags, &"tag_id"),
|
||||||
|
"items": _ids_for(_items, &"item_id"),
|
||||||
|
"resources": _ids_for(_resources, &"resource_id"),
|
||||||
|
"storages": _ids_for(_storages, &"storage_id"),
|
||||||
|
"enemies": _ids_for(_enemies, &"enemy_id"),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
func _clear_published_content() -> void:
|
func _clear_published_content() -> void:
|
||||||
_valid = false
|
_valid = false
|
||||||
_errors.clear()
|
_errors.clear()
|
||||||
_packs.clear()
|
_packs.clear()
|
||||||
_actions.clear()
|
_actions.clear()
|
||||||
_professions.clear()
|
_professions.clear()
|
||||||
|
_capability_tags.clear()
|
||||||
_items.clear()
|
_items.clear()
|
||||||
|
_resources.clear()
|
||||||
|
_storages.clear()
|
||||||
_enemies.clear()
|
_enemies.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()
|
||||||
|
_capability_tags_by_id.clear()
|
||||||
_items_by_id.clear()
|
_items_by_id.clear()
|
||||||
|
_resources_by_id.clear()
|
||||||
|
_storages_by_id.clear()
|
||||||
_enemies_by_id.clear()
|
_enemies_by_id.clear()
|
||||||
|
|
||||||
|
|
||||||
@@ -230,6 +331,54 @@ static func _collect_items(
|
|||||||
definitions.append(definition)
|
definitions.append(definition)
|
||||||
|
|
||||||
|
|
||||||
|
static func _collect_capability_tags(
|
||||||
|
pack: SimulationContentPack,
|
||||||
|
definitions: Array[CapabilityTagDefinition],
|
||||||
|
definitions_by_id: Dictionary,
|
||||||
|
errors: Array[String]
|
||||||
|
) -> void:
|
||||||
|
for definition in pack.capability_tags:
|
||||||
|
if definition == null:
|
||||||
|
continue
|
||||||
|
if definitions_by_id.has(definition.tag_id):
|
||||||
|
errors.append("Duplicate tag_id '%s'" % definition.tag_id)
|
||||||
|
continue
|
||||||
|
definitions_by_id[definition.tag_id] = definition
|
||||||
|
definitions.append(definition)
|
||||||
|
|
||||||
|
|
||||||
|
static func _collect_resources(
|
||||||
|
pack: SimulationContentPack,
|
||||||
|
definitions: Array[ResourceDefinition],
|
||||||
|
definitions_by_id: Dictionary,
|
||||||
|
errors: Array[String]
|
||||||
|
) -> void:
|
||||||
|
for definition in pack.resources:
|
||||||
|
if definition == null:
|
||||||
|
continue
|
||||||
|
if definitions_by_id.has(definition.resource_id):
|
||||||
|
errors.append("Duplicate resource_id '%s'" % definition.resource_id)
|
||||||
|
continue
|
||||||
|
definitions_by_id[definition.resource_id] = definition
|
||||||
|
definitions.append(definition)
|
||||||
|
|
||||||
|
|
||||||
|
static func _collect_storages(
|
||||||
|
pack: SimulationContentPack,
|
||||||
|
definitions: Array[StorageDefinition],
|
||||||
|
definitions_by_id: Dictionary,
|
||||||
|
errors: Array[String]
|
||||||
|
) -> void:
|
||||||
|
for definition in pack.storages:
|
||||||
|
if definition == null:
|
||||||
|
continue
|
||||||
|
if definitions_by_id.has(definition.storage_id):
|
||||||
|
errors.append("Duplicate storage_id '%s'" % definition.storage_id)
|
||||||
|
continue
|
||||||
|
definitions_by_id[definition.storage_id] = definition
|
||||||
|
definitions.append(definition)
|
||||||
|
|
||||||
|
|
||||||
static func _collect_enemies(
|
static func _collect_enemies(
|
||||||
pack: SimulationContentPack,
|
pack: SimulationContentPack,
|
||||||
definitions: Array[EnemyDefinition],
|
definitions: Array[EnemyDefinition],
|
||||||
@@ -251,10 +400,32 @@ static func _validate_action_references(
|
|||||||
actions_by_id: Dictionary,
|
actions_by_id: Dictionary,
|
||||||
professions_by_id: Dictionary,
|
professions_by_id: Dictionary,
|
||||||
items_by_id: Dictionary,
|
items_by_id: Dictionary,
|
||||||
|
capability_tags_by_id: Dictionary,
|
||||||
supported_handlers: Dictionary,
|
supported_handlers: Dictionary,
|
||||||
|
supported_cues: Dictionary,
|
||||||
errors: Array[String]
|
errors: Array[String]
|
||||||
) -> void:
|
) -> void:
|
||||||
for definition in actions:
|
for definition in actions:
|
||||||
|
_validate_tag_ids(
|
||||||
|
"Action '%s' actor" % definition.action_id,
|
||||||
|
definition.actor_capability_tags,
|
||||||
|
CapabilityTagDefinition.DOMAIN_ACTOR,
|
||||||
|
capability_tags_by_id,
|
||||||
|
errors
|
||||||
|
)
|
||||||
|
_validate_tag_ids(
|
||||||
|
"Action '%s' target" % definition.action_id,
|
||||||
|
definition.target_capability_tags,
|
||||||
|
CapabilityTagDefinition.DOMAIN_TARGET,
|
||||||
|
capability_tags_by_id,
|
||||||
|
errors
|
||||||
|
)
|
||||||
|
_validate_presentation_cue(
|
||||||
|
"Action '%s'" % definition.action_id,
|
||||||
|
definition.presentation_cue_id,
|
||||||
|
supported_cues,
|
||||||
|
errors
|
||||||
|
)
|
||||||
for handler_field in [
|
for handler_field in [
|
||||||
["selection policy", definition.selection_policy_id],
|
["selection policy", definition.selection_policy_id],
|
||||||
["target policy", definition.target_policy_id],
|
["target policy", definition.target_policy_id],
|
||||||
@@ -309,6 +480,174 @@ static func _validate_action_references(
|
|||||||
% [definition.action_id, definition.completion_cost_resource_id]
|
% [definition.action_id, definition.completion_cost_resource_id]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
for effect in definition.effects:
|
||||||
|
if effect == null:
|
||||||
|
continue
|
||||||
|
var effect_item_id: StringName = effect.get("item_id")
|
||||||
|
if not effect_item_id.is_empty() and not items_by_id.has(effect_item_id):
|
||||||
|
errors.append(
|
||||||
|
(
|
||||||
|
"Action '%s' effect references unknown item '%s'"
|
||||||
|
% [definition.action_id, effect_item_id]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
var scheduled_action_id: StringName = effect.get("scheduled_action_id")
|
||||||
|
if not scheduled_action_id.is_empty() and not actions_by_id.has(scheduled_action_id):
|
||||||
|
errors.append(
|
||||||
|
(
|
||||||
|
"Action '%s' effect references unknown action '%s'"
|
||||||
|
% [definition.action_id, scheduled_action_id]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
static func _validate_item_references(
|
||||||
|
items: Array[ItemDefinition],
|
||||||
|
capability_tags_by_id: Dictionary,
|
||||||
|
supported_cues: Dictionary,
|
||||||
|
errors: Array[String]
|
||||||
|
) -> void:
|
||||||
|
for definition in items:
|
||||||
|
_validate_tag_ids(
|
||||||
|
"Item '%s'" % definition.item_id,
|
||||||
|
definition.capability_tag_ids,
|
||||||
|
CapabilityTagDefinition.DOMAIN_ITEM,
|
||||||
|
capability_tags_by_id,
|
||||||
|
errors
|
||||||
|
)
|
||||||
|
_validate_presentation_cue(
|
||||||
|
"Item '%s'" % definition.item_id, definition.presentation_cue_id, supported_cues, errors
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
static func _validate_resource_references(
|
||||||
|
resources: Array[ResourceDefinition],
|
||||||
|
items_by_id: Dictionary,
|
||||||
|
actions_by_id: Dictionary,
|
||||||
|
capability_tags_by_id: Dictionary,
|
||||||
|
supported_cues: Dictionary,
|
||||||
|
errors: Array[String]
|
||||||
|
) -> void:
|
||||||
|
for definition in resources:
|
||||||
|
var item := items_by_id.get(definition.item_id) as ItemDefinition
|
||||||
|
if item == null:
|
||||||
|
errors.append(
|
||||||
|
(
|
||||||
|
"Resource '%s' references unknown item '%s'"
|
||||||
|
% [definition.resource_id, definition.item_id]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
elif not item.is_resource():
|
||||||
|
errors.append(
|
||||||
|
(
|
||||||
|
"Resource '%s' references non-resource item '%s'"
|
||||||
|
% [definition.resource_id, definition.item_id]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
var action := actions_by_id.get(definition.extraction_action_id) as ActionDefinition
|
||||||
|
if action == null:
|
||||||
|
errors.append(
|
||||||
|
(
|
||||||
|
"Resource '%s' references unknown action '%s'"
|
||||||
|
% [definition.resource_id, definition.extraction_action_id]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
elif action.target_type != SimulationIds.TARGET_RESOURCE:
|
||||||
|
errors.append(
|
||||||
|
(
|
||||||
|
"Resource '%s' references non-resource-target action '%s'"
|
||||||
|
% [definition.resource_id, definition.extraction_action_id]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
_validate_tag_ids(
|
||||||
|
"Resource '%s'" % definition.resource_id,
|
||||||
|
definition.capability_tag_ids,
|
||||||
|
CapabilityTagDefinition.DOMAIN_RESOURCE,
|
||||||
|
capability_tags_by_id,
|
||||||
|
errors
|
||||||
|
)
|
||||||
|
_validate_presentation_cue(
|
||||||
|
"Resource '%s'" % definition.resource_id,
|
||||||
|
definition.presentation_cue_id,
|
||||||
|
supported_cues,
|
||||||
|
errors
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
static func _validate_storage_references(
|
||||||
|
storages: Array[StorageDefinition],
|
||||||
|
items_by_id: Dictionary,
|
||||||
|
actions_by_id: Dictionary,
|
||||||
|
capability_tags_by_id: Dictionary,
|
||||||
|
supported_cues: Dictionary,
|
||||||
|
errors: Array[String]
|
||||||
|
) -> void:
|
||||||
|
for definition in storages:
|
||||||
|
for item_id in definition.accepted_item_ids:
|
||||||
|
var item := items_by_id.get(item_id) as ItemDefinition
|
||||||
|
if item == null:
|
||||||
|
errors.append(
|
||||||
|
"Storage '%s' references unknown item '%s'" % [definition.storage_id, item_id]
|
||||||
|
)
|
||||||
|
elif not item.is_resource():
|
||||||
|
errors.append(
|
||||||
|
"Storage '%s' accepts non-resource item '%s'" % [definition.storage_id, item_id]
|
||||||
|
)
|
||||||
|
_validate_tag_ids(
|
||||||
|
"Storage '%s' accepted item" % definition.storage_id,
|
||||||
|
definition.accepted_item_tag_ids,
|
||||||
|
CapabilityTagDefinition.DOMAIN_ITEM,
|
||||||
|
capability_tags_by_id,
|
||||||
|
errors
|
||||||
|
)
|
||||||
|
_validate_tag_ids(
|
||||||
|
"Storage '%s'" % definition.storage_id,
|
||||||
|
definition.capability_tag_ids,
|
||||||
|
CapabilityTagDefinition.DOMAIN_STORAGE,
|
||||||
|
capability_tags_by_id,
|
||||||
|
errors
|
||||||
|
)
|
||||||
|
for action_id in [definition.deposit_action_id, definition.withdrawal_action_id]:
|
||||||
|
if not action_id.is_empty() and not actions_by_id.has(action_id):
|
||||||
|
errors.append(
|
||||||
|
(
|
||||||
|
"Storage '%s' references unknown action '%s'"
|
||||||
|
% [definition.storage_id, action_id]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
_validate_presentation_cue(
|
||||||
|
"Storage '%s'" % definition.storage_id,
|
||||||
|
definition.presentation_cue_id,
|
||||||
|
supported_cues,
|
||||||
|
errors
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
static func _validate_tag_ids(
|
||||||
|
owner: String,
|
||||||
|
tag_ids: Array[StringName],
|
||||||
|
expected_domain: StringName,
|
||||||
|
capability_tags_by_id: Dictionary,
|
||||||
|
errors: Array[String]
|
||||||
|
) -> void:
|
||||||
|
for tag_id in tag_ids:
|
||||||
|
var tag := capability_tags_by_id.get(tag_id) as CapabilityTagDefinition
|
||||||
|
if tag == null:
|
||||||
|
errors.append("%s references unknown tag '%s'" % [owner, tag_id])
|
||||||
|
elif tag.domain != expected_domain:
|
||||||
|
errors.append(
|
||||||
|
(
|
||||||
|
"%s references '%s' tag '%s' where '%s' is required"
|
||||||
|
% [owner, tag.domain, tag_id, expected_domain]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
static func _validate_presentation_cue(
|
||||||
|
owner: String, cue_id: StringName, supported_cues: Dictionary, errors: Array[String]
|
||||||
|
) -> void:
|
||||||
|
if not cue_id.is_empty() and not supported_cues.has(cue_id):
|
||||||
|
errors.append("%s references unknown presentation cue '%s'" % [owner, cue_id])
|
||||||
|
|
||||||
|
|
||||||
static func _validate_enemy_references(
|
static func _validate_enemy_references(
|
||||||
@@ -348,9 +687,30 @@ static func _sort_professions(first: ProfessionDefinition, second: ProfessionDef
|
|||||||
return String(first.profession_id) < String(second.profession_id)
|
return String(first.profession_id) < String(second.profession_id)
|
||||||
|
|
||||||
|
|
||||||
|
static func _sort_capability_tags(
|
||||||
|
first: CapabilityTagDefinition, second: CapabilityTagDefinition
|
||||||
|
) -> bool:
|
||||||
|
return String(first.tag_id) < String(second.tag_id)
|
||||||
|
|
||||||
|
|
||||||
static func _sort_items(first: ItemDefinition, second: ItemDefinition) -> bool:
|
static func _sort_items(first: ItemDefinition, second: ItemDefinition) -> bool:
|
||||||
return String(first.item_id) < String(second.item_id)
|
return String(first.item_id) < String(second.item_id)
|
||||||
|
|
||||||
|
|
||||||
|
static func _sort_resources(first: ResourceDefinition, second: ResourceDefinition) -> bool:
|
||||||
|
return String(first.resource_id) < String(second.resource_id)
|
||||||
|
|
||||||
|
|
||||||
|
static func _sort_storages(first: StorageDefinition, second: StorageDefinition) -> bool:
|
||||||
|
return String(first.storage_id) < String(second.storage_id)
|
||||||
|
|
||||||
|
|
||||||
static func _sort_enemies(first: EnemyDefinition, second: EnemyDefinition) -> bool:
|
static func _sort_enemies(first: EnemyDefinition, second: EnemyDefinition) -> bool:
|
||||||
return String(first.enemy_id) < String(second.enemy_id)
|
return String(first.enemy_id) < String(second.enemy_id)
|
||||||
|
|
||||||
|
|
||||||
|
static func _ids_for(definitions: Array, id_property: StringName) -> Array[StringName]:
|
||||||
|
var ids: Array[StringName] = []
|
||||||
|
for definition in definitions:
|
||||||
|
ids.append(StringName(definition.get(id_property)))
|
||||||
|
return ids
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ const VALID_CATEGORIES := [CATEGORY_RESOURCE, CATEGORY_WEAPON]
|
|||||||
@export var item_id: StringName
|
@export var item_id: StringName
|
||||||
@export var display_name: String
|
@export var display_name: String
|
||||||
@export var category: StringName = CATEGORY_RESOURCE
|
@export var category: StringName = CATEGORY_RESOURCE
|
||||||
|
@export var capability_tag_ids: Array[StringName] = []
|
||||||
|
@export var presentation_cue_id: StringName
|
||||||
@export var equip_slot: StringName
|
@export var equip_slot: StringName
|
||||||
@export_range(0.0, 1000.0, 0.1) var damage := 0.0
|
@export_range(0.0, 1000.0, 0.1) var damage := 0.0
|
||||||
@export_range(0.0, 50.0, 0.1) var reach := 0.0
|
@export_range(0.0, 50.0, 0.1) var reach := 0.0
|
||||||
@@ -35,6 +37,14 @@ func validate() -> Array[String]:
|
|||||||
errors.append("attack_cooldown must be positive for '%s'" % item_id)
|
errors.append("attack_cooldown must be positive for '%s'" % item_id)
|
||||||
elif not equip_slot.is_empty():
|
elif not equip_slot.is_empty():
|
||||||
errors.append("resource item '%s' cannot declare equip_slot '%s'" % [item_id, equip_slot])
|
errors.append("resource item '%s' cannot declare equip_slot '%s'" % [item_id, equip_slot])
|
||||||
|
var seen_tags := {}
|
||||||
|
for tag_id in capability_tag_ids:
|
||||||
|
if tag_id.is_empty():
|
||||||
|
errors.append("capability tag ID is empty for '%s'" % item_id)
|
||||||
|
elif seen_tags.has(tag_id):
|
||||||
|
errors.append("duplicate capability tag '%s' for '%s'" % [tag_id, item_id])
|
||||||
|
else:
|
||||||
|
seen_tags[tag_id] = true
|
||||||
return errors
|
return errors
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
class_name PresentationCueIds
|
||||||
|
extends RefCounted
|
||||||
|
|
||||||
|
# Pure stable vocabulary only. Presentation layers may map these IDs to scenes,
|
||||||
|
# audio, particles, or animation without making the headless catalog load them.
|
||||||
|
const ITEM_FOOD := &"item.food"
|
||||||
|
const ITEM_HERBS := &"item.herbs"
|
||||||
|
const ITEM_WOOD := &"item.wood"
|
||||||
|
const RESOURCE_BERRY_PATCH := &"resource.berry_patch"
|
||||||
|
const RESOURCE_HERB_PATCH := &"resource.herb_patch"
|
||||||
|
const RESOURCE_TREE := &"resource.tree"
|
||||||
|
const STORAGE_APOTHECARY := &"storage.apothecary"
|
||||||
|
const STORAGE_PANTRY := &"storage.pantry"
|
||||||
|
const STORAGE_WOODPILE := &"storage.woodpile"
|
||||||
|
|
||||||
|
|
||||||
|
static func get_all() -> Array[StringName]:
|
||||||
|
var cue_ids: Array[StringName] = [
|
||||||
|
ITEM_FOOD,
|
||||||
|
ITEM_HERBS,
|
||||||
|
ITEM_WOOD,
|
||||||
|
RESOURCE_BERRY_PATCH,
|
||||||
|
RESOURCE_HERB_PATCH,
|
||||||
|
RESOURCE_TREE,
|
||||||
|
STORAGE_APOTHECARY,
|
||||||
|
STORAGE_PANTRY,
|
||||||
|
STORAGE_WOODPILE,
|
||||||
|
]
|
||||||
|
cue_ids.sort_custom(
|
||||||
|
func(first: StringName, second: StringName): return String(first) < String(second)
|
||||||
|
)
|
||||||
|
return cue_ids
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://c0x5hj6mps7cr
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
class_name ResourceDefinition
|
||||||
|
extends Resource
|
||||||
|
|
||||||
|
@export var resource_id: StringName
|
||||||
|
@export var display_name: String
|
||||||
|
@export var item_id: StringName
|
||||||
|
@export var extraction_action_id: StringName
|
||||||
|
@export var capability_tag_ids: Array[StringName] = []
|
||||||
|
@export var presentation_cue_id: StringName
|
||||||
|
@export_range(0.0, 100000.0, 0.1) var initial_amount := 10.0
|
||||||
|
@export_range(0.0, 100000.0, 0.1) var yield_per_action := 1.0
|
||||||
|
@export_range(0.0, 100000.0, 0.1) var max_amount := 10.0
|
||||||
|
@export_range(0.0, 1000.0, 0.01) var regrow_rate := 0.0
|
||||||
|
@export_range(0.0, 1.0, 0.05) var safety_risk := 0.0
|
||||||
|
@export_range(0.0, 1000.0, 0.1) var comfort_distance := 18.0
|
||||||
|
@export var discovery_priority := 0.0
|
||||||
|
@export var can_npcs_use := true
|
||||||
|
@export var can_player_use := true
|
||||||
|
|
||||||
|
|
||||||
|
func validate() -> Array[String]:
|
||||||
|
var errors: Array[String] = []
|
||||||
|
if resource_id.is_empty():
|
||||||
|
errors.append("resource_id is empty")
|
||||||
|
if display_name.is_empty():
|
||||||
|
errors.append("display_name is empty for '%s'" % resource_id)
|
||||||
|
if item_id.is_empty():
|
||||||
|
errors.append("item_id is empty for '%s'" % resource_id)
|
||||||
|
if extraction_action_id.is_empty():
|
||||||
|
errors.append("extraction_action_id is empty for '%s'" % resource_id)
|
||||||
|
if not is_finite(initial_amount) or initial_amount <= 0.0:
|
||||||
|
errors.append("initial_amount must be positive for '%s'" % resource_id)
|
||||||
|
if not is_finite(yield_per_action) or yield_per_action <= 0.0:
|
||||||
|
errors.append("yield_per_action must be positive for '%s'" % resource_id)
|
||||||
|
if not is_finite(max_amount) or max_amount < initial_amount:
|
||||||
|
errors.append("max_amount must cover initial_amount for '%s'" % resource_id)
|
||||||
|
if not is_finite(regrow_rate) or regrow_rate < 0.0:
|
||||||
|
errors.append("regrow_rate must be non-negative for '%s'" % resource_id)
|
||||||
|
if not is_finite(safety_risk) or safety_risk < 0.0 or safety_risk > 1.0:
|
||||||
|
errors.append("safety_risk must be between zero and one for '%s'" % resource_id)
|
||||||
|
if not is_finite(comfort_distance) or comfort_distance < 0.0:
|
||||||
|
errors.append("comfort_distance must be non-negative for '%s'" % resource_id)
|
||||||
|
if not is_finite(discovery_priority):
|
||||||
|
errors.append("discovery_priority must be finite for '%s'" % resource_id)
|
||||||
|
_validate_unique_ids(capability_tag_ids, "capability tag", errors)
|
||||||
|
return errors
|
||||||
|
|
||||||
|
|
||||||
|
static func _validate_unique_ids(
|
||||||
|
ids: Array[StringName], label: String, errors: Array[String]
|
||||||
|
) -> void:
|
||||||
|
var seen := {}
|
||||||
|
for value in ids:
|
||||||
|
if value.is_empty():
|
||||||
|
errors.append("%s ID is empty" % label)
|
||||||
|
elif seen.has(value):
|
||||||
|
errors.append("duplicate %s '%s'" % [label, value])
|
||||||
|
else:
|
||||||
|
seen[value] = true
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://m77tgpn1wpos
|
||||||
@@ -7,7 +7,10 @@ extends Resource
|
|||||||
@export var required_handler_ids: Array[StringName] = []
|
@export var required_handler_ids: Array[StringName] = []
|
||||||
@export var actions: Array[ActionDefinition] = []
|
@export var actions: Array[ActionDefinition] = []
|
||||||
@export var professions: Array[ProfessionDefinition] = []
|
@export var professions: Array[ProfessionDefinition] = []
|
||||||
|
@export var capability_tags: Array[CapabilityTagDefinition] = []
|
||||||
@export var items: Array[ItemDefinition] = []
|
@export var items: Array[ItemDefinition] = []
|
||||||
|
@export var resources: Array[ResourceDefinition] = []
|
||||||
|
@export var storages: Array[StorageDefinition] = []
|
||||||
@export var enemies: Array[EnemyDefinition] = []
|
@export var enemies: Array[EnemyDefinition] = []
|
||||||
|
|
||||||
|
|
||||||
@@ -23,7 +26,10 @@ func validate() -> Array[String]:
|
|||||||
errors.append("pack '%s' cannot require itself" % pack_id)
|
errors.append("pack '%s' cannot require itself" % pack_id)
|
||||||
_validate_actions(errors)
|
_validate_actions(errors)
|
||||||
_validate_professions(errors)
|
_validate_professions(errors)
|
||||||
|
_validate_capability_tags(errors)
|
||||||
_validate_items(errors)
|
_validate_items(errors)
|
||||||
|
_validate_resources(errors)
|
||||||
|
_validate_storages(errors)
|
||||||
_validate_enemies(errors)
|
_validate_enemies(errors)
|
||||||
return errors
|
return errors
|
||||||
|
|
||||||
@@ -55,6 +61,33 @@ func _validate_items(errors: Array[String]) -> void:
|
|||||||
errors.append("ItemDefinition: " + error)
|
errors.append("ItemDefinition: " + error)
|
||||||
|
|
||||||
|
|
||||||
|
func _validate_capability_tags(errors: Array[String]) -> void:
|
||||||
|
for definition in capability_tags:
|
||||||
|
if definition == null:
|
||||||
|
errors.append("capability_tags contains a null definition")
|
||||||
|
continue
|
||||||
|
for error in definition.validate():
|
||||||
|
errors.append("CapabilityTagDefinition: " + error)
|
||||||
|
|
||||||
|
|
||||||
|
func _validate_resources(errors: Array[String]) -> void:
|
||||||
|
for definition in resources:
|
||||||
|
if definition == null:
|
||||||
|
errors.append("resources contains a null definition")
|
||||||
|
continue
|
||||||
|
for error in definition.validate():
|
||||||
|
errors.append("ResourceDefinition: " + error)
|
||||||
|
|
||||||
|
|
||||||
|
func _validate_storages(errors: Array[String]) -> void:
|
||||||
|
for definition in storages:
|
||||||
|
if definition == null:
|
||||||
|
errors.append("storages contains a null definition")
|
||||||
|
continue
|
||||||
|
for error in definition.validate():
|
||||||
|
errors.append("StorageDefinition: " + error)
|
||||||
|
|
||||||
|
|
||||||
func _validate_enemies(errors: Array[String]) -> void:
|
func _validate_enemies(errors: Array[String]) -> void:
|
||||||
for definition in enemies:
|
for definition in enemies:
|
||||||
if definition == null:
|
if definition == null:
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
class_name StorageDefinition
|
||||||
|
extends Resource
|
||||||
|
|
||||||
|
@export var storage_id: StringName
|
||||||
|
@export var display_name: String
|
||||||
|
@export_range(0.0, 1000000.0, 0.1) var capacity := StorageStateRecord.DEFAULT_CAPACITY
|
||||||
|
@export var accepted_item_ids: Array[StringName] = []
|
||||||
|
@export var accepted_item_tag_ids: Array[StringName] = []
|
||||||
|
@export var capability_tag_ids: Array[StringName] = []
|
||||||
|
@export var routing_priority := 0
|
||||||
|
@export var deposit_action_id: StringName
|
||||||
|
@export var withdrawal_action_id: StringName
|
||||||
|
@export var presentation_cue_id: StringName
|
||||||
|
|
||||||
|
|
||||||
|
func validate() -> Array[String]:
|
||||||
|
var errors: Array[String] = []
|
||||||
|
if storage_id.is_empty():
|
||||||
|
errors.append("storage_id is empty")
|
||||||
|
if display_name.is_empty():
|
||||||
|
errors.append("display_name is empty for '%s'" % storage_id)
|
||||||
|
if not is_finite(capacity) or capacity <= 0.0:
|
||||||
|
errors.append("capacity must be positive for '%s'" % storage_id)
|
||||||
|
if accepted_item_ids.is_empty() and accepted_item_tag_ids.is_empty():
|
||||||
|
errors.append("storage '%s' must accept an item or item tag" % storage_id)
|
||||||
|
_validate_unique_ids(accepted_item_ids, "accepted item", errors)
|
||||||
|
_validate_unique_ids(accepted_item_tag_ids, "accepted item tag", errors)
|
||||||
|
_validate_unique_ids(capability_tag_ids, "capability tag", errors)
|
||||||
|
return errors
|
||||||
|
|
||||||
|
|
||||||
|
func accepts_item(item: ItemDefinition) -> bool:
|
||||||
|
if item == null:
|
||||||
|
return false
|
||||||
|
if item.item_id in accepted_item_ids:
|
||||||
|
return true
|
||||||
|
for tag_id in item.capability_tag_ids:
|
||||||
|
if tag_id in accepted_item_tag_ids:
|
||||||
|
return true
|
||||||
|
return false
|
||||||
|
|
||||||
|
|
||||||
|
func create_state(initial_amounts: Dictionary = {}) -> StorageStateRecord:
|
||||||
|
return StorageStateRecord.create(storage_id, initial_amounts, capacity)
|
||||||
|
|
||||||
|
|
||||||
|
static func _validate_unique_ids(
|
||||||
|
ids: Array[StringName], label: String, errors: Array[String]
|
||||||
|
) -> void:
|
||||||
|
var seen := {}
|
||||||
|
for value in ids:
|
||||||
|
if value.is_empty():
|
||||||
|
errors.append("%s ID is empty" % label)
|
||||||
|
elif seen.has(value):
|
||||||
|
errors.append("duplicate %s '%s'" % [label, value])
|
||||||
|
else:
|
||||||
|
seen[value] = true
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://doe6owhpwok2e
|
||||||
@@ -7,3 +7,5 @@ script = ExtResource("1")
|
|||||||
item_id = &"food"
|
item_id = &"food"
|
||||||
display_name = "Food"
|
display_name = "Food"
|
||||||
category = &"resource"
|
category = &"resource"
|
||||||
|
capability_tag_ids = Array[StringName]([&"item.food"])
|
||||||
|
presentation_cue_id = &"item.food"
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
[gd_resource type="Resource" script_class="ItemDefinition" load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://simulation/definitions/ItemDefinition.gd" id="1"]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("1")
|
||||||
|
item_id = &"herb"
|
||||||
|
display_name = "Medicinal Herb"
|
||||||
|
category = &"resource"
|
||||||
|
capability_tag_ids = Array[StringName]([&"item.medicine"])
|
||||||
|
presentation_cue_id = &"item.herbs"
|
||||||
@@ -7,3 +7,5 @@ script = ExtResource("1")
|
|||||||
item_id = &"wood"
|
item_id = &"wood"
|
||||||
display_name = "Wood"
|
display_name = "Wood"
|
||||||
category = &"resource"
|
category = &"resource"
|
||||||
|
capability_tag_ids = Array[StringName]([&"item.wood"])
|
||||||
|
presentation_cue_id = &"item.wood"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
[gd_resource type="Resource" script_class="SimulationContentPack" load_steps=28 format=3]
|
[gd_resource type="Resource" script_class="SimulationContentPack" load_steps=40 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"]
|
||||||
@@ -26,6 +26,18 @@
|
|||||||
[ext_resource type="Resource" path="res://simulation/definitions/enemies/raider.tres" id="24_raider"]
|
[ext_resource type="Resource" path="res://simulation/definitions/enemies/raider.tres" id="24_raider"]
|
||||||
[ext_resource type="Resource" path="res://simulation/definitions/enemies/wolf.tres" id="25_wolf"]
|
[ext_resource type="Resource" path="res://simulation/definitions/enemies/wolf.tres" id="25_wolf"]
|
||||||
[ext_resource type="Resource" path="res://simulation/definitions/enemies/boar.tres" id="26_boar"]
|
[ext_resource type="Resource" path="res://simulation/definitions/enemies/boar.tres" id="26_boar"]
|
||||||
|
[ext_resource type="Resource" path="res://simulation/definitions/items/herb.tres" id="27_herb"]
|
||||||
|
[ext_resource type="Resource" path="res://simulation/definitions/tags/item_food.tres" id="28_item_food"]
|
||||||
|
[ext_resource type="Resource" path="res://simulation/definitions/tags/item_medicine.tres" id="29_item_medicine"]
|
||||||
|
[ext_resource type="Resource" path="res://simulation/definitions/tags/item_wood.tres" id="30_item_wood"]
|
||||||
|
[ext_resource type="Resource" path="res://simulation/definitions/tags/resource_harvestable.tres" id="31_resource_harvestable"]
|
||||||
|
[ext_resource type="Resource" path="res://simulation/definitions/tags/storage_village.tres" id="32_storage_village"]
|
||||||
|
[ext_resource type="Resource" path="res://simulation/definitions/resources/berry_patch.tres" id="33_berry_patch"]
|
||||||
|
[ext_resource type="Resource" path="res://simulation/definitions/resources/herb_patch.tres" id="34_herb_patch"]
|
||||||
|
[ext_resource type="Resource" path="res://simulation/definitions/resources/tree.tres" id="35_tree"]
|
||||||
|
[ext_resource type="Resource" path="res://simulation/definitions/storages/apothecary.tres" id="36_apothecary"]
|
||||||
|
[ext_resource type="Resource" path="res://simulation/definitions/storages/pantry.tres" id="37_pantry"]
|
||||||
|
[ext_resource type="Resource" path="res://simulation/definitions/storages/woodpile.tres" id="38_woodpile"]
|
||||||
|
|
||||||
[resource]
|
[resource]
|
||||||
script = ExtResource("1_pack")
|
script = ExtResource("1_pack")
|
||||||
@@ -33,5 +45,8 @@ pack_id = &"core"
|
|||||||
display_name = "The Steward Core"
|
display_name = "The Steward Core"
|
||||||
actions = [ExtResource("2_defend"), ExtResource("3_deposit_food"), ExtResource("4_deposit_wood"), ExtResource("5_eat"), ExtResource("6_feed_animal"), ExtResource("7_gather_food"), ExtResource("8_gather_wood"), ExtResource("9_patrol"), ExtResource("10_rest"), ExtResource("11_sleep"), ExtResource("12_study"), ExtResource("13_wander"), ExtResource("14_withdraw_food")]
|
actions = [ExtResource("2_defend"), ExtResource("3_deposit_food"), ExtResource("4_deposit_wood"), ExtResource("5_eat"), ExtResource("6_feed_animal"), ExtResource("7_gather_food"), ExtResource("8_gather_wood"), ExtResource("9_patrol"), ExtResource("10_rest"), ExtResource("11_sleep"), ExtResource("12_study"), ExtResource("13_wander"), ExtResource("14_withdraw_food")]
|
||||||
professions = [ExtResource("15_farmer"), ExtResource("16_guard"), ExtResource("17_scholar"), ExtResource("18_wanderer"), ExtResource("19_woodcutter")]
|
professions = [ExtResource("15_farmer"), ExtResource("16_guard"), ExtResource("17_scholar"), ExtResource("18_wanderer"), ExtResource("19_woodcutter")]
|
||||||
items = [ExtResource("20_claw"), ExtResource("21_food"), ExtResource("22_sword"), ExtResource("23_wood")]
|
capability_tags = [ExtResource("28_item_food"), ExtResource("29_item_medicine"), ExtResource("30_item_wood"), ExtResource("31_resource_harvestable"), ExtResource("32_storage_village")]
|
||||||
|
items = [ExtResource("20_claw"), ExtResource("21_food"), ExtResource("22_sword"), ExtResource("23_wood"), ExtResource("27_herb")]
|
||||||
|
resources = [ExtResource("33_berry_patch"), ExtResource("34_herb_patch"), ExtResource("35_tree")]
|
||||||
|
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")]
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
[gd_resource type="Resource" script_class="ResourceDefinition" load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://simulation/definitions/ResourceDefinition.gd" id="1"]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("1")
|
||||||
|
resource_id = &"resource_berry_patch"
|
||||||
|
display_name = "Berry Patch"
|
||||||
|
item_id = &"food"
|
||||||
|
extraction_action_id = &"gather_food"
|
||||||
|
capability_tag_ids = Array[StringName]([&"resource.harvestable"])
|
||||||
|
presentation_cue_id = &"resource.berry_patch"
|
||||||
|
initial_amount = 10.0
|
||||||
|
yield_per_action = 2.0
|
||||||
|
max_amount = 10.0
|
||||||
|
safety_risk = 0.05
|
||||||
|
comfort_distance = 16.0
|
||||||
|
discovery_priority = 1.0
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
[gd_resource type="Resource" script_class="ResourceDefinition" load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://simulation/definitions/ResourceDefinition.gd" id="1"]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("1")
|
||||||
|
resource_id = &"resource_herb_patch"
|
||||||
|
display_name = "Medicinal Herb Patch"
|
||||||
|
item_id = &"herb"
|
||||||
|
extraction_action_id = &"gather_food"
|
||||||
|
capability_tag_ids = Array[StringName]([&"resource.harvestable"])
|
||||||
|
presentation_cue_id = &"resource.herb_patch"
|
||||||
|
initial_amount = 8.0
|
||||||
|
yield_per_action = 1.0
|
||||||
|
max_amount = 8.0
|
||||||
|
regrow_rate = 0.01
|
||||||
|
safety_risk = 0.15
|
||||||
|
comfort_distance = 14.0
|
||||||
|
discovery_priority = 0.5
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
[gd_resource type="Resource" script_class="ResourceDefinition" load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://simulation/definitions/ResourceDefinition.gd" id="1"]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("1")
|
||||||
|
resource_id = &"resource_tree"
|
||||||
|
display_name = "Tree"
|
||||||
|
item_id = &"wood"
|
||||||
|
extraction_action_id = &"gather_wood"
|
||||||
|
capability_tag_ids = Array[StringName]([&"resource.harvestable"])
|
||||||
|
presentation_cue_id = &"resource.tree"
|
||||||
|
initial_amount = 12.0
|
||||||
|
yield_per_action = 3.0
|
||||||
|
max_amount = 12.0
|
||||||
|
safety_risk = 0.1
|
||||||
|
comfort_distance = 20.0
|
||||||
|
discovery_priority = 1.0
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
[gd_resource type="Resource" script_class="StorageDefinition" load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://simulation/definitions/StorageDefinition.gd" id="1"]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("1")
|
||||||
|
storage_id = &"village_apothecary"
|
||||||
|
display_name = "Village Apothecary"
|
||||||
|
capacity = 40.0
|
||||||
|
accepted_item_tag_ids = Array[StringName]([&"item.medicine"])
|
||||||
|
capability_tag_ids = Array[StringName]([&"storage.village"])
|
||||||
|
routing_priority = 100
|
||||||
|
presentation_cue_id = &"storage.apothecary"
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
[gd_resource type="Resource" script_class="StorageDefinition" load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://simulation/definitions/StorageDefinition.gd" id="1"]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("1")
|
||||||
|
storage_id = &"village_pantry"
|
||||||
|
display_name = "Village Pantry"
|
||||||
|
capacity = 100.0
|
||||||
|
accepted_item_tag_ids = Array[StringName]([&"item.food"])
|
||||||
|
capability_tag_ids = Array[StringName]([&"storage.village"])
|
||||||
|
routing_priority = 100
|
||||||
|
deposit_action_id = &"deposit_food"
|
||||||
|
withdrawal_action_id = &"withdraw_food"
|
||||||
|
presentation_cue_id = &"storage.pantry"
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
[gd_resource type="Resource" script_class="StorageDefinition" load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://simulation/definitions/StorageDefinition.gd" id="1"]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("1")
|
||||||
|
storage_id = &"village_woodpile"
|
||||||
|
display_name = "Village Woodpile"
|
||||||
|
capacity = 100.0
|
||||||
|
accepted_item_tag_ids = Array[StringName]([&"item.wood"])
|
||||||
|
capability_tag_ids = Array[StringName]([&"storage.village"])
|
||||||
|
routing_priority = 100
|
||||||
|
deposit_action_id = &"deposit_wood"
|
||||||
|
presentation_cue_id = &"storage.woodpile"
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
[gd_resource type="Resource" script_class="CapabilityTagDefinition" load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://simulation/definitions/CapabilityTagDefinition.gd" id="1"]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("1")
|
||||||
|
tag_id = &"item.food"
|
||||||
|
display_name = "Food"
|
||||||
|
domain = &"item"
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
[gd_resource type="Resource" script_class="CapabilityTagDefinition" load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://simulation/definitions/CapabilityTagDefinition.gd" id="1"]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("1")
|
||||||
|
tag_id = &"item.medicine"
|
||||||
|
display_name = "Medicine"
|
||||||
|
domain = &"item"
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
[gd_resource type="Resource" script_class="CapabilityTagDefinition" load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://simulation/definitions/CapabilityTagDefinition.gd" id="1"]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("1")
|
||||||
|
tag_id = &"item.wood"
|
||||||
|
display_name = "Wood"
|
||||||
|
domain = &"item"
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
[gd_resource type="Resource" script_class="CapabilityTagDefinition" load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://simulation/definitions/CapabilityTagDefinition.gd" id="1"]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("1")
|
||||||
|
tag_id = &"resource.harvestable"
|
||||||
|
display_name = "Harvestable"
|
||||||
|
domain = &"resource"
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
[gd_resource type="Resource" script_class="CapabilityTagDefinition" load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://simulation/definitions/CapabilityTagDefinition.gd" id="1"]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("1")
|
||||||
|
tag_id = &"storage.village"
|
||||||
|
display_name = "Village Storage"
|
||||||
|
domain = &"storage"
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
class_name StorageRoutingPolicy
|
||||||
|
extends RefCounted
|
||||||
|
|
||||||
|
var catalog: ContentCatalog
|
||||||
|
|
||||||
|
|
||||||
|
func _init(content_catalog: ContentCatalog = null) -> void:
|
||||||
|
catalog = content_catalog
|
||||||
|
|
||||||
|
|
||||||
|
func resolve_definition(
|
||||||
|
item_id: StringName, available_storage_ids: Array[StringName] = []
|
||||||
|
) -> StorageDefinition:
|
||||||
|
var candidates := get_candidates(item_id, available_storage_ids)
|
||||||
|
return candidates[0] if not candidates.is_empty() else null
|
||||||
|
|
||||||
|
|
||||||
|
func resolve_state(
|
||||||
|
item_id: StringName, requested_amount: float, states: Array[StorageStateRecord]
|
||||||
|
) -> StorageStateRecord:
|
||||||
|
if not is_finite(requested_amount) or requested_amount <= 0.0:
|
||||||
|
return null
|
||||||
|
var states_by_id := {}
|
||||||
|
var available_ids: Array[StringName] = []
|
||||||
|
for state in states:
|
||||||
|
if state == null or state.get_available_capacity() < requested_amount:
|
||||||
|
continue
|
||||||
|
states_by_id[state.get_storage_id()] = state
|
||||||
|
available_ids.append(state.get_storage_id())
|
||||||
|
var definition := resolve_definition(item_id, available_ids)
|
||||||
|
return (
|
||||||
|
states_by_id.get(definition.storage_id) as StorageStateRecord
|
||||||
|
if definition != null
|
||||||
|
else null
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func get_candidates(
|
||||||
|
item_id: StringName, available_storage_ids: Array[StringName] = []
|
||||||
|
) -> Array[StorageDefinition]:
|
||||||
|
var candidates: Array[StorageDefinition] = []
|
||||||
|
if catalog == null or not catalog.is_valid():
|
||||||
|
return candidates
|
||||||
|
var item := catalog.get_item(item_id)
|
||||||
|
if item == null:
|
||||||
|
return candidates
|
||||||
|
var available := {}
|
||||||
|
for storage_id in available_storage_ids:
|
||||||
|
available[storage_id] = true
|
||||||
|
for definition in catalog.get_storages():
|
||||||
|
if not available.is_empty() and not available.has(definition.storage_id):
|
||||||
|
continue
|
||||||
|
if definition.accepts_item(item):
|
||||||
|
candidates.append(definition)
|
||||||
|
candidates.sort_custom(_is_preferred_route)
|
||||||
|
return candidates
|
||||||
|
|
||||||
|
|
||||||
|
static func _is_preferred_route(first: StorageDefinition, second: StorageDefinition) -> bool:
|
||||||
|
if first.routing_priority != second.routing_priority:
|
||||||
|
return first.routing_priority > second.routing_priority
|
||||||
|
return String(first.storage_id) < String(second.storage_id)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://cpmen6gvxr64i
|
||||||
@@ -47,8 +47,8 @@ func _test_core_pack() -> void:
|
|||||||
"Core professions should enumerate by stable ID"
|
"Core professions should enumerate by stable ID"
|
||||||
)
|
)
|
||||||
_check(
|
_check(
|
||||||
_item_ids(catalog.get_items()) == [&"food", &"item_claw", &"item_sword", &"wood"],
|
_item_ids(catalog.get_items()) == [&"food", &"herb", &"item_claw", &"item_sword", &"wood"],
|
||||||
"Core items should include food, wood, sword, and claw in stable-ID order"
|
"Core items should include legacy items and herbs in stable-ID order"
|
||||||
)
|
)
|
||||||
_check(
|
_check(
|
||||||
_enemy_ids(catalog.get_enemies()) == [&"enemy_boar", &"enemy_raider", &"enemy_wolf"],
|
_enemy_ids(catalog.get_enemies()) == [&"enemy_boar", &"enemy_raider", &"enemy_wolf"],
|
||||||
@@ -96,13 +96,16 @@ func _test_deterministic_enumeration_and_dependencies() -> void:
|
|||||||
"Pack enumeration should not depend on input order"
|
"Pack enumeration should not depend on input order"
|
||||||
)
|
)
|
||||||
_check(
|
_check(
|
||||||
_item_ids(catalog.get_items()) == [&"amber", &"food", &"item_claw", &"item_sword", &"wood"],
|
(
|
||||||
|
_item_ids(catalog.get_items())
|
||||||
|
== [&"amber", &"food", &"herb", &"item_claw", &"item_sword", &"wood"]
|
||||||
|
),
|
||||||
"Merged item enumeration should be globally stable-ID sorted"
|
"Merged item enumeration should be globally stable-ID sorted"
|
||||||
)
|
)
|
||||||
var returned_items := catalog.get_items()
|
var returned_items := catalog.get_items()
|
||||||
returned_items.clear()
|
returned_items.clear()
|
||||||
_check(
|
_check(
|
||||||
catalog.get_items().size() == 5,
|
catalog.get_items().size() == 6,
|
||||||
"Callers should not be able to mutate the catalog's ordered arrays"
|
"Callers should not be able to mutate the catalog's ordered arrays"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,237 @@
|
|||||||
|
extends GutTest
|
||||||
|
|
||||||
|
|
||||||
|
func test_core_catalog_enumerates_new_authoring_domains_deterministically() -> void:
|
||||||
|
var catalog := ContentCatalog.create_core()
|
||||||
|
assert_true(catalog.is_valid(), "%s" % [catalog.get_errors()])
|
||||||
|
assert_eq(
|
||||||
|
_tag_ids(catalog.get_capability_tags()),
|
||||||
|
[&"item.food", &"item.medicine", &"item.wood", &"resource.harvestable", &"storage.village"]
|
||||||
|
)
|
||||||
|
assert_eq(
|
||||||
|
_resource_ids(catalog.get_resources()),
|
||||||
|
[&"resource_berry_patch", &"resource_herb_patch", &"resource_tree"]
|
||||||
|
)
|
||||||
|
assert_eq(
|
||||||
|
_storage_ids(catalog.get_storages()),
|
||||||
|
[&"village_apothecary", &"village_pantry", &"village_woodpile"]
|
||||||
|
)
|
||||||
|
var report := catalog.get_authoring_report()
|
||||||
|
assert_true(report["valid"])
|
||||||
|
assert_eq(report["resources"], _resource_ids(catalog.get_resources()))
|
||||||
|
assert_eq(report["storages"], _storage_ids(catalog.get_storages()))
|
||||||
|
assert_false(report.has("presentation_assets"))
|
||||||
|
var returned_resources := catalog.get_resources()
|
||||||
|
returned_resources.clear()
|
||||||
|
assert_eq(catalog.get_resources().size(), 3)
|
||||||
|
|
||||||
|
|
||||||
|
func test_herb_route_is_data_only_and_legacy_routes_remain_stable() -> void:
|
||||||
|
var catalog := ContentCatalog.create_core()
|
||||||
|
var routing := StorageRoutingPolicy.new(catalog)
|
||||||
|
var herb_source := catalog.get_resource(&"resource_herb_patch")
|
||||||
|
assert_not_null(herb_source)
|
||||||
|
assert_eq(herb_source.item_id, &"herb")
|
||||||
|
assert_eq(herb_source.extraction_action_id, SimulationIds.ACTION_GATHER_FOOD)
|
||||||
|
assert_eq(routing.resolve_definition(herb_source.item_id).storage_id, &"village_apothecary")
|
||||||
|
assert_eq(
|
||||||
|
routing.resolve_definition(SimulationIds.RESOURCE_FOOD).storage_id,
|
||||||
|
SimulationIds.STORAGE_VILLAGE_PANTRY
|
||||||
|
)
|
||||||
|
assert_eq(
|
||||||
|
routing.resolve_definition(SimulationIds.RESOURCE_WOOD).storage_id,
|
||||||
|
SimulationIds.STORAGE_VILLAGE_WOODPILE
|
||||||
|
)
|
||||||
|
assert_null(routing.resolve_definition(&"unknown_item"))
|
||||||
|
|
||||||
|
var apothecary := catalog.get_storage(&"village_apothecary")
|
||||||
|
var nearly_full := apothecary.create_state({"herb": 39.0})
|
||||||
|
var states: Array[StorageStateRecord] = [nearly_full]
|
||||||
|
assert_same(routing.resolve_state(&"herb", 1.0, states), nearly_full)
|
||||||
|
assert_null(routing.resolve_state(&"herb", 2.0, states))
|
||||||
|
|
||||||
|
|
||||||
|
func test_routes_rank_priority_then_stable_storage_id() -> void:
|
||||||
|
var core := load(ContentCatalog.CORE_PACK_PATH) as SimulationContentPack
|
||||||
|
var addon := _pack(&"routing_addon")
|
||||||
|
addon.required_pack_ids = [&"core"]
|
||||||
|
var early := _storage(&"a_medicine_store", &"item.medicine", 100)
|
||||||
|
var low_priority := _storage(&"field_medicine_store", &"item.medicine", 10)
|
||||||
|
addon.storages = [low_priority, early]
|
||||||
|
var packs: Array[SimulationContentPack] = [addon, core]
|
||||||
|
var catalog := ContentCatalog.new()
|
||||||
|
assert_true(catalog.rebuild(packs).is_empty(), "%s" % [catalog.get_errors()])
|
||||||
|
|
||||||
|
var candidates := StorageRoutingPolicy.new(catalog).get_candidates(&"herb")
|
||||||
|
assert_eq(
|
||||||
|
_storage_ids(candidates),
|
||||||
|
[&"a_medicine_store", &"village_apothecary", &"field_medicine_store"]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func test_catalog_rejects_duplicate_content_ids_without_publishing_overrides() -> void:
|
||||||
|
var first := _complete_minimal_pack(&"first")
|
||||||
|
var second := _complete_minimal_pack(&"second")
|
||||||
|
var packs: Array[SimulationContentPack] = [second, first]
|
||||||
|
var catalog := ContentCatalog.new()
|
||||||
|
var duplicated_cues: Array[StringName] = [&"known.cue", &"known.cue"]
|
||||||
|
var errors := catalog.rebuild(packs, [], duplicated_cues)
|
||||||
|
|
||||||
|
for expected in [
|
||||||
|
"Duplicate action_id 'shared_action'",
|
||||||
|
"Duplicate item_id 'shared_item'",
|
||||||
|
"Duplicate tag_id 'shared_tag'",
|
||||||
|
"Duplicate resource_id 'shared_resource'",
|
||||||
|
"Duplicate storage_id 'shared_storage'",
|
||||||
|
"Duplicate supported presentation cue 'known.cue'",
|
||||||
|
]:
|
||||||
|
assert_true(_has_error(errors, expected), "%s missing from %s" % [expected, errors])
|
||||||
|
assert_false(catalog.is_valid())
|
||||||
|
assert_true(catalog.get_items().is_empty())
|
||||||
|
assert_null(catalog.get_storage(&"shared_storage"))
|
||||||
|
|
||||||
|
|
||||||
|
func test_catalog_rejects_unknown_action_item_tag_handler_and_cue_references() -> void:
|
||||||
|
var pack := _pack(&"broken")
|
||||||
|
var item := _item(&"broken_item", &"unknown_item_tag")
|
||||||
|
item.presentation_cue_id = &"unknown.item.cue"
|
||||||
|
var action := _action(&"broken_action")
|
||||||
|
action.actor_capability_tags = [&"unknown_actor_tag"]
|
||||||
|
action.target_capability_tags = [&"unknown_target_tag"]
|
||||||
|
action.completion_handler_id = &"unknown_handler"
|
||||||
|
action.presentation_cue_id = &"unknown.action.cue"
|
||||||
|
var effect := ActionEffect.new()
|
||||||
|
effect.effect_id = &"broken_effect"
|
||||||
|
effect.effect_kind = ActionEffect.KIND_SCHEDULE_ACTION
|
||||||
|
effect.scheduled_action_id = &"unknown_scheduled_action"
|
||||||
|
effect.item_id = &"unknown_effect_item"
|
||||||
|
action.effects = [effect]
|
||||||
|
var resource := _resource(&"broken_resource", &"unknown_resource_item", &"unknown_action")
|
||||||
|
resource.capability_tag_ids = [&"unknown_resource_tag"]
|
||||||
|
resource.presentation_cue_id = &"unknown.resource.cue"
|
||||||
|
var storage := _storage(&"broken_storage", &"unknown_storage_item_tag", 1)
|
||||||
|
storage.accepted_item_ids = [&"unknown_storage_item"]
|
||||||
|
storage.capability_tag_ids = [&"unknown_storage_tag"]
|
||||||
|
storage.deposit_action_id = &"unknown_deposit_action"
|
||||||
|
storage.presentation_cue_id = &"unknown.storage.cue"
|
||||||
|
pack.items = [item]
|
||||||
|
pack.actions = [action]
|
||||||
|
pack.resources = [resource]
|
||||||
|
pack.storages = [storage]
|
||||||
|
var packs: Array[SimulationContentPack] = [pack]
|
||||||
|
var handlers: Array[StringName] = [&"known_handler"]
|
||||||
|
var cues: Array[StringName] = [&"known.cue"]
|
||||||
|
var errors := ContentCatalog.new().rebuild(packs, handlers, cues)
|
||||||
|
|
||||||
|
for expected in [
|
||||||
|
"unknown tag 'unknown_item_tag'",
|
||||||
|
"unknown presentation cue 'unknown.item.cue'",
|
||||||
|
"unknown tag 'unknown_actor_tag'",
|
||||||
|
"unknown tag 'unknown_target_tag'",
|
||||||
|
"unknown completion handler 'unknown_handler'",
|
||||||
|
"unknown action 'unknown_scheduled_action'",
|
||||||
|
"unknown item 'unknown_effect_item'",
|
||||||
|
"unknown item 'unknown_resource_item'",
|
||||||
|
"unknown action 'unknown_action'",
|
||||||
|
"unknown tag 'unknown_resource_tag'",
|
||||||
|
"unknown item 'unknown_storage_item'",
|
||||||
|
"unknown tag 'unknown_storage_item_tag'",
|
||||||
|
"unknown tag 'unknown_storage_tag'",
|
||||||
|
"unknown action 'unknown_deposit_action'",
|
||||||
|
"unknown presentation cue 'unknown.storage.cue'",
|
||||||
|
]:
|
||||||
|
assert_true(_has_error(errors, expected), "%s missing from %s" % [expected, errors])
|
||||||
|
|
||||||
|
|
||||||
|
func _complete_minimal_pack(pack_id: StringName) -> SimulationContentPack:
|
||||||
|
var pack := _pack(pack_id)
|
||||||
|
var tag := CapabilityTagDefinition.new()
|
||||||
|
tag.tag_id = &"shared_tag"
|
||||||
|
tag.display_name = "Shared tag"
|
||||||
|
tag.domain = CapabilityTagDefinition.DOMAIN_ITEM
|
||||||
|
var item := _item(&"shared_item", tag.tag_id)
|
||||||
|
var action := _action(&"shared_action")
|
||||||
|
action.target_type = SimulationIds.TARGET_RESOURCE
|
||||||
|
action.resource_action_id = action.action_id
|
||||||
|
var resource := _resource(&"shared_resource", item.item_id, action.action_id)
|
||||||
|
var storage := _storage(&"shared_storage", tag.tag_id, 1)
|
||||||
|
pack.capability_tags = [tag]
|
||||||
|
pack.items = [item]
|
||||||
|
pack.actions = [action]
|
||||||
|
pack.resources = [resource]
|
||||||
|
pack.storages = [storage]
|
||||||
|
return pack
|
||||||
|
|
||||||
|
|
||||||
|
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 _item(item_id: StringName, tag_id: StringName) -> ItemDefinition:
|
||||||
|
var item := ItemDefinition.new()
|
||||||
|
item.item_id = item_id
|
||||||
|
item.display_name = String(item_id).capitalize()
|
||||||
|
item.category = ItemDefinition.CATEGORY_RESOURCE
|
||||||
|
item.capability_tag_ids = [tag_id]
|
||||||
|
return item
|
||||||
|
|
||||||
|
|
||||||
|
func _action(action_id: StringName) -> ActionDefinition:
|
||||||
|
var action := ActionDefinition.new()
|
||||||
|
action.action_id = action_id
|
||||||
|
action.display_name = String(action_id).capitalize()
|
||||||
|
action.target_type = SimulationIds.TARGET_ACTIVITY
|
||||||
|
return action
|
||||||
|
|
||||||
|
|
||||||
|
func _resource(
|
||||||
|
resource_id: StringName, item_id: StringName, action_id: StringName
|
||||||
|
) -> ResourceDefinition:
|
||||||
|
var resource := ResourceDefinition.new()
|
||||||
|
resource.resource_id = resource_id
|
||||||
|
resource.display_name = String(resource_id).capitalize()
|
||||||
|
resource.item_id = item_id
|
||||||
|
resource.extraction_action_id = action_id
|
||||||
|
return resource
|
||||||
|
|
||||||
|
|
||||||
|
func _storage(
|
||||||
|
storage_id: StringName, accepted_item_tag_id: StringName, priority: int
|
||||||
|
) -> StorageDefinition:
|
||||||
|
var storage := StorageDefinition.new()
|
||||||
|
storage.storage_id = storage_id
|
||||||
|
storage.display_name = String(storage_id).capitalize()
|
||||||
|
storage.accepted_item_tag_ids = [accepted_item_tag_id]
|
||||||
|
storage.routing_priority = priority
|
||||||
|
return storage
|
||||||
|
|
||||||
|
|
||||||
|
func _tag_ids(definitions: Array[CapabilityTagDefinition]) -> Array[StringName]:
|
||||||
|
var ids: Array[StringName] = []
|
||||||
|
for definition in definitions:
|
||||||
|
ids.append(definition.tag_id)
|
||||||
|
return ids
|
||||||
|
|
||||||
|
|
||||||
|
func _resource_ids(definitions: Array[ResourceDefinition]) -> Array[StringName]:
|
||||||
|
var ids: Array[StringName] = []
|
||||||
|
for definition in definitions:
|
||||||
|
ids.append(definition.resource_id)
|
||||||
|
return ids
|
||||||
|
|
||||||
|
|
||||||
|
func _storage_ids(definitions: Array[StorageDefinition]) -> Array[StringName]:
|
||||||
|
var ids: Array[StringName] = []
|
||||||
|
for definition in definitions:
|
||||||
|
ids.append(definition.storage_id)
|
||||||
|
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://deq7osx44nnn3
|
||||||
Reference in New Issue
Block a user