Figments-of-the-Night/addons/inkgd/runtime/variables_state.gd
Gerard Gascón b99855351d init
2025-04-24 17:23:34 +02:00

373 lines
12 KiB
GDScript

# warning-ignore-all:shadowed_variable
# warning-ignore-all:unused_class_variable
# warning-ignore-all:return_value_discarded
# ############################################################################ #
# Copyright © 2015-2021 inkle Ltd.
# Copyright © 2019-2022 Frédéric Maquin <fred@ephread.com>
# All Rights Reserved
#
# This file is part of inkgd.
# inkgd is licensed under the terms of the MIT license.
# ############################################################################ #
extends InkBase
class_name InkVariablesState
# ############################################################################ #
# Imports
# ############################################################################ #
var InkTryGetResult := preload("res://addons/inkgd/runtime/extra/try_get_result.gd") as GDScript
var InkStringSet := preload("res://addons/inkgd/runtime/extra/string_set.gd") as GDScript
var InkValue := load("res://addons/inkgd/runtime/values/value.gd") as GDScript
var InkListValue := load("res://addons/inkgd/runtime/values/list_value.gd") as GDScript
var InkVariablePointerValue := load("res://addons/inkgd/runtime/values/variable_pointer_value.gd") as GDScript
# ############################################################################ #
# (String, InkObject)
signal variable_changed(variable_name, new_value)
# ############################################################################ #
var patch: InkStatePatch # StatePatch
var batch_observing_variable_changes: bool:
get:
return _batch_observing_variable_changes
set(value):
_batch_observing_variable_changes = value
if value:
_changed_variables_for_batch_obs = InkStringSet.new()
else:
if _changed_variables_for_batch_obs != null:
for variable_name in _changed_variables_for_batch_obs.enumerate():
var current_value = _global_variables[variable_name]
emit_signal("variable_changed", variable_name, current_value)
_changed_variables_for_batch_obs = null
var _batch_observing_variable_changes: bool = false
var callstack: InkCallStack: get = get_callstack, set = set_callstack
func get_callstack() -> InkCallStack:
return _callstack
func set_callstack(value: InkCallStack):
_callstack = value
# (String) -> Variant
func get_variable(variable_name: String):
if self.patch != null:
var global: InkTryGetResult = patch.try_get_global(variable_name)
if global.exists:
return global.result.value_object
if _global_variables.has(variable_name):
return _global_variables[variable_name].value_object
elif _default_global_variables.has(variable_name):
return _default_global_variables[variable_name].value_object
else:
return null
# (String, Variant) -> void
func set_variable(variable_name: String, value) -> void:
if !_default_global_variables.has(variable_name):
InkUtils.throw_story_exception(
"Cannot assign to a variable (%s) that hasn't been declared in the story" \
% variable_name
)
return
var val: InkValue = InkValue.create(value)
if val == null:
if value == null:
InkUtils.throw_exception("Cannot pass null to VariableState")
else:
InkUtils.throw_exception("Invalid value passed to VariableState: %s" % str(value))
return
set_global(variable_name, val)
func enumerate() -> Array:
return _global_variables.keys()
func _init(callstack: InkCallStack, list_defs_origin: InkListDefinitionsOrigin, ink_runtime = null):
find_static_objects(ink_runtime)
_global_variables = {}
_callstack = callstack
_list_defs_origin = list_defs_origin
# () -> void
func apply_patch() -> void:
for named_var_key in self.patch.globals:
_global_variables[named_var_key] = self.patch.globals[named_var_key]
if _changed_variables_for_batch_obs != null:
for name in self.patch.changed_variables.enumerate():
_changed_variables_for_batch_obs.append(name)
patch = null
# (Dictionary<string, Variant>) -> void
func set_json_token(jtoken: Dictionary) -> void:
_global_variables.clear()
for var_val_key in _default_global_variables:
if jtoken.has(var_val_key):
var loaded_token = jtoken[var_val_key]
_global_variables[var_val_key] = self.StaticJSON.jtoken_to_runtime_object(loaded_token)
else:
_global_variables[var_val_key] = _default_global_variables[var_val_key]
func write_json(writer: InkSimpleJSON.Writer) -> void:
writer.write_object_start()
for key in _global_variables:
var name: String = key
var val: InkObject = _global_variables[key]
if self._ink_runtime.dont_save_default_values:
if self._default_global_variables.has(name):
if runtime_objects_equal(val, self._default_global_variables[name]):
continue
writer.write_property_start(name)
self.StaticJSON.write_runtime_object(writer, val)
writer.write_property_end()
writer.write_object_end()
func runtime_objects_equal(obj1: InkObject, obj2: InkObject) -> bool:
if !InkUtils.are_of_same_type(obj1, obj2):
return false
var bool_val: InkBoolValue = InkUtils.as_or_null(obj1, "BoolValue")
if bool_val != null:
return bool_val.value == InkUtils.cast(obj2, "BoolValue").value
var int_val: InkIntValue = InkUtils.as_or_null(obj1, "IntValue")
if int_val != null:
return int_val.value == InkUtils.cast(obj2, "IntValue").value
var float_val: InkFloatValue = InkUtils.as_or_null(obj1, "FloatValue")
if float_val != null:
return float_val.value == InkUtils.cast(obj2, "FloatValue").value
var val1: InkValue = InkUtils.as_or_null(obj1, "Value")
var val2: InkValue = InkUtils.as_or_null(obj2, "Value")
if val1 != null:
if val1.value_object is Object && val2.value_object is Object:
return val1.value_object.equals(val2.value_object)
else:
return val1.value_object == val2.value_object
InkUtils.throw_exception(
"FastRoughDefinitelyEquals: Unsupported runtime object type: %s" \
% obj1.get_ink_class()
)
return false
func get_variable_with_name(name: String, context_index = -1) -> InkObject:
var var_value: InkObject = get_raw_variable_with_name(name, context_index)
var var_pointer: InkVariablePointerValue = InkUtils.as_or_null(var_value, "VariablePointerValue")
if var_pointer:
var_value = value_at_variable_pointer(var_pointer)
return var_value
# (String) -> { exists: bool, result: InkObject }
func try_get_default_variable_value(name: String) -> InkTryGetResult:
if _default_global_variables.has(name):
return InkTryGetResult.new(true, _default_global_variables[name])
else:
return InkTryGetResult.new(false, null)
func global_variable_exists_with_name(name: String) -> bool:
return (
_global_variables.has(name) ||
_default_global_variables != null && _default_global_variables.has(name)
)
func get_raw_variable_with_name(name: String, context_index: int) -> InkObject:
var var_value: InkObject = null
if context_index == 0 || context_index == -1:
if self.patch != null:
var try_result: InkTryGetResult = self.patch.try_get_global(name)
if try_result.exists: return try_result.result
if _global_variables.has(name):
return _global_variables[name]
if self._default_global_variables != null:
if self._default_global_variables.has(name):
return self._default_global_variables[name]
var list_item_value: InkListValue = _list_defs_origin.find_single_item_list_with_name(name)
if list_item_value:
return list_item_value
var_value = _callstack.get_temporary_variable_with_name(name, context_index)
return var_value
# (InkVariablePointerValue) -> InkObject
func value_at_variable_pointer(pointer: InkVariablePointerValue) -> InkObject:
return get_variable_with_name(pointer.variable_name, pointer.context_index)
# (InkVariableAssignment, InkObject) -> void
func assign(var_ass: InkVariableAssignment, value: InkObject) -> void:
var name: String = var_ass.variable_name
var context_index: int = -1
var set_global: bool = false
if var_ass.is_new_declaration:
set_global = var_ass.is_global
else:
set_global = global_variable_exists_with_name(name)
if var_ass.is_new_declaration:
var var_pointer: InkVariablePointerValue = InkUtils.as_or_null(value, "VariablePointerValue")
if var_pointer:
var fully_resolved_variable_pointer: InkObject = resolve_variable_pointer(var_pointer)
value = fully_resolved_variable_pointer
else:
var existing_pointer: InkVariablePointerValue = null # VariablePointerValue
var first_time: bool = true
while existing_pointer || first_time:
first_time = false
existing_pointer = InkUtils.as_or_null(
get_raw_variable_with_name(name, context_index),
"VariablePointerValue"
)
if existing_pointer:
name = existing_pointer.variable_name
context_index = existing_pointer.context_index
set_global = (context_index == 0)
if set_global:
set_global(name, value)
else:
_callstack.set_temporary_variable(name, value, var_ass.is_new_declaration, context_index)
# () -> void
func snapshot_default_globals():
_default_global_variables = _global_variables.duplicate()
# (InkObject, InkObject) -> void
func retain_list_origins_for_assignment(old_value, new_value) -> void:
var old_list: InkListValue = InkUtils.as_or_null(old_value, "ListValue")
var new_list: InkListValue = InkUtils.as_or_null(new_value, "ListValue")
if old_list && new_list && new_list.value.size() == 0:
new_list.value.set_initial_origin_names(old_list.value.origin_names)
# (String, InkObject) -> void
func set_global(variable_name: String, value: InkObject) -> void:
var old_value = null # InkObject
# Slightly different structure from upstream, since we can't use
# try_get_global in the conditional.
if patch != null:
var patch_value: InkTryGetResult = patch.try_get_global(variable_name)
if patch_value.exists:
old_value = patch_value.result
if old_value == null:
if self._global_variables.has(variable_name):
old_value = self._global_variables[variable_name]
InkListValue.retain_list_origins_for_assignment(old_value, value)
if patch != null:
self.patch.set_global(variable_name, value)
else:
self._global_variables[variable_name] = value
if !value.equals(old_value):
if _batch_observing_variable_changes:
if patch != null:
patch.add_changed_variable(variable_name)
elif self._changed_variables_for_batch_obs != null:
_changed_variables_for_batch_obs.append(variable_name)
else:
emit_signal("variable_changed", variable_name, value)
# (VariablePointerValue) -> VariablePointerValue
func resolve_variable_pointer(var_pointer: InkVariablePointerValue) -> InkVariablePointerValue:
var context_index: int = var_pointer.context_index
if context_index == -1:
context_index = get_context_index_of_variable_named(var_pointer.variable_name)
var value_of_variable_pointed_to = get_raw_variable_with_name(
var_pointer.variable_name, context_index
)
var double_redirection_pointer: InkVariablePointerValue = InkUtils.as_or_null(
value_of_variable_pointed_to, "VariablePointerValue"
)
if double_redirection_pointer:
return double_redirection_pointer
else:
return InkVariablePointerValue.new_with_context(var_pointer.variable_name, context_index)
# ############################################################################ #
# (String) -> int
func get_context_index_of_variable_named(var_name):
if global_variable_exists_with_name(var_name):
return 0
return _callstack.current_element_index
# Dictionary<String, InkObject>
var _global_variables: Dictionary
var _default_global_variables = null # Dictionary<String, InkObject>
var _callstack: InkCallStack
var _changed_variables_for_batch_obs: InkStringSet = null
var _list_defs_origin: InkListDefinitionsOrigin
# ############################################################################ #
# GDScript extra methods
# ############################################################################ #
func is_ink_class(type: String) -> bool:
return type == "VariableState" || super.is_ink_class(type)
func get_ink_class() -> String:
return "VariableState"
# ############################################################################ #
# Static Properties
# ############################################################################ #
var StaticJSON: InkStaticJSON:
get: return self._ink_runtime.json
var _ink_runtime:
get: return _weak_ink_runtime.get_ref()
var _weak_ink_runtime: WeakRef
func find_static_objects(ink_runtime = null):
if ink_runtime != null:
_weak_ink_runtime = weakref(ink_runtime)
return
var runtime = Engine.get_main_loop().root.get_node("__InkRuntime")
InkUtils.__assert__(
runtime != null,
"[InkVariableStates] Could not retrieve 'InkRuntime' singleton from the scene tree."
)
_weak_ink_runtime = weakref(runtime)