623 lines
18 KiB
GDScript
623 lines
18 KiB
GDScript
# ############################################################################ #
|
|
# 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
|