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

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