# 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 # 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) -> 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 var _global_variables: Dictionary var _default_global_variables = null # Dictionary 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)