Save The Tree implementation and acceptance work in progress
Preserve production code, regression fixtures, audio assets and provenance, test infrastructure, and the original-completion plan and progress ledger. Final game and Windows acceptance remain pending; excluded local artifacts and native experiments are not part of this snapshot.
This commit is contained in:
+200
-35
@@ -5,6 +5,9 @@ extends RefCounted
|
||||
## choice authority. Narrative text is deliberately outside this service; only
|
||||
## localization keys, content ratings/tags and safe replacement IDs are retained.
|
||||
|
||||
signal effect_transaction_started
|
||||
signal effect_transaction_finished
|
||||
|
||||
signal event_registered(definition: EventDefinition)
|
||||
signal event_enqueued(instance: EventInstance)
|
||||
signal event_started(instance: EventInstance)
|
||||
@@ -47,6 +50,7 @@ var _cost_releaser := Callable()
|
||||
var _cost_committer := Callable()
|
||||
var _condition_callbacks: Dictionary = {}
|
||||
var _relationship_service: Variant = null
|
||||
var _candidate_eligibility_guard := Callable()
|
||||
|
||||
|
||||
func configure_callbacks(
|
||||
@@ -70,6 +74,19 @@ func configure_callbacks(
|
||||
return self
|
||||
|
||||
|
||||
## A domain-owned, read-only guard runs before queue mutation and again before
|
||||
## a queued event acquires actors or executes actions. It is rebuilt on restore.
|
||||
func configure_candidate_eligibility_guard(guard: Callable = Callable()) -> void:
|
||||
_candidate_eligibility_guard = guard
|
||||
|
||||
|
||||
func _candidate_permitted(definition: EventDefinition, context: Dictionary) -> bool:
|
||||
if not _candidate_eligibility_guard.is_valid():
|
||||
return true
|
||||
var result: Variant = _candidate_eligibility_guard.call(definition, context.duplicate(true))
|
||||
return typeof(result) == TYPE_BOOL and result
|
||||
|
||||
|
||||
func configure_relationship_service(service: Variant) -> EventService:
|
||||
_relationship_service = service
|
||||
return self
|
||||
@@ -222,6 +239,8 @@ func enqueue_candidate(
|
||||
var context := _dictionary(_value(options, &"context", {})).duplicate(true)
|
||||
context[&"absolute_minute"] = absolute_minute
|
||||
context[&"actor_ids"] = normalized_actor_ids.duplicate()
|
||||
if not _candidate_permitted(source, context) or not _candidate_permitted(definition, context):
|
||||
return _invalid("Event candidate is prevented by placement protection.", {&"reason": &"PLACEMENT_PROTECTION"})
|
||||
var instance := EventInstanceType.new().configure(
|
||||
instance_id,
|
||||
event_id,
|
||||
@@ -256,6 +275,8 @@ func start_next_event(absolute_minute: int, extra_context: Dictionary = {}) -> C
|
||||
return _invalid("Event processing requires a non-negative minute.")
|
||||
process_expired(absolute_minute)
|
||||
audit_actor_availability(absolute_minute)
|
||||
if pause_requested:
|
||||
return _invalid("Event processing stopped after a fatal failure.", {&"fatal": true})
|
||||
if not _foreground_instance_id.is_empty():
|
||||
return _invalid("A foreground Event is already active.", {
|
||||
&"event_instance_id": _foreground_instance_id,
|
||||
@@ -277,6 +298,10 @@ func start_next_event(absolute_minute: int, extra_context: Dictionary = {}) -> C
|
||||
var context := _merged_context(instance, absolute_minute, extra_context)
|
||||
if not definition.conditions_match(context, _condition_callbacks):
|
||||
continue
|
||||
var original_definition := get_definition(instance.source_event_id)
|
||||
if original_definition == null or not _candidate_permitted(original_definition, context) or not _candidate_permitted(definition, context):
|
||||
_cancel_instance(instance, &"PLACEMENT_PROTECTION", EventInstanceType.State.CANCELLED)
|
||||
continue
|
||||
if instance.foreground and not _acquire_actor_locks(instance):
|
||||
continue
|
||||
_queue.erase(instance_id)
|
||||
@@ -295,21 +320,27 @@ func start_next_event(absolute_minute: int, extra_context: Dictionary = {}) -> C
|
||||
if bool(automatic_result.get(&"fatal", false)):
|
||||
instance.state = EventInstanceType.State.ERROR
|
||||
pause_requested = true
|
||||
_finish_instance(instance, false)
|
||||
var finish := _finish_instance(instance, false)
|
||||
if not bool(finish.get(&"ok", false)):
|
||||
automatic_result[&"cost_release"] = finish
|
||||
else:
|
||||
_cancel_instance(instance, &"ACTION_FAILED", EventInstanceType.State.CANCELLED)
|
||||
var cancelled := _cancel_instance(instance, &"ACTION_FAILED", EventInstanceType.State.CANCELLED)
|
||||
if not bool(cancelled.get(&"ok", false)):
|
||||
automatic_result[&"fatal"] = true
|
||||
automatic_result[&"cost_release"] = cancelled
|
||||
return _invalid(instance.failure_reason, automatic_result)
|
||||
if not definition.choices.is_empty():
|
||||
instance.state = EventInstanceType.State.WAITING_CHOICE
|
||||
instance.choice_instance_id = StringName("choice_instance_%08d" % _next_choice_sequence)
|
||||
_next_choice_sequence += 1
|
||||
_advance_choice_attempt(instance)
|
||||
last_error = ""
|
||||
return _success({
|
||||
&"event_instance_id": instance.event_instance_id,
|
||||
&"choice_instance_id": instance.choice_instance_id,
|
||||
&"waiting_for_choice": true,
|
||||
})
|
||||
_finish_instance(instance, true)
|
||||
var finish := _finish_instance(instance, true)
|
||||
if not bool(finish.get(&"ok", false)):
|
||||
return _invalid(String(finish.get(&"error", "Event cost release failed.")), finish)
|
||||
last_error = ""
|
||||
return _success({
|
||||
&"event_instance_id": instance.event_instance_id,
|
||||
@@ -334,10 +365,12 @@ func process_expired(absolute_minute: int) -> Array[Dictionary]:
|
||||
[],
|
||||
context
|
||||
)
|
||||
_cancel_instance(instance, &"EXPIRED", EventInstanceType.State.EXPIRED)
|
||||
var cancelled := _cancel_instance(instance, &"EXPIRED", EventInstanceType.State.EXPIRED)
|
||||
results.append({
|
||||
&"event_instance_id": instance_id,
|
||||
&"fallback_ok": bool(fallback_result.get(&"ok", false)),
|
||||
&"cost_release": cancelled,
|
||||
&"fatal": bool(cancelled.get(&"fatal", false)),
|
||||
})
|
||||
return results
|
||||
|
||||
@@ -347,6 +380,18 @@ func reserve_choice_cost(
|
||||
choice_id: StringName,
|
||||
absolute_minute: int,
|
||||
extra_context: Dictionary = {}
|
||||
) -> CommandResult:
|
||||
effect_transaction_started.emit()
|
||||
var result := _reserve_choice_cost_inside_transaction(event_instance_id, choice_id, absolute_minute, extra_context)
|
||||
effect_transaction_finished.emit()
|
||||
return result
|
||||
|
||||
|
||||
func _reserve_choice_cost_inside_transaction(
|
||||
event_instance_id: StringName,
|
||||
choice_id: StringName,
|
||||
absolute_minute: int,
|
||||
extra_context: Dictionary = {}
|
||||
) -> CommandResult:
|
||||
var instance := get_instance(event_instance_id)
|
||||
if instance == null:
|
||||
@@ -360,7 +405,10 @@ func reserve_choice_cost(
|
||||
var missing := _missing_actor_ids(instance.actor_ids)
|
||||
if not missing.is_empty():
|
||||
_handle_missing_actors(instance, definition, missing, absolute_minute)
|
||||
return _invalid("Event actor is unavailable.", {&"missing_actor_ids": missing})
|
||||
return _invalid("Event actor is unavailable.", {
|
||||
&"missing_actor_ids": missing, &"fatal": instance.state == EventInstanceType.State.ERROR,
|
||||
&"failure_reason": instance.failure_reason,
|
||||
})
|
||||
var context := _merged_context(instance, absolute_minute, extra_context)
|
||||
if not choice.conditions_match(context, _condition_callbacks):
|
||||
return _invalid("Choice conditions changed and no longer pass.")
|
||||
@@ -384,6 +432,19 @@ func submit_choice(
|
||||
choice_id: StringName,
|
||||
absolute_minute: int,
|
||||
extra_context: Dictionary = {}
|
||||
) -> CommandResult:
|
||||
effect_transaction_started.emit()
|
||||
var result := _submit_choice_inside_transaction(event_instance_id, choice_instance_id, choice_id, absolute_minute, extra_context)
|
||||
effect_transaction_finished.emit()
|
||||
return result
|
||||
|
||||
|
||||
func _submit_choice_inside_transaction(
|
||||
event_instance_id: StringName,
|
||||
choice_instance_id: StringName,
|
||||
choice_id: StringName,
|
||||
absolute_minute: int,
|
||||
extra_context: Dictionary = {}
|
||||
) -> CommandResult:
|
||||
if _processed_choices.has(choice_instance_id):
|
||||
var replay := CommandResultType.from_dictionary(_processed_choices[choice_instance_id])
|
||||
@@ -402,7 +463,10 @@ func submit_choice(
|
||||
var missing := _missing_actor_ids(instance.actor_ids)
|
||||
if not missing.is_empty():
|
||||
_handle_missing_actors(instance, definition, missing, absolute_minute)
|
||||
return _invalid("Event actor is unavailable.", {&"missing_actor_ids": missing})
|
||||
return _invalid("Event actor is unavailable.", {
|
||||
&"missing_actor_ids": missing, &"fatal": instance.state == EventInstanceType.State.ERROR,
|
||||
&"failure_reason": instance.failure_reason,
|
||||
})
|
||||
var choice := definition.get_choice(choice_id)
|
||||
if choice == null:
|
||||
return _not_found("EventChoice", choice_id)
|
||||
@@ -420,30 +484,61 @@ func submit_choice(
|
||||
context
|
||||
)
|
||||
if not bool(transaction_result.get(&"ok", false)):
|
||||
_release_reserved_cost(instance, choice_id, context)
|
||||
var release := _release_reserved_cost(instance, choice_id, context)
|
||||
if not bool(release.get(&"ok", false)):
|
||||
transaction_result[&"fatal"] = true
|
||||
transaction_result[&"cost_release"] = release
|
||||
transaction_result[&"error"] = "Choice action failed and cost release failed: %s" % release.get(&"error", "")
|
||||
if bool(transaction_result.get(&"fatal", false)):
|
||||
var other_release := _release_all_reserved_costs(instance, context, [choice_id])
|
||||
if not bool(other_release.get(&"ok", false)):
|
||||
transaction_result[&"other_cost_release"] = other_release
|
||||
instance.state = EventInstanceType.State.ERROR
|
||||
instance.failure_reason = String(transaction_result.get(&"error", "Failure branch failed."))
|
||||
pause_requested = true
|
||||
_finish_instance(instance, false)
|
||||
_detach_instance(instance)
|
||||
var failed_result := _invalid(
|
||||
String(transaction_result.get(&"error", "Choice Action transaction failed.")),
|
||||
transaction_result
|
||||
)
|
||||
_cache_choice_result(choice_instance_id, failed_result)
|
||||
if instance.state == EventInstanceType.State.WAITING_CHOICE:
|
||||
_advance_choice_attempt(instance)
|
||||
return failed_result
|
||||
var commit_result := _commit_reserved_cost(instance, choice_id, context)
|
||||
if not bool(commit_result.get(&"ok", false)):
|
||||
_rollback_records(transaction_result.get(&"records", []), context)
|
||||
_release_reserved_cost(instance, choice_id, context)
|
||||
var rollback := _rollback_records(transaction_result.get(&"records", []), context)
|
||||
var release := _release_reserved_cost(instance, choice_id, context)
|
||||
if not bool(release.get(&"ok", false)):
|
||||
commit_result[&"fatal"] = true
|
||||
commit_result[&"cost_release"] = release
|
||||
commit_result[&"error"] = "Choice cost commit and release failed: %s" % release.get(&"error", "")
|
||||
if not bool(rollback.get(&"ok", false)):
|
||||
commit_result[&"fatal"] = true
|
||||
commit_result[&"rollback"] = rollback
|
||||
commit_result[&"error"] = "Choice cost failed and action rollback failed: %s" % rollback.get(&"error", "")
|
||||
if bool(commit_result.get(&"fatal", false)):
|
||||
var other_release := _release_all_reserved_costs(instance, context, [choice_id])
|
||||
if not bool(other_release.get(&"ok", false)):
|
||||
commit_result[&"other_cost_release"] = other_release
|
||||
instance.state = EventInstanceType.State.ERROR
|
||||
instance.failure_reason = String(commit_result[&"error"])
|
||||
pause_requested = true
|
||||
_detach_instance(instance)
|
||||
var commit_failure := _invalid(
|
||||
String(commit_result.get(&"error", "Choice cost commit failed.")),
|
||||
commit_result
|
||||
)
|
||||
_cache_choice_result(choice_instance_id, commit_failure)
|
||||
if instance.state == EventInstanceType.State.WAITING_CHOICE:
|
||||
_advance_choice_attempt(instance)
|
||||
return commit_failure
|
||||
instance.selected_choice_id = choice_id
|
||||
_finish_instance(instance, true)
|
||||
var finish := _finish_instance(instance, true)
|
||||
if not bool(finish.get(&"ok", false)):
|
||||
var finish_failure := _invalid(String(finish.get(&"error", "Choice final cost release failed.")), finish)
|
||||
_cache_choice_result(choice_instance_id, finish_failure)
|
||||
return finish_failure
|
||||
var result := _success({
|
||||
&"event_instance_id": event_instance_id,
|
||||
&"choice_instance_id": choice_instance_id,
|
||||
@@ -482,7 +577,9 @@ func cancel_event(event_instance_id: StringName, reason: StringName = &"CANCELLE
|
||||
return _not_found("EventInstance", event_instance_id)
|
||||
if instance.is_terminal():
|
||||
return _invalid("Event is already terminal.")
|
||||
_cancel_instance(instance, reason, EventInstanceType.State.CANCELLED)
|
||||
var cancelled := _cancel_instance(instance, reason, EventInstanceType.State.CANCELLED)
|
||||
if not bool(cancelled.get(&"ok", false)):
|
||||
return _invalid(String(cancelled.get(&"error", "Event cost release failed.")), cancelled)
|
||||
last_error = ""
|
||||
return _success({&"event_instance_id": event_instance_id})
|
||||
|
||||
@@ -765,6 +862,18 @@ func _execute_actions(
|
||||
policy: EventDefinition.TransactionPolicy,
|
||||
failure_actions: Array,
|
||||
context: Dictionary
|
||||
) -> Dictionary:
|
||||
effect_transaction_started.emit()
|
||||
var result := _execute_actions_inside_transaction(actions, policy, failure_actions, context)
|
||||
effect_transaction_finished.emit()
|
||||
return result
|
||||
|
||||
|
||||
func _execute_actions_inside_transaction(
|
||||
actions: Array,
|
||||
policy: EventDefinition.TransactionPolicy,
|
||||
failure_actions: Array,
|
||||
context: Dictionary
|
||||
) -> Dictionary:
|
||||
match policy:
|
||||
EventDefinitionType.TransactionPolicy.SKIP_FAILED:
|
||||
@@ -779,11 +888,13 @@ func _execute_actions(
|
||||
if bool(applied.get(&"ok", false)):
|
||||
records.append({&"action": action, &"token": _copy(applied.get(&"token", null))})
|
||||
else:
|
||||
if bool(applied.get(&"fatal", false)):
|
||||
return applied
|
||||
skipped.append(action.action_id)
|
||||
return {&"ok": true, &"records": records, &"skipped_action_ids": skipped}
|
||||
EventDefinitionType.TransactionPolicy.BRANCH_FAILURE:
|
||||
var primary := _run_rollback_all(actions, context)
|
||||
if bool(primary.get(&"ok", false)):
|
||||
if bool(primary.get(&"ok", false)) or bool(primary.get(&"fatal", false)):
|
||||
return primary
|
||||
var branch := _run_rollback_all(failure_actions, context)
|
||||
if bool(branch.get(&"ok", false)):
|
||||
@@ -807,18 +918,31 @@ func _run_rollback_all(actions: Array, context: Dictionary) -> Dictionary:
|
||||
for action: EventAction in actions:
|
||||
var applied := _execute_action(action, &"apply", context, null)
|
||||
if not bool(applied.get(&"ok", false)):
|
||||
_rollback_records(records, context)
|
||||
return {&"ok": false, &"error": applied.get(&"error", "Action apply failed."), &"records": []}
|
||||
var rollback := _rollback_records(records, context)
|
||||
var failed := {&"ok": false, &"error": applied.get(&"error", "Action apply failed."), &"records": []}
|
||||
if bool(applied.get(&"fatal", false)) or not bool(rollback.get(&"ok", false)):
|
||||
failed[&"fatal"] = true
|
||||
failed[&"rollback"] = rollback
|
||||
failed[&"error"] = "Action transaction rollback failed: %s; %s" % [failed[&"error"], rollback.get(&"error", "")]
|
||||
return failed
|
||||
records.append({&"action": action, &"token": _copy(applied.get(&"token", null))})
|
||||
return {&"ok": true, &"records": records, &"skipped_action_ids": []}
|
||||
|
||||
|
||||
func _rollback_records(raw_records: Variant, context: Dictionary) -> void:
|
||||
func _rollback_records(raw_records: Variant, context: Dictionary) -> Dictionary:
|
||||
if not raw_records is Array:
|
||||
return
|
||||
return {&"ok": false, &"error": "Rollback records are malformed."}
|
||||
var failed_actions: Array[StringName] = []
|
||||
var errors: Array[String] = []
|
||||
# Ask every participant even after a failure; retain reverse apply order.
|
||||
for index: int in range(raw_records.size() - 1, -1, -1):
|
||||
var record: Dictionary = raw_records[index]
|
||||
_execute_action(record[&"action"], &"rollback", context, record.get(&"token"))
|
||||
var action: EventAction = record[&"action"]
|
||||
var result := _execute_action(action, &"rollback", context, record.get(&"token"))
|
||||
if not bool(result.get(&"ok", false)):
|
||||
failed_actions.append(action.action_id)
|
||||
errors.append(String(result.get(&"error", "Rollback participant returned failure.")))
|
||||
return {&"ok": failed_actions.is_empty(), &"failed_action_ids": failed_actions, &"error": "; ".join(errors)}
|
||||
|
||||
|
||||
func _execute_action(
|
||||
@@ -876,7 +1000,7 @@ func _execute_builtin_action(
|
||||
var instance_id := StringName(rollback_token)
|
||||
var scheduled := get_instance(instance_id)
|
||||
if scheduled != null and not scheduled.is_terminal():
|
||||
_cancel_instance(scheduled, &"TRANSACTION_ROLLBACK", EventInstanceType.State.CANCELLED)
|
||||
return _cancel_instance(scheduled, &"TRANSACTION_ROLLBACK", EventInstanceType.State.CANCELLED)
|
||||
return {&"ok": true}
|
||||
EventActionType.Type.ADD_RELATIONSHIP_VALUE, EventActionType.Type.DAMAGE_SHIELD, EventActionType.Type.SET_ROUTE_LOCK:
|
||||
return _execute_relationship_action(action, phase, context, rollback_token)
|
||||
@@ -935,7 +1059,10 @@ func _handle_missing_actors(
|
||||
missing_actor_ids: Array[StringName],
|
||||
absolute_minute: int
|
||||
) -> void:
|
||||
_release_all_reserved_costs(instance, _merged_context(instance, absolute_minute, {}))
|
||||
var release := _release_all_reserved_costs(instance, _merged_context(instance, absolute_minute, {}))
|
||||
if not bool(release.get(&"ok", false)):
|
||||
_mark_release_failure(instance, release)
|
||||
return
|
||||
if definition == null:
|
||||
_cancel_instance(instance, &"DEFINITION_MISSING", EventInstanceType.State.ERROR)
|
||||
return
|
||||
@@ -995,12 +1122,26 @@ func _release_actor_locks(instance: EventInstance) -> void:
|
||||
_foreground_instance_id = &""
|
||||
|
||||
|
||||
func _finish_instance(instance: EventInstance, completed: bool) -> void:
|
||||
_release_all_reserved_costs(instance, instance.context)
|
||||
func _detach_instance(instance: EventInstance) -> void:
|
||||
_release_actor_locks(instance)
|
||||
_queue.erase(instance.event_instance_id)
|
||||
if not instance.deduplication_key.is_empty():
|
||||
_active_deduplication_keys.erase(instance.deduplication_key)
|
||||
|
||||
|
||||
func _mark_release_failure(instance: EventInstance, result: Dictionary) -> void:
|
||||
instance.state = EventInstanceType.State.ERROR
|
||||
instance.failure_reason = String(result.get(&"error", "Event cost release failed."))
|
||||
pause_requested = true
|
||||
_detach_instance(instance)
|
||||
|
||||
|
||||
func _finish_instance(instance: EventInstance, completed: bool) -> Dictionary:
|
||||
var release := _release_all_reserved_costs(instance, instance.context)
|
||||
if not bool(release.get(&"ok", false)):
|
||||
_mark_release_failure(instance, release)
|
||||
return release
|
||||
_detach_instance(instance)
|
||||
if completed:
|
||||
instance.state = EventInstanceType.State.COMPLETED
|
||||
var source := get_definition(instance.source_event_id)
|
||||
@@ -1009,17 +1150,20 @@ func _finish_instance(instance: EventInstance, completed: bool) -> void:
|
||||
if not scope_key.is_empty():
|
||||
_completed_scope_keys[scope_key] = instance.event_instance_id
|
||||
event_finished.emit(instance)
|
||||
return release
|
||||
|
||||
|
||||
func _cancel_instance(instance: EventInstance, reason: StringName, terminal_state: EventInstance.State) -> void:
|
||||
_release_all_reserved_costs(instance, instance.context)
|
||||
_release_actor_locks(instance)
|
||||
_queue.erase(instance.event_instance_id)
|
||||
if not instance.deduplication_key.is_empty():
|
||||
_active_deduplication_keys.erase(instance.deduplication_key)
|
||||
func _cancel_instance(instance: EventInstance, reason: StringName, terminal_state: EventInstance.State) -> Dictionary:
|
||||
var release := _release_all_reserved_costs(instance, instance.context)
|
||||
if not bool(release.get(&"ok", false)):
|
||||
_mark_release_failure(instance, release)
|
||||
event_cancelled.emit(instance, &"COST_RELEASE_FAILED")
|
||||
return release
|
||||
_detach_instance(instance)
|
||||
instance.state = terminal_state
|
||||
instance.failure_reason = String(reason)
|
||||
event_cancelled.emit(instance, reason)
|
||||
return release
|
||||
|
||||
|
||||
func _reserve_cost(cost: Dictionary, context: Dictionary) -> Dictionary:
|
||||
@@ -1037,14 +1181,30 @@ func _release_reserved_cost(instance: EventInstance, choice_id: StringName, cont
|
||||
var result := {&"ok": true}
|
||||
if _cost_releaser.is_valid():
|
||||
result = _normalize_operation_result(_cost_releaser.call(_copy(reservation.get(&"token")), context))
|
||||
instance.reserved_costs.erase(choice_id)
|
||||
elif not _dictionary(reservation.get(&"cost", {})).is_empty():
|
||||
result = {&"ok": false, &"error": "Choice Cost releaser is not configured."}
|
||||
if bool(result.get(&"ok", false)):
|
||||
instance.reserved_costs.erase(choice_id)
|
||||
else:
|
||||
result[&"fatal"] = true
|
||||
result[&"failed_choice_id"] = choice_id
|
||||
return result
|
||||
|
||||
|
||||
func _release_all_reserved_costs(instance: EventInstance, context: Dictionary) -> void:
|
||||
var choice_ids := _sorted_name_keys(instance.reserved_costs)
|
||||
for choice_id: StringName in choice_ids:
|
||||
_release_reserved_cost(instance, choice_id, context)
|
||||
func _release_all_reserved_costs(instance: EventInstance, context: Dictionary, excluded_choice_ids: Array[StringName] = []) -> Dictionary:
|
||||
var failed_ids: Array[StringName] = []
|
||||
var failures: Array[Dictionary] = []
|
||||
for choice_id: StringName in _sorted_name_keys(instance.reserved_costs):
|
||||
if choice_id in excluded_choice_ids:
|
||||
continue
|
||||
var released := _release_reserved_cost(instance, choice_id, context)
|
||||
if not bool(released.get(&"ok", false)):
|
||||
failed_ids.append(choice_id)
|
||||
failures.append(released)
|
||||
if failed_ids.is_empty():
|
||||
return {&"ok": true}
|
||||
return {&"ok": false, &"fatal": true, &"failed_choice_ids": failed_ids,
|
||||
&"failures": failures, &"error": "Event cost release failed for choices: %s" % [failed_ids]}
|
||||
|
||||
|
||||
func _commit_reserved_cost(instance: EventInstance, choice_id: StringName, context: Dictionary) -> Dictionary:
|
||||
@@ -1256,3 +1416,8 @@ func _invalid(message: String, details: Dictionary = {}) -> CommandResult:
|
||||
|
||||
func _not_found(kind: String, entity_id: StringName) -> CommandResult:
|
||||
return _invalid("%s not found: %s" % [kind, entity_id], {&"entity_id": entity_id})
|
||||
|
||||
|
||||
func _advance_choice_attempt(instance: EventInstance) -> void:
|
||||
instance.choice_instance_id = StringName("choice_instance_%08d" % _next_choice_sequence)
|
||||
_next_choice_sequence += 1
|
||||
|
||||
Reference in New Issue
Block a user