This commit is contained in:
Gerard Gascón 2025-04-24 17:23:34 +02:00
commit b99855351d
434 changed files with 50357 additions and 0 deletions

View file

@ -0,0 +1,457 @@
# warning-ignore-all:shadowed_variable
# warning-ignore-all:unused_class_variable
# ############################################################################ #
# 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 InkCallStack
# ############################################################################ #
class Element extends InkBase:
var current_pointer: InkPointer = InkPointer.null_pointer
var in_expression_evaluation: bool = false
var temporary_variables = null # Dictionary<String, InkObject>
var type: int = 0 # Ink.PushPopType
var evaluation_stack_height_when_pushed: int = 0
var function_start_in_ouput_stream: int = 0
# (Ink.PushPopType, Pointer, bool) -> InkElement
func _init(type, pointer, in_expression_evaluation = false):
self.current_pointer = pointer
self.in_expression_evaluation = in_expression_evaluation
self.temporary_variables = {}
self.type = type
# () -> InkElement
func copy():
var copy = Element.new(self.type, self.current_pointer, self.in_expression_evaluation)
copy.temporary_variables = self.temporary_variables.duplicate()
copy.evaluation_stack_height_when_pushed = evaluation_stack_height_when_pushed
copy.function_start_in_ouput_stream = function_start_in_ouput_stream
return copy
# ######################################################################## #
# GDScript extra methods
# ######################################################################## #
func is_ink_class(type):
return type == "CallStack.Element" || super.is_ink_class(type)
func get_ink_class():
return "CallStack.Element"
class InkThread extends InkBase:
var callstack = null # Array<Element>
var thread_index: int = 0 # int
var previous_pointer: InkPointer = InkPointer.null_pointer
func _init(static_json = null):
get_static_json(static_json)
callstack = []
# Dictionary<string, object>, Story
func _init_with(jthread_obj, story_context):
thread_index = int(jthread_obj["threadIndex"])
var jthread_callstack = jthread_obj["callstack"]
for jel_tok in jthread_callstack:
var jelement_obj = jel_tok
var push_pop_type = int(jelement_obj["type"])
var pointer = InkPointer.null_pointer
var current_container_path_str = null
var current_container_path_str_token = null
if jelement_obj.has("cPath"):
current_container_path_str_token = jelement_obj["cPath"]
current_container_path_str = str(current_container_path_str_token)
var thread_pointer_result = story_context.content_at_path(InkPath.new_with_components_string(current_container_path_str))
pointer = InkPointer.new(thread_pointer_result.container, int(jelement_obj["idx"]))
if thread_pointer_result.obj == null:
InkUtils.throw_exception(
"When loading state, internal story location " +
"couldn't be found: '%s'. " % current_container_path_str +
"Has the story changed since this save data was created?"
)
return
elif thread_pointer_result.approximate:
story_context.warning(
"When loading state, exact internal story location " +
"couldn't be found: '%s', so it was" % current_container_path_str +
"approximated to '%s' " + pointer.container.path._to_string() +
"to recover. Has the story changed since this save data was created?"
)
var in_expression_evaluation = bool(jelement_obj["exp"])
var el = Element.new(push_pop_type, pointer, in_expression_evaluation)
var temps
if jelement_obj.has("temp"):
temps = jelement_obj["temp"] # Dictionary<string, object>
el.temporary_variables = self.StaticJSON.jobject_to_dictionary_runtime_objs(temps)
else:
el.temporary_variables.clear()
callstack.append(el)
var prev_content_obj_path
if jthread_obj.has("previousContentObject"):
prev_content_obj_path = str(jthread_obj["previousContentObject"])
var prev_path = InkPath.new_with_components_string(prev_content_obj_path)
self.previous_pointer = story_context.pointer_at_path(prev_path)
# () -> InkThread
func copy():
var copy = InkThread.new(self.StaticJSON)
copy.thread_index = self.thread_index
for e in callstack:
copy.callstack.append(e.copy())
copy.previous_pointer = self.previous_pointer
return copy
# (SimpleJson.Writer) -> void
func write_json(writer):
writer.write_object_start()
writer.write_property_start("callstack")
writer.write_array_start()
for el in self.callstack:
writer.write_object_start()
if !el.current_pointer.is_null:
writer.write_property("cPath", el.current_pointer.container.path.components_string)
writer.write_property("idx", el.current_pointer.index)
writer.write_property("exp", el.in_expression_evaluation)
writer.write_property("type", int(el.type))
if el.temporary_variables.size() > 0:
writer.write_property_start("temp")
self.StaticJSON.write_dictionary_runtime_objs(writer, el.temporary_variables)
writer.write_property_end()
writer.write_object_end()
writer.write_array_end()
writer.write_property_end()
writer.write_property("threadIndex", self.thread_index)
if !self.previous_pointer.is_null:
writer.write_property("previousContentObject", self.previous_pointer.resolve().path._to_string())
writer.write_object_end()
# ######################################################################## #
# GDScript extra methods
# ######################################################################## #
func is_ink_class(type):
return type == "CallStack.InkThread" || super.is_ink_class(type)
func get_ink_class():
return "CallStack.InkThread"
# ######################################################################## #
static func new_with(jthread_obj, story_context, static_json = null):
var thread = InkThread.new(static_json)
thread._init_with(jthread_obj, story_context)
return thread
# ######################################################################## #
var StaticJSON: InkStaticJSON:
get: return _static_json.get_ref()
var _static_json = WeakRef.new()
func get_static_json(static_json = null):
if static_json != null:
_static_json = weakref(static_json)
return
var InkRuntime = Engine.get_main_loop().root.get_node("__InkRuntime")
InkUtils.__assert__(InkRuntime != null,
str("[InkCallStack.InkThread] Could not retrieve 'InkRuntime' singleton from the scene tree."))
_static_json = weakref(InkRuntime.json)
# () -> Array<InkElement>
var elements : get = get_elements
func get_elements():
return self.callstack
# () -> int
var depth : get = get_depth
func get_depth():
return self.elements.size()
# () -> InkElement
var current_element : get = get_current_element
func get_current_element():
var thread = self._threads.back()
var cs = thread.callstack
return cs.back()
# () -> int
var current_element_index : get = get_current_element_index
func get_current_element_index():
return self.callstack.size() - 1
# () -> InkThread
# (InkThread) -> void
var current_thread : get = get_current_thread, set = set_current_thread
func get_current_thread():
return self._threads.back()
func set_current_thread(value):
InkUtils.__assert__(_threads.size() == 1,
"Shouldn't be directly setting the current thread when we have a stack of them")
self._threads.clear()
self._threads.append(value)
# () -> bool
var can_pop : get = get_can_pop
func get_can_pop():
return self.callstack.size() > 1
# (InkStory | CallStack) -> CallStack
func _init(story_context_or_to_copy, static_json = null):
get_static_json(static_json)
if story_context_or_to_copy.is_ink_class("Story"):
var story_context = story_context_or_to_copy
_start_of_root = InkPointer.start_of(story_context.root_content_container)
reset()
elif story_context_or_to_copy.is_ink_class("CallStack"):
var to_copy = story_context_or_to_copy
self._threads = []
for other_thread in to_copy._threads:
self._threads.append(other_thread.copy())
self._thread_counter = to_copy._thread_counter
self._start_of_root = to_copy._start_of_root
# () -> void
func reset():
self._threads = []
self._threads.append(InkThread.new(self.StaticJSON))
self._threads[0].callstack.append(Element.new(Ink.PushPopType.TUNNEL, self._start_of_root))
# (Dictionary<string, object>, InkStory) -> void
func set_json_token(jobject, story_context):
self._threads.clear()
var jthreads = jobject["threads"]
for jthread_tok in jthreads:
var jthread_obj = jthread_tok
var thread = InkThread.new_with(jthread_obj, story_context)
self._threads.append(thread)
self._thread_counter = int(jobject["threadCounter"])
self._start_of_root = InkPointer.start_of(story_context.root_content_container)
# (SimpleJson.Writer) -> void
func write_json(writer):
writer.write_object(Callable(self, "_anonymous_write_json"))
# () -> void
func push_thread():
var new_thread = self.current_thread.copy()
self._thread_counter += 1
new_thread.thread_index = self._thread_counter
self._threads.append(new_thread)
# () -> void
func fork_thread():
var forked_thread = self.current_thread.copy()
self._thread_counter += 1
forked_thread.thread_index = self._thread_counter
return forked_thread
# () -> void
func pop_thread():
if self.can_pop_thread:
self._threads.erase(self.current_thread)
else:
InkUtils.throw_exception("Can't pop thread")
# () -> bool
var can_pop_thread : get = get_can_pop_thread
func get_can_pop_thread():
return _threads.size() > 1 && !self.element_is_evaluate_from_game
# () -> bool
var element_is_evaluate_from_game : get = get_element_is_evaluate_from_game
func get_element_is_evaluate_from_game():
return self.current_element.type == Ink.PushPopType.FUNCTION_EVALUATION_FROM_GAME
# (Ink.PushPopType, int, int) -> void
func push(type, external_evaluation_stack_height = 0, output_stream_length_with_pushed = 0):
var element = Element.new(type, self.current_element.current_pointer, false)
element.evaluation_stack_height_when_pushed = external_evaluation_stack_height
element.function_start_in_ouput_stream = output_stream_length_with_pushed
self.callstack.append(element)
# (Ink.PushPopType | null) -> void
func can_pop_type(type = null):
if !self.can_pop:
return false
if type == null:
return true
return self.current_element.type == type
# (Ink.PushPopType | null) -> void
func pop(type = null):
if can_pop_type(type):
self.callstack.pop_back()
return
else:
InkUtils.throw_exception("Mismatched push/pop in Callstack")
# (String, int) -> InkObject
func get_temporary_variable_with_name(name, context_index = -1) -> InkObject:
if context_index == -1:
context_index = self.current_element_index + 1
var var_value = null
var context_element = self.callstack[context_index - 1]
if context_element.temporary_variables.has(name):
var_value = context_element.temporary_variables[name]
return var_value
else:
return null
# (String, InkObject, bool, int) -> void
func set_temporary_variable(name, value, declare_new, context_index = -1):
if context_index == -1:
context_index = self.current_element_index + 1
var context_element = self.callstack[context_index - 1]
if !declare_new && !context_element.temporary_variables.has(name):
InkUtils.throw_exception("Could not find temporary variable to set: %s" % name)
return
if context_element.temporary_variables.has(name):
var old_value = context_element.temporary_variables[name]
InkListValue.retain_list_origins_for_assignment(old_value, value)
context_element.temporary_variables[name] = value
# (String) -> int
func context_for_variable_named(name):
if self.current_element.temporary_variables.has(name):
return self.current_element_index + 1
else:
return 0
# (int) -> InkThread | null
func thread_with_index(index):
for thread in self._threads:
if thread.thread_index == index:
return thread
return null
var callstack : get = get_callstack
func get_callstack():
return self.current_thread.callstack
var callstack_trace : get = get_callstack_trace
func get_callstack_trace():
var sb = ""
var t = 0
while t < _threads.size():
var thread = _threads[t]
var is_current = (t == _threads.size() - 1)
sb += str("=== THREAD ", str(t + 1), "/", str(_threads.size()), " ",
("(current) " if is_current else "" ), "===\n")
var i = 0
while i < thread.callstack.size():
if thread.callstack[i].type == Ink.PushPopType.FUNCTION:
sb += " [FUNCTION] "
else:
sb += " [TUNNEL] "
var pointer = thread.callstack[i].current_pointer
if !pointer.is_null:
sb += "<SOMEWHERE IN "
sb += pointer.container.path._to_string()
sb += "\n>"
i += 1
t += 1
return sb
var _threads = null # Array<InkThread>
var _thread_counter = 0 # int
var _start_of_root = InkPointer.null_pointer # Pointer
# ############################################################################ #
# GDScript extra methods
# ############################################################################ #
func is_ink_class(type):
return type == "CallStack" || super.is_ink_class(type)
func get_ink_class():
return "CallStack"
# C# Actions & Delegates ##################################################### #
# (SimpleJson.Writer) -> void
func _anonymous_write_json(writer: InkSimpleJSON.Writer) -> void:
writer.write_property_start("threads")
writer.write_array_start()
for thread in self._threads:
thread.write_json(writer)
writer.write_array_end()
writer.write_property_end()
writer.write_property_start("threadCounter")
writer.write(self._thread_counter)
writer.write_property_end()
# ######################################################################## #
var StaticJSON: InkStaticJSON:
get: return _static_json.get_ref()
var _static_json = WeakRef.new()
func get_static_json(static_json = null):
if static_json != null:
_static_json = weakref(static_json)
return
var InkRuntime = Engine.get_main_loop().root.get_node("__InkRuntime")
InkUtils.__assert__(InkRuntime != null,
str("[InkCallStack] Could not retrieve 'InkRuntime' singleton from the scene tree."))
_static_json = weakref(InkRuntime.json)

View file

@ -0,0 +1,24 @@
# ############################################################################ #
# 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.
# ############################################################################ #
class_name Ink
# ############################################################################ #
enum ErrorType {
AUTHOR,
WARNING,
ERROR
}
enum PushPopType {
TUNNEL,
FUNCTION,
FUNCTION_EVALUATION_FROM_GAME
}

View file

@ -0,0 +1,31 @@
# warning-ignore-all:shadowed_variable
# warning-ignore-all:unused_class_variable
# ############################################################################ #
# 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 RefCounted
class_name InkBase
# ############################################################################ #
func equals(_ink_base: InkBase) -> bool:
return false
# ############################################################################ #
# GDScript extra methods
# ############################################################################ #
func is_ink_class(type: String) -> bool:
return type == "InkBase"
func get_ink_class() -> String:
return "InkBase"

View file

@ -0,0 +1,212 @@
# warning-ignore-all:shadowed_variable
# warning-ignore-all:unused_class_variable
# ############################################################################ #
# 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 InkObject
# ############################################################################ #
# Encapsulating parent into a weak ref.
var parent: InkObject:
get:
return self._parent.get_ref()
set(value):
self._parent = weakref(value)
var _parent: WeakRef = WeakRef.new() # InkObject
# ############################################################################ #
var debug_metadata: InkDebugMetadata:
get:
if _debug_metadata == null:
if self.parent:
return self.parent.debug_metadata
return _debug_metadata
set(value):
_debug_metadata = value
var _debug_metadata: InkDebugMetadata = null
# ############################################################################ #
var own_debug_metadata: InkDebugMetadata:
get: return _debug_metadata
# ############################################################################ #
# (InkPath) -> int?
func debug_line_number_of_path(path: InkPath):
if path == null:
return null
var root = self.root_content_container
if root != null:
var target_content := root.content_at_path(path).obj
if target_content:
var dm = target_content.debug_metadata
if dm != null:
return dm.start_line_number
return null
# TODO: Make inspectable
# InkPath
var path: InkPath:
get:
if _path == null:
if self.parent == null:
_path = InkPath.new()
else:
var comps: Array = [] # Stack<Path3D.Component>
var child = self
var container = InkUtils.as_or_null(child.parent, "InkContainer")
while container:
var named_child = InkUtils.as_INamedContent_or_null(child)
if (named_child != null && named_child.has_valid_name):
comps.push_front(InkPath.Component.new(named_child.name))
else:
comps.push_front(InkPath.Component.new(container.content.find(child)))
child = container
container = InkUtils.as_or_null(container.parent, "InkContainer")
_path = InkPath.new_with_components(comps)
return _path
var _path: InkPath = null
func resolve_path(path: InkPath) -> InkSearchResult:
if path.is_relative:
var nearest_container = InkUtils.as_or_null(self, "InkContainer")
if !nearest_container:
InkUtils.__assert__(
self.parent != null,
"Can't resolve relative path because we don't have a parent"
)
nearest_container = InkUtils.as_or_null(self.parent, "InkContainer")
InkUtils.__assert__(nearest_container != null, "Expected parent to be a container")
InkUtils.__assert__(path.get_component(0).is_parent)
path = path.tail
return nearest_container.content_at_path(path)
else:
return self.root_content_container.content_at_path(path)
func convert_path_to_relative(global_path: InkPath) -> InkPath:
var own_path := self.path
var min_path_length: int = min(global_path.length, own_path.length)
var last_shared_path_comp_index: int = -1
var i: int = 0
while i < min_path_length:
var own_comp: InkPath.Component = own_path.get_component(i)
var other_comp: InkPath.Component = global_path.get_component(i)
if own_comp.equals(other_comp):
last_shared_path_comp_index = i
else:
break
i += 1
if last_shared_path_comp_index == -1:
return global_path
var num_upwards_moves: int = (own_path.length - 1) - last_shared_path_comp_index
var new_path_comps: Array = [] # Array<InkPath.Component>
var up = 0
while up < num_upwards_moves:
new_path_comps.append(InkPath.Component.to_parent())
up += 1
var down = last_shared_path_comp_index + 1
while down < global_path.length:
new_path_comps.append(global_path.get_component(down))
down += 1
var relative_path = InkPath.new_with_components(new_path_comps, true)
return relative_path
func compact_path_string(other_path: InkPath) -> String:
var global_path_str
var relative_path_str
if other_path.is_relative:
relative_path_str = other_path.components_string
global_path_str = self.path.path_by_appending_path(other_path).components_string
else:
var relative_path = convert_path_to_relative(other_path)
relative_path_str = relative_path.components_string
global_path_str = other_path.components_string
if (relative_path_str.length() < global_path_str.length()):
return relative_path_str
else:
return global_path_str
# () -> InkContainer
var root_content_container: InkContainer:
get:
var ancestor := self
while (ancestor.parent):
ancestor = ancestor.parent
return InkUtils.as_or_null(ancestor, "InkContainer")
# () -> InkObject
func copy():
InkUtils.throw_exception("Not Implemented: Doesn't support copying")
return null
# (InkObject, InkObject) -> void
func set_child(obj: InkObject, value: InkObject):
if obj:
obj.parent = null
obj = value
if obj:
obj.parent = self
# ############################################################################ #
# GDScript extra methods
# ############################################################################ #
func is_ink_class(type: String) -> bool:
return type == "InkObject" || super.is_ink_class(type)
func get_ink_class() -> String:
return "InkObject"

View file

@ -0,0 +1,58 @@
# warning-ignore-all:unused_class_variable
# ############################################################################ #
# 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 InkObject
class_name InkChoice
# ############################################################################ #
var text: String
var path_string_on_choice: String:
get:
# TODO: handle null case?
return target_path._to_string()
set(value):
target_path = InkPath.new_with_components_string(value)
var source_path = null # String?
var index: int = 0
var target_path: InkPath = null
var thread_at_generation: InkCallStack.InkThread = null
var original_thread_index: int = 0
var is_invisible_default: bool = false
var tags = null # Array<String>?
# ############################################################################ #
# GDScript extra methods
# ############################################################################ #
func is_ink_class(type):
return type == "Choice" || super.is_ink_class(type)
func get_ink_class():
return "Choice"

View file

@ -0,0 +1,122 @@
# warning-ignore-all:shadowed_variable
# warning-ignore-all:unused_class_variable
# ############################################################################ #
# 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 InkObject
class_name InkChoicePoint
# ############################################################################ #
# () -> InkPath
# (InkPath) -> void
var path_on_choice: InkPath:
get:
if self._path_on_choice != null && self._path_on_choice.is_relative:
var choice_target_obj := self.choice_target
if choice_target_obj:
self._path_on_choice = choice_target_obj.path
return _path_on_choice
set(value):
_path_on_choice = value
var _path_on_choice: InkPath = null
# ############################################################################ #
var choice_target: InkContainer:
get:
var cont: InkContainer = resolve_path(self._path_on_choice).container
return cont
# ############################################################################ #
var path_string_on_choice: String:
get:
return compact_path_string(self.path_on_choice)
set(value):
self.path_on_choice = InkPath.new_with_components_string(value)
# ############################################################################ #
var has_condition: bool
var has_start_content: bool
var has_choice_only_content: bool
var once_only: bool
var is_invisible_default: bool
# ############################################################################ #
var flags: int:
get:
var flags: int = 0
if has_condition:
flags |= 1
if has_start_content:
flags |= 2
if has_choice_only_content:
flags |= 4
if is_invisible_default:
flags |= 8
if once_only:
flags |= 16
return flags
set(value):
has_condition = (value & 1) > 0
has_start_content = (value & 2) > 0
has_choice_only_content = (value & 4) > 0
is_invisible_default = (value & 8) > 0
once_only = (value & 16) > 0
# ############################################################################ #
func _init(once_only: bool = true):
self.once_only = once_only
func _to_string() -> String:
var target_line_num = debug_line_number_of_path(self.path_on_choice)
var target_string := self.path_on_choice._to_string()
if target_line_num != null:
target_string = " line %d(%s)" % [target_line_num, target_string]
return "Choice: -> %s" % target_string
# ############################################################################ #
# GDScript extra methods
# ############################################################################ #
func is_ink_class(type: String) -> bool:
return type == "ChoicePoint" || super.is_ink_class(type)
func get_ink_class() -> String:
return "ChoicePoint"

