Preserve approved pixel tower prototype as development baseline

This commit is contained in:
Codex
2026-09-09 07:51:16 +09:00
commit 423fd6dd2e
915 changed files with 95193 additions and 0 deletions
+68
View File
@@ -0,0 +1,68 @@
class_name EventAction
extends RefCounted
enum Type {
SET_FLAG,
ADD_RELATIONSHIP_VALUE,
DAMAGE_SHIELD,
ADD_LEDGER_ENTRY,
ADD_OR_REMOVE_ITEM,
CREATE_TASK,
CREATE_INCIDENT,
SET_FACILITY_STATE,
TELEPORT_ENTITY_FOR_RECOVERY,
SCHEDULE_EVENT,
SET_ROUTE_LOCK,
}
var action_id: StringName = &""
var type: Type = Type.SET_FLAG
var payload: Dictionary = {}
func configure(
p_action_id: StringName,
p_type: Variant,
p_payload: Dictionary = {}
) -> EventAction:
action_id = p_action_id
type = _type_from_variant(p_type)
payload = p_payload.duplicate(true)
return self
func is_valid() -> bool:
return not action_id.is_empty() and type >= Type.SET_FLAG and type <= Type.SET_ROUTE_LOCK
func create_snapshot() -> Dictionary:
return {
&"action_id": String(action_id),
&"type": Type.keys()[type],
&"payload": payload.duplicate(true),
}
static func from_variant(raw_value: Variant) -> EventAction:
if raw_value is EventAction:
return raw_value
if not raw_value is Dictionary:
return null
return EventAction.new().configure(
StringName(_value(raw_value, &"action_id", &"")),
_value(raw_value, &"type", "SET_FLAG"),
_value(raw_value, &"payload", {}) if _value(raw_value, &"payload", {}) is Dictionary else {}
)
static func _type_from_variant(raw_value: Variant) -> Type:
if raw_value is int:
return clampi(int(raw_value), Type.SET_FLAG, Type.SET_ROUTE_LOCK) as Type
var index := Type.keys().find(String(raw_value).to_upper())
return (index if index >= 0 else Type.SET_FLAG) as Type
static func _value(source: Dictionary, key: StringName, fallback: Variant) -> Variant:
if source.has(key):
return source[key]
return source.get(String(key), fallback)
+1
View File
@@ -0,0 +1 @@
uid://5wriphjpijed
+105
View File
@@ -0,0 +1,105 @@
class_name EventChoice
extends RefCounted
const EventConditionType = preload("res://scripts/events/event_condition.gd")
const EventActionType = preload("res://scripts/events/event_action.gd")
var choice_id: StringName = &""
var text_key: StringName = &""
var conditions: Array[EventCondition] = []
var cost: Dictionary = {}
var effect_preview: Dictionary = {}
var actions: Array[EventAction] = []
func configure(
p_choice_id: StringName,
p_text_key: StringName,
p_conditions: Array = [],
p_cost: Dictionary = {},
p_effect_preview: Dictionary = {},
p_actions: Array = []
) -> EventChoice:
choice_id = p_choice_id
text_key = p_text_key
conditions.clear()
for raw_condition: Variant in p_conditions:
var condition := EventConditionType.from_variant(raw_condition)
if condition != null:
conditions.append(condition)
cost = p_cost.duplicate(true)
effect_preview = p_effect_preview.duplicate(true)
actions.clear()
for raw_action: Variant in p_actions:
var action := EventActionType.from_variant(raw_action)
if action != null:
actions.append(action)
return self
func is_valid() -> bool:
if choice_id.is_empty() or text_key.is_empty():
return false
for condition: EventCondition in conditions:
if condition == null or not condition.is_valid():
return false
for action: EventAction in actions:
if action == null or not action.is_valid():
return false
for raw_key: Variant in cost:
if StringName(raw_key).is_empty() or int(cost[raw_key]) < 0:
return false
return true
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:
var condition_snapshots: Array[Dictionary] = []
var action_snapshots: Array[Dictionary] = []
for condition: EventCondition in conditions:
condition_snapshots.append(condition.create_snapshot())
for action: EventAction in actions:
action_snapshots.append(action.create_snapshot())
return {
&"choice_id": String(choice_id),
&"text_key": String(text_key),
&"conditions": condition_snapshots,
&"cost": cost.duplicate(true),
&"effect_preview": effect_preview.duplicate(true),
&"actions": action_snapshots,
}
static func from_variant(raw_value: Variant) -> EventChoice:
if raw_value is EventChoice:
return raw_value
if not raw_value is Dictionary:
return null
return EventChoice.new().configure(
StringName(_value(raw_value, &"choice_id", &"")),
StringName(_value(raw_value, &"text_key", &"")),
_array(_value(raw_value, &"conditions", [])),
_dictionary(_value(raw_value, &"cost", {})),
_dictionary(_value(raw_value, &"effect_preview", {})),
_array(_value(raw_value, &"actions", []))
)
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 _array(value: Variant) -> Array:
return value if value is Array else []
static func _dictionary(value: Variant) -> Dictionary:
return value if value is Dictionary else {}
+1
View File
@@ -0,0 +1 @@
uid://d0r5rxpsaqgd2
+214
View File
@@ -0,0 +1,214 @@
class_name EventCondition
extends RefCounted
## Side-effect-free, data-only event predicate.
enum Operator {
EQ,
NE,
LT,
LTE,
GT,
GTE,
HAS_FLAG,
NOT_HAS_FLAG,
IN_RANGE,
ALL,
ANY,
NOT,
ENTITY_EXISTS,
PATH_EXISTS,
TIME_IN_WINDOW,
}
var operator: Operator = Operator.EQ
var path: Array[StringName] = []
var expected: Variant = null
var children: Array[EventCondition] = []
func configure(
p_operator: Variant,
p_path: Variant = [],
p_expected: Variant = null,
p_children: Array = []
) -> EventCondition:
operator = _operator_from_variant(p_operator)
path = _normalize_path(p_path)
expected = _copy(p_expected)
children.clear()
for raw_child: Variant in p_children:
var child := from_variant(raw_child)
if child != null:
children.append(child)
return self
func is_valid() -> bool:
if operator < Operator.EQ or operator > Operator.TIME_IN_WINDOW:
return false
if operator in [Operator.ALL, Operator.ANY] and children.is_empty():
return false
if operator == Operator.NOT and children.size() != 1:
return false
if operator not in [Operator.ALL, Operator.ANY, Operator.NOT, Operator.TIME_IN_WINDOW] and path.is_empty():
return false
for child: EventCondition in children:
if child == null or not child.is_valid():
return false
return true
func evaluate(context: Dictionary, callbacks: Dictionary = {}) -> bool:
match operator:
Operator.ALL:
for child: EventCondition in children:
if not child.evaluate(context, callbacks):
return false
return true
Operator.ANY:
for child: EventCondition in children:
if child.evaluate(context, callbacks):
return true
return false
Operator.NOT:
return not children[0].evaluate(context, callbacks)
Operator.TIME_IN_WINDOW:
var minute := int(_resolve(context, path)) if not path.is_empty() else int(_dictionary_value(context, &"absolute_minute", 0))
return _time_in_window(minute, expected)
var actual: Variant = _resolve(context, path)
match operator:
Operator.EQ:
return actual == expected
Operator.NE:
return actual != expected
Operator.LT:
return _numeric(actual) < _numeric(expected)
Operator.LTE:
return _numeric(actual) <= _numeric(expected)
Operator.GT:
return _numeric(actual) > _numeric(expected)
Operator.GTE:
return _numeric(actual) >= _numeric(expected)
Operator.HAS_FLAG:
return _has_flag(actual, expected)
Operator.NOT_HAS_FLAG:
return not _has_flag(actual, expected)
Operator.IN_RANGE:
return _in_range(actual, expected)
Operator.ENTITY_EXISTS:
return _callback_predicate(callbacks, &"entity_exists", actual)
Operator.PATH_EXISTS:
return _callback_predicate(callbacks, &"path_exists", actual)
return false
func create_snapshot() -> Dictionary:
var child_snapshots: Array[Dictionary] = []
for child: EventCondition in children:
child_snapshots.append(child.create_snapshot())
var path_strings: Array[String] = []
for part: StringName in path:
path_strings.append(String(part))
return {
&"operator": Operator.keys()[operator],
&"path": path_strings,
&"expected": _copy(expected),
&"children": child_snapshots,
}
static func from_variant(raw_value: Variant) -> EventCondition:
if raw_value is EventCondition:
return raw_value
if not raw_value is Dictionary:
return null
var raw_children: Variant = _dictionary_value(raw_value, &"children", [])
return EventCondition.new().configure(
_dictionary_value(raw_value, &"operator", "EQ"),
_dictionary_value(raw_value, &"path", []),
_dictionary_value(raw_value, &"expected", _dictionary_value(raw_value, &"value", null)),
raw_children if raw_children is Array else []
)
static func _operator_from_variant(raw_value: Variant) -> Operator:
if raw_value is int:
return clampi(int(raw_value), Operator.EQ, Operator.TIME_IN_WINDOW) as Operator
var index := Operator.keys().find(String(raw_value).to_upper())
return (index if index >= 0 else Operator.EQ) as Operator
static func _normalize_path(raw_value: Variant) -> Array[StringName]:
var result: Array[StringName] = []
if raw_value is String or raw_value is StringName:
for part: String in String(raw_value).split(".", false):
result.append(StringName(part))
elif raw_value is Array:
for raw_part: Variant in raw_value:
var part := StringName(raw_part)
if not part.is_empty():
result.append(part)
return result
static func _resolve(context: Dictionary, field_path: Array[StringName]) -> Variant:
var current: Variant = context
for part: StringName in field_path:
if not current is Dictionary:
return null
current = _dictionary_value(current, part, null)
return current
static func _numeric(value: Variant) -> float:
return float(value) if value is int or value is float else 0.0
static func _has_flag(actual: Variant, flag_value: Variant) -> bool:
var flag := StringName(flag_value)
if actual is Dictionary:
return bool(_dictionary_value(actual, flag, false))
if actual is Array:
for raw_entry: Variant in actual:
if StringName(raw_entry) == flag:
return true
return false
static func _in_range(actual: Variant, bounds: Variant) -> bool:
if not bounds is Array or bounds.size() < 2:
return false
var numeric := _numeric(actual)
return numeric >= _numeric(bounds[0]) and numeric <= _numeric(bounds[1])
static func _time_in_window(absolute_minute: int, raw_window: Variant) -> bool:
if not raw_window is Array or raw_window.size() < 2:
return false
var minute_of_day := posmod(absolute_minute, 1_440)
var start := posmod(int(raw_window[0]), 1_440)
var finish := posmod(int(raw_window[1]), 1_440)
if start == finish:
return true
if start < finish:
return minute_of_day >= start and minute_of_day < finish
return minute_of_day >= start or minute_of_day < finish
static func _callback_predicate(callbacks: Dictionary, callback_id: StringName, value: Variant) -> bool:
var callback: Variant = _dictionary_value(callbacks, callback_id, Callable())
if callback is Callable and callback.is_valid():
return bool(callback.call(value))
return false
static func _dictionary_value(source: Dictionary, key: StringName, fallback: Variant) -> Variant:
if source.has(key):
return source[key]
return source.get(String(key), fallback)
static func _copy(value: Variant) -> Variant:
return value.duplicate(true) if value is Dictionary or value is Array else value
+1
View File
@@ -0,0 +1 @@
uid://6enckempkxxo
+265
View File
@@ -0,0 +1,265 @@
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 {}
+1
View File
@@ -0,0 +1 @@
uid://cvyev0x3oeq1f
+151
View File
@@ -0,0 +1,151 @@
class_name EventInstance
extends RefCounted
enum State {
QUEUED,
RUNNING,
WAITING_CHOICE,
COMPLETED,
CANCELLED,
EXPIRED,
ERROR,
INTERRUPTED,
}
var event_instance_id: StringName = &""
var source_event_id: StringName = &""
var resolved_event_id: StringName = &""
var actor_ids: Array[StringName] = []
var priority := 0
var created_at := 0
var expires_at := -1
var foreground := true
var state: State = State.QUEUED
var deduplication_key: StringName = &""
var context: Dictionary = {}
var presentation_mode: StringName = &"FULL"
var choice_instance_id: StringName = &""
var selected_choice_id: StringName = &""
var reserved_costs: Dictionary = {}
var failure_reason := ""
var retry_count := 0
func configure(
p_instance_id: StringName,
p_source_event_id: StringName,
p_resolved_event_id: StringName,
p_actor_ids: Array,
p_priority: int,
p_created_at: int,
p_expires_at: int,
p_foreground: bool,
p_deduplication_key: StringName,
p_context: Dictionary = {},
presentation: StringName = &"FULL"
) -> EventInstance:
event_instance_id = p_instance_id
source_event_id = p_source_event_id
resolved_event_id = p_resolved_event_id
actor_ids = _sorted_unique_names(p_actor_ids)
priority = p_priority
created_at = maxi(p_created_at, 0)
expires_at = p_expires_at
foreground = p_foreground
deduplication_key = p_deduplication_key
context = p_context.duplicate(true)
presentation_mode = presentation
return self
func is_valid() -> bool:
if event_instance_id.is_empty() or source_event_id.is_empty() or resolved_event_id.is_empty():
return false
if created_at < 0 or (expires_at >= 0 and expires_at < created_at):
return false
if state < State.QUEUED or state > State.INTERRUPTED:
return false
return true
func is_terminal() -> bool:
return state in [State.COMPLETED, State.CANCELLED, State.EXPIRED, State.ERROR]
func create_snapshot() -> Dictionary:
return {
&"event_instance_id": String(event_instance_id),
&"source_event_id": String(source_event_id),
&"resolved_event_id": String(resolved_event_id),
&"actor_ids": _names_to_strings(actor_ids),
&"priority": priority,
&"created_at": created_at,
&"expires_at": expires_at,
&"foreground": foreground,
&"state": State.keys()[state],
&"deduplication_key": String(deduplication_key),
&"context": context.duplicate(true),
&"presentation_mode": String(presentation_mode),
&"choice_instance_id": String(choice_instance_id),
&"selected_choice_id": String(selected_choice_id),
&"reserved_costs": reserved_costs.duplicate(true),
&"failure_reason": failure_reason,
&"retry_count": retry_count,
}
static func from_snapshot(snapshot: Dictionary) -> EventInstance:
var instance := EventInstance.new().configure(
StringName(_value(snapshot, &"event_instance_id", &"")),
StringName(_value(snapshot, &"source_event_id", &"")),
StringName(_value(snapshot, &"resolved_event_id", &"")),
_array(_value(snapshot, &"actor_ids", [])),
int(_value(snapshot, &"priority", 0)),
int(_value(snapshot, &"created_at", 0)),
int(_value(snapshot, &"expires_at", -1)),
bool(_value(snapshot, &"foreground", true)),
StringName(_value(snapshot, &"deduplication_key", &"")),
_dictionary(_value(snapshot, &"context", {})),
StringName(_value(snapshot, &"presentation_mode", &"FULL"))
)
var state_name := String(_value(snapshot, &"state", "QUEUED")).to_upper()
var state_index := State.keys().find(state_name)
instance.state = (state_index if state_index >= 0 else State.ERROR) as State
instance.choice_instance_id = StringName(_value(snapshot, &"choice_instance_id", &""))
instance.selected_choice_id = StringName(_value(snapshot, &"selected_choice_id", &""))
instance.reserved_costs = _dictionary(_value(snapshot, &"reserved_costs", {})).duplicate(true)
instance.failure_reason = String(_value(snapshot, &"failure_reason", ""))
instance.retry_count = maxi(int(_value(snapshot, &"retry_count", 0)), 0)
return instance
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 _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 _value(source: Dictionary, key: StringName, fallback: Variant) -> Variant:
if source.has(key):
return source[key]
return source.get(String(key), fallback)
static func _array(value: Variant) -> Array:
return value if value is Array else []
static func _dictionary(value: Variant) -> Dictionary:
return value if value is Dictionary else {}
+1
View File
@@ -0,0 +1 @@
uid://c0xntlstpehhd
File diff suppressed because it is too large Load Diff
+1
View File
@@ -0,0 +1 @@
uid://dyvxeffcwc22y