Files
gamedev-the-steward/simulation/situations/SituationDefinition.gd
T

159 lines
5.4 KiB
GDScript

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