View file

@ -0,0 +1,333 @@
# warning-ignore-all:unused_class_variable
# ############################################################################ #
# 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 InkObject
class_name InkContainer
# ############################################################################ #
var name = null # String?
var content: Array: # Array<InkObject>
get:
return self._content
set(value):
add_content(value)
var _content: Array # Array<InkObject>
var named_content: Dictionary # Dictionary<string, INamedContent>
var named_only_content: # Dictionary<string, InkObject>?
get:
var named_only_content_dict = {} # Dictionary<string, InkObject>?
for key in self.named_content:
named_only_content_dict[key] = self.named_content[key]
for c in self.content:
var named = InkUtils.as_INamedContent_or_null(c)
if named != null && named.has_valid_name:
named_only_content_dict.erase(named.name)
if named_only_content_dict.size() == 0:
named_only_content_dict = null
return named_only_content_dict
set(value):
var existing_named_only = named_only_content
if existing_named_only != null:
for key in existing_named_only:
self.named_content.erase(key)
if value == null:
return
for key in value:
var named = InkUtils.as_INamedContent_or_null(value[key])
if named != null:
add_to_named_content_only(named)
var visits_should_be_counted: bool = false
var turn_index_should_be_counted: bool = false
var counting_at_start_only: bool = false
enum CountFlags {
VISITS = 1,
TURNS = 2,
COUNT_START_ONLY = 4
}
# CountFlags
var count_flags: int:
get:
var flags = 0
if visits_should_be_counted: flags |= CountFlags.VISITS
if turn_index_should_be_counted: flags |= CountFlags.TURNS
if counting_at_start_only: flags |= CountFlags.COUNT_START_ONLY
if flags == CountFlags.COUNT_START_ONLY:
flags = 0
return flags
set(value):
var flag = value
if (flag & CountFlags.VISITS) > 0: visits_should_be_counted = true
if (flag & CountFlags.TURNS) > 0: turn_index_should_be_counted = true
if (flag & CountFlags.COUNT_START_ONLY) > 0: counting_at_start_only = true
var has_valid_name: bool:
get: return self.name != null && self.name.length() > 0
var path_to_first_leaf_content: InkPath:
get:
if self._path_to_first_leaf_content == null:
self._path_to_first_leaf_content = self.path.path_by_appending_path(self.internal_path_to_first_leaf_content)
return self._path_to_first_leaf_content
# InkPath?
var _path_to_first_leaf_content: InkPath = null
# TODO: Make inspectable
var internal_path_to_first_leaf_content: InkPath:
get:
var components: Array = [] # Array<InkPath.InkComponent>
var container: InkContainer = self
while container != null:
if container.content.size() > 0:
components.append(InkPath.Component.new(0))
container = InkUtils.as_or_null(container.content[0], "InkContainer")
return InkPath.new_with_components(components)
func _init():
self._content = [] # Array<InkObject>
self.named_content = {} # Dictionary<string, INamedContent>
func add_content(content_obj_or_content_list) -> void:
if InkUtils.is_ink_class(content_obj_or_content_list, "InkObject"):
var content_obj: InkObject = content_obj_or_content_list
self.content.append(content_obj)
if content_obj.parent:
InkUtils.throw_exception("content is already in %s" % content_obj.parent._to_string())
return
content_obj.parent = self
try_add_named_content(content_obj)
elif content_obj_or_content_list is Array:
var content_list: Array = content_obj_or_content_list
for c in content_list:
add_content(c)
func insert_content(content_obj: InkObject, index: int) -> void:
self.content.insert(index, content_obj)
if content_obj.parent:
InkUtils.throw_exception("content is already in %s" % content_obj.parent._to_string())
return
content_obj.parent = self
try_add_named_content(content_obj)
func try_add_named_content(content_obj: InkObject) -> void:
var named_content_obj = InkUtils.as_INamedContent_or_null(content_obj)
if (named_content_obj != null && named_content_obj.has_valid_name):
add_to_named_content_only(named_content_obj)
# (INamedContent) -> void
func add_to_named_content_only(named_content_obj: InkObject) -> void:
InkUtils.__assert__(named_content_obj.is_ink_class("InkObject"), "Can only add Runtime.Objects to a Runtime.Container")
var runtime_obj = named_content_obj
runtime_obj.parent = self
named_content[named_content_obj.name] = named_content_obj
func add_contents_of_container(other_container: InkContainer) -> void:
self.content = self.content + other_container.content
for obj in other_container.content:
obj.parent = self
try_add_named_content(obj)
func content_with_path_component(component: InkPath.Component) -> InkObject:
if component.is_index:
if component.index >= 0 && component.index < self.content.size():
return self.content[component.index]
else:
return null
elif component.is_parent:
return self.parent
else:
if named_content.has(component.name):
var found_content = named_content[component.name]
return found_content
else:
return null
func content_at_path(
path: InkPath,
partial_path_start: int = 0,
partial_path_length: int = -1
) -> InkSearchResult:
if partial_path_length == -1:
partial_path_length = path.length
var result: InkSearchResult = InkSearchResult.new()
result.approximate = false
var current_container: InkContainer = self
var current_obj: InkObject = self
var i: int = partial_path_start
while i < partial_path_length:
var comp = path.get_component(i)
if current_container == null:
result.approximate = true
break
var found_obj: InkObject = current_container.content_with_path_component(comp)
if found_obj == null:
result.approximate = true
break
current_obj = found_obj
current_container = InkUtils.as_or_null(found_obj, "InkContainer")
i += 1
result.obj = current_obj
return result
func build_string_of_hierarchy(
existing_hierarchy: String,
indentation: int,
pointed_obj: InkObject
) -> String:
existing_hierarchy = _append_indentation(existing_hierarchy, indentation)
existing_hierarchy += "["
if self.has_valid_name:
existing_hierarchy += str(" (%s) " % self.name)
if self == pointed_obj:
existing_hierarchy += " <---"
existing_hierarchy += "\n"
indentation += 1
var i = 0
while i < self.content.size():
var obj = self.content[i]
if InkUtils.is_ink_class(obj, "InkContainer"):
existing_hierarchy = obj.build_string_of_hierarchy(existing_hierarchy, indentation, pointed_obj)
else:
existing_hierarchy = _append_indentation(existing_hierarchy, indentation)
if InkUtils.is_ink_class(obj, "StringValue"):
existing_hierarchy += "\""
existing_hierarchy += obj._to_string().replace("\n", "\\n")
existing_hierarchy += "\""
else:
existing_hierarchy += obj._to_string()
if i != self.content.size() - 1:
existing_hierarchy += ","
if !InkUtils.is_ink_class(obj, "InkContainer") && obj == pointed_obj:
existing_hierarchy += " <---"
existing_hierarchy += "\n"
i += 1
var only_named: Dictionary = {} # Dictionary<String, INamedContent>
for obj_key in self.named_content:
var value = self.named_content[obj_key]
if self.content.find(value) != -1:
continue
else:
only_named[obj_key] = value
if only_named.size() > 0:
existing_hierarchy = _append_indentation(existing_hierarchy, indentation)
existing_hierarchy += "-- named: --\n"
for object_key in only_named:
var value = only_named[object_key]
InkUtils.__assert__(InkUtils.is_ink_class(value, "InkContainer"), "Can only print out named Containers")
var container = value
existing_hierarchy = container.build_string_of_hierarchy(existing_hierarchy, indentation, pointed_obj)
existing_hierarchy += "\n"
indentation -= 1
existing_hierarchy = _append_indentation(existing_hierarchy, indentation)
existing_hierarchy += "]"
return existing_hierarchy
func build_full_string_of_hierarchy() -> String:
return build_string_of_hierarchy("", 0, null)
func _append_indentation(string: String, indentation: int) -> String:
var spaces_per_indent = 4
var i = 0
while(i < spaces_per_indent * indentation):
string += " "
i += 1
return string
# ############################################################################ #
# GDScript extra methods
# ############################################################################ #
func is_ink_class(type: String) -> bool:
return type == "InkContainer" || super.is_ink_class(type)
func get_ink_class() -> String:
return "InkContainer"

View file

@ -0,0 +1,213 @@
# warning-ignore-all:shadowed_variable
# ############################################################################ #
# 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 InkObject
class_name InkControlCommand
# ############################################################################ #
enum CommandType {
NOT_SET = -1,
EVAL_START,
EVAL_OUTPUT,
EVAL_END,
DUPLICATE,
POP_EVALUATED_VALUE,
POP_FUNCTION,
POP_TUNNEL,
BEGIN_STRING,
END_STRING,
NO_OP,
CHOICE_COUNT,
TURNS,
TURNS_SINCE,
READ_COUNT,
RANDOM,
SEED_RANDOM,
VISIT_INDEX,
SEQUENCE_SHUFFLE_INDEX,
START_THREAD,
DONE,
END,
LIST_FROM_INT,
LIST_RANGE,
LIST_RANDOM,
BEGIN_TAG,
END_TAG,
#----
TOTAL_VALUES
}
# ############################################################################ #
# CommandType
var command_type: int
# ############################################################################ #
@warning_ignore("shadowed_variable")
func _init(command_type: int = CommandType.NOT_SET):
self.command_type = command_type
# ############################################################################ #
func copy() -> InkControlCommand:
return InkControlCommand.new(self.command_type)
static func eval_start() -> InkControlCommand:
return InkControlCommand.new(CommandType.EVAL_START)
static func eval_output() -> InkControlCommand:
return InkControlCommand.new(CommandType.EVAL_OUTPUT)
static func eval_end() -> InkControlCommand:
return InkControlCommand.new(CommandType.EVAL_END)
static func duplicate() -> InkControlCommand:
return InkControlCommand.new(CommandType.DUPLICATE)
static func pop_evaluated_value() -> InkControlCommand:
return InkControlCommand.new(CommandType.POP_EVALUATED_VALUE)
static func pop_function() -> InkControlCommand:
return InkControlCommand.new(CommandType.POP_FUNCTION)
static func pop_tunnel() -> InkControlCommand:
return InkControlCommand.new(CommandType.POP_TUNNEL)
static func begin_string() -> InkControlCommand:
return InkControlCommand.new(CommandType.BEGIN_STRING)
static func end_string() -> InkControlCommand:
return InkControlCommand.new(CommandType.END_STRING)
static func no_op() -> InkControlCommand:
return InkControlCommand.new(CommandType.NO_OP)
static func choice_count() -> InkControlCommand:
return InkControlCommand.new(CommandType.CHOICE_COUNT)
static func turns() -> InkControlCommand:
return InkControlCommand.new(CommandType.TURNS)
static func turns_since() -> InkControlCommand:
return InkControlCommand.new(CommandType.TURNS_SINCE)
static func read_count() -> InkControlCommand:
return InkControlCommand.new(CommandType.READ_COUNT)
static func random() -> InkControlCommand:
return InkControlCommand.new(CommandType.RANDOM)
static func seed_random() -> InkControlCommand:
return InkControlCommand.new(CommandType.SEED_RANDOM)
static func visit_index() -> InkControlCommand:
return InkControlCommand.new(CommandType.VISIT_INDEX)
static func sequence_shuffle_index() -> InkControlCommand:
return InkControlCommand.new(CommandType.SEQUENCE_SHUFFLE_INDEX)
static func done() -> InkControlCommand:
return InkControlCommand.new(CommandType.DONE)
static func end() -> InkControlCommand:
return InkControlCommand.new(CommandType.END)
static func list_from_int() -> InkControlCommand:
return InkControlCommand.new(CommandType.LIST_FROM_INT)
static func list_range() -> InkControlCommand:
return InkControlCommand.new(CommandType.LIST_RANGE)
static func list_random() -> InkControlCommand:
return InkControlCommand.new(CommandType.LIST_RANDOM)
static func begin_tag() -> InkControlCommand:
return InkControlCommand.new(CommandType.BEGIN_TAG)
static func end_tag() -> InkControlCommand:
return InkControlCommand.new(CommandType.END_TAG)
# () -> String
func _to_string() -> String:
var command_name: String = ""
match self.command_type:
CommandType.NOT_SET: command_name = "NOT_SET"
CommandType.EVAL_START: command_name = "EVAL_START"
CommandType.EVAL_OUTPUT: command_name = "EVAL_OUTPUT"
CommandType.EVAL_END: command_name = "EVAL_END"
CommandType.DUPLICATE: command_name = "DUPLICATE"
CommandType.POP_EVALUATED_VALUE: command_name = "POP_EVALUATED_VALUE"
CommandType.POP_FUNCTION: command_name = "POP_FUNCTION"
CommandType.POP_TUNNEL: command_name = "POP_TUNNEL"
CommandType.BEGIN_STRING: command_name = "BEGIN_STRING"
CommandType.END_STRING: command_name = "END_STRING"
CommandType.NO_OP: command_name = "NO_OP"
CommandType.CHOICE_COUNT: command_name = "CHOICE_COUNT"
CommandType.TURNS: command_name = "TURNS"
CommandType.TURNS_SINCE: command_name = "TURNS_SINCE"
CommandType.READ_COUNT: command_name = "READ_COUNT"
CommandType.RANDOM: command_name = "RANDOM"
CommandType.SEED_RANDOM: command_name = "SEED_RANDOM"
CommandType.VISIT_INDEX: command_name = "VISIT_INDEX"
CommandType.SEQUENCE_SHUFFLE_INDEX: command_name = "SEQUENCE_SHUFFLE_INDEX"
CommandType.START_THREAD: command_name = "START_THREAD"
CommandType.DONE: command_name = "DONE"
CommandType.END: command_name = "END"
CommandType.LIST_FROM_INT: command_name = "LIST_FROM_INT"
CommandType.LIST_RANGE: command_name = "LIST_RANGE"
CommandType.LIST_RANDOM: command_name = "LIST_RANDOM"
CommandType.BEGIN_TAG: command_name = "BEGIN_TAG"
CommandType.END_TAG: command_name = "END_TAG"
CommandType.TOTAL_VALUES: command_name = "TOTAL_VALUES"
return "Command(%s)" % command_name
# ############################################################################ #
# GDScript extra methods
# ############################################################################ #
func is_ink_class(type: String) -> bool:
return type == "ControlCommand" || super.is_ink_class(type)
func get_ink_class() -> String:
return "ControlCommand"

View file

@ -0,0 +1,145 @@
# warning-ignore-all:shadowed_variable
# warning-ignore-all:unused_class_variable
# ############################################################################ #
# 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 InkObject
class_name InkDivert
# ############################################################################ #
var target_path: InkPath:
get:
if self._target_path != null && self._target_path.is_relative:
var target_obj: InkObject = self.target_pointer.resolve()
if target_obj:
self._target_path = target_obj.path
return self._target_path
set(value):
self._target_path = value
self._target_pointer = InkPointer.null_pointer
var _target_path: InkPath = null
var target_pointer: InkPointer:
get:
if self._target_pointer.is_null:
var target_obj = resolve_path(self._target_path).obj
if self._target_path.last_component.is_index:
self._target_pointer = InkPointer.new(
InkUtils.as_or_null(target_obj.parent, "InkContainer"),
self._target_path.last_component.index
)
else:
self._target_pointer = InkPointer.start_of(InkUtils.as_or_null(target_obj, "InkContainer"))
return self._target_pointer
var _target_pointer: InkPointer = InkPointer.null_pointer
var target_path_string: # String?
get:
if self.target_path == null:
return null
return self.compact_path_string(self.target_path)
set(value):
if value == null:
self.target_path = null
else:
self.target_path = InkPath.new_with_components_string(value)
var variable_divert_name = null # String?
var has_variable_target: bool:
get: return self.variable_divert_name != null
var pushes_to_stack: bool = false
var stack_push_type: int = 0 # Ink.PushPopType
var is_external: bool = false
var external_args: int = 0
var is_conditional: bool = false
# (int?) -> InkDivert
@warning_ignore("shadowed_variable")
func _init_with(stack_push_type = null):
self.pushes_to_stack = false
if stack_push_type != null:
self.pushes_to_stack = true
self.stack_push_type = stack_push_type
func equals(obj: InkBase) -> bool:
var other_divert: InkDivert = InkUtils.as_or_null(obj, "Divert")
if other_divert:
if self.has_variable_target == other_divert.has_variable_target:
if self.has_variable_target:
return self.variable_divert_name == other_divert.variable_divert_name
else:
return self.target_path.equals(other_divert.target_path)
return false
func _to_string() -> String:
if self.has_variable_target:
return "Divert(variable: %s)" % self.variable_divert_name
elif self.target_path == null:
return "Divert(null)"
else:
var _string = ""
var target_str: String = self.target_path._to_string()
var target_line_num = debug_line_number_of_path(self.target_path)
if target_line_num != null:
target_str = "line " + target_line_num
_string += "Divert"
if self.is_conditional:
_string += "?"
if self.pushes_to_stack:
if self.stack_push_type == Ink.PushPopType.FUNCTION:
_string += " function"
else:
_string += " tunnel"
_string += " -> "
_string += self.target_path_string
_string += " (%s)" % target_str
return _string
# ############################################################################ #
# GDScript extra methods
# ############################################################################ #
func is_ink_class(type: String) -> bool:
return type == "Divert" || super.is_ink_class(type)
func get_ink_class() -> String:
return "Divert"

View file

@ -0,0 +1,29 @@
# ############################################################################ #
# 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 InkObject
class_name InkGlue
# ############################################################################ #
func _to_string() -> String:
return "Glue"
# ############################################################################ #
# GDScript extra methods
# ############################################################################ #
func is_ink_class(type: String) -> bool:
return type == "Glue" || super.is_ink_class(type)
func get_ink_class() -> String:
return "Glue"

View file

