# warning-ignore-all:shadowed_variable # warning-ignore-all:unused_class_variable # ############################################################################ # # 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 InkStoryState var ValueType = preload("res://addons/inkgd/runtime/values/value_type.gd").ValueType var InkPointer = preload("res://addons/inkgd/runtime/structs/pointer.gd") var InkPath = preload("res://addons/inkgd/runtime/ink_path.gd") # ############################################################################ # var InkValue = load("res://addons/inkgd/runtime/values/value.gd") var InkStringValue = load("res://addons/inkgd/runtime/values/string_value.gd") var InkSimpleJSON = preload("res://addons/inkgd/runtime/simple_json.gd") var InkStatePatch = preload("res://addons/inkgd/runtime/state_patch.gd") var InkCallStack = load("res://addons/inkgd/runtime/callstack.gd") var InkVariablesState = load("res://addons/inkgd/runtime/variables_state.gd") var InkFlow = load("res://addons/inkgd/runtime/flow.gd") # ############################################################################ # # Self-reference # ############################################################################ # static func InkStoryState() -> GDScript: return load("res://addons/inkgd/runtime/story_state.gd") as GDScript # ############################################################################ # const INK_SAVE_STATE_VERSION: int = 10 const MIN_COMPATIBLE_LOAD_VERSION: int = 8 # ############################################################################ # signal on_did_load_state() # ############################################################################ # func to_json() -> String: var writer: InkSimpleJSON.Writer = InkSimpleJSON.Writer.new() write_json(writer) return writer._to_string() func load_json(json: String) -> void: var jobject: Dictionary = InkSimpleJSON.text_to_dictionary(json) load_json_obj(jobject) emit_signal("on_did_load_state") func visit_count_at_path_string(path_string: String) -> int: if self._patch != null: var path = InkPath.new_with_components_string(path_string) var container: InkContainer = self.story.content_at_path(path).container if container == null: InkUtils.throw_exception("Content at path not found: %s" % path_string) return 0 var visit_count: InkTryGetResult = self._patch.try_get_visit_count(container) if visit_count.exists: return visit_count.result if self._visit_counts.has(path_string): return self._visit_counts[path_string] return 0 func visit_count_for_container(container: InkContainer) -> int: if !container.visits_should_be_counted: self.story.error( "Read count for target (%s - on %s) " % [container.name, container.debugMetadata] + "unknown. The story may need to be compiled with countAllVisits flag (-c)." ) return 0 var count: int = 0 if self._patch != null: var visit_count: InkTryGetResult = self._patch.try_get_visit_count(container) if visit_count.exists: return visit_count.result var container_path_str: String = container.path._to_string() if self._visit_counts.has(container_path_str): count = self._visit_counts[container_path_str] return count func increment_visit_count_for_container(container: InkContainer) -> void: if self._patch != null: var curr_count: int = visit_count_for_container(container) curr_count += 1 self._patch.set_visit_count(container, curr_count) return var count: int = 0 var container_path_str: String = container.path._to_string() if self._visit_counts.has(container_path_str): count = self._visit_counts[container_path_str] count += 1 self._visit_counts[container_path_str] = count func record_turn_index_visit_to_container(container: InkContainer) -> void: if self._patch != null: self._patch.set_turn_index(container, self.current_turn_index) return var container_path_str: String = container.path._to_string() self._turn_indices[container_path_str] = self.current_turn_index # (InkContainer) -> int func turns_since_for_container(container: InkContainer) -> int: if !container.turn_index_should_be_counted: self.story.error( "TURNS_SINCE() for target (%s - on %s) " \ % [container.name, container.debugMetadata] + "unknown. The story may need to be compiled with countAllVisits flag (-c)." ) return 0 if self._patch != null: var turn_index: InkTryGetResult = self._patch.try_get_turn_index(container) if turn_index.exists: return self.current_turn_index - turn_index.result var container_path_str: String = container.path._to_string() if self._turn_indices.has(container_path_str): return self.current_turn_index - self._turn_indices[container_path_str] else: return -1 var callstack_depth: int: get: return self.callstack.depth var output_stream: Array: # Array get: return self._current_flow.output_stream var current_choices: Array: # Array get: if self.can_continue: return [] return self._current_flow.current_choices var generated_choices: Array: # Array get: return self._current_flow.current_choices # Array var current_errors = null # Array var current_warnings = null # InkVariablesState var variables_state var callstack: InkCallStack: get = get_callstack func get_callstack() -> InkCallStack: return self._current_flow.callstack # Array var evaluation_stack: Array # Pointer var diverted_pointer: InkPointer = InkPointer.null_pointer var current_turn_index: int = 0 var story_seed: int = 0 var previous_random: int = 0 var did_safe_exit: bool = false var story : get = get_story func get_story(): return _story.get_ref() var _story = WeakRef.new() # String? var current_path_string : get = get_current_path_string func get_current_path_string(): var pointer = self.current_pointer if pointer.is_null: return null else: return pointer.path._to_string() var current_pointer: InkPointer: get = get_current_pointer, set = set_current_pointer func get_current_pointer() -> InkPointer: var pointer = self.callstack.current_element.current_pointer return self.callstack.current_element.current_pointer func set_current_pointer(value: InkPointer): var current_element = self.callstack.current_element current_element.current_pointer = value var previous_pointer: InkPointer: get = get_previous_pointer, set = set_previous_pointer func get_previous_pointer() -> InkPointer: return self.callstack.current_thread.previous_pointer func set_previous_pointer(value: InkPointer): var current_thread = self.callstack.current_thread current_thread.previous_pointer = value var can_continue: bool: get = get_can_continue func get_can_continue() -> bool: return !self.current_pointer.is_null && !self.has_error var has_error: bool: get = get_has_error func get_has_error() -> bool: return self.current_errors != null && self.current_errors.size() > 0 var has_warning: bool: get = get_has_warning func get_has_warning() -> bool: return self.current_warnings != null && self.current_warnings.size() > 0 var current_text: String: get = get_current_text func get_current_text(): if self._output_stream_text_dirty: var _str := "" var in_tag := false for output_obj in self.output_stream: var text_content = InkUtils.as_or_null(output_obj, "StringValue") if !in_tag && text_content != null: _str += text_content.value else: var control_command = InkUtils.as_or_null(output_obj, "ControlCommand") if control_command != null: if control_command.command_type == InkControlCommand.CommandType.BEGIN_TAG: in_tag = true elif control_command.command_type == InkControlCommand.CommandType.END_TAG: in_tag = false self._current_text = self.clean_output_whitespace(_str) self._output_stream_text_dirty = false return self._current_text var _current_text: String = "" # (String) -> String func clean_output_whitespace(str_to_clean: String) -> String: var _str: String = "" var current_whitespace_start: int = -1 var start_of_line: int = 0 var i: int = 0 while(i < str_to_clean.length()): var c: String = str_to_clean[i] var is_inline_whitespace: bool = (c == " " || c == "\t") if is_inline_whitespace && current_whitespace_start == -1: current_whitespace_start = i if !is_inline_whitespace: if (c != "\n" && current_whitespace_start > 0 && current_whitespace_start != start_of_line): _str += " " current_whitespace_start = -1 if c == "\n": start_of_line = i + 1 if !is_inline_whitespace: _str += c i += 1 return _str # Array var current_tags: Array: get = get_current_tags func get_current_tags(): if self._output_stream_tags_dirty: self._current_tags = [] var in_tag := false var sb := "" for output_obj in self.output_stream: var control_command = InkUtils.as_or_null(output_obj, "ControlCommand") if control_command != null: if control_command.command_type == InkControlCommand.CommandType.BEGIN_TAG: if in_tag && sb.length() > 0: var txt = self.clean_output_whitespace(sb) self._current_tags.append(txt) sb = "" in_tag = true elif control_command.command_type == InkControlCommand.CommandType.END_TAG: if sb.length() > 0: var txt = self.clean_output_whitespace(sb) self._current_tags.append(txt) sb = "" in_tag = false elif in_tag: var str_val = InkUtils.as_or_null(output_obj, "StringValue") if str_val != null: sb += str_val.value else: var tag = InkUtils.as_or_null(output_obj, "Tag") if tag != null && tag.text != null && !tag.text.is_empty(): self._current_tags.append(tag.text) if !sb.is_empty(): var txt = self.clean_output_whitespace(sb) self._current_tags.append(txt) sb = "" self._output_stream_tags_dirty = false return self._current_tags # Array var _current_tags: Array = [] var current_flow_name: String: get = get_current_flow_name func get_current_flow_name() -> String: return self._current_flow.name var current_flow_is_default_flow: bool: get = get_current_flow_is_default_flow func get_current_flow_is_default_flow() -> bool: return self._current_flow.name == DEFAULT_FLOW_NAME var alive_flow_names: Array: get = get_alive_flow_names func get_alive_flow_names() -> Array: if self._alive_flow_names_dirty: self._alive_flow_names = [] if self._named_flows != null: for flow_name in self._named_flows.keys(): if flow_name != DEFAULT_FLOW_NAME: self._alive_flow_names.append(flow_name) self._alive_flow_names_dirty = false return self._alive_flow_names var _alive_flow_names: Array = [] var in_expression_evaluation: bool: get: return self.callstack.current_element.in_expression_evaluation set(value): var current_element = self.callstack.current_element current_element.in_expression_evaluation = value # (InkStory) -> InkStoryState func _init(story, ink_runtime = null): find_static_objects(ink_runtime) self._story = weakref(story) self._current_flow = InkFlow.new_with_name(DEFAULT_FLOW_NAME, story, self.StaticJSON) self.output_stream_dirty() self._alive_flow_names_dirty = true self.evaluation_stack = [] self.variables_state = InkVariablesState.new(self.callstack, self.story.list_definitions, self._ink_runtime) self._visit_counts = {} self._turn_indices = {} self.current_turn_index = -1 randomize() self.story_seed = randi() % 100 self.previous_random = 0 self.go_to_start() func go_to_start() -> void: var current_element = self.callstack.current_element current_element.current_pointer = InkPointer.start_of(self.story.main_content_container) func switch_flow_internal(flow_name: String) -> void: if flow_name == null: InkUtils.throw_exception("Must pass a non-null string to Story.SwitchFlow") if self._named_flows == null: self._named_flows = {} # Dictionary self._named_flows[DEFAULT_FLOW_NAME] = self._current_flow if flow_name == self._current_flow.name: return var flow if self._named_flows.has(flow_name): flow = self._named_flows[flow_name] else: flow = InkFlow.new_with_name(flow_name, self.story, self.StaticJSON) self._named_flows[flow_name] = flow self._alive_flow_names_dirty = true self._current_flow = flow self.variables_state.callstack = self._current_flow.callstack self.output_stream_dirty() func switch_to_default_flow_internal() -> void: if self._named_flows == null: return self.switch_flow_internal(DEFAULT_FLOW_NAME) func remove_flow_internal(flow_name: String) -> void: if flow_name == null: InkUtils.throw_exception("Must pass a non-null string to Story.DestroyFlow") return if flow_name == DEFAULT_FLOW_NAME: InkUtils.throw_exception("Cannot destroy default flow") return if self._current_flow.name == flow_name: self.switch_to_default_flow_internal() self._named_flows.erase(flow_name) self._alive_flow_names_dirty = true # () -> InkStoryState func copy_and_start_patching(): var copy = InkStoryState().new(self.story) copy._patch = InkStatePatch.new(self._patch) copy._current_flow.name = self._current_flow.name copy._current_flow.callstack = InkCallStack.new(self._current_flow.callstack, self.StaticJSON) copy._current_flow.current_choices += self._current_flow.current_choices copy._current_flow.output_stream += self._current_flow.output_stream copy.output_stream_dirty() if self._named_flows != null: copy._named_flows = {} # Dictionary for named_flow_key in self._named_flows.keys(): var named_flow_value = self._named_flows[named_flow_key] copy._named_flows[named_flow_key] = named_flow_value copy._named_flows[self._current_flow.name] = copy._current_flow copy._alive_flow_names_dirty = true if self.has_error: copy.current_errors = [] # Array copy.current_errors += self.current_errors if self.has_warning: copy.current_warnings = [] # Array copy.current_warnings += self.current_warnings copy.variables_state = variables_state copy.variables_state.callstack = copy.callstack copy.variables_state.patch = copy._patch copy.evaluation_stack += self.evaluation_stack if !diverted_pointer.is_null: copy.diverted_pointer = self.diverted_pointer copy.previous_pointer = self.previous_pointer copy._visit_counts = self._visit_counts copy._turn_indices = self._turn_indices copy.current_turn_index = self.current_turn_index copy.story_seed = self.story_seed copy.previous_random = self.previous_random copy.did_safe_exit = self.did_safe_exit return copy func restore_after_patch() -> void: self.variables_state.callstack = self.callstack self.variables_state.patch = self._patch func apply_any_patch() -> void: if self._patch == null: return self.variables_state.apply_patch() for path_to_count_key in self._patch.visit_counts: apply_count_changes(path_to_count_key, self._patch.visit_counts[path_to_count_key], true) for path_to_index_key in self._patch.turn_indices: apply_count_changes(path_to_index_key, self._patch.turn_indices[path_to_index_key], false) self._patch = null func apply_count_changes(container: InkContainer, new_count: int, is_visit: bool) -> void: var counts = self._visit_counts if is_visit else self._turn_indices counts[container.path._to_string()] = new_count func write_json(writer: InkSimpleJSON.Writer) -> void: writer.write_object_start() writer.write_property_start("flows") writer.write_object_start() if self._named_flows != null: for named_flow_key in self._named_flows.keys(): var named_flow_value = self._named_flows[named_flow_key] writer.write_property(named_flow_key, Callable(named_flow_value, "write_json")) else: writer.write_property(self._current_flow.name, Callable(self._current_flow, "write_json")) writer.write_object_end() writer.write_property_end() writer.write_property("currentFlowName", self._current_flow.name) writer.write_property("variablesState", Callable(self.variables_state, "write_json")) writer.write_property("evalStack", Callable(self, "_anonymous_write_property_eval_stack")) if !self.diverted_pointer.is_null: writer.write_property("currentDivertTarget", self.diverted_pointer.path.components_string) writer.write_property("visitCounts", Callable(self, "_anonymous_write_property_visit_counts")) writer.write_property("turnIndices", Callable(self, "_anonymous_write_property_turn_indices")) writer.write_property("turnIdx", self.current_turn_index) writer.write_property("storySeed", self.story_seed) writer.write_property("previousRandom", self.previous_random) writer.write_property("inkSaveVersion", INK_SAVE_STATE_VERSION) writer.write_property("inkFormatVersion", self.story.INK_VERSION_CURRENT) writer.write_object_end() func load_json_obj(jobject: Dictionary) -> void: var jsave_version = null # Variant if !jobject.has("inkSaveVersion"): InkUtils.throw_exception("ink save format incorrect, can't load.") return else: jsave_version = int(jobject["inkSaveVersion"]) if jsave_version < MIN_COMPATIBLE_LOAD_VERSION: InkUtils.throw_exception( "Ink save format isn't compatible with the current version (saw " + "'%d', but minimum is %d " % [jsave_version, MIN_COMPATIBLE_LOAD_VERSION] + "), so can't load." ) return if jobject.has("flows"): var flows_obj_dict = jobject["flows"] if flows_obj_dict.size() == 1: self._named_flows = null elif self._named_flows == null: self._named_flows = {} # Dictionary else: self._named_flows.clear() for named_flow_obj_key in flows_obj_dict.keys(): var name = named_flow_obj_key var flow_obj = flows_obj_dict[named_flow_obj_key] var flow = InkFlow.new_with_name_and_jobject(name, self.story, flow_obj, self.StaticJSON) if flows_obj_dict.size() == 1: self._current_flow = InkFlow.new_with_name_and_jobject(name, self.story, flow_obj, self.StaticJSON) else: self._named_flows[name] = flow if self._named_flows != null && self._named_flows.size() > 1: var curr_flow_name = jobject["currentFlowName"] self._current_flow = self._named_flows[curr_flow_name] else: self._named_flows = null self._current_flow.name = DEFAULT_FLOW_NAME self._current_flow.callstack.set_json_token(jobject["callstackThreads"], self.story) self._current_flow.output_stream = self.StaticJSON.jarray_to_runtime_obj_list(jobject["outputStream"]) self._current_flow.current_choices = self.StaticJSON.jarray_to_runtime_obj_list(jobject["currentChoices"]) var jchoice_threads_obj = jobject["choiceThreads"] if jobject.has("choiceThreads") else null self._current_flow.load_flow_choice_threads(jchoice_threads_obj, self.story) self.output_stream_dirty() self._alive_flow_names_dirty = true self.variables_state.set_json_token(jobject["variablesState"]) self.variables_state.callstack = self._current_flow.callstack self.evaluation_stack = self.StaticJSON.jarray_to_runtime_obj_list(jobject["evalStack"]) if jobject.has("currentDivertTarget"): var current_divert_target_path = jobject["currentDivertTarget"] var divert_path = InkPath.new_with_components_string(current_divert_target_path._to_string()) self.diverted_pointer = self.story.pointer_at_path(divert_path) self._visit_counts = self.StaticJSON.jobject_to_int_dictionary(jobject["visitCounts"]) self._turn_indices = self.StaticJSON.jobject_to_int_dictionary(jobject["turnIndices"]) self.current_turn_index = int(jobject["turnIdx"]) self.story_seed = int(jobject["storySeed"]) # inkjs bug if jobject.has("previousRandom"): self.previous_random = int(jobject["previousRandom"]) else: self.previous_random = 0 # () -> void func reset_errors() -> void: self.current_errors = null self.current_warnings = null # (Array?) -> void func reset_output(objs = null) -> void: self.output_stream.clear() if objs != null: self.output_stream += objs self.output_stream_dirty() func push_to_output_stream(obj: InkObject) -> void: var text = InkUtils.as_or_null(obj, "StringValue") if text: var list_text = self.try_splitting_head_tail_whitespace(text) if list_text != null: for text_obj in list_text: self.push_to_output_stream_individual(text_obj) self.output_stream_dirty() return self.push_to_output_stream_individual(obj) self.output_stream_dirty() func pop_from_output_stream(count: int) -> void: InkUtils.remove_range(self.output_stream, self.output_stream.size() - count, count) self.output_stream_dirty() func try_splitting_head_tail_whitespace(single: InkStringValue): # Array var _str = single.value var head_first_newline_idx = -1 var head_last_newline_idx = -1 var i = 0 while (i < _str.length()): var c = _str[i] if (c == "\n"): if head_first_newline_idx == -1: head_first_newline_idx = i head_last_newline_idx = i elif c == " " || c == "\t": i += 1 continue else: break i += 1 var tail_last_newline_idx = -1 var tail_first_newline_idx = -1 var j = _str.length() - 1 while (j >= 0): var c = _str[j] if (c == "\n"): if tail_last_newline_idx == -1: tail_last_newline_idx = j tail_first_newline_idx = j elif c == ' ' || c == '\t': j -= 1 continue else: break j -= 1 if head_first_newline_idx == -1 && tail_last_newline_idx == -1: return null var list_texts = [] # Array var inner_str_start = 0 var inner_str_end = _str.length() if head_first_newline_idx != -1: if head_first_newline_idx > 0: var leading_spaces = InkStringValue.new_with(_str.substr(0, head_first_newline_idx)) list_texts.append(leading_spaces) list_texts.append(InkStringValue.new_with("\n")) inner_str_start = head_last_newline_idx + 1 if tail_last_newline_idx != -1: inner_str_end = tail_first_newline_idx if inner_str_end > inner_str_start: var inner_str_text = _str.substr(inner_str_start, inner_str_end - inner_str_start) list_texts.append(InkStringValue.new(inner_str_text)) if tail_last_newline_idx != -1 && tail_first_newline_idx > head_last_newline_idx: list_texts.append(InkStringValue.new("\n")) if tail_last_newline_idx < _str.length() - 1: var num_spaces = (_str.length() - tail_last_newline_idx) - 1 var trailing_spaces = InkStringValue.new(_str.substr(tail_last_newline_idx + 1, num_spaces)) list_texts.append(trailing_spaces) return list_texts func push_to_output_stream_individual(obj: InkObject) -> void: var glue = InkUtils.as_or_null(obj, "Glue") var text = InkUtils.as_or_null(obj, "StringValue") var include_in_output = true if glue: self.trim_newlines_from_output_stream() include_in_output = true elif text: var function_trim_index = -1 var curr_el = self.callstack.current_element if curr_el.type == Ink.PushPopType.FUNCTION: function_trim_index = curr_el.function_start_in_ouput_stream var glue_trim_index = -1 var i = self.output_stream.size() - 1 while (i >= 0): var o = self.output_stream[i] var c = InkUtils.as_or_null(o, "ControlCommand") var g = InkUtils.as_or_null(o, "Glue") if g: glue_trim_index = i break elif c && c.command_type == InkControlCommand.CommandType.BEGIN_STRING: if i >= function_trim_index: function_trim_index = -1 break i -= 1 var trim_index = -1 if glue_trim_index != -1 && function_trim_index != -1: trim_index = min(function_trim_index, glue_trim_index) elif glue_trim_index != -1: trim_index = glue_trim_index else: trim_index = function_trim_index if trim_index != -1: if text.is_newline: include_in_output = false elif text.is_non_whitespace: if glue_trim_index > -1: self.remove_existing_glue() if function_trim_index > -1: var callstack_elements = self.callstack.elements var j = callstack_elements.size() - 1 while j >= 0: var el = callstack_elements[j] if el.type == Ink.PushPopType.FUNCTION: el.function_start_in_ouput_stream = -1 else: break j -= 1 elif text.is_newline: if self.output_stream_ends_in_newline || !self.output_stream_contains_content: include_in_output = false if include_in_output: self.output_stream.append(obj) self.output_stream_dirty() func trim_newlines_from_output_stream() -> void: var remove_whitespace_from = -1 # int var i = self.output_stream.size() - 1 while i >= 0: var obj = self.output_stream[i] var cmd = InkUtils.as_or_null(obj, "ControlCommand") var txt = InkUtils.as_or_null(obj, "StringValue") if cmd || (txt && txt.is_non_whitespace): break elif txt && txt.is_newline: remove_whitespace_from = i i -= 1 if remove_whitespace_from >= 0: i = remove_whitespace_from while i < self.output_stream.size(): var text = InkUtils.as_or_null(self.output_stream[i], "StringValue") if text: self.output_stream.remove_at(i) else: i += 1 self.output_stream_dirty() func remove_existing_glue() -> void: var i = self.output_stream.size() - 1 while (i >= 0): var c = self.output_stream[i] if InkUtils.is_ink_class(c, "Glue"): self.output_stream.remove_at(i) elif InkUtils.is_ink_class(c, "ControlCommand"): break i -= 1 self.output_stream_dirty() var output_stream_ends_in_newline: bool: get = get_output_stream_ends_in_newline func get_output_stream_ends_in_newline() -> bool: if self.output_stream.size() > 0: var i = self.output_stream.size() - 1 while (i >= 0): var obj = self.output_stream[i] if InkUtils.is_ink_class(obj, "ControlCommand"): break var text = InkUtils.as_or_null(self.output_stream[i], "StringValue") if text: if text.is_newline: return true elif text.is_non_whitespace: break i -= 1 return false var output_stream_contains_content: bool: get = get_output_stream_contains_content func get_output_stream_contains_content() -> bool: for content in self.output_stream: if InkUtils.is_ink_class(content, "StringValue"): return true return false var in_string_evaluation: bool: get = get_in_string_evaluation func get_in_string_evaluation() -> bool: var i = self.output_stream.size() - 1 while (i >= 0): var cmd = InkUtils.as_or_null(self.output_stream[i], "ControlCommand") if cmd && cmd.command_type == InkControlCommand.CommandType.BEGIN_STRING: return true i -= 1 return false # (InkObject) -> void func push_evaluation_stack(obj: InkObject) -> void: var list_value = InkUtils.as_or_null(obj, "ListValue") if list_value: var raw_list = list_value.value if raw_list.origin_names != null: if raw_list.origins == null: raw_list.origins = [] # Array raw_list.origins.clear() for n in raw_list.origin_names: var def: InkTryGetResult = self.story.list_definitions.try_list_get_definition(n) if raw_list.origins.find(def.result) < 0: raw_list.origins.append(def.result) self.evaluation_stack.append(obj) # () -> InkObject func peek_evaluation_stack() -> InkObject: return self.evaluation_stack.back() # This method combines both methods found in upstream. # (int) -> InkObject | Array func pop_evaluation_stack(number_of_objects: int = -1): if number_of_objects == -1: # This code raises an exception to match the behaviour of upstream. # `pop_back` doesn't raise an error on an empty collection. if self.evaluation_stack.size() == 0: InkUtils.throw_exception("trying to pop an empty evaluation stack") else : return self.evaluation_stack.pop_back() if number_of_objects > self.evaluation_stack.size(): InkUtils.throw_exception("trying to pop too many objects") return [] var popped = InkUtils.get_range(self.evaluation_stack, self.evaluation_stack.size() - number_of_objects, number_of_objects) InkUtils.remove_range( self.evaluation_stack, self.evaluation_stack.size() - number_of_objects, number_of_objects ) return popped # () -> void func force_end() -> void: self.callstack.reset() self._current_flow.current_choices.clear() self.current_pointer = InkPointer.null_pointer self.previous_pointer = InkPointer.null_pointer self.did_safe_exit = true func trim_whitespace_from_function_end() -> void: assert(self.callstack.current_element.type == Ink.PushPopType.FUNCTION) var function_start_point = self.callstack.current_element.function_start_in_ouput_stream if function_start_point == -1: function_start_point = 0 var i = self.output_stream.size() - 1 while (i >= function_start_point): var obj = self.output_stream[i] var txt = InkUtils.as_or_null(obj, "StringValue") var cmd = InkUtils.as_or_null(obj, "ControlCommand") if !txt: i -= 1 continue if cmd: break if txt.is_newline || txt.is_inline_whitespace: self.output_stream.remove_at(i) self.output_stream_dirty() else: break i -= 1 # (Ink.PushPopType?) -> void func pop_callstack(pop_type = null) -> void: if (self.callstack.current_element.type == Ink.PushPopType.FUNCTION): self.trim_whitespace_from_function_end() self.callstack.pop(pop_type) # (InkPath, bool) -> void func set_chosen_path(path: InkPath, incrementing_turn_index: bool) -> void: self._current_flow.current_choices.clear() var new_pointer = self.story.pointer_at_path(path) if !new_pointer.is_null && new_pointer.index == -1: new_pointer = InkPointer.new(new_pointer.container, 0) self.current_pointer = new_pointer if incrementing_turn_index: self.current_turn_index += 1 # (InkContainer, Array?) -> void func start_function_evaluation_from_game(func_container: InkContainer, arguments) -> void: self.callstack.push(Ink.PushPopType.FUNCTION_EVALUATION_FROM_GAME, self.evaluation_stack.size()) var current_element = self.callstack.current_element current_element.current_pointer = InkPointer.start_of(func_container) self.pass_arguments_to_evaluation_stack(arguments) # (Array?) -> void func pass_arguments_to_evaluation_stack(arguments) -> void: if arguments != null: var i = 0 while (i < arguments.size()): if !(arguments[i] is int || arguments[i] is float || arguments[i] is String || arguments[i] is bool || ((arguments[i] is Object) && arguments[i].is_ink_class("InkList"))): InkUtils.throw_argument_exception( "ink arguments when calling EvaluateFunction / " + "ChoosePathStringWithParameters must be int, " + "float, string, bool or InkList. Argument was " + ("null" if arguments[i] == null else InkUtils.typename_of(arguments[i])) ) return push_evaluation_stack(InkValue.create(arguments[i])) i += 1 # () -> bool func try_exit_function_evaluation_from_game() -> bool: if self.callstack.current_element.type == Ink.PushPopType.FUNCTION_EVALUATION_FROM_GAME: self.current_pointer = InkPointer.null_pointer self.did_safe_exit = true return true return false # () -> Variant func complete_function_evaluation_from_game(): if self.callstack.current_element.type != Ink.PushPopType.FUNCTION_EVALUATION_FROM_GAME: InkUtils.throw_exception( "Expected external function evaluation to be complete. Stack trace: %s" % \ self.callstack_trace ) return null var original_evaluation_stack_height = self.callstack.current_element.evaluation_stack_height_when_pushed var returned_obj = null while (self.evaluation_stack.size() > original_evaluation_stack_height): var popped_obj = self.pop_evaluation_stack() if returned_obj == null: returned_obj = popped_obj self.pop_callstack(Ink.PushPopType.FUNCTION_EVALUATION_FROM_GAME) if returned_obj: if InkUtils.is_ink_class(returned_obj, "Void"): return null var return_val = InkUtils.as_or_null(returned_obj, "Value") if return_val.value_type == ValueType.DIVERT_TARGET: return return_val.value_object._to_string() return return_val.value_object return null func add_error(message: String, is_warning: bool) -> void: if !is_warning: if self.current_errors == null: self.current_errors = [] # Array self.current_errors.append(message) else: if self.current_warnings == null: self.current_warnings = [] # Array self.current_warnings.append(message) func output_stream_dirty() -> void: self._output_stream_text_dirty = true self._output_stream_tags_dirty = true # ############################################################################ # # Dictionary var _visit_counts: Dictionary # Dictionary var _turn_indices: Dictionary var _output_stream_text_dirty: bool = true # bool var _output_stream_tags_dirty: bool = true # bool var _patch # StatePatch? var _current_flow = null # Flow? var _named_flows = null # Dictionary? const DEFAULT_FLOW_NAME: String = "DEFAULT_FLOW" # String var _alive_flow_names_dirty := true # C# Actions & Delegates ##################################################### # func _anonymous_write_property_eval_stack(writer) -> void: self.StaticJSON.write_list_runtime_objs(writer, self.evaluation_stack) func _anonymous_write_property_visit_counts(writer) -> void: self.StaticJSON.write_int_dictionary(writer, self._visit_counts) func _anonymous_write_property_turn_indices(writer) -> void: self.StaticJSON.write_int_dictionary(writer, self._turn_indices) # ############################################################################ # # GDScript extra methods # ############################################################################ # func is_ink_class(type: String) -> bool: return type == "StoryState" || super.is_ink_class(type) func get_ink_class() -> String: return "StoryState" # ############################################################################ # 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, "[StoryState] Could not retrieve 'InkRuntime' singleton from the scene tree." ) _weak_ink_runtime = weakref(runtime)