266 lines
9.1 KiB
GDScript
266 lines
9.1 KiB
GDScript
class_name EventDefinition
|
|
extends RefCounted
|
|
|
|
const EventConditionType = preload("res://scripts/events/event_condition.gd")
|
|
const EventActionType = preload("res://scripts/events/event_action.gd")
|
|
const EventChoiceType = preload("res://scripts/events/event_choice.gd")
|
|
|
|
enum TransactionPolicy {
|
|
ROLLBACK_ALL,
|
|
SKIP_FAILED,
|
|
BRANCH_FAILURE,
|
|
}
|
|
|
|
enum OnceScope {
|
|
NONE,
|
|
PER_DAY,
|
|
PER_ACTOR_PAIR,
|
|
PER_SAVE,
|
|
}
|
|
|
|
enum MissingActorPolicy {
|
|
CANCEL,
|
|
REPLACE_WITH_ROLE,
|
|
CONTINUE_WITHOUT,
|
|
BRANCH_FAILURE,
|
|
}
|
|
|
|
enum ContentRating {
|
|
GENERAL,
|
|
MATURE,
|
|
RESTRICTED,
|
|
}
|
|
|
|
enum FilterBehavior {
|
|
NONE,
|
|
FADE,
|
|
REPLACE,
|
|
DISABLE,
|
|
}
|
|
|
|
var event_id: StringName = &""
|
|
var category: StringName = &"FLAVOR"
|
|
var priority := 0
|
|
var conditions: Array[EventCondition] = []
|
|
var participant_roles: Array[StringName] = []
|
|
var location_rule: Dictionary = {}
|
|
var start_window: Array[int] = []
|
|
var expires_after := 0
|
|
var foreground := true
|
|
var interrupt_policy: StringName = &"NORMAL"
|
|
var choices: Array[EventChoice] = []
|
|
var automatic_actions: Array[EventAction] = []
|
|
var fallback_actions: Array[EventAction] = []
|
|
var failure_actions: Array[EventAction] = []
|
|
var replacement_event_id: StringName = &""
|
|
var once_scope: OnceScope = OnceScope.NONE
|
|
var transaction_policy: TransactionPolicy = TransactionPolicy.ROLLBACK_ALL
|
|
var missing_actor_policy: MissingActorPolicy = MissingActorPolicy.CANCEL
|
|
var content_rating: ContentRating = ContentRating.GENERAL
|
|
var content_tags: Array[StringName] = []
|
|
var filter_behavior: FilterBehavior = FilterBehavior.NONE
|
|
|
|
|
|
func configure(p_event_id: StringName, options: Dictionary = {}) -> EventDefinition:
|
|
event_id = p_event_id
|
|
category = StringName(_value(options, &"category", &"FLAVOR"))
|
|
priority = int(_value(options, &"priority", 0))
|
|
conditions = _conditions(_value(options, &"conditions", []))
|
|
participant_roles = _sorted_unique_names(_value(options, &"participants", []))
|
|
location_rule = _dictionary(_value(options, &"location_rule", {})).duplicate(true)
|
|
start_window = _integer_array(_value(options, &"start_window", []))
|
|
expires_after = maxi(int(_value(options, &"expires_after", 0)), 0)
|
|
foreground = bool(_value(options, &"foreground", true))
|
|
interrupt_policy = StringName(_value(options, &"interrupt_policy", &"NORMAL"))
|
|
choices = _choices(_value(options, &"choices", []))
|
|
automatic_actions = _actions(_value(options, &"automatic_actions", []))
|
|
fallback_actions = _actions(_value(options, &"fallback_actions", []))
|
|
failure_actions = _actions(_value(options, &"failure_actions", []))
|
|
replacement_event_id = StringName(_value(options, &"replacement_event_id", &""))
|
|
once_scope = _enum_value(OnceScope.keys(), _value(options, &"once_scope", OnceScope.NONE), OnceScope.NONE) as OnceScope
|
|
transaction_policy = _enum_value(TransactionPolicy.keys(), _value(options, &"transaction_policy", TransactionPolicy.ROLLBACK_ALL), TransactionPolicy.ROLLBACK_ALL) as TransactionPolicy
|
|
missing_actor_policy = _enum_value(MissingActorPolicy.keys(), _value(options, &"missing_actor_policy", MissingActorPolicy.CANCEL), MissingActorPolicy.CANCEL) as MissingActorPolicy
|
|
content_rating = _enum_value(ContentRating.keys(), _value(options, &"content_rating", ContentRating.GENERAL), ContentRating.GENERAL) as ContentRating
|
|
content_tags = _sorted_unique_names(_value(options, &"content_tags", []))
|
|
filter_behavior = _enum_value(FilterBehavior.keys(), _value(options, &"filter_behavior", FilterBehavior.NONE), FilterBehavior.NONE) as FilterBehavior
|
|
return self
|
|
|
|
|
|
func is_valid() -> bool:
|
|
if event_id.is_empty() or category.is_empty():
|
|
return false
|
|
if filter_behavior == FilterBehavior.REPLACE and replacement_event_id.is_empty():
|
|
return false
|
|
if replacement_event_id == event_id:
|
|
return false
|
|
if start_window.size() not in [0, 2]:
|
|
return false
|
|
for condition: EventCondition in conditions:
|
|
if condition == null or not condition.is_valid():
|
|
return false
|
|
var choice_ids: Dictionary = {}
|
|
for choice: EventChoice in choices:
|
|
if choice == null or not choice.is_valid() or choice_ids.has(choice.choice_id):
|
|
return false
|
|
choice_ids[choice.choice_id] = true
|
|
for action_list: Array in [automatic_actions, fallback_actions, failure_actions]:
|
|
for action: EventAction in action_list:
|
|
if action == null or not action.is_valid():
|
|
return false
|
|
return true
|
|
|
|
|
|
func get_choice(choice_id: StringName) -> EventChoice:
|
|
for choice: EventChoice in choices:
|
|
if choice.choice_id == choice_id:
|
|
return choice
|
|
return null
|
|
|
|
|
|
func conditions_match(context: Dictionary, callbacks: Dictionary = {}) -> bool:
|
|
for condition: EventCondition in conditions:
|
|
if not condition.evaluate(context, callbacks):
|
|
return false
|
|
return true
|
|
|
|
|
|
func create_snapshot() -> Dictionary:
|
|
return {
|
|
&"event_id": String(event_id),
|
|
&"category": String(category),
|
|
&"priority": priority,
|
|
&"conditions": _snapshots(conditions),
|
|
&"participants": _names_to_strings(participant_roles),
|
|
&"location_rule": location_rule.duplicate(true),
|
|
&"start_window": start_window.duplicate(),
|
|
&"expires_after": expires_after,
|
|
&"foreground": foreground,
|
|
&"interrupt_policy": String(interrupt_policy),
|
|
&"choices": _snapshots(choices),
|
|
&"automatic_actions": _snapshots(automatic_actions),
|
|
&"fallback_actions": _snapshots(fallback_actions),
|
|
&"failure_actions": _snapshots(failure_actions),
|
|
&"replacement_event_id": String(replacement_event_id),
|
|
&"once_scope": OnceScope.keys()[once_scope],
|
|
&"transaction_policy": TransactionPolicy.keys()[transaction_policy],
|
|
&"missing_actor_policy": MissingActorPolicy.keys()[missing_actor_policy],
|
|
&"content_rating": ContentRating.keys()[content_rating],
|
|
&"content_tags": _names_to_strings(content_tags),
|
|
&"filter_behavior": FilterBehavior.keys()[filter_behavior],
|
|
}
|
|
|
|
|
|
static func from_variant(raw_value: Variant) -> EventDefinition:
|
|
if raw_value is EventDefinition:
|
|
return raw_value
|
|
if not raw_value is Dictionary:
|
|
return null
|
|
# Serialized definitions are authoritative data, not a user-facing settings
|
|
# form. Unknown enum names and out-of-range integer values must fail closed
|
|
# instead of silently selecting a less restrictive fallback.
|
|
var enum_fields := [
|
|
[&"once_scope", OnceScope.keys()],
|
|
[&"transaction_policy", TransactionPolicy.keys()],
|
|
[&"missing_actor_policy", MissingActorPolicy.keys()],
|
|
[&"content_rating", ContentRating.keys()],
|
|
[&"filter_behavior", FilterBehavior.keys()],
|
|
]
|
|
for field: Array in enum_fields:
|
|
var key: StringName = field[0]
|
|
if raw_value.has(key) or raw_value.has(String(key)):
|
|
if _strict_enum_value(field[1], _value(raw_value, key, null)) < 0:
|
|
return null
|
|
return EventDefinition.new().configure(
|
|
StringName(_value(raw_value, &"event_id", &"")),
|
|
raw_value
|
|
)
|
|
|
|
|
|
static func _conditions(raw_value: Variant) -> Array[EventCondition]:
|
|
var result: Array[EventCondition] = []
|
|
if raw_value is Array:
|
|
for raw_entry: Variant in raw_value:
|
|
var entry := EventConditionType.from_variant(raw_entry)
|
|
if entry != null:
|
|
result.append(entry)
|
|
return result
|
|
|
|
|
|
static func _choices(raw_value: Variant) -> Array[EventChoice]:
|
|
var result: Array[EventChoice] = []
|
|
if raw_value is Array:
|
|
for raw_entry: Variant in raw_value:
|
|
var entry := EventChoiceType.from_variant(raw_entry)
|
|
if entry != null:
|
|
result.append(entry)
|
|
return result
|
|
|
|
|
|
static func _actions(raw_value: Variant) -> Array[EventAction]:
|
|
var result: Array[EventAction] = []
|
|
if raw_value is Array:
|
|
for raw_entry: Variant in raw_value:
|
|
var entry := EventActionType.from_variant(raw_entry)
|
|
if entry != null:
|
|
result.append(entry)
|
|
return result
|
|
|
|
|
|
static func _snapshots(values: Array) -> Array[Dictionary]:
|
|
var result: Array[Dictionary] = []
|
|
for value: Variant in values:
|
|
result.append(value.create_snapshot())
|
|
return result
|
|
|
|
|
|
static func _sorted_unique_names(raw_value: Variant) -> Array[StringName]:
|
|
var result: Array[StringName] = []
|
|
if raw_value is Array:
|
|
for raw_entry: Variant in raw_value:
|
|
var entry := StringName(raw_entry)
|
|
if not entry.is_empty() and not result.has(entry):
|
|
result.append(entry)
|
|
result.sort_custom(func(a: StringName, b: StringName) -> bool: return String(a) < String(b))
|
|
return result
|
|
|
|
|
|
static func _integer_array(raw_value: Variant) -> Array[int]:
|
|
var result: Array[int] = []
|
|
if raw_value is Array:
|
|
for raw_entry: Variant in raw_value:
|
|
result.append(int(raw_entry))
|
|
return result
|
|
|
|
|
|
static func _names_to_strings(values: Array[StringName]) -> Array[String]:
|
|
var result: Array[String] = []
|
|
for value: StringName in values:
|
|
result.append(String(value))
|
|
return result
|
|
|
|
|
|
static func _enum_value(keys: Array, raw_value: Variant, fallback: int) -> int:
|
|
if raw_value is int:
|
|
return clampi(int(raw_value), 0, keys.size() - 1)
|
|
var index := keys.find(String(raw_value).to_upper())
|
|
return index if index >= 0 else fallback
|
|
|
|
|
|
static func _strict_enum_value(keys: Array, raw_value: Variant) -> int:
|
|
if raw_value is int:
|
|
var numeric := int(raw_value)
|
|
return numeric if numeric >= 0 and numeric < keys.size() else -1
|
|
if raw_value is String or raw_value is StringName:
|
|
return keys.find(String(raw_value).to_upper())
|
|
return -1
|
|
|
|
|
|
static func _value(source: Dictionary, key: StringName, fallback: Variant) -> Variant:
|
|
if source.has(key):
|
|
return source[key]
|
|
return source.get(String(key), fallback)
|
|
|
|
|
|
static func _dictionary(value: Variant) -> Dictionary:
|
|
return value if value is Dictionary else {}
|