@ -0,0 +1,372 @@
# warning-ignore-all:shadowed_variable
# warning-ignore-all:unused_class_variable
# ############################################################################ #
# 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 InkObject
class_name InkNativeFunctionCall
# ############################################################################ #
# Imports
# ############################################################################ #
# TODO: Migrate to Ink.ValueType
const ValueType = preload("res://addons/inkgd/runtime/values/value_type.gd").ValueType
# ############################################################################ #
# (String) -> NativeFunctionCall
@warning_ignore("shadowed_variable")
static func call_with_name(
function_name: String,
_static_native_function_call: InkStaticNativeFunctionCall = null
) -> InkNativeFunctionCall:
return InkNativeFunctionCall.new_with_name(function_name, _static_native_function_call)
var name: String:
get:
return _name
set(value):
_name = value
if !_is_prototype:
_prototype = self._static_native_function_call.native_functions[_name]
var _name: String
var number_of_parameters: int:
get:
if _prototype:
return _prototype.number_of_parameters
else:
return _number_of_parameters
set(value):
_number_of_parameters = value
var _number_of_parameters: int = 0
# (Array<InkObject>) -> InkObject
#
# The name is different to avoid shadowing 'Object.call'
#
# The method takes a `StoryErrorMetadata` object as a parameter that
# doesn't exist in upstream. The metadat are used in case an 'exception'
# is raised. For more information, see story.gd.
func call_with_parameters(parameters: Array, metadata: StoryErrorMetadata) -> InkObject:
if _prototype:
return _prototype.call_with_parameters(parameters, metadata)
if self.number_of_parameters != parameters.size():
InkUtils.throw_exception("Unexpected number of parameters")
return null
var has_list = false
for p in parameters:
if InkUtils.is_ink_class(p, "Void"):
InkUtils.throw_story_exception(
"Attempting to perform operation on a void value. Did you forget to " +
"'return' a value from a function you called here?",
false,
metadata
)
return null
if InkUtils.is_ink_class(p, "ListValue"):
has_list = true
if parameters.size() == 2 && has_list:
return call_binary_list_operation(parameters, metadata)
var coerced_params: Array = coerce_values_to_single_type(parameters, metadata)
# ValueType
var coerced_type: int = coerced_params[0].value_type
if (
coerced_type == ValueType.INT ||
coerced_type == ValueType.FLOAT ||
coerced_type == ValueType.STRING ||
coerced_type == ValueType.DIVERT_TARGET ||
coerced_type == ValueType.LIST
):
return call_coerced(coerced_params, metadata)
return null
# (Array<Value>) -> Value # Call<T> in the original code
#
# The method takes a `StoryErrorMetadata` object as a parameter that
# doesn't exist in upstream. The metadat are used in case an 'exception'
# is raised. For more information, see story.gd.
func call_coerced(parameters_of_single_type: Array, metadata: StoryErrorMetadata) -> InkValue:
var param1: InkValue = parameters_of_single_type[0]
var val_type: int = param1.value_type
var param_count: int = parameters_of_single_type.size()
if param_count == 2 || param_count == 1:
var op_for_type = null
if _operation_funcs.has(val_type):
op_for_type = _operation_funcs[val_type]
else:
var type_name = InkUtils.value_type_name(val_type)
InkUtils.throw_story_exception(
"Cannot perform operation '%s' on value of type (%d)" \
% [self.name, type_name],
false,
metadata
)
return null
if param_count == 2:
var param2 = parameters_of_single_type[1]
var result_val = self._static_native_function_call.call(op_for_type, param1.value, param2.value)
return InkValue.create(result_val)
else:
var result_val = self._static_native_function_call.call(op_for_type, param1.value)
return InkValue.create(result_val)
else:
InkUtils.throw_exception(
"Unexpected number of parameters to NativeFunctionCall: %d" % \
parameters_of_single_type.size()
)
return null
# (Array<InkObject>) -> Value
#
# The method takes a `StoryErrorMetadata` object as a parameter that
# doesn't exist in upstream. The metadat are used in case an 'exception'
# is raised. For more information, see story.gd.
func call_binary_list_operation(parameters: Array, metadata: StoryErrorMetadata) -> InkValue:
if ((self.name == "+" || self.name == "-") &&
InkUtils.is_ink_class(parameters[0], "ListValue") &&
InkUtils.is_ink_class(parameters [1], "IntValue")
):
return call_list_increment_operation(parameters)
var v1 = InkUtils.as_or_null(parameters[0], "Value")
var v2 = InkUtils.as_or_null(parameters[1], "Value")
if ((self.name == "&&" || self.name == "||") &&
(v1.value_type != ValueType.LIST || v2.value_type != ValueType.LIST)
):
var op: String = _operation_funcs[ValueType.INT]
var result = bool(self._static_native_function_call.call(
"op_for_type",
1 if v1.is_truthy else 0,
1 if v2.is_truthy else 0
))
return InkBoolValue.new_with(result)
if v1.value_type == ValueType.LIST && v2.value_type == ValueType.LIST:
return call_coerced([v1, v2], metadata)
var v1_type_name = InkUtils.value_type_name(v1.value_type)
var v2_type_name = InkUtils.value_type_name(v2.value_type)
InkUtils.throw_story_exception(
"Can not call use '%s' operation on %s and %s" % \
[self.name, v1_type_name, v2_type_name],
false,
metadata
)
return null
# (Array<InkObject>) -> Value
func call_list_increment_operation(list_int_params: Array) -> InkValue:
var list_val: InkListValue = InkUtils.cast(list_int_params[0], "ListValue")
var int_val: InkIntValue = InkUtils.cast(list_int_params [1], "IntValue")
var result_raw_list = InkList.new()
for list_item in list_val.value.keys(): # TODO: Optimize?
var list_item_value = list_val.value.get_item(list_item)
var int_op: String = _operation_funcs[ValueType.INT]
var target_int = int(
self._static_native_function_call.call(
int_op,
list_item_value,
int_val.value
)
)
var item_origin: InkListDefinition = null
for origin in list_val.value.origins:
if origin.name == list_item.origin_name:
item_origin = origin
break
if item_origin != null:
var incremented_item: InkTryGetResult = item_origin.try_get_item_with_value(target_int)
if incremented_item.exists:
result_raw_list.set_item(incremented_item.result, target_int)
return InkListValue.new_with(result_raw_list)
# (Array<InkObject>) -> Array<Value>?
#
# The method takes a `StoryErrorMetadata` object as a parameter that
# doesn't exist in upstream. The metadata are used in case an 'exception'
# is raised. For more information, see story.gd.
func coerce_values_to_single_type(parameters_in: Array, metadata: StoryErrorMetadata):
var val_type: int = ValueType.INT
var special_case_list: InkListValue = null
for obj in parameters_in:
var val: InkValue = obj
if val.value_type > val_type:
val_type = val.value_type
if val.value_type == ValueType.LIST:
special_case_list = InkUtils.as_or_null(val, "ListValue")
var parameters_out: Array = [] # Array<Value>
if val_type == ValueType.LIST:
for val in parameters_in:
if val.value_type == ValueType.LIST:
parameters_out.append(val)
elif val.value_type == ValueType.INT:
var int_val = int(val.value_object)
var list = special_case_list.value.origin_of_max_item
var item: InkTryGetResult = list.try_get_item_with_value(int_val)
if item.exists:
var casted_value = InkListValue.new_with_single_item(item.result, int_val)
parameters_out.append(casted_value)
else:
InkUtils.throw_story_exception(
"Could not find List item with the value %d in %s" \
% [int_val, list.name],
false,
metadata
)
return null
else:
var type_name = InkUtils.value_type_name(val.value_type)
InkUtils.throw_story_exception(
"Cannot mix Lists and %s values in this operation" % type_name,
false,
metadata
)
return null
else:
for val in parameters_in:
var casted_value = val.cast(val_type)
parameters_out.append(casted_value)
return parameters_out
func _init(static_native_function_call: InkStaticNativeFunctionCall = null):
generate_native_functions_if_necessary(static_native_function_call)
@warning_ignore("shadowed_variable")
func _init_with_name(name: String):
self.name = name
@warning_ignore("shadowed_variable")
func _init_with_name_and_number_of_parameters(name: String, number_of_parameters: int):
_is_prototype = true
self.name = name
self.number_of_parameters = number_of_parameters
func generate_native_functions_if_necessary(static_native_function_call: InkStaticNativeFunctionCall) -> void:
find_static_objects(static_native_function_call)
self._static_native_function_call.generate_native_functions_if_necessary()
func add_op_func_for_type(val_type: int, op: String) -> void:
if _operation_funcs == null:
_operation_funcs = {}
_operation_funcs[val_type] = op
func _to_string() -> String:
return "Native '%s'" % self.name
var _prototype: InkNativeFunctionCall = null
var _is_prototype: bool = false
# Dictionary<ValueType, String>
var _operation_funcs: Dictionary = {}
# ############################################################################ #
# GDScript extra methods
# ############################################################################ #
func is_ink_class(type):
return type == "NativeFunctionCall" || super.is_ink_class(type)
func get_ink_class():
return "NativeFunctionCall"
var _static_native_function_call: InkStaticNativeFunctionCall:
get: return _weak_static_native_function_call.get_ref()
var _weak_static_native_function_call = WeakRef.new()
func find_static_objects(static_native_function_call: InkStaticNativeFunctionCall = null):
if _static_native_function_call == null:
if static_native_function_call:
_weak_static_native_function_call = weakref(static_native_function_call)
else:
var ink_runtime = Engine.get_main_loop().root.get_node("__InkRuntime")
_weak_static_native_function_call = weakref(ink_runtime.native_function_call)
# ############################################################################ #
@warning_ignore("shadowed_variable")
static func new_with_name(
name: String,
static_native_function_call: InkStaticNativeFunctionCall = null
):
var native_function_call = InkNativeFunctionCall.new(static_native_function_call)
native_function_call._init_with_name(name)
return native_function_call
@warning_ignore("shadowed_variable")
static func new_with_name_and_number_of_parameters(
name: String,
number_of_parameters: int,
static_native_function_call: InkStaticNativeFunctionCall = null
):
var native_function_call = InkNativeFunctionCall.new(static_native_function_call)
native_function_call._init_with_name_and_number_of_parameters(name, number_of_parameters)
return native_function_call

View file

@ -0,0 +1,36 @@
# ############################################################################ #
# 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 InkObject
class_name InkTag
# ############################################################################ #
var text: String
func _init(tag_text: String):
text = tag_text
func _to_string() -> String:
return '# ' + text
# ############################################################################ #
# GDScript extra methods
# ############################################################################ #
func is_ink_class(type: String) -> bool:
return type == "Tag" || super.is_ink_class(type)
func get_ink_class() -> String:
return "Tag"

View file

@ -0,0 +1,60 @@
# warning-ignore-all:shadowed_variable
# warning-ignore-all:unused_class_variable
# ############################################################################ #
# 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 InkObject
class_name InkVariableAssignment
# ############################################################################ #
var variable_name = null # String?
var is_new_declaration: bool = false
var is_global: bool = false
func _init():
_init_with(null, false)
# (String?, bool) -> InkVariableAssignment
@warning_ignore("shadowed_variable")
func _init_with(variable_name, is_new_declaration: bool):
self.variable_name = variable_name
self.is_new_declaration = is_new_declaration
func _to_string() -> String:
return "VarAssign to %s" % variable_name
# ############################################################################ #
# GDScript extra methods
# ############################################################################ #
func is_ink_class(type: String) -> bool:
return type == "VariableAssignment" || super.is_ink_class(type)
func get_ink_class() -> String:
return "VariableAssignment"
# (String?, bool) -> InkVariableAssignment
@warning_ignore("shadowed_variable")
static func new_with(
variable_name: String,
is_new_declaration: bool
) -> InkVariableAssignment:
var variable_assignment = InkVariableAssignment.new()
variable_assignment._init_with(variable_name, is_new_declaration)
return variable_assignment

View file

@ -0,0 +1,69 @@
# warning-ignore-all:shadowed_variable
# warning-ignore-all:unused_class_variable
# ############################################################################ #
# 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 InkObject
class_name InkVariableReference
# ############################################################################ #
var name = null # String?
# InkPath
var path_for_count: InkPath = null
# Container?
var container_for_count: InkContainer:
get: return self.resolve_path(path_for_count).container
# String?
var path_string_for_count:
get:
if path_for_count == null:
return null
return compact_path_string(path_for_count)
set(value):
if value == null:
path_for_count = null
else:
path_for_count = InkPath.new_with_components_string(value)
# ############################################################################ #
@warning_ignore("shadowed_variable")
func _init(name = null):
if name:
self.name = name
# ############################################################################ #
func _to_string() -> String:
if name != null:
return "var(%s)" % name
else:
var path_str = self.path_string_for_count
return "read_count(%s)" % path_str
# ############################################################################ #
# GDScript extra methods
# ############################################################################ #
func is_ink_class(type: String) -> bool:
return type == "VariableReference" || super.is_ink_class(type)
func get_ink_class() -> String:
return "VariableReference"

View file

@ -0,0 +1,27 @@
# ############################################################################ #
# 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 InkObject
class_name InkVoid
# ############################################################################ #
# GDScript extra methods
# ############################################################################ #
func is_ink_class(type: String) -> bool:
return type == "Void" || super.is_ink_class(type)
func get_ink_class() -> String:
return "Void"
func _to_string() -> String:
return "Void"

View file

@ -0,0 +1,76 @@
# warning-ignore-all:unused_class_variable
# ############################################################################ #
# 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 InkDebugMetadata
# ############################################################################ #
var start_line_number: int = 0
var end_line_number: int = 0
var start_character_number: int = 0
var end_character_number: int = 0
# String?
var file_name = null
# String?
var source_name = null
# ############################################################################ #
func merge(dm: InkDebugMetadata) -> InkDebugMetadata:
var new_debug_metadata = DebugMetadata().new()
new_debug_metadata.file_name = self.file_name
new_debug_metadata.source_name = self.source_name
if self.start_line_number < dm.start_line_number:
new_debug_metadata.start_line_number = self.start_line_number
new_debug_metadata.start_character_number = self.start_character_number
elif self.start_line_number > dm.start_line_number:
new_debug_metadata.start_line_number = dm.start_line_number
new_debug_metadata.start_character_number = dm.start_character_number
else:
var min_scn = min(self.start_character_number, dm.start_character_number)
new_debug_metadata.start_line_number = self.start_line_number
new_debug_metadata.start_character_number = min_scn
if self.end_line_number > dm.end_line_number:
new_debug_metadata.end_line_number = self.end_line_number
new_debug_metadata.end_character_number = self.end_character_number
elif self.end_line_number < dm.end_line_number:
new_debug_metadata.end_line_number = dm.end_line_number
new_debug_metadata.end_character_number = dm.end_character_number
else:
var max_scn = min(self.end_character_number, dm.end_character_number)
new_debug_metadata.end_line_number = self.end_line_number
new_debug_metadata.end_character_number = max_scn
return new_debug_metadata
# () -> String
func _to_string() -> String:
if file_name != null:
return str("line ", start_line_number, " of ", file_name)
else:
return str("line ", start_line_number)
# ############################################################################ #
# GDScript extra methods
# ############################################################################ #
func is_ink_class(type: String) -> bool:
return type == "DebugMetadata" || super.is_ink_class(type)
func get_ink_class() -> String:
return "DebugMetadata"
static func DebugMetadata():
return load("res://addons/inkgd/runtime/debug_metadata.gd")

View file

@ -0,0 +1,24 @@
# warning-ignore-all:shadowed_variable
# ############################################################################ #
# 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 RefCounted
class_name InkFunctionResult
# ############################################################################ #
var text_output: String = ""
var return_value = null
# ############################################################################ #
func _init(text_output: String, return_value):
self.text_output = text_output
self.return_value = return_value

View file

@ -0,0 +1,39 @@
# warning-ignore-all:shadowed_variable
# ############################################################################ #
# 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 RefCounted
class_name InkKeyValuePair
# ############################################################################ #
var key = null
var value = null
# ############################################################################ #
# TODO: Use _init instead of _init_with_key_value.
func _init():
pass
func _init_with_key_value(key, value):
self.key = key
self.value = value
func _to_string():
return ("[KeyValuePair (%s, %s)]" % [key, value])
# ############################################################################ #
static func new_with_key_value(key, value) -> InkKeyValuePair:
var key_value_pair = InkKeyValuePair.new()
key_value_pair._init_with_key_value(key, value)
return key_value_pair

View file

@ -0,0 +1,50 @@
# warning-ignore-all:shadowed_variable
# warning-ignore-all:unused_class_variable
# ############################################################################ #
# 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.
# ############################################################################ #
# ############################################################################ #
# !! VALUE TYPE
# ############################################################################ #
# This element is only used during JSON parsing and is never duplicated / passed
# around so it doesn't need to be either immutable or have a 'duplicate' method.
class_name InkStateElement
# ############################################################################ #
enum State {
NONE,
OBJECT,
ARRAY,
PROPERTY,
PROPERTY_NAME,
STRING,
}
# ############################################################################ #
var type: int = State.NONE # State
var child_count: int = 0
# ############################################################################ #
func _init(type: int):
self.type = type
# ############################################################################ #
# GDScript extra methods
# ############################################################################ #
func is_ink_class(type) -> bool:
return type == "StateElement"
func get_ink_class() -> String:
return "StateElement"

View file

@ -0,0 +1,34 @@
# warning-ignore-all:unused_class_variable
# ############################################################################ #
# 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.
# ############################################################################ #
# Simple replacement of the Stopwatch class from the .NET Framework.
# Less accurate than the original implemntation, but good enough for
# the use-case.
class_name InkStopWatch
# ############################################################################ #
var _start_time: int = -1
var elapsed_milliseconds : get = get_elapsed_milliseconds
func get_elapsed_milliseconds() -> int:
if _start_time == -1:
return 0
return Time.get_ticks_msec() - _start_time
# ############################################################################ #
func start() -> void:
_start_time = Time.get_ticks_msec()
func stop() -> void:
_start_time = -1

View file

@ -0,0 +1,31 @@
# ############################################################################ #
# 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.
# ############################################################################ #
# An object tha represents a "Story Error", which is equivalent in certain
# context to upstream's StoryException.
class_name StoryError
# ############################################################################ #
# Properties
# ############################################################################ #
var message: String
var use_end_line_number: bool
var metadata # StoryErrorMetadata | null
# ############################################################################ #
# Initialization
# ############################################################################ #
@warning_ignore("shadowed_variable")
func _init(message: String, use_end_line_number: bool, metadata):
self.message = message
self.use_end_line_number = use_end_line_number
self.metadata = metadata

View file

@ -0,0 +1,30 @@
# ############################################################################ #
# 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.
# ############################################################################ #
# An object that keeps track of the Debug Metadata and current pointer at the
# exact moment an error was raised, so that they can be processed and reported
# later. It's required because GDScript doesn't support exceptions and
# errors don't bubble up the stack.
class_name StoryErrorMetadata
# ############################################################################ #
# Properties
# ############################################################################ #
var debug_metadata # InkDebugMetadata | null
var pointer: InkPointer
# ############################################################################ #
# Initialization
# ############################################################################ #
func _init(debug_metadata: InkDebugMetadata, pointer: InkPointer):
self.debug_metadata = debug_metadata
self.pointer = pointer

View file

@ -0,0 +1,63 @@
# ############################################################################ #
# 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.
# ############################################################################ #
# Using an dictionary as the backing structure for a not-too-bad, super-simple
# set. The Ink runtime doesn't use C#'s HashSet full potential, so this trick
# should be good enough for the use-case.
# This simple set is designed to hold Strings only.
extends RefCounted
class_name InkStringSet
# ############################################################################ #
# Self-reference
# ############################################################################ #
static func InkStringSet() -> GDScript:
return load("res://addons/inkgd/runtime/extra/string_set.gd") as GDScript
# ############################################################################ #
var _dictionary: Dictionary = {}
# ############################################################################ #
func clear() -> void:
_dictionary.clear()
func duplicate() -> InkStringSet:
var set = InkStringSet().new()
set._dictionary = _dictionary.duplicate()
return set
func enumerate() -> Array:
return _dictionary.keys()
func is_empty() -> bool:
return _dictionary.is_empty()
func contains(element: String) -> bool:
return _dictionary.has(element)
func contains_all(elements: Array) -> bool:
return _dictionary.has_all(elements)
func size() -> int:
return _dictionary.size()
func to_array() -> Array:
return _dictionary.keys()
func append(value: String) -> void:
_dictionary[value] = null
func erase(value: String) -> bool:
return _dictionary.erase(value)

View file

@ -0,0 +1,33 @@
# warning-ignore-all:unused_class_variable
# ############################################################################ #
# 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.
# ############################################################################ #
# Simple replacement of the StringWriter class from the .NET Framework.
# It has none of the optimisations of original class and merely wraps
# a plain old string.
class_name InkStringWriter
# ############################################################################ #
var _internal_string: String = ""
# ############################################################################ #
func _init():
pass
# ############################################################################ #
func write(s: String) -> void:
_internal_string += str(s)
func _to_string() -> String:
return _internal_string

View file

@ -0,0 +1,25 @@
# warning-ignore-all:shadowed_variable
# ############################################################################ #
# 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 RefCounted
class_name InkTryGetResult
# ############################################################################ #
var exists: bool = false # Bool
var result = null # Variant
# ############################################################################ #
func _init(exists: bool, result):
self.exists = exists
self.result = result

View file

