feat: generate world-backed situations and commitments
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
class_name SituationDefinition
|
||||
extends Resource
|
||||
|
||||
@export var situation_definition_id: StringName
|
||||
@export var display_name: String
|
||||
@export_multiline var description: String
|
||||
@export_range(0, 100, 1) var severity := 1
|
||||
@export_range(-100, 100, 1) var priority := 0
|
||||
@export var trigger_predicates: Array[SituationPredicate] = []
|
||||
@export var resolution_predicates: Array[SituationPredicate] = []
|
||||
@export var expiry_predicate_ids: Array[StringName] = []
|
||||
@export var dedupe_key_fields: Array[StringName] = []
|
||||
@export_range(0, 1000000, 1) var expiry_ticks := 0
|
||||
@export_range(0, 1000000, 1) var cooldown_ticks := 0
|
||||
@export var dialogue_topic_ids: Array[StringName] = []
|
||||
@export var alternatives: Array[SituationAlternativeDefinition] = []
|
||||
|
||||
|
||||
func validate(
|
||||
supported_event_predicate_ids: Array[StringName] = [],
|
||||
supported_state_predicate_ids: Array[StringName] = [],
|
||||
supported_expiry_predicate_ids: Array[StringName] = []
|
||||
) -> Array[String]:
|
||||
var errors: Array[String] = []
|
||||
if situation_definition_id.is_empty():
|
||||
errors.append("situation_definition_id is empty")
|
||||
if display_name.is_empty():
|
||||
errors.append("display_name is empty for '%s'" % situation_definition_id)
|
||||
if trigger_predicates.is_empty():
|
||||
errors.append("'%s' has no trigger predicates" % situation_definition_id)
|
||||
if severity < 0 or severity > 100:
|
||||
errors.append("severity is outside 0..100 for '%s'" % situation_definition_id)
|
||||
if priority < -100 or priority > 100:
|
||||
errors.append("priority is outside -100..100 for '%s'" % situation_definition_id)
|
||||
if expiry_ticks < 0 or cooldown_ticks < 0:
|
||||
errors.append("expiry/cooldown ticks cannot be negative for '%s'" % situation_definition_id)
|
||||
_validate_predicates(
|
||||
trigger_predicates,
|
||||
"trigger",
|
||||
supported_event_predicate_ids,
|
||||
supported_state_predicate_ids,
|
||||
errors
|
||||
)
|
||||
_validate_predicates(
|
||||
resolution_predicates,
|
||||
"resolution",
|
||||
supported_event_predicate_ids,
|
||||
supported_state_predicate_ids,
|
||||
errors
|
||||
)
|
||||
for predicate_id in expiry_predicate_ids:
|
||||
if predicate_id.is_empty():
|
||||
errors.append("'%s' has an empty expiry predicate ID" % situation_definition_id)
|
||||
elif (
|
||||
not supported_expiry_predicate_ids.is_empty()
|
||||
and predicate_id not in supported_expiry_predicate_ids
|
||||
):
|
||||
errors.append(
|
||||
(
|
||||
"'%s' references unsupported expiry predicate '%s'"
|
||||
% [situation_definition_id, predicate_id]
|
||||
)
|
||||
)
|
||||
_validate_unique_names(dedupe_key_fields, "dedupe key", errors)
|
||||
_validate_unique_names(dialogue_topic_ids, "dialogue topic", errors)
|
||||
var alternatives_by_id: Dictionary = {}
|
||||
for alternative in alternatives:
|
||||
if alternative == null:
|
||||
errors.append("'%s' contains a null alternative" % situation_definition_id)
|
||||
continue
|
||||
for error in alternative.validate():
|
||||
errors.append("Alternative '%s': %s" % [alternative.alternative_id, error])
|
||||
if alternatives_by_id.has(alternative.alternative_id):
|
||||
errors.append(
|
||||
(
|
||||
"'%s' has duplicate alternative '%s'"
|
||||
% [situation_definition_id, alternative.alternative_id]
|
||||
)
|
||||
)
|
||||
alternatives_by_id[alternative.alternative_id] = true
|
||||
for predicate_id in alternative.resolution_predicate_ids:
|
||||
if not _has_predicate(predicate_id):
|
||||
errors.append(
|
||||
(
|
||||
"Alternative '%s' references unknown resolution predicate '%s'"
|
||||
% [alternative.alternative_id, predicate_id]
|
||||
)
|
||||
)
|
||||
return errors
|
||||
|
||||
|
||||
func get_predicate(predicate_id: StringName) -> SituationPredicate:
|
||||
for predicate in trigger_predicates:
|
||||
if predicate != null and predicate.predicate_id == predicate_id:
|
||||
return predicate
|
||||
for predicate in resolution_predicates:
|
||||
if predicate != null and predicate.predicate_id == predicate_id:
|
||||
return predicate
|
||||
return null
|
||||
|
||||
|
||||
func get_alternative(alternative_id: StringName) -> SituationAlternativeDefinition:
|
||||
for alternative in alternatives:
|
||||
if alternative != null and alternative.alternative_id == alternative_id:
|
||||
return alternative
|
||||
return null
|
||||
|
||||
|
||||
func _has_predicate(predicate_id: StringName) -> bool:
|
||||
return get_predicate(predicate_id) != null
|
||||
|
||||
|
||||
func _validate_predicates(
|
||||
predicates: Array[SituationPredicate],
|
||||
label: String,
|
||||
supported_event_ids: Array[StringName],
|
||||
supported_state_ids: Array[StringName],
|
||||
errors: Array[String]
|
||||
) -> void:
|
||||
var seen: Dictionary = {}
|
||||
for predicate in predicates:
|
||||
if predicate == null:
|
||||
errors.append("'%s' contains a null %s predicate" % [situation_definition_id, label])
|
||||
continue
|
||||
for error in predicate.validate():
|
||||
errors.append(
|
||||
"%s predicate '%s': %s" % [label.capitalize(), predicate.predicate_id, error]
|
||||
)
|
||||
if seen.has(predicate.predicate_id):
|
||||
errors.append(
|
||||
(
|
||||
"'%s' has duplicate %s predicate '%s'"
|
||||
% [situation_definition_id, label, predicate.predicate_id]
|
||||
)
|
||||
)
|
||||
seen[predicate.predicate_id] = true
|
||||
var supported_ids := (
|
||||
supported_event_ids
|
||||
if StringName(predicate.source) == SituationPredicate.SOURCE_EVENT
|
||||
else supported_state_ids
|
||||
)
|
||||
if not supported_ids.is_empty() and predicate.evaluator_id not in supported_ids:
|
||||
errors.append(
|
||||
(
|
||||
"'%s' references unsupported %s predicate '%s'"
|
||||
% [situation_definition_id, predicate.source, predicate.evaluator_id]
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
func _validate_unique_names(names: Array[StringName], label: String, errors: Array[String]) -> void:
|
||||
var seen: Dictionary = {}
|
||||
for name in names:
|
||||
if name.is_empty():
|
||||
errors.append("'%s' has an empty %s" % [situation_definition_id, label])
|
||||
elif seen.has(name):
|
||||
errors.append("'%s' has duplicate %s '%s'" % [situation_definition_id, label, name])
|
||||
seen[name] = true
|
||||
Reference in New Issue
Block a user