init
This commit is contained in:
commit
b99855351d
434 changed files with 50357 additions and 0 deletions
886
addons/inkgd/ink_player.gd
Normal file
886
addons/inkgd/ink_player.gd
Normal file
|
@ -0,0 +1,886 @@
|
|||
# ############################################################################ #
|
||||
# Copyright © 2018-2022 Paul Joannon
|
||||
# Copyright © 2019-2022 Frédéric Maquin <fred@ephread.com>
|
||||
# Licensed under the MIT License.
|
||||
# See LICENSE in the project root for license information.
|
||||
# ############################################################################ #
|
||||
|
||||
extends Node
|
||||
|
||||
class_name InkPlayer
|
||||
|
||||
# ############################################################################ #
|
||||
# Imports
|
||||
# ############################################################################ #
|
||||
|
||||
var InkRuntime = load("res://addons/inkgd/runtime.gd")
|
||||
var InkResource = load("res://addons/inkgd/editor/import_plugins/ink_resource.gd")
|
||||
var InkStory = load("res://addons/inkgd/runtime/story.gd")
|
||||
var InkFunctionResult = load("res://addons/inkgd/runtime/extra/function_result.gd")
|
||||
|
||||
|
||||
# ############################################################################ #
|
||||
# Signals
|
||||
# ############################################################################ #
|
||||
|
||||
## Emitted when the ink runtime encountered an exception. Exception are
|
||||
## usually not recoverable as they corrupt the state. `stack_trace` is
|
||||
## an optional PoolStringArray containing a stack trace, for logging purposes.
|
||||
signal exception_raised(message, stack_trace)
|
||||
|
||||
## Emitted when the _story encountered an error. These errors are usually
|
||||
## recoverable.
|
||||
signal error_encountered(message, type)
|
||||
|
||||
## Emitted with `true` when the runtime had loaded the JSON file and created
|
||||
## the _story. If an error was encountered, `successfully` will be `false` and
|
||||
## and error will appear Godot's output.
|
||||
signal loaded(successfully)
|
||||
|
||||
## Emitted with the text and tags of the current line when the _story
|
||||
## successfully continued.
|
||||
signal continued(text, tags)
|
||||
|
||||
## Emitted when using `continue_async`, if the time spent evaluating the ink
|
||||
## exceeded the alloted time.
|
||||
signal interrupted()
|
||||
|
||||
## Emitted when the player should pick a choice.
|
||||
signal prompt_choices(choices)
|
||||
|
||||
## Emitted when a choice was reported back to the runtime.
|
||||
signal choice_made(choice)
|
||||
|
||||
## Emitted when an external function is about to evaluate.
|
||||
signal function_evaluating(function_name, arguments)
|
||||
|
||||
## Emitted when an external function evaluated.
|
||||
signal function_evaluated(function_name, arguments, function_result)
|
||||
|
||||
## Emitted when a valid path string was choosen.
|
||||
signal path_choosen(path, arguments)
|
||||
|
||||
## Emitted when the _story ended.
|
||||
signal ended()
|
||||
|
||||
|
||||
# ############################################################################ #
|
||||
# Exported Properties
|
||||
# ############################################################################ #
|
||||
|
||||
## The compiled Ink file (.json) to play.
|
||||
@export var ink_file: Resource
|
||||
|
||||
## When `true` the _story will be created in a separate threads, to
|
||||
## prevent the UI from freezing if the _story is too big. Note that
|
||||
## on platforms where threads aren't available, the value of this
|
||||
## property is ignored.
|
||||
@export var loads_in_background: bool = true
|
||||
|
||||
|
||||
# ############################################################################ #
|
||||
# Properties
|
||||
# ############################################################################ #
|
||||
|
||||
# These properties aren't exported because they depend on the runtime or the
|
||||
# story to be set. The runtime insn't always available upon instantiation,
|
||||
# and the story is only available after calling 'create_story' so rather than
|
||||
# losing the values and confusing everybody, those properties are code-only.
|
||||
|
||||
## `true` to allow external function fallbacks, `false` otherwise. If this
|
||||
## property is `false` and the appropriate function hasn't been binded, the
|
||||
## _story will output an error.
|
||||
var allow_external_function_fallbacks: bool: get = get_aeff, set = set_aeff
|
||||
func set_aeff(value: bool):
|
||||
if _story == null:
|
||||
_push_null_story_error()
|
||||
return
|
||||
|
||||
_story.allow_external_function_fallbacks = value
|
||||
func get_aeff() -> bool:
|
||||
if _story == null:
|
||||
_push_null_story_error()
|
||||
return false
|
||||
|
||||
return _story.allow_external_function_fallbacks
|
||||
|
||||
# skips saving global values that remain equal to the initial values that were
|
||||
# declared in Ink.
|
||||
var do_not_save_default_values: bool: get = get_dnsdv, set = set_dnsdv
|
||||
func set_dnsdv(value: bool):
|
||||
var ink_runtime = _ink_runtime.get_ref()
|
||||
if ink_runtime == null:
|
||||
_push_null_runtime_error()
|
||||
return false
|
||||
|
||||
ink_runtime.dont_save_default_values = value
|
||||
func get_dnsdv() -> bool:
|
||||
var ink_runtime = _ink_runtime.get_ref()
|
||||
if ink_runtime == null:
|
||||
_push_null_runtime_error()
|
||||
return false
|
||||
|
||||
return ink_runtime.dont_save_default_values
|
||||
|
||||
## Uses `assert` instead of `push_error` to report critical errors, thus
|
||||
## making them more explicit during development.
|
||||
var stop_execution_on_exception: bool: get = get_seoex, set = set_seoex
|
||||
func set_seoex(value: bool):
|
||||
var ink_runtime = _ink_runtime.get_ref()
|
||||
if ink_runtime == null:
|
||||
_push_null_runtime_error()
|
||||
return
|
||||
|
||||
ink_runtime.stop_execution_on_exception = value
|
||||
func get_seoex() -> bool:
|
||||
var ink_runtime = _ink_runtime.get_ref()
|
||||
if ink_runtime == null:
|
||||
_push_null_runtime_error()
|
||||
return false
|
||||
|
||||
return ink_runtime.stop_execution_on_exception
|
||||
|
||||
## Uses `assert` instead of `push_error` to report _story errors, thus
|
||||
## making them more explicit during development.
|
||||
var stop_execution_on_error: bool: get = get_seoer, set = set_seoer
|
||||
func set_seoer(value: bool):
|
||||
var ink_runtime = _ink_runtime.get_ref()
|
||||
if ink_runtime == null:
|
||||
_push_null_runtime_error()
|
||||
return
|
||||
|
||||
ink_runtime.stop_execution_on_error = value
|
||||
|
||||
func get_seoer() -> bool:
|
||||
var ink_runtime = _ink_runtime.get_ref()
|
||||
if ink_runtime == null:
|
||||
_push_null_runtime_error()
|
||||
return false
|
||||
|
||||
return ink_runtime.stop_execution_on_error
|
||||
|
||||
|
||||
# ############################################################################ #
|
||||
# Read-only Properties
|
||||
# ############################################################################ #
|
||||
|
||||
## `true` if the _story can continue (i. e. is not expecting a choice to be
|
||||
## choosen and hasn't reached the end).
|
||||
var can_continue: bool: get = get_can_continue
|
||||
func get_can_continue() -> bool:
|
||||
if _story == null:
|
||||
_push_null_story_error()
|
||||
return false
|
||||
|
||||
return _story.can_continue
|
||||
|
||||
|
||||
## If `continue_async` was called (with milliseconds limit > 0) then this
|
||||
## property will return false if the ink evaluation isn't yet finished, and
|
||||
## you need to call it again in order for the continue to fully complete.
|
||||
var async_continue_complete: bool: get = get_async_continue_complete
|
||||
func get_async_continue_complete() -> bool:
|
||||
if _story == null:
|
||||
_push_null_story_error()
|
||||
return false
|
||||
|
||||
return _story.async_continue_complete
|
||||
|
||||
|
||||
## The content of the current line.
|
||||
var current_text: String: get = get_current_text
|
||||
func get_current_text() -> String:
|
||||
if _story == null:
|
||||
_push_null_story_error()
|
||||
return ""
|
||||
|
||||
|
||||
return _story.current_text
|
||||
|
||||
|
||||
## The current choices. Empty is there are no choices for the current line.
|
||||
var current_choices: Array: get = get_current_choices
|
||||
func get_current_choices() -> Array:
|
||||
if _story == null:
|
||||
_push_null_story_error()
|
||||
return []
|
||||
|
||||
return _story.current_choices.duplicate()
|
||||
|
||||
|
||||
## The current tags. Empty is there are no tags for the current line.
|
||||
var current_tags: Array: get = get_current_tags
|
||||
func get_current_tags() -> Array:
|
||||
if _story == null:
|
||||
_push_null_story_error()
|
||||
return []
|
||||
|
||||
if _story.current_tags == null:
|
||||
return []
|
||||
|
||||
return _story.current_tags
|
||||
|
||||
|
||||
## The global tags for the _story. Empty if none have been declared.
|
||||
var global_tags: Array: get = get_global_tags
|
||||
func get_global_tags() -> Array:
|
||||
if _story == null:
|
||||
_push_null_story_error()
|
||||
return []
|
||||
|
||||
if _story.global_tags == null:
|
||||
return []
|
||||
|
||||
return _story.global_tags
|
||||
|
||||
|
||||
## `true` if the _story currently has choices, `false` otherwise.
|
||||
var has_choices: bool: get = get_has_choices
|
||||
func get_has_choices() -> bool:
|
||||
return !self.current_choices.is_empty()
|
||||
|
||||
|
||||
## The name of the current flow.
|
||||
var current_flow_name: String: get = get_current_flow_name
|
||||
func get_current_flow_name() -> String:
|
||||
if _story == null:
|
||||
_push_null_story_error()
|
||||
return ""
|
||||
|
||||
return _story.state.current_flow_name
|
||||
|
||||
|
||||
## The names of all flows currently alive.
|
||||
var alive_flow_names: Array: get = get_alive_flow_names
|
||||
func get_alive_flow_names() -> Array:
|
||||
if _story == null:
|
||||
_push_null_story_error()
|
||||
return []
|
||||
|
||||
return _story.alive_flow_names
|
||||
|
||||
|
||||
## `true` if the current flow is the default flow.
|
||||
var current_flow_is_default_flow: bool: get = get_current_flow_is_default_flow
|
||||
func get_current_flow_is_default_flow() -> bool:
|
||||
if _story == null:
|
||||
_push_null_story_error()
|
||||
return false
|
||||
|
||||
return _story.current_flow_is_default_flow
|
||||
|
||||
|
||||
## The current story path.
|
||||
var current_path: String: get = get_current_path
|
||||
func get_current_path() -> String:
|
||||
if _story == null:
|
||||
_push_null_story_error()
|
||||
return ""
|
||||
|
||||
var path = _story.state.current_path_string
|
||||
if path == null:
|
||||
return ""
|
||||
else:
|
||||
return path
|
||||
|
||||
|
||||
# ############################################################################ #
|
||||
# Private Properties
|
||||
# ############################################################################ #
|
||||
|
||||
var _ink_runtime: WeakRef = WeakRef.new()
|
||||
var _story: InkStory = null
|
||||
var _thread: Thread
|
||||
var _manages_runtime: bool = false
|
||||
|
||||
|
||||
# ############################################################################ #
|
||||
# Initialization
|
||||
# ############################################################################ #
|
||||
|
||||
func _init():
|
||||
name = "InkPlayer"
|
||||
|
||||
|
||||
# ############################################################################ #
|
||||
# Overrides
|
||||
# ############################################################################ #
|
||||
|
||||
func _exit_tree():
|
||||
call_deferred("_remove_runtime")
|
||||
|
||||
|
||||
# ############################################################################ #
|
||||
# Methods
|
||||
# ############################################################################ #
|
||||
|
||||
## Creates the _story, based on the value of `ink_file`. The result of this
|
||||
## method is reported through the 'story_loaded' signal.
|
||||
func create_story() -> int:
|
||||
if ink_file == null:
|
||||
_push_error("'ink_file' is null, did Godot import the resource correctly?")
|
||||
call_deferred("emit_signal", "loaded", false)
|
||||
return ERR_CANT_CREATE
|
||||
|
||||
if !("json" in ink_file) || typeof(ink_file.json) != TYPE_STRING:
|
||||
_push_error(
|
||||
"'ink_file' doesn't have the appropriate resource type." + \
|
||||
"Are you sure you imported a JSON file?"
|
||||
)
|
||||
call_deferred("emit_signal", "loaded", false)
|
||||
return ERR_CANT_CREATE
|
||||
|
||||
if loads_in_background && _current_platform_supports_threads():
|
||||
_thread = Thread.new()
|
||||
var error = _thread.start(_async_create_story.bind(ink_file.json, _ink_runtime.get_ref()))
|
||||
if error != OK:
|
||||
printerr("[inkgd] [ERROR] Could not start the thread: error code %d", error)
|
||||
call_deferred("emit_signal", "loaded", false)
|
||||
return error
|
||||
else:
|
||||
return OK
|
||||
else:
|
||||
call_deferred("_create_and_finalize_story", ink_file.json, _ink_runtime.get_ref())
|
||||
return OK
|
||||
|
||||
|
||||
## Reset the Story back to its initial state as it was when it was
|
||||
## first constructed.
|
||||
func reset() -> void:
|
||||
if _story == null:
|
||||
_push_null_story_error()
|
||||
return
|
||||
|
||||
_story.reset_state()
|
||||
|
||||
|
||||
## Destroys the current story.
|
||||
func destroy() -> void:
|
||||
_story = null
|
||||
|
||||
# ############################################################################ #
|
||||
# Methods | Story Flow
|
||||
# ############################################################################ #
|
||||
|
||||
## Continues the story.
|
||||
func continue_story() -> String:
|
||||
if _story == null:
|
||||
_push_null_story_error()
|
||||
return ""
|
||||
|
||||
var text: String = ""
|
||||
if self.can_continue:
|
||||
_story.continue_story()
|
||||
|
||||
text = self.current_text
|
||||
|
||||
elif self.has_choices:
|
||||
emit_signal("prompt_choices", self.current_choices)
|
||||
else:
|
||||
emit_signal("ended")
|
||||
|
||||
return text
|
||||
|
||||
## An "asynchronous" version of `continue_story` that only partially evaluates
|
||||
## the ink, with a budget of a certain time limit. It will exit ink evaluation
|
||||
## early if the evaluation isn't complete within the time limit, with the
|
||||
## `async_continue_complete` property being false. This is useful if the
|
||||
## evaluation takes a long time, and you want to distribute it over multiple
|
||||
## game frames for smoother animation. If you pass a limit of zero, then it will
|
||||
## fully evaluate the ink in the same way as calling continue_story.
|
||||
##
|
||||
## To get notified when the evaluation is exited early, you can connect to the
|
||||
## `interrupted` signal.
|
||||
func continue_story_async(millisecs_limit_async: float) -> void:
|
||||
if _story == null:
|
||||
_push_null_story_error()
|
||||
return
|
||||
|
||||
if self.can_continue:
|
||||
_story.continue_async(millisecs_limit_async)
|
||||
|
||||
if !self.async_continue_complete:
|
||||
emit_signal("interrupted")
|
||||
return
|
||||
|
||||
elif self.has_choices:
|
||||
emit_signal("prompt_choices", self.current_choices)
|
||||
else:
|
||||
emit_signal("ended")
|
||||
|
||||
## Continue the story until the next choice point or until it runs out of
|
||||
## content. This is as opposed to `continue` which only evaluates one line
|
||||
## of output at a time. It returns the resulting text evaluated by the ink
|
||||
## engine, concatenated together.
|
||||
func continue_story_maximally() -> String:
|
||||
if _story == null:
|
||||
_push_null_story_error()
|
||||
return ""
|
||||
|
||||
var text: String = ""
|
||||
if self.can_continue:
|
||||
_story.continue_story_maximally()
|
||||
|
||||
text = self.current_text
|
||||
|
||||
elif self.has_choices:
|
||||
emit_signal("prompt_choices", self.current_choices)
|
||||
else:
|
||||
emit_signal("ended")
|
||||
|
||||
return text
|
||||
|
||||
## Chooses a choice. If the _story is not currently expected choices or
|
||||
## the index is out of bounds, this method does nothing.
|
||||
func choose_choice_index(index: int) -> void:
|
||||
if _story == null:
|
||||
_push_null_story_error()
|
||||
return
|
||||
|
||||
if index >= 0 && index < self.current_choices.size():
|
||||
_story.choose_choice_index(index);
|
||||
|
||||
|
||||
## Moves the _story to the specified knot/stitch/gather. This method
|
||||
## will throw an error through the 'exception' signal if the path string
|
||||
## does not match any known path.
|
||||
func choose_path(path: String) -> void:
|
||||
if _story == null:
|
||||
_push_null_story_error()
|
||||
return
|
||||
|
||||
_story.choose_path_string(path)
|
||||
|
||||
|
||||
## Switches the flow, creating a new flow if it doesn't exist.
|
||||
func switch_flow(flow_name: String) -> void:
|
||||
if _story == null:
|
||||
_push_null_story_error()
|
||||
return
|
||||
|
||||
_story.switch_flow(flow_name)
|
||||
|
||||
|
||||
## Switches the the default flow.
|
||||
func switch_to_default_flow() -> void:
|
||||
if _story == null:
|
||||
_push_null_story_error()
|
||||
return
|
||||
|
||||
_story.switch_to_default_flow()
|
||||
|
||||
|
||||
## Remove the given flow.
|
||||
func remove_flow(flow_name: String) -> void:
|
||||
if _story == null:
|
||||
_push_null_story_error()
|
||||
return
|
||||
|
||||
_story.remove_flow(flow_name)
|
||||
|
||||
|
||||
# ############################################################################ #
|
||||
# Methods | Tags
|
||||
# ############################################################################ #
|
||||
|
||||
## Returns the tags declared at the given path.
|
||||
func tags_for_content_at_path(path: String) -> Array:
|
||||
if _story == null:
|
||||
_push_null_story_error()
|
||||
return []
|
||||
|
||||
return _story.tags_for_content_at_path(path)
|
||||
|
||||
|
||||
# ############################################################################ #
|
||||
# Methods | Visit Count
|
||||
# ############################################################################ #
|
||||
|
||||
## Returns the visit count of the given path.
|
||||
func visit_count_at_path(path: String) -> int:
|
||||
if _story == null:
|
||||
_push_null_story_error()
|
||||
return 0
|
||||
|
||||
return _story.state.visit_count_at_path_string(path)
|
||||
|
||||
|
||||
# ############################################################################ #
|
||||
# Methods | State Management
|
||||
# ############################################################################ #
|
||||
|
||||
## Gets the current state as a JSON string. It can then be saved somewhere.
|
||||
func get_state() -> String:
|
||||
if _story == null:
|
||||
_push_null_story_error()
|
||||
return ""
|
||||
|
||||
return _story.state.to_json()
|
||||
|
||||
|
||||
## If you have a large story, and saving state to JSON takes too long for your
|
||||
## framerate, you can temporarily freeze a copy of the state for saving on
|
||||
## a separate thread. Internally, the engine maintains a "diff patch".
|
||||
## When you've finished saving your state, call `background_save_complete`
|
||||
## and that diff patch will be applied, allowing the story to continue
|
||||
## in its usual mode.
|
||||
func copy_state_for_background_thread_save() -> String:
|
||||
if _story == null:
|
||||
_push_null_story_error()
|
||||
return ""
|
||||
|
||||
return _story.copy_state_for_background_thread_save().to_json()
|
||||
|
||||
|
||||
## See `copy_state_for_background_thread_save`. This method releases the
|
||||
## "frozen" save state, applying its patch that it was using internally.
|
||||
func background_save_complete() -> void:
|
||||
if _story == null:
|
||||
_push_null_story_error()
|
||||
return
|
||||
|
||||
_story.background_save_complete()
|
||||
|
||||
|
||||
## Sets the state from a JSON string.
|
||||
func set_state(state: String) -> void:
|
||||
if _story == null:
|
||||
_push_null_story_error()
|
||||
return
|
||||
|
||||
_story.state.load_json(state)
|
||||
|
||||
|
||||
## Saves the current state to the given path.
|
||||
func save_state_to_path(path: String):
|
||||
if _story == null:
|
||||
_push_null_story_error()
|
||||
return
|
||||
|
||||
if !path.begins_with("res://") && !path.begins_with("user://"):
|
||||
path = "user://%s" % path
|
||||
|
||||
var file := FileAccess.open(path, FileAccess.WRITE)
|
||||
save_state_to_file(file)
|
||||
file.close()
|
||||
|
||||
|
||||
## Saves the current state to the file.
|
||||
func save_state_to_file(file: FileAccess):
|
||||
if _story == null:
|
||||
_push_null_story_error()
|
||||
return
|
||||
|
||||
if file.is_open():
|
||||
file.store_string(get_state())
|
||||
|
||||
# TODO: Add save and load in background
|
||||
|
||||
## Loads the state from the given path.
|
||||
func load_state_from_path(path: String):
|
||||
if _story == null:
|
||||
_push_null_story_error()
|
||||
return
|
||||
|
||||
if !path.begins_with("res://") && !path.begins_with("user://"):
|
||||
path = "user://%s" % path
|
||||
|
||||
var file := FileAccess.open(path, FileAccess.READ)
|
||||
load_state_from_file(file)
|
||||
file.close()
|
||||
|
||||
|
||||
## Loads the state from the given file.
|
||||
func load_state_from_file(file: FileAccess):
|
||||
if _story == null:
|
||||
_push_null_story_error()
|
||||
return
|
||||
|
||||
if !file.is_open():
|
||||
return
|
||||
|
||||
file.seek(0);
|
||||
if file.get_length() > 0:
|
||||
_story.state.load_json(file.get_as_text())
|
||||
|
||||
|
||||
# ############################################################################ #
|
||||
# Methods | Variables
|
||||
# ############################################################################ #
|
||||
|
||||
## Returns the value of variable named 'name' or 'null' if it doesn't exist.
|
||||
@warning_ignore("shadowed_variable_base_class")
|
||||
func get_variable(name: String):
|
||||
if _story == null:
|
||||
_push_null_story_error()
|
||||
return null
|
||||
|
||||
return _story.variables_state.get_variable(name)
|
||||
|
||||
|
||||
## Sets the value of variable named 'name'.
|
||||
@warning_ignore("shadowed_variable_base_class")
|
||||
func set_variable(name: String, value):
|
||||
if _story == null:
|
||||
_push_null_story_error()
|
||||
return
|
||||
|
||||
_story.variables_state.set_variable(name, value)
|
||||
|
||||
|
||||
# ############################################################################ #
|
||||
# Methods | Variable Observers
|
||||
# ############################################################################ #
|
||||
|
||||
## Registers an observer for the given variables.
|
||||
func observe_variables(variable_names: Array, object: Object, method_name: String):
|
||||
if _story == null:
|
||||
_push_null_story_error()
|
||||
return
|
||||
|
||||
_story.observe_variables(variable_names, object, method_name)
|
||||
|
||||
|
||||
## Registers an observer for the given variable.
|
||||
func observe_variable(variable_name: String, object: Object, method_name: String):
|
||||
if _story == null:
|
||||
_push_null_story_error()
|
||||
return
|
||||
|
||||
_story.observe_variable(variable_name, object, method_name)
|
||||
|
||||
|
||||
## Removes an observer for the given variable name. This method is highly
|
||||
## specific and will only remove one observer.
|
||||
func remove_variable_observer(object: Object, method_name: String, specific_variable_name: String) -> void:
|
||||
if _story == null:
|
||||
_push_null_story_error()
|
||||
return
|
||||
|
||||
_story.remove_variable_observer(object, method_name, specific_variable_name)
|
||||
|
||||
|
||||
## Removes all observers registered with the couple object/method_name,
|
||||
## regardless of which variable they observed.
|
||||
func remove_variable_observer_for_all_variables(object: Object, method_name: String) -> void:
|
||||
if _story == null:
|
||||
_push_null_story_error()
|
||||
return
|
||||
|
||||
_story.remove_variable_observer(object, method_name)
|
||||
|
||||
|
||||
## Removes all observers observing the given variable.
|
||||
func remove_all_variable_observers(specific_variable_name: String) -> void:
|
||||
if _story == null:
|
||||
_push_null_story_error()
|
||||
return
|
||||
|
||||
_story.remove_variable_observer(null, null, specific_variable_name)
|
||||
|
||||
|
||||
# ############################################################################ #
|
||||
# Methods | External Functions
|
||||
# ############################################################################ #
|
||||
|
||||
## Binds an external function.
|
||||
func bind_external_function(
|
||||
func_name: String,
|
||||
object: Object,
|
||||
method_name: String,
|
||||
lookahead_safe = false
|
||||
) -> void:
|
||||
if _story == null:
|
||||
_push_null_story_error()
|
||||
return
|
||||
|
||||
_story.bind_external_function(func_name, object, method_name, lookahead_safe)
|
||||
|
||||
|
||||
## Unbinds an external function.
|
||||
func unbind_external_function(func_name: String) -> void:
|
||||
if _story == null:
|
||||
_push_null_story_error()
|
||||
return
|
||||
|
||||
_story.unbind_external_function(func_name)
|
||||
|
||||
|
||||
# ############################################################################ #
|
||||
# Methods | Functions
|
||||
# ############################################################################ #
|
||||
|
||||
func has_function(function_name: String) -> bool:
|
||||
return _story.has_function(function_name)
|
||||
|
||||
## Evaluate a given ink function, returning its return value (but not
|
||||
## its output).
|
||||
func evaluate_function(function_name: String, arguments = []) -> InkFunctionResult:
|
||||
if _story == null:
|
||||
_push_null_story_error()
|
||||
return null
|
||||
|
||||
var result = _story.evaluate_function(function_name, arguments, true)
|
||||
|
||||
if result != null:
|
||||
return InkFunctionResult.new(result["output"], result["result"])
|
||||
else:
|
||||
return null
|
||||
|
||||
# ############################################################################ #
|
||||
# Methods | Ink List Creation
|
||||
# ############################################################################ #
|
||||
|
||||
## Creates a new empty InkList that's intended to hold items from a particular
|
||||
## origin list definition.
|
||||
func create_ink_list_with_origin(single_origin_list_name: String) -> InkList:
|
||||
return InkList.new_with_origin(single_origin_list_name, _story)
|
||||
|
||||
## Creates a new InkList from the name of a preexisting item.
|
||||
func create_ink_list_from_item_name(item_name: String) -> InkList:
|
||||
return InkList.from_string(item_name, _story)
|
||||
|
||||
|
||||
# ############################################################################ #
|
||||
# Private Methods | Signal Forwarding
|
||||
# ############################################################################ #
|
||||
|
||||
func _exception_raised(message, stack_trace) -> void:
|
||||
emit_signal("exception_raised", message, stack_trace)
|
||||
|
||||
|
||||
func _on_error(message, type) -> void:
|
||||
if get_signal_connection_list("error_encountered").size() == 0:
|
||||
_push_story_error(message, type)
|
||||
else:
|
||||
emit_signal("error_encountered", message, type)
|
||||
|
||||
|
||||
func _on_did_continue() -> void:
|
||||
emit_signal("continued", self.current_text, self.current_tags)
|
||||
|
||||
|
||||
func _on_make_choice(choice) -> void:
|
||||
emit_signal("choice_made", choice.text)
|
||||
|
||||
|
||||
func _on_evaluate_function(function_name, arguments) -> void:
|
||||
emit_signal("function_evaluating", function_name, arguments)
|
||||
|
||||
|
||||
func _on_complete_evaluate_function(function_name, arguments, text_output, return_value) -> void:
|
||||
var function_result = InkFunctionResult.new(text_output, return_value)
|
||||
emit_signal("function_evaluated", function_name, arguments, function_result)
|
||||
|
||||
|
||||
func _on_choose_path_string(path, arguments) -> void:
|
||||
emit_signal("path_choosen", path, arguments)
|
||||
|
||||
|
||||
# ############################################################################ #
|
||||
# Private Methods
|
||||
# ############################################################################ #
|
||||
|
||||
func _create_story(json_story, runtime) -> void:
|
||||
_story = InkStory.new(json_story, runtime)
|
||||
|
||||
|
||||
func _async_create_story(json_story, runtime) -> void:
|
||||
_create_story(json_story, runtime)
|
||||
call_deferred("_async_creation_completed")
|
||||
|
||||
|
||||
func _async_creation_completed() -> void:
|
||||
_thread.wait_to_finish()
|
||||
_thread = null
|
||||
|
||||
_finalise_story_creation()
|
||||
|
||||
|
||||
func _create_and_finalize_story(json_story, runtime) -> void:
|
||||
_create_story(json_story, runtime)
|
||||
_finalise_story_creation()
|
||||
|
||||
|
||||
func _finalise_story_creation() -> void:
|
||||
_story.connect("on_error", Callable(self, "_on_error"))
|
||||
_story.connect("on_did_continue", Callable(self, "_on_did_continue"))
|
||||
_story.connect("on_make_choice", Callable(self, "_on_make_choice"))
|
||||
_story.connect("on_evaluate_function", Callable(self, "_on_evaluate_function"))
|
||||
_story.connect("on_complete_evaluate_function", Callable(self, "_on_complete_evaluate_function"))
|
||||
_story.connect("on_choose_path_string", Callable(self, "_on_choose_path_string"))
|
||||
|
||||
var ink_runtime = _ink_runtime.get_ref()
|
||||
if ink_runtime == null:
|
||||
_push_null_runtime_error()
|
||||
emit_signal("loaded", false)
|
||||
return
|
||||
|
||||
emit_signal("loaded", true)
|
||||
|
||||
|
||||
func _add_runtime(root) -> void:
|
||||
# The InkRuntime is normaly an auto-loaded singleton,
|
||||
# but if it's not present, it's added here.
|
||||
var runtime: Node
|
||||
if root.has_node("__InkRuntime"):
|
||||
runtime = root.get_node("__InkRuntime")
|
||||
else:
|
||||
_manages_runtime = true
|
||||
runtime = InkRuntime.init(root)
|
||||
|
||||
if !runtime.is_connected("exception_raised", _exception_raised):
|
||||
runtime.connect("exception_raised", _exception_raised)
|
||||
|
||||
_ink_runtime = weakref(runtime)
|
||||
|
||||
|
||||
func _remove_runtime() -> void:
|
||||
if _manages_runtime:
|
||||
InkRuntime.deinit(get_tree().root)
|
||||
|
||||
|
||||
func _current_platform_supports_threads() -> bool:
|
||||
return OS.get_name() != "HTML5"
|
||||
|
||||
|
||||
func _push_null_runtime_error() -> void:
|
||||
_push_error(
|
||||
"InkRuntime could not found, did you remove it from the tree?"
|
||||
)
|
||||
|
||||
|
||||
func _push_null_story_error() -> void:
|
||||
_push_error("The _story is 'null', was it loaded properly?")
|
||||
|
||||
|
||||
func _push_story_error(message: String, type: int) -> void:
|
||||
if Engine.is_editor_hint():
|
||||
match type:
|
||||
Ink.ErrorType.ERROR:
|
||||
printerr(message)
|
||||
Ink.ErrorType.WARNING, Ink.ErrorType.AUTHOR:
|
||||
print(message)
|
||||
else:
|
||||
match type:
|
||||
Ink.ErrorType.ERROR:
|
||||
push_error(message)
|
||||
Ink.ErrorType.WARNING, Ink.ErrorType.AUTHOR:
|
||||
push_warning(message)
|
||||
|
||||
func _push_error(message: String):
|
||||
if Engine.is_editor_hint():
|
||||
printerr(message)
|
||||
|
||||
var i = 1
|
||||
for stack_element in get_stack():
|
||||
if i <= 2:
|
||||
i += 1
|
||||
continue
|
||||
|
||||
printerr(
|
||||
" ", (i - 2), " - ", stack_element["source"], ":",
|
||||
stack_element["line"], " - at function: ", stack_element["function"]
|
||||
)
|
||||
else:
|
||||
push_error(message)
|
Loading…
Add table
Add a link
Reference in a new issue