@ -0,0 +1,272 @@
# ############################################################################ #
# 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 RefCounted
class_name InkUtils
# ############################################################################ #
# Imports
# ############################################################################ #
const ValueType = preload("res://addons/inkgd/runtime/values/value_type.gd").ValueType
# ############################################################################ #
# Exceptions
# ############################################################################ #
static func throw_exception(message: String) -> void:
InkRuntime().handle_exception(message)
static func throw_story_exception(
message: String,
use_end_line_number = false,
metadata = null
) -> void:
InkRuntime().handle_story_exception(message, use_end_line_number, metadata)
static func throw_argument_exception(message: String) -> void:
InkRuntime().handle_argument_exception(message)
# ############################################################################ #
# Assertions
# ############################################################################ #
static func __assert__(condition: bool, message = "") -> void:
if !condition && message != "":
printerr(message)
assert(condition)
# ############################################################################ #
# Type Assertion
# ############################################################################ #
static func as_or_null(variant, name_of_class: String):
if (
is_ink_class(variant, name_of_class) ||
(name_of_class == "Dictionary" && variant is Dictionary) ||
(name_of_class == "Array" && variant is Array)
):
return variant
else:
return null
static func cast(variant, name_of_class: String):
if is_ink_class(variant, name_of_class):
return variant
else:
push_error(
"Original implementation threw a RuntimeException here, because of a " +
"cast issue. Undefined behaviors should be expected."
)
assert(false)
return null
static func as_INamedContent_or_null(variant):
var properties = variant.get_property_list()
var has_has_valid_name = false
var has_name = false
for property in properties:
if property["name"] == "has_valid_name":
has_has_valid_name = true
if has_has_valid_name && has_name:
return variant
elif property["name"] == "name":
has_name = true
if has_has_valid_name && has_name:
return variant
return null
static func is_ink_class(object: Variant, name_of_class: String) -> bool:
return (object is Object) && object.is_ink_class(name_of_class)
static func are_of_same_type(object1: Variant, object2: Variant) -> bool:
if (object1 is Object) && (object2 is Object):
return object1.get_ink_class() == object2.get_ink_class()
return typeof(object1) == typeof(object2)
static func value_type_name(value_type: int) -> String:
match value_type:
ValueType.BOOL: return "Boolean"
ValueType.INT: return "Int"
ValueType.FLOAT: return "Float"
ValueType.LIST: return "List"
ValueType.STRING: return "String"
ValueType.DIVERT_TARGET: return "Divert Target"
ValueType.VARIABLE_POINTER: return "Variable Pointer"
_: return "unknown"
static func typename_of(variant) -> String:
match typeof(variant):
TYPE_NIL: return "null"
TYPE_BOOL: return "bool"
TYPE_INT: return "int"
TYPE_FLOAT: return "float"
TYPE_STRING: return "String"
TYPE_VECTOR2: return "Vector2"
TYPE_RECT2: return "Rect2"
TYPE_VECTOR3: return "Vector3"
TYPE_TRANSFORM2D: return "Transform2D"
TYPE_PLANE: return "Plane"
TYPE_QUATERNION: return "Quaternion"
TYPE_AABB: return "AABB"
TYPE_BASIS: return "Basis"
TYPE_TRANSFORM3D: return "Transform3D"
TYPE_COLOR: return "Color"
TYPE_NODE_PATH: return "NodePath"
TYPE_RID: return "RID"
TYPE_OBJECT: return variant.get_ink_class()
TYPE_DICTIONARY: return "Dictionary"
TYPE_ARRAY: return "Array"
TYPE_PACKED_BYTE_ARRAY: return "PackedByteArray"
TYPE_PACKED_INT32_ARRAY: return "PackedInt32Array"
TYPE_PACKED_FLOAT32_ARRAY: return "PackedFloat32Array"
TYPE_PACKED_STRING_ARRAY: return "PackedStringArray"
TYPE_PACKED_VECTOR2_ARRAY: return "PackedVector2Array"
TYPE_PACKED_VECTOR3_ARRAY: return "PackedVector3Array"
TYPE_PACKED_COLOR_ARRAY: return "PackedColorArray"
_: return "unknown"
# ############################################################################ #
# String Utils
# ############################################################################ #
static func trim(string_to_trim: String, characters = []) -> String:
if characters.is_empty():
return string_to_trim.strip_edges()
var length = string_to_trim.length()
var beginning = 0
var end = length
var i = 0
while i < string_to_trim.length():
var character = string_to_trim[i]
if characters.find(character) != -1:
beginning += 1
else:
break
i += 1
i = string_to_trim.length() - 1
while i >= 0:
var character = string_to_trim[i]
if characters.find(character) != -1:
end -= 1
else:
break
i -= 1
if beginning == 0 && end == length:
return string_to_trim
return string_to_trim.substr(beginning, end - beginning)
# ############################################################################ #
# Array Utils
# ############################################################################ #
static func join(joiner: String, array: Array) -> String:
var joined_string = ""
var i = 0
for element in array:
var element_string
if is_ink_class(element, "InkBase"):
element_string = element._to_string()
else:
element_string = str(element)
joined_string += element_string
if i >= 0 && i < array.size() - 1:
joined_string += joiner
i += 1
return joined_string
static func get_range(array: Array, index: int, count: int) -> Array:
if !(index >= 0 && index < array.size()):
printerr("get_range: index (%d) is out of bounds." % index)
return array.duplicate()
if index + count > array.size():
printerr("get_range: [index (%d) + count (%d)] is out of bounds." % [index, count])
return array.duplicate()
var new_array = []
var i = index
var c = 0
while (c < count):
new_array.append(array[i + c])
c += 1
return new_array
static func remove_range(array: Array, index: int, count: int) -> void:
if !(index >= 0 && index < array.size()):
printerr("get_range: index (%d) is out of bounds." % index)
return
if index + count > array.size():
printerr("get_range: [index (%d) + count (%d)] is out of bounds." % [index, count])
return
var i = index
var c = 0
while (c < count):
array.remove_at(i)
c += 1
static func array_equal(a1: Array, a2: Array, use_equals = false) -> bool:
if a1.size() != a2.size():
return false
var i = 0
while (i < a1.size()):
var first_element = a1[i]
var second_element = a2[i]
if use_equals:
if !first_element.equals(second_element):
return false
else:
i += 1
continue
else:
if first_element != second_element:
return false
else:
i += 1
continue
i += 1
return true
# ############################################################################ #
static func InkRuntime():
return Engine.get_main_loop().root.get_node("__InkRuntime")

View file

@ -0,0 +1,154 @@
# warning-ignore-all:shadowed_variable
# ############################################################################ #
# 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 InkFlow
# ############################################################################ #
# Imports
# ############################################################################ #
var CallStack = load("res://addons/inkgd/runtime/callstack.gd")
# ############################################################################ #
# Self-reference
# ############################################################################ #
static func Flow():
return load("res://addons/inkgd/runtime/flow.gd")
# ############################################################################ #
var name # string
var callstack # CallStack
var output_stream # Array<InkObject>
var current_choices # Array<Choice>
func _init(static_json = null):
get_static_json(static_json)
# (String, Story) -> Flow
func _init_with_name(name, story):
self.name = name
self.callstack = CallStack.new(story, self.StaticJSON)
self.output_stream = []
self.current_choices = []
# (String, Story, Dictionary<String, Variant>) -> Flow
func _init_with_name_and_jobject(name, story, jobject):
self.name = name
self.callstack = CallStack.new(story, self.StaticJSON)
self.callstack.set_json_token(jobject["callstack"], story)
self.output_stream = self.StaticJSON.jarray_to_runtime_obj_list(jobject["outputStream"])
self.current_choices = self.StaticJSON.jarray_to_runtime_obj_list(jobject["currentChoices"])
# jchoice_threads_obj is null if 'choiceThreads' doesn't exist.
var jchoice_threads_obj = jobject.get("choiceThreads");
self.load_flow_choice_threads(jchoice_threads_obj, story)
# (SimpleJson.Writer) -> void
func write_json(writer):
writer.write_object_start()
writer.write_property("callstack", Callable(self.callstack, "write_json"))
writer.write_property(
"outputStream",
Callable(self, "_anonymous_write_property_output_stream")
)
var has_choice_threads = false
for c in self.current_choices:
c.original_thread_index = c.thread_at_generation.thread_index
if self.callstack.thread_with_index(c.original_thread_index) == null:
if !has_choice_threads:
has_choice_threads = true
writer.write_property_start("choiceThreads")
writer.write_object_start()
writer.write_property_start(c.original_thread_index)
c.thread_at_generation.write_json(writer)
writer.write_property_end()
if has_choice_threads:
writer.write_object_end()
writer.write_property_end()
writer.write_property(
"currentChoices",
Callable(self, "_anonymous_write_property_current_choices")
)
writer.write_object_end()
# (Dictionary, Story) -> void
func load_flow_choice_threads(jchoice_threads, story):
for choice in self.current_choices:
var found_active_thread = self.callstack.thread_with_index(choice.original_thread_index)
if found_active_thread != null:
choice.thread_at_generation = found_active_thread.copy()
else:
var jsaved_choice_thread = jchoice_threads[str(choice.original_thread_index)]
choice.thread_at_generation = CallStack.InkThread.new_with(jsaved_choice_thread, story)
# (SimpleJson.Writer) -> void
func _anonymous_write_property_output_stream(w):
self.StaticJSON.write_list_runtime_objs(w, self.output_stream)
# (SimpleJson.Writer) -> void
func _anonymous_write_property_current_choices(w):
w.write_array_start()
for c in self.current_choices:
self.StaticJSON.write_choice(w, c)
w.write_array_end()
func equals(ink_base) -> bool:
return false
func _to_string() -> String:
return str(self)
# ############################################################################ #
# GDScript extra methods
# ############################################################################ #
func is_ink_class(type):
return type == "Flow" || super.is_ink_class(type)
func get_ink_class():
return "Flow"
static func new_with_name(name, story, static_json = null):
var flow = Flow().new(static_json)
flow._init_with_name(name, story)
return flow
static func new_with_name_and_jobject(name, story, jobject, static_json = null):
var flow = Flow().new(static_json)
flow._init_with_name_and_jobject(name, story, jobject)
return flow
# ############################################################################ #
var StaticJSON: InkStaticJSON:
get: return _static_json.get_ref()
var _static_json = WeakRef.new()
func get_static_json(static_json = null):
if static_json != null:
_static_json = weakref(static_json)
return
var InkRuntime = Engine.get_main_loop().root.get_node("__InkRuntime")
InkUtils.__assert__(InkRuntime != null,
str("[InkFlow] Could not retrieve 'InkRuntime' singleton from the scene tree."))
_static_json = weakref(InkRuntime.json)

View file

@ -0,0 +1,294 @@
# warning-ignore-all:shadowed_variable
# warning-ignore-all:unused_class_variable
# ############################################################################ #
# 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 InkPath
# ############################################################################ #
const parent_id = "^"
# ############################################################################ #
class Component extends InkBase:
var index: int = 0
var name = null # String?
var is_index: bool:
get: return index >= 0
var is_parent: bool:
get: return name == parent_id
# ######################################################################## #
func _init(index_or_name):
if index_or_name is int:
var index = index_or_name
assert(index >= 0)
self.index = index
self.name = null
elif index_or_name is String:
var name = index_or_name
assert(name != null && name.length() > 0)
self.name = name
self.index = -1
# () -> Component
static func to_parent() -> Component:
return Component.new(parent_id)
# () -> String
func _to_string() -> String:
if self.is_index:
return str(index)
else:
return name
# (Component) -> bool
func equals(other_comp) -> bool:
# Simple test to make sure the object is of the right type.
if !(other_comp is Object && other_comp.is_ink_class("InkPath.Component")): return false
if other_comp.is_index == self.is_index:
if self.is_index:
return index == other_comp.index
else:
return name == other_comp.name
return false
# ######################################################################## #
# GDScript extra methods
# ######################################################################## #
func is_ink_class(type):
return type == "InkPath.Component" || super.is_ink_class(type)
func get_ink_class():
return "InkPath.Component"
# ############################################################################ #
func get_component(index: int) -> InkPath.Component:
return self._components[index]
var is_relative: bool = false
var head: InkPath.Component:
get:
if _components.size() > 0:
return _components.front()
else:
return null
# TODO: Make inspectable
var tail: InkPath:
get:
if _components.size() >= 2:
var tail_comps = _components.duplicate()
tail_comps.pop_front()
return InkPath().new_with_components(tail_comps)
else:
return InkPath().__self()
var length: int:
get: return _components.size()
var last_component: InkPath.Component:
get:
if _components.size() > 0:
return _components.back()
else:
return null
var contains_named_component: bool:
get:
for comp in _components:
if !comp.is_index:
return true
return false
func _init():
self._components = []
func _init_with_head_tail(head, tail):
self._components = []
self._components.append(head)
self._components = self._components + self.tail._components
func _init_with_components(components, relative = false):
self._components = []
self._components = self._components + components
self.is_relative = relative
func _init_with_components_string(components_string):
self._components = []
self.components_string = components_string
# () -> InkPath
static func __self() -> InkPath:
var path = InkPath().new()
path.is_relative = true
return path
# (InkPath) -> InkPath
func path_by_appending_path(path_to_append):
var p = InkPath().new()
var upward_moves = 0
var i = 0
while(i < path_to_append._components.size()):
if path_to_append._components[i].is_parent:
upward_moves += 1
else:
break
i += 1
i = 0
while(i < self._components.size() - upward_moves):
p._components.append(self._components[i])
i += 1
i = upward_moves
while(i < path_to_append._components.size()):
p._components.append(path_to_append._components[i])
i += 1
return p
# (Component) -> InkPath
func path_by_appending_component(c):
var p = InkPath().new()
p._components = p._components + self._components
p._components.append(c)
return p
var components_string: String:
get:
if _components_string == null:
_components_string = InkUtils.join(".", _components)
if self.is_relative:
_components_string = "." + _components_string
return _components_string
set(value):
_components.clear()
_components_string = value
if (_components_string == null || _components_string.length() == 0):
return
if _components_string[0] == '.':
self.is_relative = true
_components_string = _components_string.substr(1, _components_string.length() - 1)
else:
self.is_relative = false
var components_strings = _components_string.split(".")
for _str in components_strings:
if _str.is_valid_int():
_components.append(Component.new(int(_str)))
else:
_components.append(Component.new(_str))
var _components_string # String
func _to_string() -> String:
return self.components_string
# (Component) -> bool
func equals(other_path):
# Simple test to make sure the object is of the right type.
if !(other_path is Object && other_path.is_ink_class("InkPath")): return false
if other_path._components.size() != self._components.size():
return false
if other_path.is_relative != self.is_relative:
return false
return InkUtils.array_equal(other_path._components, self._components, true)
var _components = null # Array<Component>
# ############################################################################ #
# GDScript extra methods
# ############################################################################ #
static func new_with_head_tail(head, tail):
var path = InkPath().new()
path._init_with_head_tail(head, tail)
return path
static func new_with_components(components, relative = false):
var path = InkPath().new()
path._init_with_components(components, relative)
return path
static func new_with_components_string(components_string):
var path = InkPath().new()
path._init_with_components_string(components_string)
return path
# ############################################################################ #
# GDScript extra methods
# ############################################################################ #
func is_ink_class(type):
return type == "InkPath" || super.is_ink_class(type)
func get_ink_class():
return "InkPath"
static func InkPath():
return load("res://addons/inkgd/runtime/ink_path.gd")

View file

@ -0,0 +1,522 @@
# warning-ignore-all:unused_class_variable
# ############################################################################ #
# 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 InkObject
class_name InkList
# ############################################################################ #
# (Dictionary<InkItem, int>, Array<String>, Array<InkListDefinition>)
func _init_from_csharp(items: Dictionary, origin_names: Array, origins: Array):
_dictionary = items
_origin_names = origin_names
self.origins = origins
# (InkList) -> InkList
func _init_with_ink_list(other_list: InkList):
_dictionary = other_list._dictionary.duplicate()
var other_origin_names = other_list.origin_names
if other_origin_names != null:
_origin_names = other_list.origin_names.duplicate()
if other_list.origins != null:
self.origins = other_list.origins.duplicate()
# (string, Story) -> InkList
func _init_with_origin(single_origin_list_name: String, origin_story: InkStory):
set_initial_origin_name(single_origin_list_name)
var def: InkTryGetResult = origin_story.list_definitions.try_list_get_definition(single_origin_list_name)
if def.exists:
origins = [def.result]
else:
InkUtils.throw_exception(
"InkList origin could not be found in story when constructing new list: %s" \
% single_origin_list_name
)
# (InkListItem, int) -> InkList
func _init_with_single_item(single_item: InkListItem, single_value: int):
set_item(single_item, single_value)
# (string, Story) -> InkList
static func from_string(my_list_item: String, origin_story: InkStory) -> InkList:
var list_value: InkListValue = origin_story.list_definitions.find_single_item_list_with_name(my_list_item)
if list_value:
return InkList.new_with_ink_list(list_value.value)
else:
InkUtils.throw_exception(
"Could not find the InkListItem from the string '%s' to create an InkList because " +
"it doesn't exist in the original list definition in ink." % my_list_item
)
return null
func add_item(item: InkListItem) -> void:
if item.origin_name == null:
add_item(item.item_name)
return
for origin in self.origins:
if origin.name == item.origin_name:
var int_val: InkTryGetResult = origin.try_get_value_for_item(item)
if int_val.exists:
set_item(item, int_val.result)
return
else:
InkUtils.throw_exception(
"Could not add the item '%s' to this list because it doesn't exist in the " +
"original list definition in ink." % item._to_string()
)
return
InkUtils.throw_exception(
"Failed to add item to list because the item was from a new list definition that " +
"wasn't previously known to this list. Only items from previously known lists can " +
"be used, so that the int value can be found."
)
func add_item_by_string(item_name: String) -> void:
var found_list_def: InkListDefinition = null
for origin in self.origins:
if origin.contains_item_with_name(item_name):
if found_list_def != null:
InkUtils.throw_exception(
"Could not add the item " + item_name + " to this list because it could " +
"come from either " + origin.name + " or " + found_list_def.name
)
return
else:
found_list_def = origin
if found_list_def == null:
InkUtils.throw_exception(
"Could not add the item " + item_name + " to this list because it isn't known " +
"to any list definitions previously associated with this list."
)
return
var item = InkListItem.new_with_origin_name(found_list_def.name, item_name)
var item_val: int = found_list_def.value_for_item(item)
set_item(item, item_val)
func contains_item_named(item_name: String) -> bool:
for item_key in keys():
if item_key.item_name == item_name:
return true
return false
# Array<ListDefinition>
var origins = null
var origin_of_max_item: InkListDefinition: get = get_origin_of_max_item
func get_origin_of_max_item() -> InkListDefinition:
if self.origins == null:
return null
var max_origin_name = self.max_item.key.origin_name
for origin in self.origins:
if origin.name == max_origin_name:
return origin
return null
# Array<String>
var origin_names : get = get_origin_names
func get_origin_names():
if self.size() > 0:
if _origin_names == null && self.size() > 0:
_origin_names = []
else:
_origin_names.clear()
for item_key in keys():
_origin_names.append(item_key.origin_name)
return _origin_names
var _origin_names = null # Array<String>
func set_initial_origin_name(initial_origin_name: String) -> void:
_origin_names = [ initial_origin_name ]
# (Array<String>) -> void
func set_initial_origin_names(initial_origin_names) -> void:
if initial_origin_names == null:
_origin_names = null
else:
_origin_names = initial_origin_names.duplicate()
# TODO: Make inspectable
var max_item: InkKeyValuePair: # InkKeyValuePair<InkListItem, int>
get:
var _max_item: InkKeyValuePair = InkKeyValuePair.new_with_key_value(InkListItem.null_item, 0)
for k in keys():
if (_max_item.key.is_null || get_item(k) > _max_item.value):
_max_item = InkKeyValuePair.new_with_key_value(k, get_item(k))
return _max_item
# TODO: Make inspectable
var min_item: InkKeyValuePair: # InkKeyValuePair<InkListItem, int>
get:
var _min_item: InkKeyValuePair = InkKeyValuePair.new_with_key_value(InkListItem.null_item, 0)
for k in keys():
if (_min_item.key.is_null || get_item(k) < _min_item.value):
_min_item = InkKeyValuePair.new_with_key_value(k, get_item(k))
return _min_item
# TODO: Make inspectable
var inverse: InkList: get = get_inverse
func get_inverse() -> InkList:
var list: InkList = InkList.new()
if self.origins != null:
for origin in self.origins:
for serialized_item_key in origin.items:
if !_dictionary.has(serialized_item_key):
list._dictionary[serialized_item_key] = origin.items[serialized_item_key]
return list
# TODO: Make inspectable
var all: InkList: get = get_all
func get_all() -> InkList:
var list: InkList = InkList.new()
if self.origins != null:
for origin in self.origins:
for serialized_item_key in origin.items:
list._dictionary[serialized_item_key] = origin.items[serialized_item_key]
return list
# TODO: Make inspectable
func union(other_list: InkList) -> InkList:
var union: InkList = InkList.new_with_ink_list(self)
for key in other_list._dictionary:
union._dictionary[key] = other_list._dictionary[key]
return union
# TODO: Make inspectable
func intersection(other_list: InkList) -> InkList:
var intersection: InkList = InkList.new()
for key in other_list._dictionary:
if self._dictionary.has(key):
intersection._dictionary[key] = other_list._dictionary[key]
return intersection
func has_intersection(other_list: InkList) -> bool:
for key in other_list._dictionary:
if self._dictionary.has(key):
return true
return false
# TODO: Make inspectable
func without(list_to_remove: InkList) -> InkList:
var result = InkList.new_with_ink_list(self)
for key in list_to_remove._dictionary:
result._dictionary.erase(key)
return result
func contains(other_list: InkList) -> bool:
if other_list._dictionary.is_empty() || self._dictionary.is_empty():
return false
for key in other_list._dictionary:
if !_dictionary.has(key):
return false
return true
# In the original source code 'list_item_name' is of type (String | null),
# but the method doesn't need to allow null names.
func contains_item(list_item_name: String) -> bool:
for key in self._dictionary:
var list_item = InkListItem.from_serialized_key(key)
if list_item.item_name == list_item_name:
return true
return false
func greater_than(other_list: InkList) -> bool:
if size() == 0:
return false
if other_list.size() == 0:
return true
return self.min_item.value > other_list.max_item.value
func greater_than_or_equals(other_list: InkList) -> bool:
if size() == 0:
return false
if other_list.size() == 0:
return true
return (
self.min_item.value >= other_list.min_item.value &&
self.max_item.value >= other_list.max_item.value
)
func less_than(other_list: InkList) -> bool:
if other_list.size() == 0:
return false
if size() == 0:
return true
return self.max_item.value < other_list.min_item.value
func less_than_or_equals(other_list: InkList) -> bool:
if other_list.size() == 0:
return false
if size() == 0:
return true
return (
self.max_item.value <= other_list.max_item.value &&
self.min_item.value <= other_list.min_item.value
)
func max_as_list() -> InkList:
if size() > 0:
var _max_item: InkKeyValuePair = self.max_item
return InkList.new_with_single_item(_max_item.key, _max_item.value)
else:
return InkList.new()
func min_as_list() -> InkList:
if size() > 0:
var _min_item: InkKeyValuePair = self.min_item
return InkList.new_with_single_item(_min_item.key, _min_item.value)
else:
return InkList.new()
# (Variant, Variant) -> InkList
func list_with_sub_range(min_bound, max_bound) -> InkList:
if size() == 0:
return InkList.new()
var ordered: Array = self.ordered_items
var min_value: int = 0
var max_value: int = 9_223_372_036_854_775_807 # MAX_INT
if min_bound is int:
min_value = min_bound
else:
if min_bound.is_ink_class("InkList") && min_bound.size() > 0:
min_value = min_bound.min_item.value
if max_bound is int:
max_value = max_bound
else:
if min_bound.is_ink_class("InkList") && min_bound.size() > 0:
max_value = max_bound.max_item.value
var sub_list = InkList.new()
sub_list.set_initial_origin_names(self.origin_names)
for item in ordered:
if item.value >= min_value && item.value <= max_value:
sub_list.set_item(item.key, item.value)
return sub_list
func equals(other: InkBase) -> bool:
var other_raw_list: InkList = other
# Simple test to make sure the object is of the right type.
if !(other_raw_list is Object):
return false
if !(other_raw_list.is_ink_class("InkList")):
return false
if other_raw_list.size() != self.size():
return false
for key in keys():
if (!other_raw_list.has_item(key)):
return false
return true
var ordered_items: Array: # Array<InkKeyValuePair<InkListItem, int>>
get:
var ordered: Array = []
for key in keys():
ordered.append(InkKeyValuePair.new_with_key_value(key, get_item(key)))
ordered.sort_custom(Callable(KeyValueInkListItemSorter, "sort"))
return ordered
func _to_string() -> String:
var ordered: Array = self.ordered_items
var description: String = ""
var i: int = 0
while (i < ordered.size()):
if i > 0:
description += ", "
var item = ordered[i].key
description += item.item_name
i += 1
return description
static func new_with_dictionary(other_dictionary: Dictionary) -> InkList:
var ink_list: InkList = InkList.new()
ink_list._init_with_dictionary(other_dictionary)
return ink_list
static func new_with_ink_list(other_list: InkList) -> InkList:
var ink_list: InkList = InkList.new()
ink_list._init_with_ink_list(other_list)
return ink_list
static func new_with_origin(single_origin_list_name: String, origin_story) -> InkList:
var ink_list: InkList = InkList.new()
ink_list._init_with_origin(single_origin_list_name, origin_story)
return ink_list
static func new_with_single_item(single_item: InkListItem, single_value: int) -> InkList:
var ink_list: InkList = InkList.new()
ink_list._init_with_single_item(single_item, single_value)
return ink_list
class KeyValueInkListItemSorter:
static func sort(a, b):
if a.value == b.value:
return a.key.origin_name.nocasecmp_to(b.key.origin_name) <= 0
else:
return a.value <= b.value
# ############################################################################ #
# Originally, this class would inherit Dictionary. This isn't possible in
# GDScript. Instead, this class will encapsulate a dictionary and forward
# needed calls.
# ############################################################################ #
var _dictionary: Dictionary = {}
# Name set_item instead of set to prevent shadowing 'Object.set'.
func set_item(key: InkListItem, value: int) -> void:
_dictionary[key.serialized()] = value
# Name get_item instead of get to prevent shadowing 'Object.get'.
func get_item(key: InkListItem, default = null):
return _dictionary.get(key.serialized(), default)
# Name has_item instead of has to prevent shadowing 'Object.get'.
func has_item(key: InkListItem) -> bool:
return _dictionary.has(key.serialized())
func keys() -> Array:
var deserialized_keys = []
for key in _dictionary.keys():
deserialized_keys.append(InkListItem.from_serialized_key(key))
return deserialized_keys
func size() -> int:
return _dictionary.size()
# ############################################################################ #
# Additional methods
# ############################################################################ #
func set_raw(key: String, value: int) -> void:
if OS.is_debug_build() && !(key is String):
print("Warning: Expected serialized key in InkList.set_raw().")
_dictionary[key] = value
func erase_raw(key: String) -> bool:
if OS.is_debug_build() && !(key is String):
print("Warning: Expected serialized key in InkList.erase_raw().")
return _dictionary.erase(key)
func get_raw(key: String, default = null):
if OS.is_debug_build() && !(key is String):
print("Warning: Expected serialized key in InkList.get_raw().")
return _dictionary.get(key, default)
func has_raw(key: String) -> bool:
if OS.is_debug_build() && !(key is String):
print("Warning: Expected serialized key in InkList.has_raw().")
return _dictionary.has(key)
func has_all_raw(keys: Array) -> bool:
return _dictionary.has_all(keys)
func raw_keys() -> Array:
return _dictionary.keys()
# ############################################################################ #
# GDScript extra methods
# ############################################################################ #
func is_ink_class(type: String) -> bool:
return type == "InkList" || super.is_ink_class(type)
func get_ink_class() -> String:
return "InkList"

View file

@ -0,0 +1,102 @@
# warning-ignore-all:shadowed_variable
# warning-ignore-all:unused_class_variable
# ############################################################################ #
# 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 InkObject
class_name InkListDefinition
# ############################################################################ #
# Imports
# ############################################################################ #
var InkTryGetResult = preload("res://addons/inkgd/runtime/extra/try_get_result.gd")
# ############################################################################ #
var name: String: get = get_name
func get_name() -> String:
return _name
# Dictionary<InkListItem, int> => Dictionary<String, int>
# Note: 'InkListItem' should actually be serialized into a String, because it
# needs to be a value type.
var items: Dictionary: get = get_items
func get_items() -> Dictionary:
if _items == null:
_items = {}
for item_name_and_value_key in _item_name_to_values:
var item = InkListItem.new_with_origin_name(self.name, item_name_and_value_key)
_items[item.serialized()] = _item_name_to_values[item_name_and_value_key]
return _items
var _items
# ############################################################################ #
func value_for_item(item: InkListItem) -> int:
if (_item_name_to_values.has(item.item_name)):
var intVal = _item_name_to_values[item.item_name]
return intVal
else:
return 0
func contains_item(item: InkListItem) -> bool:
if item.origin_name != self.name:
return false
return _item_name_to_values.has(item.item_name)
func contains_item_with_name(item_name: String) -> bool:
return _item_name_to_values.has(item_name)
# (int) -> { result: InkListItem, exists: bool }
func try_get_item_with_value(val: int) -> InkTryGetResult:
for named_item_key in _item_name_to_values:
if (_item_name_to_values[named_item_key] == val):
return InkTryGetResult.new(
true,
InkListItem.new_with_origin_name(self.name, named_item_key)
)
return InkTryGetResult.new(false, InkListItem.null_item)
# (InkListItem) -> { result: InkListItem, exists: bool }
func try_get_value_for_item(item: InkListItem) -> InkTryGetResult:
if !item.item_name:
return InkTryGetResult.new(false, 0)
var value = _item_name_to_values.get(item.item_name)
if (!value):
InkTryGetResult.new(false, 0)
return InkTryGetResult.new(true, value)
# (String name, Dictionary<String, int>) -> InkListDefinition
func _init(name: String, items: Dictionary):
_name = name
_item_name_to_values = items
var _name: String
var _item_name_to_values: Dictionary # Dictionary<String, int>
# ############################################################################ #
# GDScript extra methods
# ############################################################################ #
func is_ink_class(type: String) -> bool:
return type == "InkListDefinition" || super.is_ink_class(type)
func get_ink_class() -> String:
return "InkListDefinition"
func _to_string() -> String:
return "[InkListDefinition \"%s\"]" % get_name()

View file

@ -0,0 +1,86 @@
# warning-ignore-all:shadowed_variable
# warning-ignore-all:unused_class_variable
# ############################################################################ #
# 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 InkObject
class_name InkListDefinitionsOrigin
# ############################################################################ #
# Imports
# ############################################################################ #
var InkTryGetResult = preload("res://addons/inkgd/runtime/extra/try_get_result.gd")
var InkListValue = load("res://addons/inkgd/runtime/values/list_value.gd")
# ############################################################################ #
# Array<InkListDefinition>
var lists: Array: get = get_lists
func get_lists() -> Array:
var list_of_lists = []
for named_list_key in _lists:
list_of_lists.append(_lists[named_list_key])
return list_of_lists
# ############################################################################ #
# (Array<InkListDefinition>) -> InkListDefinitionOrigin
func _init(lists: Array):
_lists = {} # Dictionary<String, InkListDefinition>
_all_unambiguous_list_value_cache = {} # Dictionary<String, InkListValue>()
for list in lists:
_lists[list.name] = list
for item_with_value_key in list.items:
var item = InkListItem.from_serialized_key(item_with_value_key)
var val = list.items[item_with_value_key]
var list_value = InkListValue.new_with_single_item(item, val)
_all_unambiguous_list_value_cache[item.item_name] = list_value
_all_unambiguous_list_value_cache[item.full_name] = list_value
# ############################################################################ #
# (String) -> { result: String, exists: bool }
func try_list_get_definition(name: String) -> InkTryGetResult:
if name == null:
return InkTryGetResult.new(false, null)
var definition = _lists.get(name)
if !definition:
return InkTryGetResult.new(false, null)
return InkTryGetResult.new(true, definition)
func find_single_item_list_with_name(name: String) -> InkListValue:
if _all_unambiguous_list_value_cache.has(name):
return _all_unambiguous_list_value_cache[name]
return null
# ############################################################################ #
var _lists: Dictionary # Dictionary<String, InkListDefinition>
var _all_unambiguous_list_value_cache: Dictionary # Dictionary<String, InkListValue>
# ############################################################################ #
# GDScript extra methods
# ############################################################################ #
func is_ink_class(type: String) -> bool:
return type == "InkListDefinitionsOrigin" || super.is_ink_class(type)
func get_ink_class() -> String:
return "InkListDefinitionsOrigin"

View file

@ -0,0 +1,159 @@
# warning-ignore-all:shadowed_variable
# warning-ignore-all:unused_class_variable
# ############################################################################ #
# 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.
# ############################################################################ #
# ############################################################################ #
# !! VALUE TYPE
# ############################################################################ #
extends InkObject
class_name InkListItem
# ############################################################################ #
# Originally these were simple variables, but they are turned into properties to
# make the object "immutable". That way it can be passed around without being
# duplicated.
var origin_name:
get: return _origin_name
var _origin_name = null # String
var item_name:
get: return _item_name
var _item_name = null # String
# ############################################################################ #
# (string, string) -> InkListItem
@warning_ignore("shadowed_variable")
func _init_with_origin_name(origin_name, item_name):
self._origin_name = origin_name
self._item_name = item_name
# (string) -> InkListItem
@warning_ignore("shadowed_variable")
func _init_with_full_name(full_name):
var name_parts = full_name.split(".")
self._origin_name = name_parts[0]
self._item_name = name_parts[1]
static var null_item: InkListItem:
get: return InkListItem.new_with_origin_name(null, null)
# ############################################################################ #
var is_null: bool:
get:
return self.origin_name == null && self.item_name == null
# String
var full_name:
get:
# In C#, concatenating null produce nothing, in GDScript, it appends "Null".
return (
(self.origin_name if self.origin_name else "?") + "." +
(self.item_name if self.item_name else "")
)
# ############################################################################ #
# () -> String
func _to_string() -> String:
return self.full_name
# (InkObject) -> bool
func equals(obj: InkBase) -> bool:
if obj.is_ink_class("InkListItem"):
var other_item = obj
return (
other_item.item_name == self.item_name &&
self.other_item.origin_name == self.origin_name
)
return false
# ############################################################################ #
# (string, string) -> InkListItem
@warning_ignore("shadowed_variable")
static func new_with_origin_name(origin_name, item_name) -> InkListItem:
var list_item = InkListItem.new()
list_item._init_with_origin_name(origin_name, item_name)
return list_item
# (string) -> InkListItem
@warning_ignore("shadowed_variable")
static func new_with_full_name(full_name) -> InkListItem:
var list_item = InkListItem.new()
list_item._init_with_full_name(full_name)
return list_item
# ############################################################################ #
# GDScript extra methods
# ############################################################################ #
func is_ink_class(type: String) -> bool:
return type == "InkListItem" || super.is_ink_class(type)
func get_ink_class() -> String:
return "InkListItem"
# ############################################################################ #
# These methods did not exist in the original C# code. Their purpose is to
# make `InkListItem` mimic the value-type semantics of the original
# struct, as well as offering a serialization mechanism to use `InkListItem`
# as keys in dictionaries.
# Returns a `SerializedInkListItem` representing the current
# instance. The result is intended to be used as a key inside a Map.
func serialized() -> String:
# We are simply using a JSON representation as a value-typed key.
var json_print = JSON.stringify(
{ "originName": self.origin_name, "itemName": self.item_name }
)
return json_print
# Reconstructs a `InkListItem` from the given SerializedInkListItem.
#
# (String) -> InkListItem
static func from_serialized_key(key: String) -> InkListItem:
var obj = JSON.parse_string(key)
if !InkListItem._is_like_ink_list_item(obj):
return InkListItem.null_item
return InkListItem.new_with_origin_name(obj["originName"], obj["itemName"])
# Determines whether the given item is sufficiently `InkListItem`-like
# to be used as a template when reconstructing the InkListItem.
#
# (Variant) -> bool
static func _is_like_ink_list_item(item) -> bool:
if !(item is Dictionary):
return false
if !(item.has("originName") && item.has("itemName")):
return false
if !(item["originName"] is String):
return false
if !(item["itemName"] is String):
return false
return true

View file

@ -0,0 +1,57 @@
# ############################################################################ #
# 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 InkProfiler
func _init():
pass
# () -> String
func report() -> String:
return ""
# () -> void
func pre_continue() -> void:
pass
# () -> void
func post_continue() -> void:
pass
# () -> void
func pre_step() -> void:
pass
# (CallStack) -> void
func step(callstack: InkCallStack) -> void:
pass
# () -> void
func post_step() -> void:
pass
func step_length_record() -> String:
return ""
func mega_log() -> String:
return ""
func pre_snapshot() -> void:
pass
func post_snapshot() -> void:
pass
func millisecs(watch: InkStopWatch) -> float:
return 0.0
static func format_millisecs(num: float) -> String:
return ""

View file

@ -0,0 +1,41 @@
# warning-ignore-all:unused_class_variable
# ############################################################################ #
# 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.
# ############################################################################ #
# ############################################################################ #
# !! VALUE TYPE
# ############################################################################ #
# Search results are never duplicated / passed around so they don't need to
# be either immutable or have a 'duplicate' method.
extends InkBase
class_name InkSearchResult
# ############################################################################ #
var obj: InkObject = null
var approximate: bool = false
var correct_obj: InkObject:
get: return null if approximate else obj
var container: InkContainer:
get: return InkUtils.as_or_null(obj, "InkContainer")
# ############################################################################ #
# GDScript extra methods
# ############################################################################ #
func is_ink_class(type: String) -> bool:
return type == "SearchResult" || super.is_ink_class(type)
func get_ink_class() -> String:
return "SearchResult"

View file

@ -0,0 +1,588 @@
# ############################################################################ #
# 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 InkSimpleJSON
# ############################################################################ #
# (String) -> Dictionary<String, Variant>
static func text_to_dictionary(text: String) -> Dictionary:
return Reader.new(text).to_dictionary()
# (String) -> Array<Variant>
static func text_to_array(text: String) -> Array:
return Reader.new(text).to_array()
class Reader extends InkBase:
# (String) -> Reader
func _init(text: String):
_text = text
_offset = 0
skip_whitespace()
_root_object = read_object()
# () -> Dictionary<String, Variant>
func to_dictionary() -> Dictionary:
return _root_object
# () -> Array<Variant>
func to_array() -> Array:
return _root_object
# (String) -> bool
func is_number_char(c: String) -> bool:
if c.length() > 1:
return false
return c.is_valid_int() || c == "." || c == "-" || c == "+" || c == 'E' || c == 'e'
# (String) -> bool
func is_first_number_char(c: String) -> bool:
if c.length() > 1:
return false
return c.is_valid_int() || c == "-" || c == "+"
# () -> Variant
func read_object():
var current_char = _text[_offset]
if current_char == "{":
return read_dictionary()
elif current_char == "[":
return read_array()
elif current_char == "\"":
return read_string()
elif is_first_number_char(current_char):
return read_number()
elif try_read("true"):
return true
elif try_read("false"):
return false
elif try_read("null"):
return null
InkUtils.throw_exception("Unhandled object type in JSON: %s" % _text.substr(_offset, 30))
return JsonError.new()
# () -> Dictionary<String, Variant>?
func read_dictionary():
var dict = {} # Dictionary<String, Variant>
if !expect("{"):
return null
skip_whitespace()
if try_read("}"):
return dict
var first_time = true
while first_time || try_read(","):
first_time = false
skip_whitespace()
var key = read_string()
if !expect(key != null, "dictionary key"):
return null
skip_whitespace()
if !expect(":"):
return null
skip_whitespace()
var val = read_object()
if !expect(val != null, "dictionary value"):
return null
dict[key] = val
skip_whitespace()
if !expect("}"):
return null
return dict
# () -> Array<Variant>?
func read_array():
var list = []
if !expect("["):
return null
skip_whitespace()
if try_read("]"):
return list
var first_time = true
while first_time || try_read(","):
first_time = false
skip_whitespace()
var val = read_object()
list.append(val)
skip_whitespace()
if !expect("]"):
return null
return list
# () -> String?
func read_string():
if !expect("\""):
return null
var sb = ""
while(_offset < _text.length()):
var c = _text[_offset]
if c == "\\":
_offset += 1
if _offset >= _text.length():
InkUtils.throw_exception("Unexpected EOF while reading string")
return null
c = _text[_offset]
match c:
"\"", "\\", "/":
sb += c
"n":
sb += "\n"
"t":
sb += "\t"
"r", "b", "f":
pass
"u":
if _offset + 4 >= _text.length():
InkUtils.throw_exception("Unexpected EOF while reading string")
return null
var digits = _text.substr(_offset + 1, 4)
var test_json_conv = JSON.new()
test_json_conv.parse("\"\\u" + digits + "\"")
var json_parse_result = test_json_conv.get_data()
if json_parse_result.error != OK:
InkUtils.throw_exception("Invalid Unicode escape character at offset %d" % (_offset - 1))
return null
sb += json_parse_result.result
_offset += 4
break
_:
InkUtils.throw_exception("Invalid Unicode escape character at offset %d " % (_offset - 1))
return null
elif c == "\"":
break
else:
sb += c
_offset += 1
if !expect("\""):
return null
return sb
# () -> Variant
func read_number():
var start_offset = _offset
var is_float = false
while(_offset < _text.length()):
var c = _text[_offset]
if (c == "." || c == "e" || c == "E"): is_float = true
if is_number_char(c):
_offset += 1
continue
else:
break
_offset += 1
var num_str = _text.substr(start_offset, _offset - start_offset)
if is_float:
if num_str.is_valid_float():
return float(num_str)
else:
if num_str.is_valid_int():
return int(num_str)
InkUtils.throw_exception("Failed to parse number value: " + num_str)
return JsonError.new()
# (String) -> bool
func try_read(text_to_read: String) -> bool:
if _offset + text_to_read.length() > _text.length():
return false
var i = 0
while (i < text_to_read.length()):
if text_to_read[i] != _text[_offset + i]:
return false
i += 1
_offset += text_to_read.length()
return true
# (bool | String, String) -> bool
func expect(condition_or_expected_str, message = null) -> bool:
var _condition = false
if condition_or_expected_str is String:
_condition = try_read(condition_or_expected_str)
elif condition_or_expected_str is bool:
_condition = condition_or_expected_str
if !_condition:
if message == null:
message = "Unexpected token"
else:
message = "Expected " + message
message += str(" at offset ", _offset)
InkUtils.throw_exception(message)
return false
return true
func skip_whitespace():
while _offset < _text.length():
var c = _text[_offset]
if c == " " || c == "\t" || c == "\n" || c == "\r":
_offset += 1
else:
break
var _text = null # String
var _offset: int = 0 # int
var _root_object # Variant
# ######################################################################## #
# GDScript extra methods
# ######################################################################## #
func is_ink_class(type: String) -> bool:
return type == "InkSimpleJSON.Reader" || super.is_ink_class(type)
func get_ink_class() -> String:
return "InkSimpleJSON.Reader"
class Writer extends InkBase:
# ######################################################################## #
# Imports
# ######################################################################## #
var InkStringWriter := load("res://addons/inkgd/runtime/extra/string_writer.gd") as GDScript
var InkStateElement := load("res://addons/inkgd/runtime/extra/state_element.gd") as GDScript
# (String) -> Writer
func _init():
self._writer = InkStringWriter.new()
# (Callable) -> void
func write_object(inner: Callable) -> void:
write_object_start()
inner.call(self)
write_object_end()
func write_object_start() -> void:
start_new_object(true)
self._state_stack.push_front(InkStateElement.new(InkStateElement.State.OBJECT))
self._writer.write("{")
func write_object_end() -> void:
assert_that(self.state == InkStateElement.State.OBJECT)
self._writer.write("}")
self._state_stack.pop_front()
# These two methods don't need to be implemented in GDScript.
#
# public void WriteProperty(string name, Action<Writer> inner)
# public void WriteProperty(int id, Action<Writer> inner)
# Also include:
# void WriteProperty<T>(T name, Action<Writer> inner)
# (String, Variant) -> void
func write_property(name: String, content) -> void:
if (content is String || content is int || content is bool):
write_property_start(name)
write(content)
write_property_end()
elif content is Callable:
write_property_start(name)
content.call(self)
write_property_end()
else:
push_error("Wrong type for 'content': %s" % str(content))
# These two methods don't need to be implemented in GDScript.
#
# public void WritePropertyStart(string name)
# public void WritePropertyStart(int id)
# () -> void
func write_property_end() -> void:
assert_that(self.state == InkStateElement.State.PROPERTY)
assert_that(self.child_count == 1)
self._state_stack.pop_front()
# (String) -> void
func write_property_name_start() -> void:
assert_that(self.state == InkStateElement.State.OBJECT)
if self.child_count > 0:
self._writer.write(',')
self._writer.write('"')
increment_child_count()
self._state_stack.push_front(InkStateElement.new(InkStateElement.State.PROPERTY))
self._state_stack.push_front(InkStateElement.new(InkStateElement.State.PROPERTY_NAME))
# () -> void
func write_property_name_end() -> void:
assert_that(self.state == InkStateElement.State.PROPERTY_NAME)
self._writer.write('":')
self._state_stack.pop_front()
# (String) -> void
func write_property_name_inner(string: String) -> void:
assert_that(self.state == InkStateElement.State.PROPERTY_NAME)
self._writer.write(string)
# (Variant) -> void
func write_property_start(name) -> void:
assert_that(self.state == InkStateElement.State.OBJECT)
if self.child_count > 0:
self._writer.write(',')
self._writer.write('"')
self._writer.write(str(name))
self._writer.write('":')
increment_child_count()
_state_stack.push_front(InkStateElement.new(InkStateElement.State.PROPERTY))
# () -> void
func write_array_start() -> void:
start_new_object(true)
_state_stack.push_front(InkStateElement.new(InkStateElement.State.ARRAY))
_writer.write("[")
# () -> void
func write_array_end() -> void:
assert_that(self.state == InkStateElement.State.ARRAY)
_writer.write("]")
_state_stack.pop_front()
# This method didn't exist as-is in the original implementation.
# (Variant) -> void
func write(content) -> void:
if content is int:
write_int(content)
elif content is float:
write_float(content)
elif content is String:
write_string(content)
elif content is bool:
write_bool(content)
else:
push_error("Wrong type for 'content': %s" % str(content))
# (int) -> void
func write_int(i: int) -> void:
start_new_object(false)
_writer.write(str(i))
# (float) -> void
func write_float(f: float) -> void:
start_new_object(false)
var float_str = str(f)
# We could probably use 3.402823e+38, but keeping
# ±3.4e+38 for compatibility with the reference implementation.
if float_str == "inf":
_writer.write("3.4e+38")
elif float_str == "-inf":
_writer.write("-3.4e+38")
elif float_str == "nan":
_writer.write("0.0")
else:
_writer.write(float_str)
# The exponent part is defensive as Godot doesn't seem to convert
# floats to string in such a way.
if !("." in float_str) && !("e" in float_str) && !("E" in float_str):
_writer.write(".0")
# (String, bool) -> void
func write_string(string: String, escape: bool = true):
start_new_object(false)
_writer.write('"')
if escape:
write_escaped_string(string)
else:
_writer.write(string)
_writer.write('"')
# (bool) -> void
func write_bool(b: bool) -> void:
start_new_object(false)
_writer.write("true" if b else "false")
# () -> void
func write_null() -> void:
start_new_object(false)
_writer.write("null")
# () -> void
func write_string_start() -> void:
start_new_object(true)
_state_stack.push_front(InkStateElement.new(InkStateElement.State.STRING))
_writer.write('"')
# () -> void
func write_string_end() -> void:
assert_that(state == InkStateElement.State.STRING)
_writer.write('"')
_state_stack.pop_front()
# (string, bool) -> void
func write_string_inner(string: String, escape: bool = true) -> void:
assert_that(self.state == InkStateElement.State.STRING)
if escape:
write_escaped_string(string)
else:
_writer.write(string)
# (String) -> void
func write_escaped_string(string: String) -> void:
for c in string:
if c < ' ':
match c:
"\n":
_writer.write("\\n")
"\t":
_writer.write("\\t")
else:
match c:
'\\', '"':
_writer.write("\\")
_writer.write(c)
_:
_writer.write(c)
# (bool) -> void
func start_new_object(container: bool) -> void:
if container:
assert_that(
self.state == InkStateElement.State.NONE ||
self.state == InkStateElement.State.PROPERTY ||
self.state == InkStateElement.State.ARRAY
)
else:
assert_that(
self.state == InkStateElement.State.PROPERTY ||
self.state == InkStateElement.State.ARRAY
)
if self.state == InkStateElement.State.ARRAY && self.child_count > 0:
_writer.write(",")
if self.state == InkStateElement.State.PROPERTY:
assert_that(self.child_count == 0)
if (
self.state == InkStateElement.State.ARRAY ||
self.state == InkStateElement.State.PROPERTY
):
increment_child_count()
var state: int: # StateElement.State
get:
if _state_stack.size() > 0:
return _state_stack.front().type
else:
return InkStateElement.State.NONE
var child_count: int: # int
get:
if _state_stack.size() > 0:
return _state_stack.front().child_count
else:
return 0
# () -> void
func increment_child_count() -> void:
assert_that(_state_stack.size() > 0)
var curr_el = _state_stack.pop_front()
curr_el.child_count += 1
_state_stack.push_front(curr_el)
# (bool) -> void
func assert_that(condition: bool) -> void:
if OS.is_debug_build():
return
if !condition:
push_error("Assert failed while writing JSON")
assert(condition)
# () -> String
func _to_string() -> String:
return _writer._to_string()
var _state_stack: Array = [] # Array<StateElement>
var _writer: InkStringWriter
# ######################################################################## #
# GDScript extra methods
# ######################################################################## #
func is_ink_class(type: String) -> bool:
return type == "InkSimpleJSON.Writer" || super.is_ink_class(type)
func get_ink_class() -> String:
return "InkSimpleJSON.Writer"
class JsonError:
func init():
pass

View file

@ -0,0 +1,105 @@
# warning-ignore-all:shadowed_variable
# warning-ignore-all:unused_class_variable
# ############################################################################ #
# 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 InkStatePatch
# ############################################################################ #
# 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
# ############################################################################ #
# Dictionary<String, InkObject>
var globals: Dictionary: get = get_globals
func get_globals() -> Dictionary:
return _globals
# StringSet
var changed_variables: InkStringSet: get = get_changed_variables
func get_changed_variables() -> InkStringSet:
return _changed_variables
# Dictionary<InkContainer, int>
var visit_counts: Dictionary: get = get_visit_counts
func get_visit_counts() -> Dictionary:
return _visit_counts
# Dictionary<InkContainer, int>
var turn_indices : get = get_turn_indices
func get_turn_indices() -> Dictionary:
return _turn_indices
# ############################################################################ #
func _init(to_copy: InkStatePatch):
if to_copy != null:
_globals = to_copy._globals.duplicate()
_changed_variables = to_copy._changed_variables.duplicate()
_visit_counts = to_copy._visit_counts.duplicate()
_turn_indices = to_copy._turn_indices.duplicate()
else:
_globals = {}
_changed_variables = InkStringSet.new()
_visit_counts = {}
_turn_indices = {}
# (String) -> { exists: bool, result: InkObject }
func try_get_global(name) -> InkTryGetResult:
if _globals.has(name):
return InkTryGetResult.new(true, _globals[name])
return InkTryGetResult.new(false, null)
func set_global(name: String, value: InkObject) -> void:
_globals[name] = value
func add_changed_variable(name: String) -> void:
_changed_variables.append(name)
# (InkContainer) -> { exists: bool, result: int }
func try_get_visit_count(container) -> InkTryGetResult:
if _visit_counts.has(container):
return InkTryGetResult.new(true, _visit_counts[container])
return InkTryGetResult.new(false, 0)
func set_visit_count(container: InkContainer, index: int) -> void:
_visit_counts[container] = index
func set_turn_index(container: InkContainer, index: int) -> void:
_turn_indices[container] = index
# (InkContainer) -> { exists: bool, result: int }
func try_get_turn_index(container) -> InkTryGetResult:
if _turn_indices.has(container):
return InkTryGetResult.new(true, _turn_indices[container])
return InkTryGetResult.new(false, 0)
var _globals: Dictionary
var _changed_variables: InkStringSet
var _visit_counts: Dictionary
var _turn_indices: Dictionary
# ############################################################################ #
# GDScript extra methods
# ############################################################################ #
func is_ink_class(type: String) -> bool:
return type == "StatePatch" || super.is_ink_class(type)
func get_ink_class() -> String:
return "StatePatch"

View file

@ -0,0 +1,200 @@
# warning-ignore-all:unused_class_variable
# warning-ignore-all:shadowed_variable
# ############################################################################ #
# 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 Node
# Hiding this type to prevent registration of "private" nodes.
# See https://github.com/godotengine/godot-proposals/issues/1047
# class_name InkRuntimeNode
# Expected to be added to the SceneTree as a singleton object.
# ############################################################################ #
# Imports
# ############################################################################ #
var InkStaticJSON := load("res://addons/inkgd/runtime/static/json.gd") as GDScript
var InkStaticNativeFunctionCall := load("res://addons/inkgd/runtime/static/native_function_call.gd") as GDScript
# ############################################################################ #
# Signals
# ############################################################################ #
## Emitted when the runtime encountered an exception. Exception are not
## recoverable and may corrupt the state. They are the consequence of either
## a programmer error or a bug in the runtime.
signal exception_raised(message, stack_trace)
# ############################################################################ #
# Properties
# ############################################################################ #
# Skips saving global values that remain equal to the initial values that were
# declared in Ink.
var dont_save_default_values: bool = true
## Uses `assert` instead of `push_error` to report critical errors, thus
## making them more explicit during development.
var stop_execution_on_exception: bool = true
## Uses `assert` instead of `push_error` to report story errors, thus
## making them more explicit during development.
var stop_execution_on_error: bool = true
# ############################################################################ #
var should_pause_execution_on_runtime_error: bool: get = get_speore, set = set_speore
func get_speore() -> bool:
printerr(
"'should_pause_execution_on_runtime_error' is deprecated, " +
"use 'stop_execution_on_exception' instead."
)
return stop_execution_on_exception
func set_speore(value: bool):
printerr(
"'should_pause_execution_on_runtime_error' is deprecated, " +
"use 'stop_execution_on_exception' instead."
)
stop_execution_on_exception = value
var should_pause_execution_on_story_error: bool: get = get_speose, set = set_speose
func get_speose() -> bool:
printerr(
"'should_pause_execution_on_story_error' is deprecated, " +
"use 'stop_execution_on_error' instead."
)
return stop_execution_on_error
func set_speose(value: bool):
printerr(
"'should_pause_execution_on_story_error' is deprecated, " +
"use 'stop_execution_on_error' instead."
)
stop_execution_on_error = value
# ############################################################################ #
# "Static" Properties
# ############################################################################ #
var native_function_call: InkStaticNativeFunctionCall = InkStaticNativeFunctionCall.new()
var json: InkStaticJSON = InkStaticJSON.new(native_function_call)
# ############################################################################ #
# Internal Properties
# ############################################################################ #
# Recorded exceptions don't emit the 'exception' signal, since they are
# expected to be processed by the story and emitted through 'on_error'.
var record_story_exceptions: bool = false
var current_story_exceptions: Array = []
var _argument_exception_raised: bool
var _exception_raised: bool
# ############################################################################ #
# Overrides
# ############################################################################ #
func _init():
name = "__InkRuntime"
# ############################################################################ #
# Internal Methods
# ############################################################################ #
func clear_raised_exceptions() -> bool:
if _argument_exception_raised:
_argument_exception_raised = false
return true
if _argument_exception_raised:
_argument_exception_raised = false
return true
return false
func handle_exception(message: String) -> void:
var exception_message = "EXCEPTION: %s" % message
var stack_trace = _get_stack_trace()
_handle_generic_exception(
exception_message,
stop_execution_on_exception,
stack_trace
)
_exception_raised
emit_signal("exception_raised", exception_message, stack_trace)
func handle_argument_exception(message: String) -> void:
var exception_message = "ARGUMENT EXCEPTION: %s" % message
var stack_trace = _get_stack_trace()
_handle_generic_exception(
exception_message,
stop_execution_on_error,
stack_trace
)
_argument_exception_raised = true
emit_signal("exception_raised", exception_message, stack_trace)
func handle_story_exception(message: String, use_end_line_number: bool, metadata) -> void:
# When exceptions are "recorded", they are not reported immediately.
# 'Story' will take care of that at the end of the step.
if record_story_exceptions:
current_story_exceptions.append(StoryError.new(message, use_end_line_number, metadata))
else:
var exception_message = "STORY EXCEPTION: %s" % message
var stack_trace = _get_stack_trace()
_handle_generic_exception(exception_message, stop_execution_on_error, stack_trace)
emit_signal("exception_raised", exception_message, stack_trace)
# ############################################################################ #
# Private Methods
# ############################################################################ #
func _handle_generic_exception(
message: String,
should_pause_execution: bool,
stack_trace: PackedStringArray
) -> void:
if OS.is_debug_build():
if should_pause_execution:
assert(false, message)
elif Engine.is_editor_hint():
printerr(message)
if stack_trace.size() > 0:
printerr("Stack trace:")
for line in stack_trace:
printerr(line)
else:
push_error(message)
func _get_stack_trace() -> PackedStringArray:
var trace := PackedStringArray()
var i = 1
for stack_element in get_stack():
if i <= 3:
i += 1
continue
trace.append(str(
" ", (i - 3), " - ", stack_element["source"], ":",
stack_element["line"], " - at function: ", stack_element["function"]
))
i += 1
return trace

View file

@ -0,0 +1,623 @@
# ############################################################################ #
# 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 InkStaticJSON
# In the C# code this class has only static methods. In the GDScript, it will rather
# be a unique object, added to the InkRuntime singleton.
# ############################################################################ #
var InkValue = load("res://addons/inkgd/runtime/values/value.gd")
var InkStringValue = load("res://addons/inkgd/runtime/values/string_value.gd")
var InkDivertTargetValue = load("res://addons/inkgd/runtime/values/divert_target_value.gd")
var InkVariablePointerValue = load("res://addons/inkgd/runtime/values/variable_pointer_value.gd")
var InkListValue = load("res://addons/inkgd/runtime/values/list_value.gd")
var InkList = load("res://addons/inkgd/runtime/lists/ink_list.gd")
var InkListDefinition = load("res://addons/inkgd/runtime/lists/list_definition.gd")
var InkListDefinitionsOrigin = load("res://addons/inkgd/runtime/lists/list_definitions_origin.gd")
# ############################################################################ #
# (Array<Variant>, bool) -> Array
func jarray_to_runtime_obj_list(jarray: Array, skip_last = false) -> Array:
var count = jarray.size()
if skip_last:
count -= 1
var list = []
var i = 0
while (i < count):
var jtok = jarray[i]
var runtime_obj = jtoken_to_runtime_object(jtok)
list.append(runtime_obj)
i += 1
return list
# (self.Json.Writer, Dictionary<String, InkObject>) -> void
func write_dictionary_runtime_objs(writer, dictionary: Dictionary) -> void:
writer.write_object_start()
for key in dictionary:
writer.write_property_start(key)
write_runtime_object(writer, dictionary[key])
writer.write_property_end()
writer.write_object_end()
# (self.Json.Writer, Array<InkObject>) -> void
func write_list_runtime_objs(writer, list: Array) -> void:
writer.write_array_start()
for val in list:
write_runtime_object(writer, val)
writer.write_array_end()
# (self.Json.Writer, Array<Int>) -> void
func write_int_dictionary(writer, dict: Dictionary) -> void:
writer.write_object_start()
for key in dict:
writer.write_property(key, dict[key])
writer.write_object_end()
# (self.Json.Writer, InkObject) -> void
func write_runtime_object(writer, obj: InkObject) -> void:
var container = InkUtils.as_or_null(obj, "InkContainer")
if container:
write_runtime_container(writer, container)
return
var divert = InkUtils.as_or_null(obj, "Divert")
if divert:
var div_type_key = "->" # String
if divert.is_external:
div_type_key = "x()"
elif divert.pushes_to_stack:
if divert.stack_push_type == Ink.PushPopType.FUNCTION:
div_type_key = "f()"
elif divert.stackPushType == Ink.PushPopType.TUNNEL:
div_type_key = "->t->"
var target_str = null # String
if divert.has_variable_target:
target_str = divert.variable_divert_name
else:
target_str = divert.target_path_string
writer.write_object_start()
writer.write_property(div_type_key, target_str)
if divert.has_variable_target:
writer.write_property("var", true)
if divert.is_conditional:
writer.write_property("c", true)
if divert.external_args > 0:
writer.write_property("exArgs", divert.external_args)
writer.write_object_end()
return
var choice_point = InkUtils.as_or_null(obj, "ChoicePoint")
if choice_point:
writer.write_object_start()
writer.write_property("*", choice_point.path_string_on_choice)
writer.write_property("flg", choice_point.flags)
writer.write_object_end()
return
var bool_val = InkUtils.as_or_null(obj, "BoolValue")
if bool_val:
writer.write(bool_val.value)
return
var int_val = InkUtils.as_or_null(obj, "IntValue")
if int_val:
writer.write(int_val.value)
return
var float_val = InkUtils.as_or_null(obj, "FloatValue")
if float_val:
writer.write(float_val.value)
return
var str_val = InkUtils.as_or_null(obj, "StringValue")
if str_val:
if str_val.is_newline:
writer.write_string("\\n", false)
else:
writer.write_string_start()
writer.write_string_inner("^")
writer.write_string_inner(str_val.value)
writer.write_string_end()
return
var list_val = InkUtils.as_or_null(obj, "ListValue")
if list_val:
write_ink_list(writer, list_val)
return
var div_target_val = InkUtils.as_or_null(obj, "DivertTargetValue")
if div_target_val:
writer.write_object_start()
writer.write_property("^->", div_target_val.value.components_string)
writer.write_object_end()
return
var var_ptr_val = InkUtils.as_or_null(obj, "VariablePointerValue")
if var_ptr_val:
writer.write_object_start()
writer.write_property("^var", var_ptr_val.value)
writer.write_property("ci", var_ptr_val.context_index)
writer.write_object_end()
return
var glue = InkUtils.as_or_null(obj, "Glue")
if glue:
writer.write("<>")
return
var control_cmd = InkUtils.as_or_null(obj, "ControlCommand")
if control_cmd:
writer.write(self._control_command_names[control_cmd.command_type])
return
var native_func = InkUtils.as_or_null(obj, "NativeFunctionCall")
if native_func:
var name = native_func.name
if name == "^": name = "L^"
writer.write(name)
return
var var_ref = InkUtils.as_or_null(obj, "VariableReference")
if var_ref:
writer.write_object_start()
var read_count_path = var_ref.path_string_for_count
if read_count_path != null:
writer.write_property(["CNT?"], read_count_path)
else:
writer.write_property(["VAR?"], var_ref.name)
writer.write_object_end()
return
var var_ass = InkUtils.as_or_null(obj, "VariableAssignment")
if var_ass:
writer.write_object_start()
var key = "VAR=" if var_ass.is_global else "temp="
writer.write_property(key, var_ass.variable_name)
if !var_ass.is_new_declaration:
writer.write_property("re", true)
writer.write_object_end()
return
var void_obj = InkUtils.as_or_null(obj, "Void")
if void_obj:
writer.write("void")
return
# Legacy Tags (replaced in 1.1+)
var tag = InkUtils.as_or_null(obj, "Tag")
if tag:
writer.write_object_start()
writer.write_property("#", tag.text)
writer.write_object_end()
return
var choice = InkUtils.as_or_null(obj, "Choice")
if choice:
write_choice(writer, choice)
return
InkUtils.throw_exception("Failed to convert runtime object to Json token: %s" % obj)
return
# (Dictionary<String, Variant>) -> Dictionary<String, InkObject>
func jobject_to_dictionary_runtime_objs(jobject: Dictionary) -> Dictionary:
var dict = {}
for key in jobject:
dict[key] = jtoken_to_runtime_object(jobject[key])
return dict
# (Dictionary<String, Variant>) -> Dictionary<String, int>
func jobject_to_int_dictionary(jobject: Dictionary) -> Dictionary:
var dict = {}
for key in jobject:
dict[key] = int(jobject[key])
return dict
# (Variant) -> InkObject
func jtoken_to_runtime_object(token) -> InkObject:
if token is int || token is float || token is bool:
return InkValue.create(token)
if token is String:
var _str = token
var first_char = _str[0]
if first_char == "^":
return InkStringValue.new_with(_str.substr(1, _str.length() - 1))
elif first_char == "\n" && _str.length() == 1:
return InkStringValue.new_with("\n")
if _str == "<>": return InkGlue.new()
var i = 0
while (i < _control_command_names.size()):
var cmd_name = _control_command_names[i]
if _str == cmd_name:
return InkControlCommand.new(i)
i += 1
if _str == "L^": _str = "^"
if _static_native_function_call.call_exists_with_name(_str):
return InkNativeFunctionCall.call_with_name(_str, _static_native_function_call)
if _str == "->->":
return InkControlCommand.pop_tunnel()
elif _str == "~ret":
return InkControlCommand.pop_function()
if _str == "void":
return InkVoid.new()
if token is Dictionary:
var obj = token
var prop_value
if obj.has("^->"):
prop_value = obj["^->"]
return InkDivertTargetValue.new_with(
InkPath.new_with_components_string(str(prop_value))
)
if obj.has("^var"):
prop_value = obj["^var"]
var var_ptr = InkVariablePointerValue.new_with_context(str(prop_value))
if (obj.has("ci")):
prop_value = obj["ci"]
var_ptr.context_index = int(prop_value)
return var_ptr
var is_divert = false
var pushes_to_stack = false
var div_push_type = Ink.PushPopType.FUNCTION
var external = false
if obj.has("->"):
prop_value = obj["->"]
is_divert = true
elif obj.has("f()"):
prop_value = obj["f()"]
is_divert = true
pushes_to_stack = true
div_push_type = Ink.PushPopType.FUNCTION
elif obj.has("->t->"):
prop_value = obj["->t->"]
is_divert = true
pushes_to_stack = true
div_push_type = Ink.PushPopType.TUNNEL
elif obj.has("x()"):
prop_value = obj["x()"]
is_divert = true
external = true
pushes_to_stack = false
div_push_type = Ink.PushPopType.FUNCTION
if is_divert:
var divert = InkDivert.new()
divert.pushes_to_stack = pushes_to_stack
divert.stack_push_type = div_push_type
divert.is_external = external
var target = str(prop_value)
if obj.has("var"):
prop_value = obj["var"]
divert.variable_divert_name = target
else:
divert.target_path_string = target
divert.is_conditional = obj.has("c")
#if divert.is_conditional: prop_value = obj["c"]
if external:
if obj.has("exArgs"):
prop_value = obj["exArgs"]
divert.external_args = int(prop_value)
return divert
if obj.has("*"):
prop_value = obj["*"]
var choice = InkChoicePoint.new()
choice.path_string_on_choice = str(prop_value)
if obj.has("flg"):
prop_value = obj["flg"]
choice.flags = int(prop_value)
return choice
if obj.has("VAR?"):
prop_value = obj["VAR?"]
return InkVariableReference.new(str(prop_value))
elif obj.has("CNT?"):
prop_value = obj["CNT?"]
var read_count_var_ref = InkVariableReference.new()
read_count_var_ref.path_string_for_count = str(prop_value)
return read_count_var_ref
var is_var_ass = false
var is_global_var = false
if obj.has("VAR="):
prop_value = obj["VAR="]
is_var_ass = true
is_global_var = true
elif obj.has("temp="):
prop_value = obj["temp="]
is_var_ass = true
is_global_var = false
if is_var_ass:
var var_name = str(prop_value)
var is_new_decl = !obj.has("re")
var var_ass = InkVariableAssignment.new_with(var_name, is_new_decl)
var_ass.is_global = is_global_var
return var_ass
# Legacy Tags with texts (replaced in 1.1+)
if obj.has("#"):
prop_value = obj["#"]
return InkTag.new(str(prop_value))
if obj.has("list"):
prop_value = obj["list"]
var list_content = prop_value
var raw_list = InkList.new()
if obj.has("origins"):
prop_value = obj["origins"]
var names_as_objs = prop_value
raw_list.set_initial_origin_names(names_as_objs)
for name_to_val_key in list_content:
var item = InkListItem.new_with_full_name(name_to_val_key)
var val = list_content[name_to_val_key]
raw_list.set_item(item, val)
return InkListValue.new_with(raw_list)
if obj.has("originalChoicePath"):
return jobject_to_choice(obj)
if token is Array:
var container = jarray_to_container(token)
return container
if token == null:
return null
InkUtils.throw_exception("Failed to convert token to runtime object: %s" % str(token))
return null
# (self.Json.Writer, InkContainer, Bool) -> void
func write_runtime_container(writer, container: InkContainer, without_name = false) -> void:
writer.write_array_start()
for c in container.content:
write_runtime_object(writer, c)
var named_only_content = container.named_only_content
var count_flags = container.count_flags
var has_name_property = (container.name != null) && !without_name
var has_terminator = named_only_content != null || count_flags > 0 || has_name_property
if has_terminator:
writer.write_object_start()
if named_only_content != null:
for named_content_key in named_only_content:
var name = named_content_key
var named_container = InkUtils.as_or_null(named_only_content[named_content_key], "InkContainer")
writer.write_property_start(name)
write_runtime_container(writer, named_container, true)
writer.write_property_end()
if count_flags > 0:
writer.write_property("#f", count_flags)
if has_name_property:
writer.write_property("#n", container.name)
if has_terminator:
writer.write_object_end()
else:
writer.write_null()
writer.write_array_end()
# (Array<Variant>) -> InkContainer
func jarray_to_container(jarray: Array) -> InkContainer:
var container = InkContainer.new()
container.content = jarray_to_runtime_obj_list(jarray, true)
var terminating_obj = InkUtils.as_or_null(jarray.back(), "Dictionary") # Dictionary<string, Variant>
if terminating_obj != null:
var named_only_content = {} # new Dictionary<String, InkObject>
for key in terminating_obj:
if key == "#f":
container.count_flags = int(terminating_obj[key])
elif key == "#n":
container.name = str(terminating_obj[key])
else:
var named_content_item = jtoken_to_runtime_object(terminating_obj[key])
var named_sub_container = InkUtils.as_or_null(named_content_item, "InkContainer")
if named_sub_container:
named_sub_container.name = key
named_only_content[key] = named_content_item
container.named_only_content = named_only_content
return container
# (Dictionary<String, Variant>) -> Choice
func jobject_to_choice(jobj: Dictionary) -> InkChoice:
var choice = InkChoice.new()
choice.text = str(jobj["text"])
choice.index = int(jobj["index"])
choice.source_path = str(jobj["originalChoicePath"])
choice.original_thread_index = int(jobj["originalThreadIndex"])
choice.path_string_on_choice = str(jobj["targetPath"])
return choice
# (self.Json.Writer, Choice) -> Void
func write_choice(writer, choice: InkChoice) -> void:
writer.write_object_start()
writer.write_property("text", choice.text)
writer.write_property("index", choice.index)
writer.write_property("originalChoicePath", choice.source_path)
writer.write_property("originalThreadIndex", choice.original_thread_index)
writer.write_property("targetPath", choice.path_string_on_choice)
writer.write_object_end()
# (self.Json.Writer, ListValue) -> Void
func write_ink_list(writer, list_val):
var raw_list = list_val.value
writer.write_object_start()
writer.write_property_start("list")
writer.write_object_start()
for item_key in raw_list.raw_keys():
var item = InkListItem.from_serialized_key(item_key)
var item_val = raw_list.get_raw(item_key)
writer.write_property_name_start()
writer.write_property_name_inner(item.origin_name if item.origin_name else "?")
writer.write_property_name_inner(".")
writer.write_property_name_inner(item.item_name)
writer.write_property_name_end()
writer.write(item_val)
writer.write_property_end()
writer.write_object_end()
writer.write_property_end()
if raw_list.size() == 0 && raw_list.origin_names != null && raw_list.origin_names.size() > 0:
writer.write_property_start("origins")
writer.write_array_start()
for name in raw_list.origin_names:
writer.write(name)
writer.write_array_end()
writer.write_property_end()
writer.write_object_end()
# (ListDefinitionsOrigin) -> Dictionary<String, Variant>
func list_definitions_to_jtoken (origin):
var result = {} # Dictionary<String, Variant>
for def in origin.lists:
var list_def_json = {} # Dictionary<String, Variant>
for item_to_val_key in def.items:
var item = InkListItem.from_serialized_key(item_to_val_key)
var val = def.items[item_to_val_key]
list_def_json[item.item_name] = val
result[def.name] = list_def_json
return result
# (Variant) -> ListDefinitionsOrigin
func jtoken_to_list_definitions(obj):
var defs_obj = obj
var all_defs = [] # Array<ListDefinition>
for k in defs_obj:
var name = str(k) # String
var list_def_json = defs_obj[k] # Dictionary<String, Variant>
var items = {} # Dictionary<String, int>
for name_value_key in list_def_json:
items[name_value_key] = int(list_def_json[name_value_key])
var def = InkListDefinition.new(name, items)
all_defs.append(def)
return InkListDefinitionsOrigin.new(all_defs)
func _init(native_function_call):
_static_native_function_call = native_function_call
_control_command_names = []
_control_command_names.append("ev") # EVAL_START
_control_command_names.append("out") # EVAL_OUTPUT
_control_command_names.append("/ev") # EVAL_END
_control_command_names.append("du") # DUPLICATE
_control_command_names.append("pop") # POP_EVALUATED_VALUE
_control_command_names.append("~ret") # POP_FUNCTION
_control_command_names.append("->->") # POP_TUNNEL
_control_command_names.append("str") # BEGIN_STRING
_control_command_names.append("/str") # END_STRING
_control_command_names.append("nop") # NO_OP
_control_command_names.append("choiceCnt") # CHOICE_COUNT
_control_command_names.append("turn") # TURNS
_control_command_names.append("turns") # TURNS_SINCE
_control_command_names.append("readc") # READ_COUNT
_control_command_names.append("rnd") # RANDOM
_control_command_names.append("srnd") # SEED_RANDOM
_control_command_names.append("visit") # VISIT_INDEX
_control_command_names.append("seq") # SEQUENCE_SHUFFLE_INDEX
_control_command_names.append("thread") # START_THREAD
_control_command_names.append("done") # DONE
_control_command_names.append("end") # END
_control_command_names.append("listInt") # LIST_FROM_INT
_control_command_names.append("range") # LIST_RANGE
_control_command_names.append("lrnd") # LIST_RANDOM
_control_command_names.append("#") # BEGIN_TAG
_control_command_names.append("/#") # END_TAG
var i = 0
while i < InkControlCommand.CommandType.TOTAL_VALUES:
if _control_command_names[i] == null:
InkUtils.throw_exception("Control command not accounted for in serialisation")
i += 1
# Array<String>
var _control_command_names = null
# ############################################################################ #
# Eventually a pointer to InkRuntime.StaticJson
var _static_native_function_call = null

View file

@ -0,0 +1,284 @@
# warning-ignore-all:unused_class_variable
# ############################################################################ #
# 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 RefCounted
class_name InkStaticNativeFunctionCall
# ############################################################################ #
# Imports
# ############################################################################ #
const ValueType = preload("res://addons/inkgd/runtime/values/value_type.gd").ValueType
# ############################################################################ #
const ADD = "+"
const SUBTRACT = "-"
const DIVIDE = "/"
const MULTIPLY = "*"
const MOD = "%"
const NEGATE = "_"
const EQUALS = "=="
const GREATER = ">"
const LESS = "<"
const GREATER_THAN_OR_EQUALS = ">="
const LESS_THAN_OR_EQUALS = "<="
const NOT_EQUALS = "!="
const NOT = "!"
const AND = "&&"
const OR = "||"
const MIN = "MIN"
const MAX = "MAX"
const POW = "POW"
const FLOOR = "FLOOR"
const CEILING = "CEILING"
const INT = "INT"
const FLOAT = "FLOAT"
const HAS = "?"
const HASNT = "!?"
const INTERSECT = "^"
const LIST_MIN = "LIST_MIN"
const LIST_MAX = "LIST_MAX"
const ALL = "LIST_ALL"
const COUNT = "LIST_COUNT"
const VALUE_OF_LIST = "LIST_VALUE"
const INVERT = "LIST_INVERT"
# ############################################################################ #
var native_functions = null # Dictionary<String, String>
# ############################################################################ #
# (String) -> Bool
func call_exists_with_name(function_name):
generate_native_functions_if_necessary()
return native_functions.has(function_name)
# () -> void
func generate_native_functions_if_necessary():
if native_functions == null:
native_functions = {}
add_int_binary_op(ADD, "int_binary_op_add")
add_int_binary_op(SUBTRACT, "int_binary_op_substract")
add_int_binary_op(MULTIPLY, "int_binary_op_multiply")
add_int_binary_op(DIVIDE, "int_binary_op_divide")
add_int_binary_op(MOD, "int_binary_op_mod")
add_int_unary_op (NEGATE, "int_unary_op_negate")
add_int_binary_op(EQUALS, "int_binary_op_equals")
add_int_binary_op(GREATER, "int_binary_op_greater")
add_int_binary_op(LESS, "int_binary_op_less")
add_int_binary_op(GREATER_THAN_OR_EQUALS, "int_binary_op_greater_than_or_equals")
add_int_binary_op(LESS_THAN_OR_EQUALS, "int_binary_op_less_than_or_equals")
add_int_binary_op(NOT_EQUALS, "int_binary_op_not_equals")
add_int_unary_op (NOT, "int_unary_op_not")
add_int_binary_op(AND, "int_binary_op_and")
add_int_binary_op(OR, "int_binary_op_or")
add_int_binary_op(MAX, "int_binary_op_max")
add_int_binary_op(MIN, "int_binary_op_min")
add_int_binary_op(POW, "int_binary_op_pow")
add_int_unary_op (FLOOR, "int_unary_op_floor")
add_int_unary_op (CEILING, "int_unary_op_ceiling")
add_int_unary_op (INT, "int_unary_op_int")
add_int_unary_op (FLOAT, "int_unary_op_float")
add_float_binary_op(ADD, "float_binary_op_add")
add_float_binary_op(SUBTRACT, "float_binary_op_substract")
add_float_binary_op(MULTIPLY, "float_binary_op_multiply")
add_float_binary_op(DIVIDE, "float_binary_op_divide")
add_float_binary_op(MOD, "float_binary_op_mod")
add_float_unary_op (NEGATE, "float_unary_op_negate")
add_float_binary_op(EQUALS, "float_binary_op_equals")
add_float_binary_op(GREATER, "float_binary_op_greater")
add_float_binary_op(LESS, "float_binary_op_less")
add_float_binary_op(GREATER_THAN_OR_EQUALS, "float_binary_op_greater_than_or_equals")
add_float_binary_op(LESS_THAN_OR_EQUALS, "float_binary_op_less_than_or_equals")
add_float_binary_op(NOT_EQUALS, "float_binary_op_not_equals")
add_float_unary_op (NOT, "float_unary_op_not")
add_float_binary_op(AND, "float_binary_op_and")
add_float_binary_op(OR, "float_binary_op_or")
add_float_binary_op(MAX, "float_binary_op_max")
add_float_binary_op(MIN, "float_binary_op_min")
add_float_binary_op(POW, "float_binary_op_pow")
add_float_unary_op (FLOOR, "float_unary_op_floor")
add_float_unary_op (CEILING, "float_unary_op_ceiling")
add_float_unary_op (INT, "float_unary_op_int")
add_float_unary_op (FLOAT, "float_unary_op_float")
add_string_binary_op(ADD, "string_binary_op_add")
add_string_binary_op(EQUALS, "string_binary_op_equals")
add_string_binary_op(NOT_EQUALS, "string_binary_op_not_equals")
add_string_binary_op(HAS, "string_binary_op_has")
add_string_binary_op(HASNT, "string_binary_op_hasnt")
add_list_binary_op (ADD, "list_binary_op_add")
add_list_binary_op (SUBTRACT, "list_binary_op_substract")
add_list_binary_op (HAS, "list_binary_op_has")
add_list_binary_op (HASNT, "list_binary_op_hasnt")
add_list_binary_op (INTERSECT, "list_binary_op_intersect")
add_list_binary_op (EQUALS, "list_binary_op_equals")
add_list_binary_op (GREATER, "list_binary_op_greater")
add_list_binary_op (LESS, "list_binary_op_less")
add_list_binary_op (GREATER_THAN_OR_EQUALS, "list_binary_op_greater_than_or_equals")
add_list_binary_op (LESS_THAN_OR_EQUALS, "list_binary_op_less_than_or_equals")
add_list_binary_op (NOT_EQUALS, "list_binary_op_not_equals")
add_list_binary_op (AND, "list_binary_op_and")
add_list_binary_op (OR, "list_binary_op_or")
add_list_unary_op (NOT, "list_unary_op_not")
add_list_unary_op (INVERT, "list_unary_op_invert")
add_list_unary_op (ALL, "list_unary_op_all")
add_list_unary_op (LIST_MIN, "list_unary_op_list_min")
add_list_unary_op (LIST_MAX, "list_unary_op_list_max")
add_list_unary_op (COUNT, "list_unary_op_count")
add_list_unary_op (VALUE_OF_LIST, "list_unary_op_value_of_list")
add_op_to_native_func(EQUALS, 2, ValueType.DIVERT_TARGET,
"native_func_divert_targets_equal")
add_op_to_native_func(NOT_EQUALS, 2, ValueType.DIVERT_TARGET,
"native_func_divert_targets_not_equal")
# (String, int, ValueType, Variant)
func add_op_to_native_func(name, args, val_type, op):
var native_func = null # NativeFunctionCall
if native_functions.has(name):
native_func = native_functions[name]
else:
native_func = InkNativeFunctionCall.new_with_name_and_number_of_parameters(name, args, self)
native_functions[name] = native_func
native_func.add_op_func_for_type(val_type, op)
func add_int_binary_op(name, op_function_name):
add_op_to_native_func(name, 2, ValueType.INT, op_function_name)
func add_int_unary_op(name, op_function_name):
add_op_to_native_func(name, 1, ValueType.INT, op_function_name)
func add_float_binary_op(name, op_function_name):
add_op_to_native_func(name, 2, ValueType.FLOAT, op_function_name)
func add_float_unary_op(name, op_function_name):
add_op_to_native_func(name, 1, ValueType.FLOAT, op_function_name)
func add_string_binary_op(name, op_function_name):
add_op_to_native_func(name, 2, ValueType.STRING, op_function_name)
func add_list_binary_op(name, op_function_name):
add_op_to_native_func(name, 2, ValueType.LIST, op_function_name)
func add_list_unary_op(name, op_function_name):
add_op_to_native_func(name, 1, ValueType.LIST, op_function_name)
# ############################################################################ #
func int_binary_op_add(x, y): return x + y
func int_binary_op_substract(x, y): return x - y
func int_binary_op_multiply(x, y): return x * y
func int_binary_op_divide(x, y): return x / y
func int_binary_op_mod(x, y): return x % y
func int_unary_op_negate(x): return -x
func int_binary_op_equals(x, y): return x == y
func int_binary_op_greater(x, y): return x > y
func int_binary_op_less(x, y): return x < y
func int_binary_op_greater_than_or_equals(x, y): return x >= y
func int_binary_op_less_than_or_equals(x, y): return x <= y
func int_binary_op_not_equals(x, y): return x != y
func int_unary_op_not(x): return x == 0
func int_binary_op_and(x, y): return x != 0 && y != 0
func int_binary_op_or(x, y): return x != 0 || y != 0
func int_binary_op_max(x, y): return max(x, y)
func int_binary_op_min(x, y): return min(x, y)
func int_binary_op_pow(x, y): return pow(float(x), float(y))
func int_unary_op_floor(x): return x
func int_unary_op_ceiling(x): return x
func int_unary_op_int(x): return x
func int_unary_op_float(x): return float(x)
func float_binary_op_add(x, y): return x + y
func float_binary_op_substract(x, y): return x - y
func float_binary_op_multiply(x, y): return x * y
func float_binary_op_divide(x, y): return x / y
func float_binary_op_mod(x, y): return fmod(x, y)
func float_unary_op_negate(x): return -x
func float_binary_op_equals(x, y): return x == y
func float_binary_op_greater(x, y): return x > y
func float_binary_op_less(x, y): return x < y
func float_binary_op_greater_than_or_equals(x, y): return x >= y
func float_binary_op_less_than_or_equals(x, y): return x <= y
func float_binary_op_not_equals(x, y): return x != y
func float_unary_op_not(x): return x == 0.0
func float_binary_op_and(x, y): return x != 0.0 && y != 0.0
func float_binary_op_or(x, y): return x != 0.0 || y != 0.0
func float_binary_op_max(x, y): return max(x, y)
func float_binary_op_min(x, y): return min(x, y)
func float_binary_op_pow(x, y): return pow(x, y)
func float_unary_op_floor(x): return floor(x)
func float_unary_op_ceiling(x): return ceil(x)
func float_unary_op_int(x): return int(x)
func float_unary_op_float(x): return x
func string_binary_op_add(x, y): return str(x, y)
func string_binary_op_equals(x, y): return x == y
func string_binary_op_not_equals(x, y): return x != y
# Note: The Content Test (in) operator does not returns true when testing
# against the empty string, unlike the behaviour of the original C# runtime.
func string_binary_op_has(x, y): return y == "" || (y in x)
func string_binary_op_hasnt(x, y): return !(y in x) && y != ""
func list_binary_op_add(x, y): return x.union(y)
func list_binary_op_substract(x, y): return x.without(y)
func list_binary_op_has(x, y): return x.contains(y)
func list_binary_op_hasnt(x, y): return !x.contains(y)
func list_binary_op_intersect(x, y): return x.intersection(y)
func list_binary_op_equals(x, y): return x.equals(y)
func list_binary_op_greater(x, y): return x.greater_than(y)
func list_binary_op_less(x, y): return x.less_than(y)
func list_binary_op_greater_than_or_equals(x, y): return x.greater_than_or_equals(y)
func list_binary_op_less_than_or_equals(x, y): return x.less_than_or_equals(y)
func list_binary_op_not_equals(x, y): return !x.equals(y)
func list_binary_op_and(x, y): return x.size() > 0 && y.size() > 0
func list_binary_op_or(x, y): return x.size() > 0 || y.size() > 0
func list_unary_op_not(x): return 1 if x.size() == 0 else 0
func list_unary_op_invert(x): return x.inverse
func list_unary_op_all(x): return x.all
func list_unary_op_list_min(x): return x.min_as_list()
func list_unary_op_list_max(x): return x.max_as_list()
func list_unary_op_count(x): return x.size()
func list_unary_op_value_of_list(x): return x.max_item.value
func native_func_divert_targets_equal(d1, d2): return d1.equals(d2)
func native_func_divert_targets_not_equal(d1, d2): return !d1.equals(d2)

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,125 @@
# warning-ignore-all:shadowed_variable
# warning-ignore-all:unused_class_variable
# ############################################################################ #
# 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.
# ############################################################################ #
# ############################################################################ #
# !! VALUE TYPE
# ############################################################################ #
# Pointers are passed around a lot, to prevent duplicating them all the time
# and confusing the inspector when the debugger is attached, they are
# immutable rather than being duplicated.
extends InkBase
class_name InkPointer
# ############################################################################ #
# InkContainer
# Encapsulating container into a weak ref.
var container: InkContainer:
get:
return self._container.get_ref()
set(value):
assert(false, "Pointer is immutable, cannot set container.")
var _container: WeakRef = WeakRef.new()
var index: int:
get:
return _index
set(value):
assert(false, "Pointer is immutable, cannot set index.")
var _index: int = 0 # int
# (InkContainer, int) -> InkPointer
func _init(container: InkContainer = null, index: int = 0):
if container == null:
self._container = WeakRef.new()
else:
self._container = weakref(container)
self._index = index
# () -> InkContainer
func resolve():
if self.index < 0: return self.container
if self.container == null: return null
if self.container.content.size() == 0: return self.container
if self.index >= self.container.content.size(): return null
return self.container.content[self.index]
# ############################################################################ #
# () -> bool
var is_null: bool: get = get_is_null
func get_is_null() -> bool:
return self.container == null
# ############################################################################ #
# TODO: Make inspectable
# () -> InkPath
var path: InkPath:
get:
if self.is_null:
return null
if self.index >= 0:
return self.container.path.path_by_appending_component(
InkPath.Component.new(self.index)
)
else:
return self.container.path
############################################################################# #
func _to_string() -> String:
if self.container == null:
return "Ink Pointer (null)"
return "Ink Pointer -> %s -- index %d" % [self.container.path._to_string(), self.index]
# (InkContainer) -> InkPointer
static func start_of(container: InkContainer) -> InkPointer:
return InkPointer.new(container, 0)
# ############################################################################ #
# () -> InkPointer
static var null_pointer: InkPointer:
get: return InkPointer.new(null, -1)
# ############################################################################ #
# GDScript extra methods
# ############################################################################ #
func is_ink_class(type: String) -> bool:
return type == "InkPointer" || super.is_ink_class(type)
func get_ink_class() -> String:
return "InkPointer"
func duplicate() -> InkPointer:
return InkPointer.new(self.container, self.index)

View file

@ -0,0 +1,62 @@
# warning-ignore-all:shadowed_variable
# warning-ignore-all:unused_class_variable
# ############################################################################ #
# 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 InkValue
class_name InkBoolValue
# ############################################################################ #
func get_value_type() -> int:
return ValueType.BOOL
func get_is_truthy() -> bool:
return value
func _init():
value = false
# The method takes a `StoryErrorMetadata` object as a parameter that
# doesn't exist in upstream. The metadat are used in case an 'exception'
# is raised. For more information, see story.gd.
func cast(new_type, metadata = null):
if new_type == self.value_type:
return self
if new_type == ValueType.INT:
return IntValue().new_with(1 if value else 0)
if new_type == ValueType.FLOAT:
return FloatValue().new_with(1.0 if value else 0.0)
if new_type == ValueType.STRING:
return StringValue().new_with("true" if value else "false")
InkUtils.throw_story_exception(bad_cast_exception_message(new_type), false, metadata)
return null
func _to_string() -> String:
return "true" if value else "false"
# ######################################################################## #
# GDScript extra methods
# ######################################################################## #
func is_ink_class(type):
return type == "BoolValue" || super.is_ink_class(type)
func get_ink_class():
return "BoolValue"
static func new_with(val):
var value = BoolValue().new()
value._init_with(val)
return value

View file

@ -0,0 +1,60 @@
# warning-ignore-all:shadowed_variable
# warning-ignore-all:unused_class_variable
# ############################################################################ #
# 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 InkValue
class_name InkDivertTargetValue
# ############################################################################ #
var target_path : get = get_target_path, set = set_target_path # InkPath
func get_target_path():
return value
func set_target_path(value):
self.value = value
func get_value_type():
return ValueType.DIVERT_TARGET
func get_is_truthy():
InkUtils.throw_exception("Shouldn't be checking the truthiness of a divert target")
return false
func _init():
value = null
# The method takes a `StoryErrorMetadata` object as a parameter that
# doesn't exist in upstream. The metadat are used in case an 'exception'
# is raised. For more information, see story.gd.
func cast(new_type, metadata = null):
if new_type == self.value_type:
return self
InkUtils.throw_story_exception(bad_cast_exception_message(new_type), false, metadata)
return null
func _to_string() -> String:
return "DivertTargetValue(" + self.target_path._to_string() + ")"
# ######################################################################## #
# GDScript extra methods
# ######################################################################## #
func is_ink_class(type):
return type == "DivertTargetValue" || super.is_ink_class(type)
func get_ink_class():
return "DivertTargetValue"
static func new_with(val):
var value = DivertTargetValue().new()
value._init_with(val)
return value

View file

@ -0,0 +1,59 @@
# warning-ignore-all:shadowed_variable
# warning-ignore-all:unused_class_variable
# ############################################################################ #
# 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 InkValue
class_name InkFloatValue
# ############################################################################ #
func get_value_type():
return ValueType.FLOAT
func get_is_truthy():
return value != 0.0
func _init():
value = 0.0
# The method takes a `StoryErrorMetadata` object as a parameter that
# doesn't exist in upstream. The metadat are used in case an 'exception'
# is raised. For more information, see story.gd.
func cast(new_type, metadata = null):
if new_type == self.value_type:
return self
if new_type == ValueType.BOOL:
return BoolValue().new_with(false if value == 0 else true)
if new_type == ValueType.INT:
return IntValue().new_with(int(value))
if new_type == ValueType.STRING:
return StringValue().new_with(str(value)) # TODO: Check formating
InkUtils.throw_story_exception(bad_cast_exception_message(new_type), false, metadata)
return null
# ######################################################################## #
# GDScript extra methods
# ######################################################################## #
func is_ink_class(type):
return type == "FloatValue" || super.is_ink_class(type)
func get_ink_class():
return "FloatValue"
static func new_with(val):
var value = FloatValue().new()
value._init_with(val)
return value

View file

@ -0,0 +1,59 @@
# warning-ignore-all:shadowed_variable
# warning-ignore-all:unused_class_variable
# ############################################################################ #
# 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 InkValue
class_name InkIntValue
# ############################################################################ #
func get_value_type():
return ValueType.INT
func get_is_truthy():
return value != 0
func _init():
value = 0
# The method takes a `StoryErrorMetadata` object as a parameter that
# doesn't exist in upstream. The metadat are used in case an 'exception'
# is raised. For more information, see story.gd.
func cast(new_type, metadata = null):
if new_type == self.value_type:
return self
if new_type == ValueType.BOOL:
return BoolValue().new_with(false if value == 0 else true)
if new_type == ValueType.FLOAT:
return FloatValue().new_with(float(value))
if new_type == ValueType.STRING:
return StringValue().new_with(str(value))
InkUtils.throw_story_exception(bad_cast_exception_message(new_type), false, metadata)
return null
# ######################################################################## #
# GDScript extra methods
# ######################################################################## #
func is_ink_class(type):
return type == "IntValue" || super.is_ink_class(type)
func get_ink_class():
return "IntValue"
static func new_with(val):
var value = IntValue().new()
value._init_with(val)
return value

View file

@ -0,0 +1,90 @@
# warning-ignore-all:shadowed_variable
# warning-ignore-all:unused_class_variable
# ############################################################################ #
# 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 InkValue
class_name InkListValue
# ############################################################################ #
func get_value_type():
return ValueType.LIST
func get_is_truthy():
return value.size() > 0
# The method takes a `StoryErrorMetadata` object as a parameter that
# doesn't exist in upstream. The metadat are used in case an 'exception'
# is raised. For more information, see story.gd.
func cast(new_type, metadata = null):
if new_type == ValueType.INT:
var max_item = value.max_item
if max_item.key.is_null:
return IntValue().new_with(0)
else:
return IntValue().new_with(max_item.value)
elif new_type == ValueType.FLOAT:
var max_item = value.max_item
if max_item.key.is_null:
return FloatValue().new_with(0.0)
else:
return FloatValue().new_with(float(max_item.value))
elif new_type == ValueType.STRING:
var max_item = value.max_item
if max_item.key.is_null:
return StringValue().new_with("")
else:
return StringValue().new_with(max_item.key._to_string())
if new_type == self.value_type:
return self
InkUtils.throw_story_exception(bad_cast_exception_message(new_type), false, metadata)
return null
func _init():
value = InkList.new()
func _init_with_list(list):
value = InkList.new_with_ink_list(list)
func _init_with_single_item(single_item, single_value):
value = InkList.new_with_single_item(single_item, single_value)
# (InkObject, InkObject) -> void
static func retain_list_origins_for_assignment(old_value, new_value):
var old_list = InkUtils.as_or_null(old_value, "ListValue")
var new_list = 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)
# ############################################################################ #
# GDScript extra methods
# ############################################################################ #
func is_ink_class(type):
return type == "ListValue" || super.is_ink_class(type)
func get_ink_class():
return "ListValue"
static func new_with(list):
var value = ListValue().new()
value._init_with_list(list)
return value
static func new_with_single_item(single_item, single_value):
var value = ListValue().new()
value._init_with_single_item(single_item, single_value)
return value

View file

@ -0,0 +1,82 @@
# warning-ignore-all:shadowed_variable
# warning-ignore-all:unused_class_variable
# ############################################################################ #
# 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 InkValue
class_name InkStringValue
# ############################################################################ #
func get_value_type():
return ValueType.STRING
func get_is_truthy():
return value.length() > 0
var is_newline: bool
var is_inline_whitespace: bool
var is_non_whitespace: bool:
get:
return !is_newline && !is_inline_whitespace
func _init():
value = ""
self._sanitize_value()
func _init_with(val):
super._init_with(val)
self._sanitize_value()
# The method takes a `StoryErrorMetadata` object as a parameter that
# doesn't exist in upstream. The metadat are used in case an 'exception'
# is raised. For more information, see story.gd.
func cast(new_type, metadata = null):
if new_type == self.value_type:
return self
if new_type == ValueType.INT:
if self.value.is_valid_int():
return IntValue().new_with(int(self.value))
else:
return null
if new_type == ValueType.FLOAT:
if self.value.is_valid_float():
return FloatValue().new_with(float(self.value))
else:
return null
InkUtils.throw_story_exception(bad_cast_exception_message(new_type), false, metadata)
return null
# ######################################################################## #
# GDScript extra methods
# ######################################################################## #
func is_ink_class(type):
return type == "StringValue" || super.is_ink_class(type)
func get_ink_class():
return "StringValue"
func _sanitize_value():
is_newline = (self.value == "\n")
is_inline_whitespace = true
for c in self.value:
if c != ' ' && c != "\t":
is_inline_whitespace = false
break
static func new_with(val):
var value = StringValue().new()
value._init_with(val)
return value

View file

@ -0,0 +1,131 @@
# warning-ignore-all:shadowed_variable
# warning-ignore-all:unused_class_variable
# ############################################################################ #
# 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 InkObject
# This is a merge of the original Value class and its Value<T> subclass.
class_name InkValue
# ############################################################################ #
# IMPORTS
# ############################################################################ #
const ValueType = preload("res://addons/inkgd/runtime/values/value_type.gd").ValueType
var InkList = load("res://addons/inkgd/runtime/lists/ink_list.gd")
# ############################################################################ #
# STATIC REFERENCE
# ############################################################################ #
# TODO: Remove
#static func Utils():
# return load("res://addons/inkgd/runtime/extra/InkUtils.gd")
#
#static func Value():
# return load("res://addons/inkgd/runtime/values/value.gd")
static func BoolValue():
return load("res://addons/inkgd/runtime/values/bool_value.gd")
static func IntValue():
return load("res://addons/inkgd/runtime/values/int_value.gd")
static func FloatValue():
return load("res://addons/inkgd/runtime/values/float_value.gd")
static func StringValue():
return load("res://addons/inkgd/runtime/values/string_value.gd")
static func DivertTargetValue():
return load("res://addons/inkgd/runtime/values/divert_target_value.gd")
static func VariablePointerValue():
return load("res://addons/inkgd/runtime/values/variable_pointer_value.gd")
static func ListValue():
return load("res://addons/inkgd/runtime/values/list_value.gd")
# ############################################################################ #
var value # Variant
# ValueType
var value_type: int: get = get_value_type
func get_value_type() -> int:
return -1
var is_truthy: bool: get = get_is_truthy
func get_is_truthy() -> bool:
return false
# ############################################################################ #
# (ValueType) -> ValueType
func cast(new_type: int) -> InkValue:
return null
var value_object: # Variant
get: return value
# ############################################################################ #
# (Variant) -> Value
func _init_with(val):
value = val
# (Variant) -> Value
static func create(val) -> InkValue:
# Original code lost precision from double to float.
# But it's not applicable here.
if val is bool:
return BoolValue().new_with(val)
if val is int:
return IntValue().new_with(val)
elif val is float:
return FloatValue().new_with(val)
elif val is String:
return StringValue().new_with(val)
elif InkUtils.is_ink_class(val, "InkPath"):
return DivertTargetValue().new_with(val)
elif InkUtils.is_ink_class(val, "InkList"):
return ListValue().new_with(val)
return null
func copy() -> InkValue:
return create(self.value_object)
# (Ink.ValueType) -> StoryException
func bad_cast_exception_message(target_ink_class) -> String:
return "Can't cast " + self.value_object + " from " + self.value_type + " to " + target_ink_class
# () -> String
func _to_string() -> String:
if value is int || value is float || value is String:
return str(value)
else:
return value._to_string()
# ############################################################################ #
# GDScript extra methods
# ############################################################################ #
func is_ink_class(type) -> bool:
return type == "Value" || super.is_ink_class(type)
func get_ink_class() -> String:
return "Value"
static func new_with(val) -> InkValue:
var value = InkValue.new()
value._init_with(val)
return value

View file

@ -0,0 +1,23 @@
# warning-ignore-all:shadowed_variable
# warning-ignore-all:unused_class_variable
# ############################################################################ #
# 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.
# ############################################################################ #
enum ValueType {
BOOL = -1,
INT,
FLOAT,
LIST,
STRING,
DIVERT_TARGET,
VARIABLE_POINTER
}

View file

@ -0,0 +1,69 @@
# warning-ignore-all:shadowed_variable
# warning-ignore-all:unused_class_variable
# ############################################################################ #
# 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 InkValue
class_name InkVariablePointerValue
# ############################################################################ #
var variable_name : get = get_variable_name, set = set_variable_name # InkPath
func get_variable_name():
return value
func set_variable_name(value):
self.value = value
func get_value_type():
return ValueType.VARIABLE_POINTER
func get_is_truthy():
InkUtils.throw_exception("Shouldn't be checking the truthiness of a variable pointer")
return false
var context_index = 0 # int
func _init_with_context(variable_name, context_index = -1):
super._init_with(variable_name)
self.context_index = context_index
func _init():
value = null
# The method takes a `StoryErrorMetadata` object as a parameter that
# doesn't exist in upstream. The metadat are used in case an 'exception'
# is raised. For more information, see story.gd.
func cast(new_type, metadata = null):
if new_type == self.value_type:
return self
InkUtils.throw_story_exception(bad_cast_exception_message(new_type), false, metadata)
return null
func _to_string() -> String:
return "VariablePointerValue(" + self.variable_name + ")"
func copy():
return VariablePointerValue().new_with_context(self.variable_name, context_index)
# ######################################################################## #
# GDScript extra methods
# ######################################################################## #
func is_ink_class(type):
return type == "VariablePointerValue" || super.is_ink_class(type)
func get_ink_class():
return "VariablePointerValue"
static func new_with_context(variable_name, context_index = -1):
var value = VariablePointerValue().new()
value._init_with_context(variable_name, context_index)
return value

View file

@ -0,0 +1,373 @@
# 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)