init
This commit is contained in:
commit
b99855351d
434 changed files with 50357 additions and 0 deletions
57
.gitattributes
vendored
Normal file
57
.gitattributes
vendored
Normal file
|
@ -0,0 +1,57 @@
|
|||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
||||
# 3D models
|
||||
*.3dm filter=lfs diff=lfs merge=lfs -text
|
||||
*.3ds filter=lfs diff=lfs merge=lfs -text
|
||||
*.blend filter=lfs diff=lfs merge=lfs -text
|
||||
*.c4d filter=lfs diff=lfs merge=lfs -text
|
||||
*.collada filter=lfs diff=lfs merge=lfs -text
|
||||
*.dae filter=lfs diff=lfs merge=lfs -text
|
||||
*.dxf filter=lfs diff=lfs merge=lfs -text
|
||||
*.fbx filter=lfs diff=lfs merge=lfs -text
|
||||
*.jas filter=lfs diff=lfs merge=lfs -text
|
||||
*.lws filter=lfs diff=lfs merge=lfs -text
|
||||
*.lxo filter=lfs diff=lfs merge=lfs -text
|
||||
*.ma filter=lfs diff=lfs merge=lfs -text
|
||||
*.max filter=lfs diff=lfs merge=lfs -text
|
||||
*.mb filter=lfs diff=lfs merge=lfs -text
|
||||
*.obj filter=lfs diff=lfs merge=lfs -text
|
||||
*.ply filter=lfs diff=lfs merge=lfs -text
|
||||
*.skp filter=lfs diff=lfs merge=lfs -text
|
||||
*.stl filter=lfs diff=lfs merge=lfs -text
|
||||
*.ztl filter=lfs diff=lfs merge=lfs -text
|
||||
*.glb filter=lfs diff=lfs merge=lfs -text
|
||||
# Audio
|
||||
*.aif filter=lfs diff=lfs merge=lfs -text
|
||||
*.aiff filter=lfs diff=lfs merge=lfs -text
|
||||
*.it filter=lfs diff=lfs merge=lfs -text
|
||||
*.mod filter=lfs diff=lfs merge=lfs -text
|
||||
*.mp3 filter=lfs diff=lfs merge=lfs -text
|
||||
*.ogg filter=lfs diff=lfs merge=lfs -text
|
||||
*.s3m filter=lfs diff=lfs merge=lfs -text
|
||||
*.wav filter=lfs diff=lfs merge=lfs -text
|
||||
*.xm filter=lfs diff=lfs merge=lfs -text
|
||||
*.bank filter=lfs diff=lfs merge=lfs -text
|
||||
# Fonts
|
||||
*.otf filter=lfs diff=lfs merge=lfs -text
|
||||
*.ttf filter=lfs diff=lfs merge=lfs -text
|
||||
# Images
|
||||
*.bmp filter=lfs diff=lfs merge=lfs -text
|
||||
*.exr filter=lfs diff=lfs merge=lfs -text
|
||||
*.gif filter=lfs diff=lfs merge=lfs -text
|
||||
*.hdr filter=lfs diff=lfs merge=lfs -text
|
||||
*.iff filter=lfs diff=lfs merge=lfs -text
|
||||
*.jpeg filter=lfs diff=lfs merge=lfs -text
|
||||
*.jpg filter=lfs diff=lfs merge=lfs -text
|
||||
*.svg filter=lfs diff=lfs merge=lfs -text
|
||||
*.pict filter=lfs diff=lfs merge=lfs -text
|
||||
*.png filter=lfs diff=lfs merge=lfs -text
|
||||
*.psd filter=lfs diff=lfs merge=lfs -text
|
||||
*.tga filter=lfs diff=lfs merge=lfs -text
|
||||
*.tif filter=lfs diff=lfs merge=lfs -text
|
||||
*.tiff filter=lfs diff=lfs merge=lfs -text
|
||||
Assets/Fonts/Pangolin-Regular_SDF.asset filter=lfs diff=lfs merge=lfs -text
|
||||
Assets/Fonts/LiberationSans_SDF.asset filter=lfs diff=lfs merge=lfs -text
|
||||
*.dll filter=lfs diff=lfs merge=lfs -text
|
||||
*.so filter=lfs diff=lfs merge=lfs -text
|
||||
*.dylib filter=lfs diff=lfs merge=lfs -text
|
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
# Godot 4+ specific ignores
|
||||
.godot/
|
32
README.md
Normal file
32
README.md
Normal file
|
@ -0,0 +1,32 @@
|
|||
# Figments of the Night
|
||||
|
||||
## Description
|
||||
|
||||
You wake up on a deserted island with no memory of who you are or what you are doing there. You will have to explore its landscapes, meet its other visitors and find clues in order to discover the truth behind this mysterious place…
|
||||
|
||||
or
|
||||
|
||||
you can simply relax in this paradise in the middle of the ocean and accept ignorance… whatever you do, do it before you have to go to sleep… to sleep…
|
||||
|
||||
## Honors
|
||||
|
||||
Indie Spain Jam 2023 — Top 25 Finalist
|
||||
|
||||
## The Team
|
||||
|
||||
> Gerard Gascón - Programming\
|
||||
> Marc Batlle - Character Design, 3D Modeling & Narrative\
|
||||
> Oriol Jaumot - Level Design & Environment Art\
|
||||
> Pedro Astasio - SFXs & Music
|
||||
|
||||
## Game Jam
|
||||
|
||||
Indie Spain Jam 2023 — September 18th - 25th 2023 (1 week)
|
||||
|
||||
## Theme
|
||||
|
||||
The night falls
|
||||
|
||||
## Languages
|
||||
|
||||
Spanish
|
23
addons/inkgd/LICENSE
Normal file
23
addons/inkgd/LICENSE
Normal file
|
@ -0,0 +1,23 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015-2021 inkle Ltd.
|
||||
Copyright (c) 2018-2021 Paul Joannon
|
||||
Copyright (c) 2019-2022 Frédéric Maquin <fred@ephread.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
146
addons/inkgd/editor/common/executors/ink_compiler.gd
Normal file
146
addons/inkgd/editor/common/executors/ink_compiler.gd
Normal file
|
@ -0,0 +1,146 @@
|
|||
# ############################################################################ #
|
||||
# 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 InkExternalCommandExecutor
|
||||
|
||||
class_name InkCompiler
|
||||
|
||||
# ############################################################################ #
|
||||
# Imports
|
||||
# ############################################################################ #
|
||||
|
||||
var InkExecutionResult = load("res://addons/inkgd/editor/common/executors/structures/ink_execution_result.gd")
|
||||
|
||||
# ############################################################################ #
|
||||
# Private Properties
|
||||
# ############################################################################ #
|
||||
|
||||
## Ink Configuration
|
||||
var _configuration: InkCompilationConfiguration
|
||||
|
||||
# ############################################################################ #
|
||||
# Signals
|
||||
# ############################################################################ #
|
||||
|
||||
## Emitted when a compilation completed. Note that this doesn't imply that
|
||||
## the compulation was successful. Check the content of result
|
||||
## (InkCompiler.Result) for more information.
|
||||
signal story_compiled(result)
|
||||
|
||||
# ############################################################################ #
|
||||
# Overrides
|
||||
# ############################################################################ #
|
||||
|
||||
func _init(configuration: InkCompilationConfiguration):
|
||||
_configuration = configuration
|
||||
|
||||
if _configuration.use_threads:
|
||||
_thread = Thread.new()
|
||||
|
||||
# ############################################################################ #
|
||||
# Methods
|
||||
# ############################################################################ #
|
||||
|
||||
## Compile the story, based on the compilation configuration provided
|
||||
## by this object. If `configuration.use_thread` is set to `false`,
|
||||
## this method will return `true` if the compilation succeeded and `false`
|
||||
## otherwise. If `configuration.use_thread` is set to `true`, this method
|
||||
## always returns `true`.
|
||||
func compile_story() -> bool:
|
||||
if _configuration.use_threads:
|
||||
var error = _thread.start(Callable(self, "_compile_story").bind(_configuration), Thread.PRIORITY_HIGH)
|
||||
|
||||
if error != OK:
|
||||
var result = InkExecutionResult.new(
|
||||
self.identifier,
|
||||
_configuration.use_threads,
|
||||
_configuration.user_triggered,
|
||||
false,
|
||||
""
|
||||
)
|
||||
|
||||
call_deferred("emit_signal", "story_compiled", result)
|
||||
|
||||
return true
|
||||
else:
|
||||
return _compile_story(_configuration)
|
||||
|
||||
# ############################################################################ #
|
||||
# Private Helpers
|
||||
# ############################################################################ #
|
||||
|
||||
## Compile the story, based on the given compilation configuration
|
||||
## If `configuration.use_thread` is set to `false`, this method will
|
||||
## return `true` if the compilation succeeded and `false` otherwise.
|
||||
## If `configuration.use_thread` is set to `false`, this method always
|
||||
## returns `true`.
|
||||
func _compile_story(config: InkCompilationConfiguration) -> bool:
|
||||
print("[inkgd] [INFO] Executing compilation command…")
|
||||
var return_code = 0
|
||||
var output = []
|
||||
|
||||
var start_time = Time.get_ticks_msec()
|
||||
|
||||
if config.use_mono:
|
||||
var args = [config.inklecate_path, '-o', config.target_file_path, config.source_file_path]
|
||||
return_code = OS.execute(config.mono_path, args, output, true, false)
|
||||
else:
|
||||
var args = ['-o', config.target_file_path, config.source_file_path]
|
||||
return_code = OS.execute(config.inklecate_path, args, output, true, false)
|
||||
|
||||
var end_time = Time.get_ticks_msec()
|
||||
|
||||
print("[inkgd] [INFO] Command executed in %dms." % (end_time - start_time))
|
||||
|
||||
var string_output = PackedStringArray(output)
|
||||
if _configuration.use_threads:
|
||||
call_deferred("_handle_compilation_result", config, return_code, string_output)
|
||||
return true
|
||||
else:
|
||||
var result = _process_compilation_result(config, return_code, string_output)
|
||||
return result.success
|
||||
|
||||
|
||||
## Handles the compilation results when exectuted in a different thread.
|
||||
##
|
||||
## This method should always be executed on the main thread.
|
||||
func _handle_compilation_result(
|
||||
config: InkCompilationConfiguration,
|
||||
return_code: int,
|
||||
output: Array
|
||||
):
|
||||
_thread.wait_to_finish()
|
||||
|
||||
var result = _process_compilation_result(config, return_code, output)
|
||||
emit_signal("story_compiled", result)
|
||||
|
||||
|
||||
## Process the compilation results turning them into an instance of `Result`.
|
||||
##
|
||||
## This method will also print to the editor's output panel.
|
||||
func _process_compilation_result(
|
||||
config: InkCompilationConfiguration,
|
||||
return_code: int,
|
||||
output: PackedStringArray
|
||||
) -> InkExecutionResult:
|
||||
var success: bool = (return_code == 0)
|
||||
var output_text: String = "\n".join(output).replace(BOM, "").strip_edges()
|
||||
|
||||
if success:
|
||||
print("[inkgd] [INFO] %s was successfully compiled." % config.source_file_path)
|
||||
if output_text.length() > 0:
|
||||
print(output_text)
|
||||
else:
|
||||
printerr("[inkgd] [ERROR] Could not compile %s." % config.source_file_path)
|
||||
printerr(output_text)
|
||||
|
||||
return InkExecutionResult.new(
|
||||
self.identifier,
|
||||
config.use_threads,
|
||||
config.user_triggered,
|
||||
success,
|
||||
output_text
|
||||
)
|
152
addons/inkgd/editor/common/executors/ink_configuration_tester.gd
Normal file
152
addons/inkgd/editor/common/executors/ink_configuration_tester.gd
Normal file
|
@ -0,0 +1,152 @@
|
|||
# ############################################################################ #
|
||||
# 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 InkExternalCommandExecutor
|
||||
|
||||
class_name InkConfigurationTester
|
||||
|
||||
# ############################################################################ #
|
||||
# Imports
|
||||
# ############################################################################ #
|
||||
|
||||
var InkExecutionResult = load("res://addons/inkgd/editor/common/executors/structures/ink_execution_result.gd")
|
||||
|
||||
# ############################################################################ #
|
||||
# Private Properties
|
||||
# ############################################################################ #
|
||||
|
||||
## Ink Configuration
|
||||
var _configuration: InkExecutionConfiguration
|
||||
|
||||
# ############################################################################ #
|
||||
# Signals
|
||||
# ############################################################################ #
|
||||
|
||||
## Emitted when a test completed. Note that this doesn't imply that
|
||||
## the test was successful. Check the content of result
|
||||
## (InkConfigurationTester.Result) for more information.
|
||||
signal availability_tested(result)
|
||||
|
||||
# ############################################################################ #
|
||||
# Overrides
|
||||
# ############################################################################ #
|
||||
|
||||
func _init(configuration: InkExecutionConfiguration):
|
||||
_configuration = configuration
|
||||
|
||||
if _configuration.use_threads:
|
||||
_thread = Thread.new()
|
||||
|
||||
# ############################################################################ #
|
||||
# Methods
|
||||
# ############################################################################ #
|
||||
|
||||
## Test inklecate's availability, based on the configuration provided by this object.
|
||||
## If `configuration.use_thread` is set to `false`, this method will return
|
||||
## an instance of `InkExecutionResult`, otherwise, it will return `null`.
|
||||
func test_availability():
|
||||
if _configuration.use_threads:
|
||||
var error = _thread.start(Callable(self, "_test_availablity").bind(_configuration), Thread.PRIORITY_HIGH)
|
||||
if error != OK:
|
||||
var result = InkExecutionResult.new(
|
||||
self.identifier,
|
||||
_configuration.use_threads,
|
||||
_configuration.user_triggered,
|
||||
false,
|
||||
""
|
||||
)
|
||||
|
||||
emit_signal("availability_tested", result)
|
||||
return true
|
||||
else:
|
||||
return _test_availability(_configuration)
|
||||
|
||||
# ############################################################################ #
|
||||
# Private Helpers
|
||||
# ############################################################################ #
|
||||
|
||||
## Test inklecate's availability, based on the configuration provided by this object
|
||||
## If `configuration.use_thread` is set to `false`, this method will return
|
||||
## an instance of `InkExecutionResult`, otherwise, it will return `null`.
|
||||
func _test_availability(config: InkExecutionConfiguration):
|
||||
print("[inkgd] [INFO] Executing test command…")
|
||||
var return_code = 0
|
||||
var output = []
|
||||
|
||||
var start_time = Time.get_ticks_msec()
|
||||
|
||||
if config.use_mono:
|
||||
var args = [config.inklecate_path]
|
||||
return_code = OS.execute(config.mono_path, args, output, true, false)
|
||||
|
||||
else:
|
||||
return_code = OS.execute(config.inklecate_path, [], output, true, false)
|
||||
|
||||
var end_time = Time.get_ticks_msec()
|
||||
|
||||
print("[inkgd] [INFO] Command executed in %dms." % (end_time - start_time))
|
||||
|
||||
var string_output = PackedStringArray(output)
|
||||
if _configuration.use_threads:
|
||||
call_deferred("_handle_test_result", config, return_code, string_output)
|
||||
return null
|
||||
else:
|
||||
return _process_test_result(config, return_code, string_output)
|
||||
|
||||
|
||||
## Handles the test result when exectuted in a different thread.
|
||||
##
|
||||
## This method should always be executed on the main thread.
|
||||
func _handle_test_result(config: InkExecutionConfiguration, return_code: int, output: Array):
|
||||
_thread.wait_to_finish()
|
||||
|
||||
var result = _process_test_result(config, return_code, output)
|
||||
emit_signal("availability_tested", result)
|
||||
|
||||
|
||||
## Process the compilation results turning them into an instance of `Result`.
|
||||
##
|
||||
## This method will also print to the editor's output panel.
|
||||
func _process_test_result(
|
||||
config: InkExecutionConfiguration,
|
||||
return_code: int,
|
||||
output: PackedStringArray
|
||||
) -> InkExecutionResult:
|
||||
var success: bool = (return_code == 0 || _contains_inklecate_output_prefix(output))
|
||||
var output_text: String = "\n".join(output).replace(BOM, "").strip_edges()
|
||||
|
||||
if success:
|
||||
if !output_text.is_empty():
|
||||
print("[inkgd] [INFO] inklecate was found and executed:")
|
||||
print(output_text)
|
||||
else:
|
||||
print("[inkgd] [INFO] inklecate was found and executed.")
|
||||
else:
|
||||
printerr("[inkgd] [ERROR] Something went wrong while testing inklecate's setup.")
|
||||
printerr(output_text)
|
||||
|
||||
return InkExecutionResult.new(
|
||||
self.identifier,
|
||||
config.use_threads,
|
||||
config.user_triggered,
|
||||
success,
|
||||
output_text
|
||||
)
|
||||
|
||||
|
||||
## Guess whether the provided `output_array` looks like the usage inklecate
|
||||
## outputs when run with no parameters.
|
||||
func _contains_inklecate_output_prefix(output_array: PackedStringArray):
|
||||
# No valid output -> it's not inklecate.
|
||||
if output_array.size() == 0: return false
|
||||
|
||||
# The first line of the output is cleaned up by removing the BOM and
|
||||
# any sort of whitespace/unprintable character.
|
||||
var cleaned_line = output_array[0].replace(BOM, "").strip_edges()
|
||||
|
||||
# If the first line starts with the correct substring, it's likely
|
||||
# to be inklecate!
|
||||
return cleaned_line.find("Usage: inklecate2") == 0
|
|
@ -0,0 +1,32 @@
|
|||
# ############################################################################ #
|
||||
# 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 RefCounted
|
||||
|
||||
class_name InkExternalCommandExecutor
|
||||
|
||||
# ############################################################################ #
|
||||
# Properties
|
||||
# ############################################################################ #
|
||||
|
||||
## The identifier of this compiler.
|
||||
var identifier: int: get = get_identifier
|
||||
func get_identifier() -> int:
|
||||
return get_instance_id()
|
||||
|
||||
# ############################################################################ #
|
||||
# Constants
|
||||
# ############################################################################ #
|
||||
|
||||
const BOM = "\ufeff"
|
||||
|
||||
# ############################################################################ #
|
||||
# Private Properties
|
||||
# ############################################################################ #
|
||||
|
||||
## Thread used to compile the story.
|
||||
@warning_ignore("unused_private_class_variable") # Used by subclasses.
|
||||
var _thread: Thread
|
|
@ -0,0 +1,44 @@
|
|||
# ############################################################################ #
|
||||
# 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 InkExecutionConfiguration
|
||||
|
||||
## Contains all the configuration settings necessary to perform a compilation.
|
||||
class_name InkCompilationConfiguration
|
||||
|
||||
# ############################################################################ #
|
||||
# Properties
|
||||
# ############################################################################ #
|
||||
|
||||
## The path to the story to compile, local to the file system.
|
||||
var source_file_path: String = ""
|
||||
|
||||
## The path to the compiled story, local to the file system.
|
||||
var target_file_path: String = ""
|
||||
|
||||
# ############################################################################ #
|
||||
# Overrides
|
||||
# ############################################################################ #
|
||||
|
||||
@warning_ignore("shadowed_variable")
|
||||
func _init(
|
||||
configuration: InkConfiguration,
|
||||
use_threads: bool,
|
||||
user_triggered: bool,
|
||||
source_file_path: String,
|
||||
target_file_path: String
|
||||
):
|
||||
super(configuration, use_threads, user_triggered)
|
||||
self.source_file_path = ProjectSettings.globalize_path(source_file_path)
|
||||
self.target_file_path = ProjectSettings.globalize_path(target_file_path)
|
||||
|
||||
# ############################################################################ #
|
||||
# Private Methods
|
||||
# ############################################################################ #
|
||||
|
||||
func _is_running_on_windows():
|
||||
var os_name = OS.get_name()
|
||||
return (os_name == "Windows" || os_name == "UWP")
|
|
@ -0,0 +1,49 @@
|
|||
@tool
|
||||
# ############################################################################ #
|
||||
# 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 RefCounted
|
||||
|
||||
## Contains all the configuration settings necessary to perform an execution.
|
||||
class_name InkExecutionConfiguration
|
||||
|
||||
# ############################################################################ #
|
||||
# Properties
|
||||
# ############################################################################ #
|
||||
|
||||
var use_threads: bool = false
|
||||
var user_triggered: bool = false
|
||||
|
||||
|
||||
var use_mono: bool = false
|
||||
var mono_path: String = ""
|
||||
var inklecate_path: String = ""
|
||||
|
||||
# ############################################################################ #
|
||||
# Overrides
|
||||
# ############################################################################ #
|
||||
|
||||
@warning_ignore("shadowed_variable")
|
||||
func _init(
|
||||
configuration: InkConfiguration,
|
||||
use_threads: bool,
|
||||
user_triggered: bool
|
||||
):
|
||||
self.use_threads = use_threads
|
||||
self.user_triggered = user_triggered
|
||||
|
||||
self.use_mono = !_is_running_on_windows() && configuration.use_mono
|
||||
|
||||
self.mono_path = configuration.mono_path
|
||||
self.inklecate_path = configuration.inklecate_path
|
||||
|
||||
# ############################################################################ #
|
||||
# Private Methods
|
||||
# ############################################################################ #
|
||||
|
||||
func _is_running_on_windows():
|
||||
var os_name = OS.get_name()
|
||||
return (os_name == "Windows" || os_name == "UWP")
|
|
@ -0,0 +1,44 @@
|
|||
# ############################################################################ #
|
||||
# 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 RefCounted
|
||||
|
||||
## A test result, containing information about whether the test
|
||||
## suceeded and the generated output.
|
||||
class_name InkExecutionResult
|
||||
|
||||
# ############################################################################ #
|
||||
# Properties
|
||||
# ############################################################################ #
|
||||
|
||||
## The identifier of the compiler that generated this result.
|
||||
## This is the value of 'InkExecutor.identifier'.
|
||||
var identifier: int = 0
|
||||
|
||||
var use_threads: bool = false
|
||||
var user_triggered: bool = false
|
||||
|
||||
var success: bool = false
|
||||
|
||||
var output: String = ""
|
||||
|
||||
# ############################################################################ #
|
||||
# Overrides
|
||||
# ############################################################################ #
|
||||
|
||||
@warning_ignore("shadowed_variable")
|
||||
func _init(
|
||||
identifier: int,
|
||||
use_threads: bool,
|
||||
user_triggered: bool,
|
||||
success: bool,
|
||||
output: String
|
||||
):
|
||||
self.identifier = identifier
|
||||
self.use_threads = use_threads
|
||||
self.user_triggered = user_triggered
|
||||
self.success = success
|
||||
self.output = output
|
204
addons/inkgd/editor/common/ink_configuration.gd
Normal file
204
addons/inkgd/editor/common/ink_configuration.gd
Normal file
|
@ -0,0 +1,204 @@
|
|||
# ############################################################################ #
|
||||
# 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 RefCounted
|
||||
|
||||
class_name InkConfiguration
|
||||
|
||||
# ############################################################################ #
|
||||
# Enums
|
||||
# ############################################################################ #
|
||||
|
||||
enum BuildMode {
|
||||
MANUAL = 0,
|
||||
DURING_BUILD,
|
||||
AFTER_CHANGE
|
||||
}
|
||||
|
||||
# ############################################################################ #
|
||||
# Constants
|
||||
# ############################################################################ #
|
||||
|
||||
const ROOT_DIR = "res://"
|
||||
|
||||
const COMPILER_CONFIG = ROOT_DIR + ".inkgd_compiler.cfg"
|
||||
const INK_CONFIG = ROOT_DIR + ".inkgd_ink.cfg"
|
||||
|
||||
const COMPILER_CONFIG_FORMAT_VERSION = 2
|
||||
const INK_CONFIG_FORMAT_VERSION = 2
|
||||
|
||||
const FORMAT_SECTION = "format"
|
||||
const VERSION = "version"
|
||||
|
||||
const INKGD_SECTION = "inkgd"
|
||||
const USE_MONO = "use_mono"
|
||||
const MONO_PATH = "mono_path"
|
||||
const INKLECATE_PATH = "inklecate_path"
|
||||
const COMPILATION_MODE = "compilation_mode"
|
||||
const STORIES = "stories"
|
||||
const SOURCE_FILE_PATH = "source_file_path"
|
||||
const TARGET_FILE_PATH = "target_file_path"
|
||||
const WATCHED_FOLDER_PATH = "watched_folder_path"
|
||||
|
||||
const DEFAULT_STORIES = [
|
||||
{
|
||||
SOURCE_FILE_PATH: "",
|
||||
TARGET_FILE_PATH: "",
|
||||
WATCHED_FOLDER_PATH: ""
|
||||
}
|
||||
]
|
||||
|
||||
# ############################################################################ #
|
||||
# Signals
|
||||
# ############################################################################ #
|
||||
|
||||
signal story_configuration_changed()
|
||||
signal compilation_mode_changed(compilation_mode)
|
||||
|
||||
# ############################################################################ #
|
||||
# Properties
|
||||
# ############################################################################ #
|
||||
|
||||
var use_mono: bool = false
|
||||
var mono_path: String = ""
|
||||
var inklecate_path: String = ""
|
||||
|
||||
var compilation_mode: int = BuildMode.MANUAL: set = set_compilation_mode
|
||||
func set_compilation_mode(new_value: int):
|
||||
compilation_mode = new_value
|
||||
emit_signal("compilation_mode_changed", compilation_mode)
|
||||
|
||||
var stories: Array = DEFAULT_STORIES
|
||||
|
||||
# ############################################################################ #
|
||||
# Private Properties
|
||||
# ############################################################################ #
|
||||
|
||||
var _compiler_config_file = ConfigFile.new()
|
||||
var _ink_config_file = ConfigFile.new()
|
||||
|
||||
# ############################################################################ #
|
||||
# Overrides
|
||||
# ############################################################################ #
|
||||
|
||||
func _init():
|
||||
pass
|
||||
|
||||
# ############################################################################ #
|
||||
# Public Methods
|
||||
# ############################################################################ #
|
||||
|
||||
## Loads the content of the configuration files from disk.
|
||||
func retrieve():
|
||||
_retrieve_inklecate()
|
||||
_retrieve_ink()
|
||||
|
||||
## Stores the content of the configuration to the disk.
|
||||
func persist():
|
||||
_persist_inklecate()
|
||||
_persist_ink()
|
||||
|
||||
func append_new_story_configuration(
|
||||
source_file_path: String,
|
||||
target_file_path: String,
|
||||
wacthed_folder_path: String
|
||||
):
|
||||
stories.append({
|
||||
SOURCE_FILE_PATH: source_file_path,
|
||||
TARGET_FILE_PATH: target_file_path,
|
||||
WATCHED_FOLDER_PATH: wacthed_folder_path
|
||||
})
|
||||
|
||||
emit_signal("story_configuration_changed")
|
||||
|
||||
func remove_story_configuration_at_index(index: int):
|
||||
if index >= 0 && index < stories.size():
|
||||
stories.remove_at(index)
|
||||
|
||||
emit_signal("story_configuration_changed")
|
||||
|
||||
func get_story_configuration_at_index(index):
|
||||
if index >= 0 && index < stories.size():
|
||||
return stories[index]
|
||||
else:
|
||||
return null
|
||||
|
||||
func get_source_file_path(story_configuration):
|
||||
return story_configuration[SOURCE_FILE_PATH]
|
||||
|
||||
func get_target_file_path(story_configuration):
|
||||
return story_configuration[TARGET_FILE_PATH]
|
||||
|
||||
func get_watched_folder_path(story_configuration):
|
||||
return story_configuration[WATCHED_FOLDER_PATH]
|
||||
|
||||
# ############################################################################ #
|
||||
# Private Methods
|
||||
# ############################################################################ #
|
||||
|
||||
## Loads the content of the inklecate configuration file from disk.
|
||||
func _retrieve_inklecate():
|
||||
var err = _compiler_config_file.load(COMPILER_CONFIG)
|
||||
if err != OK:
|
||||
# Assuming it doesn't exist.
|
||||
return
|
||||
|
||||
use_mono = _compiler_config_file.get_value(INKGD_SECTION, USE_MONO, false)
|
||||
mono_path = _compiler_config_file.get_value(INKGD_SECTION, MONO_PATH, "")
|
||||
inklecate_path = _compiler_config_file.get_value(INKGD_SECTION, INKLECATE_PATH, "")
|
||||
|
||||
if _compiler_config_file.get_value(FORMAT_SECTION, VERSION, 0) >= 2:
|
||||
compilation_mode = _compiler_config_file.get_value(INKGD_SECTION, COMPILATION_MODE, 0)
|
||||
|
||||
## Loads the content of the story configuration file from disk.
|
||||
func _retrieve_ink():
|
||||
var err = _ink_config_file.load(INK_CONFIG)
|
||||
if err != OK:
|
||||
# Assuming it doesn't exist.
|
||||
return
|
||||
|
||||
if _ink_config_file.get_value(FORMAT_SECTION, VERSION, 0) >= 2:
|
||||
stories = _ink_config_file.get_value(INKGD_SECTION, STORIES, DEFAULT_STORIES)
|
||||
else:
|
||||
var source_file_path = _ink_config_file.get_value(INKGD_SECTION, SOURCE_FILE_PATH, "")
|
||||
var target_file_path = _ink_config_file.get_value(INKGD_SECTION, TARGET_FILE_PATH, "")
|
||||
var watched_folder_path = _ink_config_file.get_value(INKGD_SECTION, WATCHED_FOLDER_PATH, "")
|
||||
|
||||
stories[0] = {
|
||||
SOURCE_FILE_PATH: source_file_path,
|
||||
TARGET_FILE_PATH: target_file_path,
|
||||
WATCHED_FOLDER_PATH: watched_folder_path
|
||||
}
|
||||
|
||||
## Stores the content of the inklecate configuration to the disk.
|
||||
func _persist_inklecate():
|
||||
_compiler_config_file.set_value(FORMAT_SECTION, VERSION, COMPILER_CONFIG_FORMAT_VERSION)
|
||||
|
||||
_compiler_config_file.set_value(INKGD_SECTION, USE_MONO, use_mono)
|
||||
_compiler_config_file.set_value(INKGD_SECTION, MONO_PATH, mono_path)
|
||||
_compiler_config_file.set_value(INKGD_SECTION, INKLECATE_PATH, inklecate_path)
|
||||
_compiler_config_file.set_value(INKGD_SECTION, COMPILATION_MODE, compilation_mode)
|
||||
|
||||
var err = _compiler_config_file.save(COMPILER_CONFIG)
|
||||
if err != OK:
|
||||
printerr("[inkgd] [ERROR] Could not save: %s" % COMPILER_CONFIG)
|
||||
|
||||
## Stores the content of the story configuration to the disk.
|
||||
func _persist_ink():
|
||||
# Clean up the file if it was created before version 2.
|
||||
if _ink_config_file.has_section_key(INKGD_SECTION, SOURCE_FILE_PATH):
|
||||
_ink_config_file.erase_section_key(INKGD_SECTION, SOURCE_FILE_PATH)
|
||||
|
||||
if _ink_config_file.has_section_key(INKGD_SECTION, TARGET_FILE_PATH):
|
||||
_ink_config_file.erase_section_key(INKGD_SECTION, TARGET_FILE_PATH)
|
||||
|
||||
# Write version 2 values.
|
||||
_ink_config_file.set_value(FORMAT_SECTION, VERSION, COMPILER_CONFIG_FORMAT_VERSION)
|
||||
_ink_config_file.set_value(INKGD_SECTION, STORIES, stories)
|
||||
|
||||
var err = _ink_config_file.save(INK_CONFIG)
|
||||
if err != OK:
|
||||
printerr("[inkgd] [ERROR] Could not save: %s" % INK_CONFIG)
|
103
addons/inkgd/editor/common/ink_csharp_validator.gd
Normal file
103
addons/inkgd/editor/common/ink_csharp_validator.gd
Normal file
|
@ -0,0 +1,103 @@
|
|||
# ############################################################################ #
|
||||
# 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 RefCounted
|
||||
|
||||
# A crude validator catching the most common mistakes.
|
||||
|
||||
class_name InkCSharpValidator
|
||||
|
||||
const INK_ENGINE_RUNTIME = "ink-engine-runtime.dll"
|
||||
|
||||
# ############################################################################ #
|
||||
# Methods
|
||||
# ############################################################################ #
|
||||
|
||||
func validate_csharp_project_files(project_name) -> bool:
|
||||
var ink_engine_runtime := get_runtime_path()
|
||||
|
||||
if ink_engine_runtime.is_empty():
|
||||
print(
|
||||
"[inkgd] [INFO] 'ink-engine-runtime.dll' seems to be missing " +
|
||||
"from the project. If you encounter errors while building the " +
|
||||
"solution, please refer to [TO BE ADDED] for help."
|
||||
)
|
||||
return false
|
||||
|
||||
return _validate_csproj(project_name, ink_engine_runtime)
|
||||
|
||||
func get_runtime_path() -> String:
|
||||
return _scan_directory("res://")
|
||||
|
||||
func _validate_csproj(project_name: String, runtime_path: String) -> bool:
|
||||
var csproj_path = "res://%s.csproj" % project_name
|
||||
|
||||
if !FileAccess.file_exists(csproj_path):
|
||||
printerr(
|
||||
("[inkgd] [ERROR] The C# project (%s.csproj) doesn't exist. " % project_name) +
|
||||
"You can create a new C# project through " +
|
||||
"Project > Tools > C# > Create C# Solution. Alternatively, you can also set " +
|
||||
"Project Settings > General > Inkgd > Do Not Use Mono Runtime to 'Yes' " +
|
||||
"if you do not wish to use the C# version of Ink. "
|
||||
)
|
||||
return false
|
||||
|
||||
var file := FileAccess.open(csproj_path, FileAccess.READ)
|
||||
var error := FileAccess.get_open_error()
|
||||
if error != OK:
|
||||
printerr(
|
||||
"[inkgd] [ERROR] The C# project (%s.csproj) exists but it could not be opened." +
|
||||
"(Code %d)" % [project_name, error]
|
||||
)
|
||||
return false
|
||||
|
||||
var content := file.get_as_text()
|
||||
file.close()
|
||||
|
||||
if content.find(runtime_path.replace("res://", "")) == -1:
|
||||
print(
|
||||
"[inkgd] [INFO] '%s.csproj' seems to be missing a " % project_name +
|
||||
"<RefCounted> item matching '%s'. If you encounter " % runtime_path +
|
||||
"further errors please refer to [TO BE ADDED] for help."
|
||||
)
|
||||
return false
|
||||
|
||||
print("[inkgd] [INFO] The C# Project seems to be configured correctly.")
|
||||
return true
|
||||
|
||||
func _scan_directory(path) -> String:
|
||||
var directory := DirAccess.open(path)
|
||||
var error := DirAccess.get_open_error()
|
||||
if error != OK:
|
||||
printerr(
|
||||
"[inkgd] [ERROR] Could not open '%s', " % path +
|
||||
"can't look for ink-engine-runtime.dll."
|
||||
)
|
||||
return ""
|
||||
|
||||
if directory.list_dir_begin() != OK:# TODOConverter3To4 fill missing arguments https://github.com/godotengine/godot/pull/40547
|
||||
printerr(
|
||||
"[inkgd] [ERROR] Could not list contents of '%s', " % path +
|
||||
"can't look for ink-engine-runtime.dll."
|
||||
)
|
||||
return ""
|
||||
|
||||
var file_name := directory.get_next()
|
||||
while file_name != "":
|
||||
if directory.current_is_dir():
|
||||
var ink_runtime = _scan_directory(
|
||||
"%s/%s" % [directory.get_current_dir(), file_name]
|
||||
)
|
||||
|
||||
if !ink_runtime.is_empty():
|
||||
return ink_runtime
|
||||
else:
|
||||
if file_name == INK_ENGINE_RUNTIME:
|
||||
return "%s/%s" % [directory.get_current_dir(), file_name]
|
||||
|
||||
file_name = directory.get_next()
|
||||
|
||||
return ""
|
84
addons/inkgd/editor/common/ink_editor_interface.gd
Normal file
84
addons/inkgd/editor/common/ink_editor_interface.gd
Normal file
|
@ -0,0 +1,84 @@
|
|||
# ############################################################################ #
|
||||
# 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 RefCounted
|
||||
|
||||
class_name InkEditorInterface
|
||||
|
||||
# ############################################################################ #
|
||||
# Signals
|
||||
# ############################################################################ #
|
||||
|
||||
## Emitted when 'Ink' resources (i. e. files with the '.ink' extension) were
|
||||
## reimported by Godot.
|
||||
signal ink_ressources_reimported(resources)
|
||||
|
||||
# ############################################################################ #
|
||||
# Properties
|
||||
# ############################################################################ #
|
||||
|
||||
## The pixel display scale of the editor.
|
||||
var scale: float = 1.0
|
||||
|
||||
var editor_interface: EditorInterface
|
||||
var editor_settings: EditorSettings
|
||||
var editor_filesystem: EditorFileSystem
|
||||
|
||||
## `true` if the editor is running on Windows, `false` otherwise.
|
||||
var is_running_on_windows: bool: get = get_is_running_on_windows
|
||||
func get_is_running_on_windows() -> bool:
|
||||
var os_name = OS.get_name()
|
||||
return (os_name == "Windows" || os_name == "UWP")
|
||||
|
||||
|
||||
# ############################################################################ #
|
||||
# Overrides
|
||||
# ############################################################################ #
|
||||
|
||||
@warning_ignore("shadowed_variable")
|
||||
func _init(editor_interface: EditorInterface):
|
||||
self.editor_interface = editor_interface
|
||||
self.editor_settings = editor_interface.get_editor_settings()
|
||||
self.editor_filesystem = editor_interface.get_resource_filesystem()
|
||||
|
||||
scale = editor_interface.get_editor_scale()
|
||||
|
||||
self.editor_filesystem.connect("resources_reimported", Callable(self, "_resources_reimported"))
|
||||
|
||||
# ############################################################################ #
|
||||
# Methods
|
||||
# ############################################################################ #
|
||||
|
||||
## Tell Godot to scan for updated resources.
|
||||
func scan_file_system():
|
||||
self.editor_filesystem.scan()
|
||||
|
||||
## Tell Godot to scan the given resource.
|
||||
func update_file(path: String):
|
||||
self.editor_filesystem.update_file(path)
|
||||
|
||||
## Returns a custom header color based on the editor's base color.
|
||||
##
|
||||
## If the base color is not found, return 'Color.transparent'.
|
||||
func get_custom_header_color() -> Color:
|
||||
var color = self.editor_settings.get_setting("interface/theme/base_color")
|
||||
if color != null:
|
||||
return Color.from_hsv(color.h * 0.99, color.s * 0.6, color.v * 1.1)
|
||||
else:
|
||||
return Color.TRANSPARENT
|
||||
|
||||
# ############################################################################ #
|
||||
# Signal Receivers
|
||||
# ############################################################################ #
|
||||
|
||||
func _resources_reimported(resources):
|
||||
var ink_resources := PackedStringArray()
|
||||
|
||||
for resource in resources:
|
||||
if resource.get_extension() == "ink":
|
||||
ink_resources.append(resource)
|
||||
|
||||
emit_signal("ink_ressources_reimported", ink_resources)
|
BIN
addons/inkgd/editor/icons/compile.svg
(Stored with Git LFS)
Normal file
BIN
addons/inkgd/editor/icons/compile.svg
(Stored with Git LFS)
Normal file
Binary file not shown.
37
addons/inkgd/editor/icons/compile.svg.import
Normal file
37
addons/inkgd/editor/icons/compile.svg.import
Normal file
|
@ -0,0 +1,37 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://b3mk2i51yhbcn"
|
||||
path="res://.godot/imported/compile.svg-8aaa0400caa294ef4be4b83b3d6de9aa.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/inkgd/editor/icons/compile.svg"
|
||||
dest_files=["res://.godot/imported/compile.svg-8aaa0400caa294ef4be4b83b3d6de9aa.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
BIN
addons/inkgd/editor/icons/ink_player.svg
(Stored with Git LFS)
Normal file
BIN
addons/inkgd/editor/icons/ink_player.svg
(Stored with Git LFS)
Normal file
Binary file not shown.
37
addons/inkgd/editor/icons/ink_player.svg.import
Normal file
37
addons/inkgd/editor/icons/ink_player.svg.import
Normal file
|
@ -0,0 +1,37 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://tq48ux26onni"
|
||||
path="res://.godot/imported/ink_player.svg-69488eb4b789a6c4d2f7a8b28953ad01.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/inkgd/editor/icons/ink_player.svg"
|
||||
dest_files=["res://.godot/imported/ink_player.svg-69488eb4b789a6c4d2f7a8b28953ad01.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
94
addons/inkgd/editor/import_plugins/ink_json_import_plugin.gd
Normal file
94
addons/inkgd/editor/import_plugins/ink_json_import_plugin.gd
Normal file
|
@ -0,0 +1,94 @@
|
|||
# ############################################################################ #
|
||||
# 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 EditorImportPlugin
|
||||
|
||||
class_name InkJsonImportPlugin
|
||||
|
||||
# ############################################################################ #
|
||||
# Imports
|
||||
# ############################################################################ #
|
||||
|
||||
var InkConfiguration = load("res://addons/inkgd/editor/common/ink_configuration.gd")
|
||||
var InkResource = load("res://addons/inkgd/editor/import_plugins/ink_resource.gd")
|
||||
|
||||
# ############################################################################ #
|
||||
# Properties
|
||||
# ############################################################################ #
|
||||
|
||||
var _configuration = InkConfiguration.new()
|
||||
|
||||
# ############################################################################ #
|
||||
# Overrides
|
||||
# ############################################################################ #
|
||||
|
||||
func _get_importer_name():
|
||||
return "inkgd.ink.json";
|
||||
|
||||
func _get_visible_name():
|
||||
return "Compiled ink story";
|
||||
|
||||
func _get_recognized_extensions():
|
||||
return ["json"];
|
||||
|
||||
func _get_save_extension():
|
||||
return "res";
|
||||
|
||||
func _get_resource_type():
|
||||
return "Resource";
|
||||
|
||||
func _get_priority():
|
||||
return 1.0
|
||||
|
||||
func _get_import_options(_path, _preset):
|
||||
return [
|
||||
{
|
||||
"name": "compress",
|
||||
"default_value": true
|
||||
}
|
||||
]
|
||||
|
||||
func _get_import_order():
|
||||
return 0
|
||||
|
||||
func _get_option_visibility(_path, _option_name, _options):
|
||||
return true
|
||||
|
||||
func _get_preset_count():
|
||||
return 0
|
||||
|
||||
func _import(source_file, save_path, options, _platform_variants, _gen_files):
|
||||
_configuration.retrieve()
|
||||
|
||||
var raw_json = _get_file_content(source_file)
|
||||
|
||||
var test_json_conv = JSON.new()
|
||||
test_json_conv.parse(raw_json)
|
||||
var json = test_json_conv.get_data()
|
||||
if !json.has("inkVersion"):
|
||||
return ERR_FILE_UNRECOGNIZED
|
||||
|
||||
var resource = InkResource.new()
|
||||
resource.json = raw_json
|
||||
|
||||
var flags = ResourceSaver.FLAG_COMPRESS if options["compress"] else 0
|
||||
return ResourceSaver.save(resource, "%s.%s" % [save_path, _get_save_extension()], flags)
|
||||
|
||||
# ############################################################################ #
|
||||
# Private Helpers
|
||||
# ############################################################################ #
|
||||
|
||||
func _get_file_content(source_file):
|
||||
var file := FileAccess.open(source_file, FileAccess.READ)
|
||||
var err := FileAccess.get_open_error()
|
||||
if err != OK:
|
||||
return err
|
||||
|
||||
var text_content = file.get_as_text()
|
||||
|
||||
file.close()
|
||||
return text_content
|
16
addons/inkgd/editor/import_plugins/ink_resource.gd
Normal file
16
addons/inkgd/editor/import_plugins/ink_resource.gd
Normal file
|
@ -0,0 +1,16 @@
|
|||
# ############################################################################ #
|
||||
# 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 Resource
|
||||
|
||||
# A very simple resource to store the content of a json file, as a string.
|
||||
class_name InkResource
|
||||
|
||||
# ############################################################################ #
|
||||
# Properties
|
||||
# ############################################################################ #
|
||||
|
||||
@export var json: String = ""
|
|
@ -0,0 +1,51 @@
|
|||
# ############################################################################ #
|
||||
# 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 EditorImportPlugin
|
||||
|
||||
class_name InkSourceImportPlugin
|
||||
|
||||
# ############################################################################ #
|
||||
# Overrides
|
||||
# ############################################################################ #
|
||||
|
||||
func _get_importer_name():
|
||||
return "inkgd.ink";
|
||||
|
||||
func _get_visible_name():
|
||||
return "Ink file";
|
||||
|
||||
func _get_recognized_extensions():
|
||||
return ["ink"];
|
||||
|
||||
func _get_save_extension():
|
||||
return "res";
|
||||
|
||||
func _get_resource_type():
|
||||
return "Resource";
|
||||
|
||||
func _get_priority():
|
||||
return 1.0
|
||||
|
||||
func _get_import_options(_path, _preset):
|
||||
return []
|
||||
|
||||
func _get_import_order():
|
||||
return 0
|
||||
|
||||
func _get_option_visibility(_path, _option_name, _options):
|
||||
return true
|
||||
|
||||
func _get_preset_count():
|
||||
return 0
|
||||
|
||||
func _import(_source_file, save_path, _options, _platform_variants, _gen_files):
|
||||
return ResourceSaver.save(
|
||||
Resource.new(),
|
||||
"%s.%s" % [save_path, _get_save_extension()],
|
||||
ResourceSaver.FLAG_COMPRESS
|
||||
)
|
283
addons/inkgd/editor/ink_editor_plugin.gd
Normal file
283
addons/inkgd/editor/ink_editor_plugin.gd
Normal file
|
@ -0,0 +1,283 @@
|
|||
@tool
|
||||
# ############################################################################ #
|
||||
# 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 EditorPlugin
|
||||
|
||||
# Hiding this type to prevent registration of "private" nodes.
|
||||
# See https://github.com/godotengine/godot-proposals/issues/1047
|
||||
# class_name InkEditorPlugin
|
||||
|
||||
# ############################################################################ #
|
||||
# Imports
|
||||
# ############################################################################ #
|
||||
|
||||
var InkJsonImportPlugin = preload("res://addons/inkgd/editor/import_plugins/ink_json_import_plugin.gd")
|
||||
var InkSourceImportPlugin = preload("res://addons/inkgd/editor/import_plugins/ink_source_import_plugin.gd")
|
||||
|
||||
var InkBottomPanel = preload("res://addons/inkgd/editor/panel/ink_bottom_panel.tscn")
|
||||
|
||||
var InkCSharpValidator = preload("res://addons/inkgd/editor/common/ink_csharp_validator.gd")
|
||||
|
||||
var InkEditorInterface = load("res://addons/inkgd/editor/common/ink_editor_interface.gd")
|
||||
var InkConfiguration = load("res://addons/inkgd/editor/common/ink_configuration.gd")
|
||||
|
||||
var InkCompilationConfiguration = load("res://addons/inkgd/editor/common/executors/structures/ink_compilation_configuration.gd")
|
||||
var InkCompiler = load("res://addons/inkgd/editor/common/executors/ink_compiler.gd")
|
||||
|
||||
# ############################################################################ #
|
||||
# Constant
|
||||
# ############################################################################ #
|
||||
|
||||
const USE_MONO_RUNTIME_SETTING = "inkgd/use_mono_runtime"
|
||||
const REGISTER_TEMPLATES_SETTING = "inkgd/register_templates"
|
||||
|
||||
|
||||
# ############################################################################ #
|
||||
# Private Properties
|
||||
# ############################################################################ #
|
||||
|
||||
var _editor_interface: InkEditorInterface = null
|
||||
var _configuration: InkConfiguration = null
|
||||
var _panel = null
|
||||
|
||||
var _ink_source_import_plugin: InkSourceImportPlugin = null
|
||||
var _ink_json_import_plugin: InkJsonImportPlugin = null
|
||||
|
||||
var _tool_button: Button = null
|
||||
|
||||
|
||||
# ############################################################################ #
|
||||
# Overrides
|
||||
# ############################################################################ #
|
||||
|
||||
func _enter_tree():
|
||||
var ink_player_icon = load("res://addons/inkgd/editor/icons/ink_player.svg")
|
||||
if ink_player_icon == null:
|
||||
printerr(
|
||||
"[inkgd] [ERROR] The plugin could not be initialized because the required assets " +
|
||||
"haven't been imported by Godot yet. This can happen when cloning a fresh project or " +
|
||||
"after deleting the '.import' folder. Disabling and reenabling InkGD in " +
|
||||
"Project > Project setting… > Plugins or reloading the project should fix the problem."
|
||||
)
|
||||
return
|
||||
|
||||
# Note: assets are not preloaded to prevent the script from failing
|
||||
# its interpretation phase if the resources have never been imported before.
|
||||
if _should_use_mono() && _validate_csproj():
|
||||
print("[inkgd] [INFO] Using the Mono runtime.")
|
||||
_register_custom_settings()
|
||||
add_custom_type(
|
||||
"InkPlayer",
|
||||
"Node",
|
||||
load("res://addons/inkgd/mono/InkPlayer.cs"),
|
||||
ink_player_icon
|
||||
)
|
||||
else:
|
||||
print("[inkgd] [INFO] Using the GDScript runtime.")
|
||||
_register_custom_settings()
|
||||
add_custom_type(
|
||||
"InkPlayer",
|
||||
"Node",
|
||||
load("res://addons/inkgd/ink_player.gd"),
|
||||
ink_player_icon
|
||||
)
|
||||
|
||||
_editor_interface = InkEditorInterface.new(get_editor_interface())
|
||||
|
||||
_configuration = InkConfiguration.new()
|
||||
_configuration.retrieve()
|
||||
|
||||
_add_bottom_panel()
|
||||
_add_import_plugin()
|
||||
|
||||
_add_autoloads()
|
||||
_add_templates()
|
||||
|
||||
|
||||
func _exit_tree():
|
||||
# The plugin hasn't been intialised properly, nothing to do.
|
||||
if _panel == null:
|
||||
return
|
||||
|
||||
_remove_bottom_panel()
|
||||
_remove_import_plugin()
|
||||
|
||||
_remove_autoloads()
|
||||
_remove_templates()
|
||||
|
||||
remove_custom_type("InkPlayer")
|
||||
|
||||
|
||||
func build():
|
||||
if _configuration.compilation_mode == InkConfiguration.BuildMode.DURING_BUILD:
|
||||
var previous_result = true
|
||||
for story_configuration in _configuration.stories:
|
||||
if !previous_result:
|
||||
break
|
||||
|
||||
var source_file_path = _configuration.get_source_file_path(story_configuration)
|
||||
var target_file_path = _configuration.get_target_file_path(story_configuration)
|
||||
|
||||
var compiler_configuration = InkCompilationConfiguration.new(
|
||||
_configuration,
|
||||
false,
|
||||
false,
|
||||
source_file_path,
|
||||
target_file_path
|
||||
)
|
||||
|
||||
var compiler = InkCompiler.new(compiler_configuration)
|
||||
var current_result = compiler.compile_story()
|
||||
|
||||
if current_result:
|
||||
_editor_interface.call_deferred("update_file", target_file_path)
|
||||
|
||||
previous_result = previous_result && current_result
|
||||
|
||||
return previous_result
|
||||
else:
|
||||
return true
|
||||
|
||||
|
||||
# ############################################################################ #
|
||||
# Private Helpers
|
||||
# ############################################################################ #
|
||||
|
||||
func _add_import_plugin():
|
||||
_ink_source_import_plugin = InkSourceImportPlugin.new()
|
||||
_ink_json_import_plugin = InkJsonImportPlugin.new()
|
||||
add_import_plugin(_ink_source_import_plugin)
|
||||
add_import_plugin(_ink_json_import_plugin)
|
||||
|
||||
|
||||
func _remove_import_plugin():
|
||||
remove_import_plugin(_ink_source_import_plugin)
|
||||
remove_import_plugin(_ink_json_import_plugin)
|
||||
_ink_source_import_plugin = null
|
||||
_ink_json_import_plugin = null
|
||||
|
||||
|
||||
func _add_bottom_panel():
|
||||
_panel = InkBottomPanel.instantiate()
|
||||
_panel.editor_interface = _editor_interface
|
||||
_panel.configuration = _configuration
|
||||
|
||||
_tool_button = add_control_to_bottom_panel(_panel, "Ink")
|
||||
|
||||
|
||||
func _remove_bottom_panel():
|
||||
remove_control_from_bottom_panel(_panel)
|
||||
_panel.queue_free()
|
||||
|
||||
|
||||
## Registers the Ink runtime node as an autoloaded singleton.
|
||||
func _add_autoloads():
|
||||
add_autoload_singleton("__InkRuntime", "res://addons/inkgd/runtime/static/ink_runtime.gd")
|
||||
|
||||
|
||||
## Unregisters the Ink runtime node from autoloaded singletons.
|
||||
func _remove_autoloads():
|
||||
remove_autoload_singleton("__InkRuntime")
|
||||
|
||||
|
||||
## Registers the script templates provided by the plugin.
|
||||
func _add_templates():
|
||||
if ProjectSettings.has_setting(REGISTER_TEMPLATES_SETTING):
|
||||
var register_template = ProjectSettings.get_setting(REGISTER_TEMPLATES_SETTING)
|
||||
if !register_template: return
|
||||
|
||||
var names = _get_plugin_templates_names()
|
||||
|
||||
# Setup the templates folder for the project
|
||||
var template_dir_path = ProjectSettings.get_setting("editor/script/templates_search_path")
|
||||
if !DirAccess.dir_exists_absolute(template_dir_path):
|
||||
DirAccess.make_dir_absolute(template_dir_path)
|
||||
|
||||
for template_name in names:
|
||||
var template_file_path = template_dir_path + "/" + template_name
|
||||
DirAccess.copy_absolute("res://addons/inkgd/editor/templates/" + template_name, template_file_path)
|
||||
|
||||
|
||||
## Unregisters the script templates provided by the plugin.
|
||||
func _remove_templates():
|
||||
var names = _get_plugin_templates_names()
|
||||
var template_dir_path = ProjectSettings.get_setting("editor/script/templates_search_path")
|
||||
|
||||
for template_name in names:
|
||||
var template_file_path = template_dir_path + "/" + template_name
|
||||
if FileAccess.file_exists(template_file_path):
|
||||
DirAccess.remove_absolute(template_file_path)
|
||||
|
||||
|
||||
## Get all the script templates provided by the plugin.
|
||||
func _get_plugin_templates_names() -> Array:
|
||||
var plugin_template_names = []
|
||||
|
||||
var dir = DirAccess.open("res://addons/inkgd/editor/templates/")
|
||||
if dir:
|
||||
dir.list_dir_begin()
|
||||
var temp = dir.get_next()
|
||||
while temp != "":
|
||||
plugin_template_names.append(temp)
|
||||
temp = dir.get_next()
|
||||
|
||||
return plugin_template_names
|
||||
else:
|
||||
print("An error occurred when trying to access the path.")
|
||||
return []
|
||||
|
||||
|
||||
func _register_custom_settings():
|
||||
if _can_run_mono():
|
||||
if !ProjectSettings.has_setting(USE_MONO_RUNTIME_SETTING):
|
||||
ProjectSettings.set_setting(USE_MONO_RUNTIME_SETTING, true)
|
||||
|
||||
var mono_property_info = {
|
||||
"name": USE_MONO_RUNTIME_SETTING,
|
||||
"type": TYPE_BOOL,
|
||||
"hint_string": "If `true` _inkgd_ will alwaus use the Mono runtime when available.",
|
||||
"default": false
|
||||
}
|
||||
|
||||
ProjectSettings.add_property_info(mono_property_info)
|
||||
|
||||
if !ProjectSettings.has_setting(REGISTER_TEMPLATES_SETTING):
|
||||
ProjectSettings.set_setting(REGISTER_TEMPLATES_SETTING, true)
|
||||
|
||||
var template_property_info = {
|
||||
"name": REGISTER_TEMPLATES_SETTING,
|
||||
"type": TYPE_BOOL,
|
||||
"hint_string": "If `true` _inkgd_ will register its script templates with the current project.",
|
||||
"default": false
|
||||
}
|
||||
|
||||
ProjectSettings.add_property_info(template_property_info)
|
||||
|
||||
|
||||
func _validate_csproj() -> bool:
|
||||
var project_name = ProjectSettings.get_setting("application/config/name")
|
||||
if project_name.is_empty():
|
||||
printerr("[inkgd] [ERROR] The project is missing a name.")
|
||||
return false
|
||||
|
||||
var validator = InkCSharpValidator.new()
|
||||
return validator.validate_csharp_project_files(project_name)
|
||||
|
||||
|
||||
func _should_use_mono():
|
||||
if ProjectSettings.has_setting(USE_MONO_RUNTIME_SETTING):
|
||||
var use_mono = ProjectSettings.get_setting(USE_MONO_RUNTIME_SETTING)
|
||||
if use_mono == null:
|
||||
use_mono = true
|
||||
|
||||
return _can_run_mono() && use_mono
|
||||
else:
|
||||
return _can_run_mono()
|
||||
|
||||
|
||||
func _can_run_mono():
|
||||
return type_exists("_GodotSharp")
|
57
addons/inkgd/editor/panel/common/ink_progress_dialog.gd
Normal file
57
addons/inkgd/editor/panel/common/ink_progress_dialog.gd
Normal file
|
@ -0,0 +1,57 @@
|
|||
@tool
|
||||
# ############################################################################ #
|
||||
# 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 Popup
|
||||
|
||||
# A custom dialog showing a progress bar.
|
||||
|
||||
# Hiding this type to prevent registration of "private" nodes.
|
||||
# See https://github.com/godotengine/godot-proposals/issues/1047
|
||||
# class_name InkProgressDialog
|
||||
|
||||
# ############################################################################ #
|
||||
# Nodes
|
||||
# ############################################################################ #
|
||||
|
||||
@onready var _margin_container = $MarginContainer
|
||||
@onready var _vbox_container = $MarginContainer/VBoxContainer
|
||||
@onready var _title_label = $MarginContainer/VBoxContainer/TitleLabel
|
||||
@onready var _progress_bar = $MarginContainer/VBoxContainer/ProgressBar
|
||||
@onready var _current_step_label = $MarginContainer/VBoxContainer/CurrentStepLabel
|
||||
|
||||
# ############################################################################ #
|
||||
# Properties
|
||||
# ############################################################################ #
|
||||
|
||||
## The title of the progress.
|
||||
var progress_title: String: get = get_progress_title, set = set_progress_title
|
||||
func set_progress_title(text: String):
|
||||
_title_label.text = text
|
||||
func get_progress_title() -> String:
|
||||
return _title_label.text
|
||||
|
||||
## The name of the current step.
|
||||
var current_step_name: String: get = get_current_step_name, set = set_current_step_name
|
||||
func set_current_step_name(text: String):
|
||||
_current_step_label.text = text
|
||||
func get_current_step_name() -> String:
|
||||
return _current_step_label.text
|
||||
|
||||
## The current progress.
|
||||
var progress: float:
|
||||
get:
|
||||
return _progress_bar.value
|
||||
set(value):
|
||||
_progress_bar.value = value
|
||||
|
||||
|
||||
func update_layout(scale: float) -> void:
|
||||
_margin_container.add_theme_constant_override("offset_right", 10 * scale)
|
||||
_margin_container.add_theme_constant_override("offset_top", 10 * scale)
|
||||
_margin_container.add_theme_constant_override("offset_left", 10 * scale)
|
||||
_margin_container.add_theme_constant_override("offset_bottom", 10 * scale)
|
||||
_vbox_container.add_theme_constant_override("separation", 5 * scale)
|
63
addons/inkgd/editor/panel/common/ink_progress_dialog.tscn
Normal file
63
addons/inkgd/editor/panel/common/ink_progress_dialog.tscn
Normal file
|
@ -0,0 +1,63 @@
|
|||
[gd_scene load_steps=2 format=2]
|
||||
|
||||
[ext_resource path="res://addons/inkgd/editor/panel/common/ink_progress_dialog.gd" type="Script" id=1]
|
||||
|
||||
[node name="InkProgressDialog" type="Popup"]
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -250.0
|
||||
offset_top = -42.5
|
||||
offset_right = 250.0
|
||||
offset_bottom = 42.5
|
||||
custom_minimum_size = Vector2( 500, 85 )
|
||||
script = ExtResource( 1 )
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="MarginContainer" type="MarginContainer" parent="."]
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
theme_override_constants/margin_right = 10
|
||||
theme_override_constants/margin_top = 10
|
||||
theme_override_constants/margin_left = 10
|
||||
theme_override_constants/margin_bottom = 10
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"]
|
||||
offset_left = 10.0
|
||||
offset_top = 10.0
|
||||
offset_right = 490.0
|
||||
offset_bottom = 75.0
|
||||
theme_override_constants/separation = 5
|
||||
alignment = 1
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="TitleLabel" type="Label" parent="MarginContainer/VBoxContainer"]
|
||||
offset_top = 1.0
|
||||
offset_right = 480.0
|
||||
offset_bottom = 15.0
|
||||
text = "Compiling..."
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="ProgressBar" type="ProgressBar" parent="MarginContainer/VBoxContainer"]
|
||||
offset_top = 25.0
|
||||
offset_right = 480.0
|
||||
offset_bottom = 39.0
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="CurrentStepLabel" type="Label" parent="MarginContainer/VBoxContainer"]
|
||||
offset_top = 49.0
|
||||
offset_right = 480.0
|
||||
offset_bottom = 63.0
|
||||
text = "the_intercept.ink"
|
103
addons/inkgd/editor/panel/common/ink_rich_dialog.gd
Normal file
103
addons/inkgd/editor/panel/common/ink_rich_dialog.gd
Normal file
|
@ -0,0 +1,103 @@
|
|||
@tool
|
||||
# ############################################################################ #
|
||||
# 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 Window
|
||||
|
||||
# A custom dialog showing a message and, optionally, a command output.
|
||||
|
||||
# Hiding this type to prevent registration of "private" nodes.
|
||||
# See https://github.com/godotengine/godot-proposals/issues/1047
|
||||
# class_name InkRichDialog
|
||||
|
||||
# ############################################################################ #
|
||||
# Nodes
|
||||
# ############################################################################ #
|
||||
|
||||
@onready var _margin_container = $MarginContainer
|
||||
@onready var _vbox_container = $MarginContainer/VBoxContainer
|
||||
@onready var _message_label = $MarginContainer/VBoxContainer/MessageLabel
|
||||
@onready var _accept_button = $MarginContainer/VBoxContainer/AcceptButton
|
||||
@onready var _output_panel = $MarginContainer/VBoxContainer/OutputPanel
|
||||
@onready var _output_label = find_child("OutputLabel")
|
||||
|
||||
# ############################################################################ #
|
||||
# Properties
|
||||
# ############################################################################ #
|
||||
|
||||
## The message displayed in the dialog.
|
||||
var message_text: String: get = get_message_text, set = set_message_text
|
||||
func set_message_text(text: String):
|
||||
_message_label.text = text
|
||||
func get_message_text() -> String:
|
||||
return _message_label.text
|
||||
|
||||
## An output, often the result of a command, than can optionally be displayed
|
||||
## in the dialog.
|
||||
##
|
||||
## Setting this property to null hides the corresponding panel in the dialog.
|
||||
var output_text: String: get = get_output_text, set = set_output_text
|
||||
func set_output_text(text: String):
|
||||
_output_label.text = text
|
||||
_output_label.visible = !(text == null || text.length() == 0)
|
||||
func get_output_text() -> String:
|
||||
return _output_label.text
|
||||
|
||||
# ############################################################################ #
|
||||
# Overriden Methods
|
||||
# ############################################################################ #
|
||||
|
||||
func _ready():
|
||||
_accept_button.connect("pressed", Callable(self, "_accept_button_pressed"))
|
||||
|
||||
var font = _get_source_font()
|
||||
if font != null:
|
||||
_output_panel.add_theme_font_override("font", font)
|
||||
|
||||
# ############################################################################ #
|
||||
# Methods
|
||||
# ############################################################################ #
|
||||
|
||||
func update_layout(scale: float) -> void:
|
||||
_margin_container.add_theme_constant_override("offset_right", 10 * scale)
|
||||
_margin_container.add_theme_constant_override("offset_top", 10 * scale)
|
||||
_margin_container.add_theme_constant_override("offset_left", 10 * scale)
|
||||
_margin_container.add_theme_constant_override("offset_bottom", 10 * scale)
|
||||
_vbox_container.add_theme_constant_override("separation", 10 * scale)
|
||||
|
||||
|
||||
# ############################################################################ #
|
||||
# Signal Receivers
|
||||
# ############################################################################ #
|
||||
|
||||
func _accept_button_pressed():
|
||||
self.get_parent().remove_child(self)
|
||||
self.queue_free()
|
||||
|
||||
# ############################################################################ #
|
||||
# Private helpers
|
||||
# ############################################################################ #
|
||||
|
||||
## Gets the monospaced font used by the editor.
|
||||
func _get_source_font():
|
||||
var base_theme = _retrieve_base_theme()
|
||||
if base_theme:
|
||||
return base_theme.get_font("output_source", "EditorFonts")
|
||||
else:
|
||||
return null
|
||||
|
||||
## Gets the theme currently used by the editor.
|
||||
func _retrieve_base_theme():
|
||||
var parent = self
|
||||
|
||||
while(parent != null && parent.theme == null):
|
||||
var older_parent = parent.get_parent()
|
||||
if older_parent is Control:
|
||||
parent = older_parent
|
||||
else:
|
||||
break
|
||||
|
||||
return parent.theme
|
89
addons/inkgd/editor/panel/common/ink_rich_dialog.tscn
Normal file
89
addons/inkgd/editor/panel/common/ink_rich_dialog.tscn
Normal file
|
@ -0,0 +1,89 @@
|
|||
[gd_scene load_steps=2 format=2]
|
||||
|
||||
[ext_resource path="res://addons/inkgd/editor/panel/common/ink_rich_dialog.gd" type="Script" id=1]
|
||||
|
||||
[node name="Window" type="Window"]
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -250.0
|
||||
offset_top = -150.0
|
||||
offset_right = 250.0
|
||||
offset_bottom = 150.0
|
||||
custom_minimum_size = Vector2( 500, 300 )
|
||||
window_title = "Error"
|
||||
resizable = true
|
||||
script = ExtResource( 1 )
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="MarginContainer" type="MarginContainer" parent="."]
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
theme_override_constants/margin_right = 10
|
||||
theme_override_constants/margin_top = 10
|
||||
theme_override_constants/margin_left = 10
|
||||
theme_override_constants/margin_bottom = 10
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"]
|
||||
offset_left = 10.0
|
||||
offset_top = 10.0
|
||||
offset_right = 490.0
|
||||
offset_bottom = 290.0
|
||||
theme_override_constants/separation = 10
|
||||
|
||||
[node name="MessageLabel" type="Label" parent="MarginContainer/VBoxContainer"]
|
||||
offset_right = 480.0
|
||||
offset_bottom = 31.0
|
||||
text = "Something went wrong while testing inklecate's setup. Please see the output below."
|
||||
autowrap = true
|
||||
|
||||
[node name="OutputPanel" type="Panel" parent="MarginContainer/VBoxContainer"]
|
||||
offset_top = 41.0
|
||||
offset_right = 480.0
|
||||
offset_bottom = 250.0
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="OutputScrollContainer" type="ScrollContainer" parent="MarginContainer/VBoxContainer/OutputPanel"]
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="OutputMarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/OutputPanel/OutputScrollContainer"]
|
||||
offset_right = 480.0
|
||||
offset_bottom = 136.0
|
||||
size_flags_horizontal = 3
|
||||
theme_override_constants/margin_right = 10
|
||||
theme_override_constants/margin_top = 10
|
||||
theme_override_constants/margin_left = 10
|
||||
theme_override_constants/margin_bottom = 10
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="OutputLabel" type="Label" parent="MarginContainer/VBoxContainer/OutputPanel/OutputScrollContainer/OutputMarginContainer"]
|
||||
offset_left = 10.0
|
||||
offset_top = 10.0
|
||||
offset_right = 470.0
|
||||
offset_bottom = 126.0
|
||||
size_flags_vertical = 0
|
||||
text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
|
||||
autowrap = true
|
||||
|
||||
[node name="AcceptButton" type="Button" parent="MarginContainer/VBoxContainer"]
|
||||
offset_left = 224.0
|
||||
offset_top = 260.0
|
||||
offset_right = 255.0
|
||||
offset_bottom = 280.0
|
||||
size_flags_horizontal = 4
|
||||
text = "OK"
|
|
@ -0,0 +1,306 @@
|
|||
@tool
|
||||
# ############################################################################ #
|
||||
# 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 Control
|
||||
|
||||
# Hiding this type to prevent registration of "private" nodes.
|
||||
# See https://github.com/godotengine/godot-proposals/issues/1047
|
||||
# class_name InkConfigurationPanel
|
||||
|
||||
# ############################################################################ #
|
||||
# Imports
|
||||
# ############################################################################ #
|
||||
|
||||
var InkExecutionConfiguration = load("res://addons/inkgd/editor/common/executors/structures/ink_execution_configuration.gd")
|
||||
var InkConfigurationTester = load("res://addons/inkgd/editor/common/executors/ink_configuration_tester.gd")
|
||||
|
||||
var InkCSharpValidator = preload("res://addons/inkgd/editor/common/ink_csharp_validator.gd")
|
||||
|
||||
var InkRichDialog = load("res://addons/inkgd/editor/panel/common/ink_rich_dialog.tscn")
|
||||
|
||||
|
||||
# ############################################################################ #
|
||||
# Enums
|
||||
# ############################################################################ #
|
||||
|
||||
## Represents which configuration setting triggered the file dialog.
|
||||
enum FileDialogSelection {
|
||||
UNKNOWN,
|
||||
MONO_EXECUTABLE,
|
||||
INKLECATE_EXECUTABLE
|
||||
}
|
||||
|
||||
|
||||
# ############################################################################ #
|
||||
# Constants
|
||||
# ############################################################################ #
|
||||
|
||||
const BOM = "\ufeff"
|
||||
|
||||
|
||||
# ############################################################################ #
|
||||
# Properties
|
||||
# ############################################################################ #
|
||||
|
||||
var editor_interface: InkEditorInterface = null
|
||||
var configuration: InkConfiguration = null
|
||||
|
||||
|
||||
# ############################################################################ #
|
||||
# Private Properties
|
||||
# ############################################################################ #
|
||||
|
||||
var _file_dialog = EditorFileDialog.new()
|
||||
|
||||
## Configuration item for which the FileDialog is currently shown.
|
||||
##
|
||||
## Unknown by default.
|
||||
var _file_dialog_selection = FileDialogSelection.UNKNOWN
|
||||
|
||||
|
||||
# ############################################################################ #
|
||||
# Nodes
|
||||
# ############################################################################ #
|
||||
|
||||
@onready var _test_button = find_child("TestButton")
|
||||
|
||||
@onready var _use_mono_label = find_child("UseMonoLabel")
|
||||
@onready var _use_mono_checkbox = find_child("UseMonoCheckBox")
|
||||
|
||||
@onready var _mono_label = find_child("MonoLabel")
|
||||
@onready var _mono_container = find_child("MonoH")
|
||||
@onready var _mono_line_edit = find_child("MonoLineEdit")
|
||||
@onready var _mono_dialog_button = find_child("MonoDialogButton")
|
||||
|
||||
@onready var _executable_line_edit = find_child("ExecutableLineEdit")
|
||||
@onready var _executable_dialog_button = find_child("ExecutableDialogButton")
|
||||
|
||||
@onready var _recompilation_mode_button = find_child("RecompilationModeOptionButton")
|
||||
|
||||
@onready var _mono_support_container = find_child("MonoSupportV")
|
||||
@onready var _mono_support_documentation_button = find_child("DocumentationButton")
|
||||
@onready var _mono_support_presence_label = _mono_support_container.find_child("PresenceLabel")
|
||||
@onready var _mono_support_refresh_button = _mono_support_container.find_child("RefreshButton")
|
||||
|
||||
|
||||
# ############################################################################ #
|
||||
# Overrides
|
||||
# ############################################################################ #
|
||||
|
||||
func _ready():
|
||||
# FIXME: This needs investigating.
|
||||
# Sanity check. It seems the editor instantiates tools script on their
|
||||
# own, probably to add them to its tree. In that case, they won't have
|
||||
# their dependencies injected, so we're not doing anything.
|
||||
if editor_interface == null || configuration == null:
|
||||
print("[inkgd] [INFO] Ink Configuration Tab: dependencies not met, ignoring.")
|
||||
return
|
||||
|
||||
_set_button_icons()
|
||||
_apply_configuration()
|
||||
_connect_signals()
|
||||
_check_runtime_presence()
|
||||
|
||||
_mono_support_container.visible = _can_run_mono()
|
||||
|
||||
add_child(_file_dialog)
|
||||
|
||||
|
||||
# ############################################################################ #
|
||||
# Signal Receivers
|
||||
# ############################################################################ #
|
||||
|
||||
func _configuration_entered(_new_text: String):
|
||||
_configuration_focus_exited()
|
||||
|
||||
|
||||
func _configuration_focus_exited():
|
||||
configuration.mono_path = _mono_line_edit.text
|
||||
configuration.inklecate_path = _executable_line_edit.text
|
||||
|
||||
configuration.persist()
|
||||
|
||||
|
||||
func _use_mono_toggled(_toggled: bool):
|
||||
configuration.use_mono = !configuration.use_mono
|
||||
configuration.persist()
|
||||
|
||||
_update_mono_availability(false)
|
||||
|
||||
|
||||
func _mono_button_pressed():
|
||||
_reset_file_dialog()
|
||||
|
||||
_file_dialog_selection = FileDialogSelection.MONO_EXECUTABLE
|
||||
_file_dialog.current_path = configuration.mono_path
|
||||
_file_dialog.current_dir = configuration.mono_path.get_base_dir()
|
||||
_file_dialog.current_file = configuration.mono_path.get_file()
|
||||
_file_dialog.file_mode = FileDialog.FILE_MODE_OPEN_FILE
|
||||
_file_dialog.access = FileDialog.ACCESS_FILESYSTEM
|
||||
_file_dialog.popup_centered(Vector2(1280, 800) * editor_interface.scale)
|
||||
|
||||
|
||||
func _executable_button_pressed():
|
||||
_reset_file_dialog()
|
||||
|
||||
_file_dialog_selection = FileDialogSelection.INKLECATE_EXECUTABLE
|
||||
_file_dialog.current_file = configuration.inklecate_path
|
||||
_file_dialog.current_dir = configuration.inklecate_path.get_base_dir()
|
||||
_file_dialog.current_file = configuration.inklecate_path.get_file()
|
||||
_file_dialog.file_mode = FileDialog.FILE_MODE_OPEN_FILE
|
||||
_file_dialog.access = FileDialog.ACCESS_FILESYSTEM
|
||||
_file_dialog.popup_centered(Vector2(1280, 800) * editor_interface.scale)
|
||||
|
||||
|
||||
func _recompilation_mode_button_selected(index):
|
||||
configuration.compilation_mode = index
|
||||
configuration.persist()
|
||||
|
||||
|
||||
func _test_button_pressed():
|
||||
var test_configuration = InkExecutionConfiguration.new(configuration, false, true)
|
||||
var tester = InkConfigurationTester.new(test_configuration)
|
||||
|
||||
var result = tester.test_availability()
|
||||
|
||||
# NOTE: At the moment, inklecate doesn't support a subcommand that would just
|
||||
# exit with 0 so `_contains_inklecate_output_prefix` will always be executed.
|
||||
if result.success:
|
||||
var dialog = AcceptDialog.new()
|
||||
add_child(dialog)
|
||||
|
||||
dialog.title = "Success"
|
||||
dialog.dialog_text = "The configuration seems to be valid!"
|
||||
|
||||
dialog.popup_centered()
|
||||
else:
|
||||
var dialog = InkRichDialog.instantiate()
|
||||
add_child(dialog)
|
||||
|
||||
|
||||
dialog.title = "Error"
|
||||
dialog.message_text = "Something went wrong while testing inklecate's setup. Please see the output below."
|
||||
dialog.output_text = result.output
|
||||
dialog.update_layout(editor_interface.scale)
|
||||
|
||||
dialog.popup_centered(Vector2(700, 400) * editor_interface.scale)
|
||||
|
||||
|
||||
func _on_file_selected(path: String):
|
||||
match _file_dialog_selection:
|
||||
FileDialogSelection.MONO_EXECUTABLE:
|
||||
configuration.mono_path = ProjectSettings.globalize_path(path)
|
||||
_update_save_and_cleanup(configuration.mono_path, _mono_line_edit)
|
||||
|
||||
FileDialogSelection.INKLECATE_EXECUTABLE:
|
||||
configuration.inklecate_path = ProjectSettings.globalize_path(path)
|
||||
_update_save_and_cleanup(configuration.inklecate_path, _executable_line_edit)
|
||||
|
||||
_:
|
||||
printerr("[inkgd] [ERROR] Unknown FileDialogSelection, failed to save FileDialog file.")
|
||||
|
||||
_file_dialog_selection = FileDialogSelection.UNKNOWN
|
||||
|
||||
|
||||
func _check_runtime_presence():
|
||||
var ink_engine_runtime = InkCSharpValidator.new().get_runtime_path()
|
||||
var is_present = !ink_engine_runtime.is_empty()
|
||||
|
||||
if is_present:
|
||||
_mono_support_presence_label.add_theme_color_override("font_color", Color.GREEN)
|
||||
_mono_support_presence_label.text = "PRESENT"
|
||||
else:
|
||||
_mono_support_presence_label.add_theme_color_override("font_color", Color.RED)
|
||||
_mono_support_presence_label.text = "MISSING"
|
||||
|
||||
|
||||
func _mono_support_documentation_pressed():
|
||||
OS.shell_open("https://inkgd.readthedocs.io/en/latest/advanced/migrating_to_godot_mono.html")
|
||||
|
||||
|
||||
# ############################################################################ #
|
||||
# Private helpers
|
||||
# ############################################################################ #
|
||||
|
||||
func _reset_file_dialog():
|
||||
_file_dialog.current_file = ""
|
||||
_file_dialog.clear_filters()
|
||||
|
||||
|
||||
func _update_save_and_cleanup(value, line_edit):
|
||||
line_edit.text = value
|
||||
line_edit.queue_redraw()
|
||||
|
||||
configuration.persist()
|
||||
|
||||
|
||||
func _apply_configuration():
|
||||
var compilation_mode = configuration.compilation_mode
|
||||
var item_count = _recompilation_mode_button.get_item_count()
|
||||
|
||||
if compilation_mode >= 0 && compilation_mode < item_count:
|
||||
_recompilation_mode_button.select(configuration.compilation_mode)
|
||||
else:
|
||||
_recompilation_mode_button.select(0)
|
||||
|
||||
_mono_line_edit.text = configuration.mono_path
|
||||
_executable_line_edit.text = configuration.inklecate_path
|
||||
|
||||
_update_mono_availability(true)
|
||||
|
||||
|
||||
func _update_mono_availability(updates_checkbox: bool = false):
|
||||
var is_running_on_windows: bool = editor_interface.is_running_on_windows
|
||||
var is_control_visible: bool = !is_running_on_windows && configuration.use_mono
|
||||
|
||||
_use_mono_label.visible = !is_running_on_windows
|
||||
_use_mono_checkbox.visible = !is_running_on_windows
|
||||
|
||||
_mono_label.visible = is_control_visible
|
||||
_mono_container.visible = is_control_visible
|
||||
|
||||
if updates_checkbox:
|
||||
_use_mono_checkbox.set_pressed(configuration.use_mono)
|
||||
|
||||
|
||||
func _set_button_icons():
|
||||
var folder_icon = get_theme_icon("Folder", "EditorIcons")
|
||||
var reload_icon = get_theme_icon("Reload", "EditorIcons")
|
||||
var instance_icon = get_theme_icon("Instance", "EditorIcons")
|
||||
|
||||
_mono_dialog_button.icon = folder_icon
|
||||
_executable_dialog_button.icon = folder_icon
|
||||
|
||||
_mono_support_documentation_button.icon = instance_icon
|
||||
_mono_support_refresh_button.icon = reload_icon
|
||||
|
||||
|
||||
func _connect_signals():
|
||||
editor_interface.editor_filesystem.connect("filesystem_changed", Callable(self, "_check_runtime_presence"))
|
||||
|
||||
_test_button.connect("pressed", Callable(self, "_test_button_pressed"))
|
||||
_use_mono_checkbox.connect("toggled", Callable(self, "_use_mono_toggled"))
|
||||
|
||||
_mono_line_edit.connect("text_submitted", Callable(self, "_configuration_entered"))
|
||||
_executable_line_edit.connect("text_submitted", Callable(self, "_configuration_entered"))
|
||||
|
||||
_mono_line_edit.connect("focus_exited", Callable(self, "_configuration_focus_exited"))
|
||||
_executable_line_edit.connect("focus_exited", Callable(self, "_configuration_focus_exited"))
|
||||
|
||||
_mono_dialog_button.connect("pressed", Callable(self, "_mono_button_pressed"))
|
||||
_executable_dialog_button.connect("pressed", Callable(self, "_executable_button_pressed"))
|
||||
|
||||
_recompilation_mode_button.connect("item_selected", Callable(self, "_recompilation_mode_button_selected"))
|
||||
|
||||
_mono_support_documentation_button.connect("pressed", Callable(self, "_mono_support_documentation_pressed"))
|
||||
_mono_support_refresh_button.connect("pressed", Callable(self, "_check_runtime_presence"))
|
||||
|
||||
_file_dialog.connect("file_selected", Callable(self, "_on_file_selected"))
|
||||
|
||||
|
||||
func _can_run_mono():
|
||||
return type_exists("_GodotSharp")
|
|
@ -0,0 +1,258 @@
|
|||
[gd_scene load_steps=6 format=3 uid="uid://cbuep470x6krw"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/inkgd/editor/panel/configuration/ink_configuration_panel.gd" id="1"]
|
||||
|
||||
[sub_resource type="Image" id="Image_h71nq"]
|
||||
data = {
|
||||
"data": PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
|
||||
"format": "LumAlpha8",
|
||||
"height": 16,
|
||||
"mipmaps": false,
|
||||
"width": 16
|
||||
}
|
||||
|
||||
[sub_resource type="ImageTexture" id="7"]
|
||||
image = SubResource("Image_h71nq")
|
||||
|
||||
[sub_resource type="Image" id="Image_e0kbj"]
|
||||
data = {
|
||||
"data": PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
|
||||
"format": "LumAlpha8",
|
||||
"height": 16,
|
||||
"mipmaps": false,
|
||||
"width": 16
|
||||
}
|
||||
|
||||
[sub_resource type="ImageTexture" id="5"]
|
||||
image = SubResource("Image_e0kbj")
|
||||
|
||||
[node name="Configuration" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
script = ExtResource("1")
|
||||
|
||||
[node name="ScrollContainer" type="ScrollContainer" parent="."]
|
||||
layout_mode = 0
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
pivot_offset = Vector2(1346, -25)
|
||||
|
||||
[node name="V" type="VBoxContainer" parent="ScrollContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_override_constants/separation = 25
|
||||
|
||||
[node name="InklecateV" type="VBoxContainer" parent="ScrollContainer/V"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="H" type="HBoxContainer" parent="ScrollContainer/V/InklecateV"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Label" type="Label" parent="ScrollContainer/V/InklecateV/H"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "Inklecate"
|
||||
|
||||
[node name="TestButton" type="Button" parent="ScrollContainer/V/InklecateV/H"]
|
||||
layout_mode = 2
|
||||
mouse_filter = 1
|
||||
text = "Test configuration"
|
||||
|
||||
[node name="M" type="MarginContainer" parent="ScrollContainer/V/InklecateV"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Panel" type="Panel" parent="ScrollContainer/V/InklecateV/M"]
|
||||
self_modulate = Color(1, 1, 1, 0.686275)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="M" type="MarginContainer" parent="ScrollContainer/V/InklecateV/M"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
theme_override_constants/margin_left = 5
|
||||
theme_override_constants/margin_top = 5
|
||||
theme_override_constants/margin_right = 5
|
||||
theme_override_constants/margin_bottom = 5
|
||||
|
||||
[node name="V" type="VBoxContainer" parent="ScrollContainer/V/InklecateV/M/M"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="G" type="GridContainer" parent="ScrollContainer/V/InklecateV/M/M/V"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
columns = 2
|
||||
|
||||
[node name="UseMonoLabel" type="Label" parent="ScrollContainer/V/InklecateV/M/M/V/G"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 1
|
||||
tooltip_text = "When toggled, run inklecate through Mono.
|
||||
|
||||
Only enable this setting is you want to use an older version of inklecate or you have a custom setup.
|
||||
Modern versions of inklecate come bundled with a mono runtime on all platforms."
|
||||
mouse_filter = 1
|
||||
text = "Use Mono / .NET Core"
|
||||
|
||||
[node name="UseMonoCheckBox" type="CheckBox" parent="ScrollContainer/V/InklecateV/M/M/V/G"]
|
||||
layout_mode = 2
|
||||
text = "Yes"
|
||||
|
||||
[node name="MonoLabel" type="Label" parent="ScrollContainer/V/InklecateV/M/M/V/G"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 1
|
||||
tooltip_text = "The path to Mono."
|
||||
mouse_filter = 1
|
||||
text = "Mono / .NET Core Executable"
|
||||
|
||||
[node name="MonoH" type="HBoxContainer" parent="ScrollContainer/V/InklecateV/M/M/V/G"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="MonoLineEdit" type="LineEdit" parent="ScrollContainer/V/InklecateV/M/M/V/G/MonoH"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "/Library/Frameworks/Mono.framework/Versions/Current/Commands/mono"
|
||||
|
||||
[node name="MonoDialogButton" type="Button" parent="ScrollContainer/V/InklecateV/M/M/V/G/MonoH"]
|
||||
layout_mode = 2
|
||||
icon = SubResource("7")
|
||||
|
||||
[node name="ExecutableLabel" type="Label" parent="ScrollContainer/V/InklecateV/M/M/V/G"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 1
|
||||
tooltip_text = "The path to inklecate."
|
||||
mouse_filter = 1
|
||||
text = "Executable"
|
||||
|
||||
[node name="ExecutableH" type="HBoxContainer" parent="ScrollContainer/V/InklecateV/M/M/V/G"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="ExecutableLineEdit" type="LineEdit" parent="ScrollContainer/V/InklecateV/M/M/V/G/ExecutableH"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "/opt/homebrew/bin/inklecate"
|
||||
|
||||
[node name="ExecutableDialogButton" type="Button" parent="ScrollContainer/V/InklecateV/M/M/V/G/ExecutableH"]
|
||||
layout_mode = 2
|
||||
icon = SubResource("7")
|
||||
|
||||
[node name="Recompilation Mode" type="Label" parent="ScrollContainer/V/InklecateV/M/M/V/G"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 1
|
||||
tooltip_text = "Define if/when your stories should be recompiled.
|
||||
|
||||
– Manual: no automatic compilation.
|
||||
– During Build: every time the project is built/ran, the stories will be recompiled.
|
||||
– On change: as soon as an Ink resource is reimported by Godot, trigger a recompilation. A folder to watch can be defined in the \"Story\" tab."
|
||||
mouse_filter = 1
|
||||
text = "Recompilation Mode"
|
||||
|
||||
[node name="RecompilationModeOptionButton" type="OptionButton" parent="ScrollContainer/V/InklecateV/M/M/V/G"]
|
||||
layout_mode = 2
|
||||
item_count = 3
|
||||
selected = 0
|
||||
popup/item_0/text = "Manual"
|
||||
popup/item_0/id = 0
|
||||
popup/item_1/text = "During Build"
|
||||
popup/item_1/id = 1
|
||||
popup/item_2/text = "On change (experimental)"
|
||||
popup/item_2/id = 2
|
||||
|
||||
[node name="InkDirectoryLabel" type="Label" parent="ScrollContainer/V/InklecateV/M/M/V/G"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 1
|
||||
tooltip_text = "The path to Mono."
|
||||
mouse_filter = 1
|
||||
text = "Mono"
|
||||
|
||||
[node name="InkDirectoryH" type="HBoxContainer" parent="ScrollContainer/V/InklecateV/M/M/V/G"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="InkDirectoryLineEdit" type="LineEdit" parent="ScrollContainer/V/InklecateV/M/M/V/G/InkDirectoryH"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="InkDIrectoryDialogButton" type="Button" parent="ScrollContainer/V/InklecateV/M/M/V/G/InkDirectoryH"]
|
||||
layout_mode = 2
|
||||
icon = SubResource("5")
|
||||
|
||||
[node name="MonoSupportV" type="VBoxContainer" parent="ScrollContainer/V"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="H" type="HBoxContainer" parent="ScrollContainer/V/MonoSupportV"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Label" type="Label" parent="ScrollContainer/V/MonoSupportV/H"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "Mono Support"
|
||||
|
||||
[node name="DocumentationButton" type="Button" parent="ScrollContainer/V/MonoSupportV/H"]
|
||||
layout_mode = 2
|
||||
mouse_filter = 1
|
||||
theme_override_constants/h_separation = 8
|
||||
text = "Documentation"
|
||||
|
||||
[node name="M" type="MarginContainer" parent="ScrollContainer/V/MonoSupportV"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Panel" type="Panel" parent="ScrollContainer/V/MonoSupportV/M"]
|
||||
self_modulate = Color(1, 1, 1, 0.686275)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="M" type="MarginContainer" parent="ScrollContainer/V/MonoSupportV/M"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
theme_override_constants/margin_left = 5
|
||||
theme_override_constants/margin_top = 5
|
||||
theme_override_constants/margin_right = 5
|
||||
theme_override_constants/margin_bottom = 5
|
||||
|
||||
[node name="V" type="VBoxContainer" parent="ScrollContainer/V/MonoSupportV/M/M"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="G" type="GridContainer" parent="ScrollContainer/V/MonoSupportV/M/M/V"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
columns = 2
|
||||
|
||||
[node name="DLLLabel" type="Label" parent="ScrollContainer/V/MonoSupportV/M/M/V/G"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 1
|
||||
tooltip_text = "Whether the ink runtime DLL (ink-engine-runtime.dll) can be found in the project or not."
|
||||
mouse_filter = 1
|
||||
text = "ink DLL"
|
||||
|
||||
[node name="DLLH" type="HBoxContainer" parent="ScrollContainer/V/MonoSupportV/M/M/V/G"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="PresenceLabel" type="Label" parent="ScrollContainer/V/MonoSupportV/M/M/V/G/DLLH"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 1
|
||||
mouse_filter = 1
|
||||
theme_override_colors/font_color = Color(1, 0, 0, 1)
|
||||
text = "MISSING"
|
||||
|
||||
[node name="RefreshButton" type="Button" parent="ScrollContainer/V/MonoSupportV/M/M/V/G/DLLH"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/h_separation = 8
|
||||
text = "Scan"
|
||||
icon = SubResource("7")
|
106
addons/inkgd/editor/panel/ink_bottom_panel.gd
Normal file
106
addons/inkgd/editor/panel/ink_bottom_panel.gd
Normal file
|
@ -0,0 +1,106 @@
|
|||
@tool
|
||||
# warning-ignore-all:return_value_discarded
|
||||
|
||||
# ############################################################################ #
|
||||
# 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 Control
|
||||
|
||||
# Hiding this type to prevent registration of "private" nodes.
|
||||
# See https://github.com/godotengine/godot-proposals/issues/1047
|
||||
# class_name InkBottomPanel
|
||||
|
||||
# ############################################################################ #
|
||||
# Imports
|
||||
# ############################################################################ #
|
||||
|
||||
var InkStoryPanelScene = load("res://addons/inkgd/editor/panel/stories/ink_story_panel.tscn")
|
||||
var InkPreviewPanelScene = load("res://addons/inkgd/editor/panel/preview/ink_preview_panel.tscn")
|
||||
var InkConfigurationPanelScene = load("res://addons/inkgd/editor/panel/configuration/ink_configuration_panel.tscn")
|
||||
|
||||
# ############################################################################ #
|
||||
# Properties
|
||||
# ############################################################################ #
|
||||
|
||||
var editor_interface: InkEditorInterface = null
|
||||
var configuration: InkConfiguration = null
|
||||
|
||||
# ############################################################################ #
|
||||
# Private Properties
|
||||
# ############################################################################ #
|
||||
|
||||
var _progress_texture: AnimatedTexture
|
||||
|
||||
# ############################################################################ #
|
||||
# Hierarchy Nodes
|
||||
# ############################################################################ #
|
||||
|
||||
@onready var _tab_container: TabContainer = $TabContainer
|
||||
@onready var _beta_button: LinkButton = $MarginContainer/LinkButton
|
||||
|
||||
@onready var _story_panel = InkStoryPanelScene.instantiate()
|
||||
@onready var _preview_panel = InkPreviewPanelScene.instantiate()
|
||||
@onready var _configuration_panel = InkConfigurationPanelScene.instantiate()
|
||||
|
||||
# ############################################################################ #
|
||||
# Overrides
|
||||
# ############################################################################ #
|
||||
|
||||
func _ready():
|
||||
# FIXME: This needs investigating.
|
||||
# Sanity check. It seems the editor instantiates tools script on their
|
||||
# own, probably to add them to its tree. In that case, they won't have
|
||||
# their dependencies injected, so we're not doing anything.
|
||||
if editor_interface == null || configuration == null:
|
||||
print("[inkgd] [INFO] Ink Bottom Panel: dependencies not met, ignoring.")
|
||||
return
|
||||
|
||||
_progress_texture = _create_progress_texture()
|
||||
|
||||
_story_panel.editor_interface = editor_interface
|
||||
_story_panel.configuration = configuration
|
||||
_story_panel.progress_texture = _progress_texture
|
||||
|
||||
_preview_panel.editor_interface = editor_interface
|
||||
_preview_panel.configuration = configuration
|
||||
_preview_panel.progress_texture = _progress_texture
|
||||
|
||||
_configuration_panel.editor_interface = editor_interface
|
||||
_configuration_panel.configuration = configuration
|
||||
|
||||
_tab_container.add_child(_story_panel)
|
||||
_tab_container.add_child(_preview_panel)
|
||||
_tab_container.add_child(_configuration_panel)
|
||||
|
||||
_beta_button.connect("pressed", Callable(self, "_open_github_issues"))
|
||||
|
||||
_set_minimum_panel_size()
|
||||
|
||||
|
||||
# ############################################################################ #
|
||||
# Signals Receivers
|
||||
# ############################################################################ #
|
||||
|
||||
func _open_github_issues():
|
||||
OS.shell_open("https://github.com/ephread/inkgd/issues/new?assignees=&labels=&template=bug_report.md")
|
||||
|
||||
# ############################################################################ #
|
||||
# Private helpers
|
||||
# ############################################################################ #
|
||||
|
||||
func _create_progress_texture() -> AnimatedTexture:
|
||||
var animated_texture = AnimatedTexture.new()
|
||||
animated_texture.frames = 8
|
||||
|
||||
for index in range(8):
|
||||
var texture = get_theme_icon(str("Progress", (index + 1)), "EditorIcons")
|
||||
animated_texture.set_frame_texture(index, texture)
|
||||
|
||||
return animated_texture
|
||||
|
||||
func _set_minimum_panel_size():
|
||||
# Adapting the minimum size of the panel to the scale of the editor.
|
||||
custom_minimum_size = Vector2(900, 245) * editor_interface.scale
|
51
addons/inkgd/editor/panel/ink_bottom_panel.tscn
Normal file
51
addons/inkgd/editor/panel/ink_bottom_panel.tscn
Normal file
|
@ -0,0 +1,51 @@
|
|||
[gd_scene load_steps=2 format=2]
|
||||
|
||||
[ext_resource path="res://addons/inkgd/editor/panel/ink_bottom_panel.gd" type="Script" id=1]
|
||||
|
||||
[node name="Control" type="Control"]
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
script = ExtResource( 1 )
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="TabContainer" type="TabContainer" parent="."]
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
custom_minimum_size = Vector2( 0, 428.75 )
|
||||
size_flags_vertical = 3
|
||||
tab_alignment = 0
|
||||
drag_to_rearrange_enabled = true
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="MarginContainer" type="MarginContainer" parent="."]
|
||||
anchor_right = 1.0
|
||||
offset_bottom = 24.0
|
||||
clip_contents = true
|
||||
mouse_filter = 2
|
||||
size_flags_horizontal = 0
|
||||
size_flags_vertical = 0
|
||||
theme_override_constants/margin_right = 5
|
||||
theme_override_constants/margin_top = 5
|
||||
theme_override_constants/margin_left = 5
|
||||
theme_override_constants/margin_bottom = 5
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="LinkButton" type="LinkButton" parent="MarginContainer"]
|
||||
self_modulate = Color( 1, 1, 1, 0.647059 )
|
||||
offset_left = 1887.0
|
||||
offset_top = 5.0
|
||||
offset_right = 1915.0
|
||||
offset_bottom = 19.0
|
||||
tooltip_text = "Open a new issue on Github to report a problem!"
|
||||
size_flags_horizontal = 8
|
||||
text = "beta"
|
||||
underline = 1
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
366
addons/inkgd/editor/panel/preview/ink_preview_panel.gd
Normal file
366
addons/inkgd/editor/panel/preview/ink_preview_panel.gd
Normal file
|
@ -0,0 +1,366 @@
|
|||
@tool
|
||||
# ############################################################################ #
|
||||
# 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 Control
|
||||
|
||||
# Hiding this type to prevent registration of "private" nodes.
|
||||
# See https://github.com/godotengine/godot-proposals/issues/1047
|
||||
# class_name InkPreviewPanel
|
||||
|
||||
# ############################################################################ #
|
||||
# Imports
|
||||
# ############################################################################ #
|
||||
|
||||
var InkPlayerFactory := preload("res://addons/inkgd/ink_player_factory.gd") as GDScript
|
||||
|
||||
# ############################################################################ #
|
||||
# Enums
|
||||
# ############################################################################ #
|
||||
|
||||
enum StoryOrigin {
|
||||
CONFIGURATION,
|
||||
FILE
|
||||
}
|
||||
|
||||
# ############################################################################ #
|
||||
# Constants
|
||||
# ############################################################################ #
|
||||
|
||||
const NAME = "name"
|
||||
const STORY_ORIGIN = "story_origin"
|
||||
const FILE_PATH = "file_path"
|
||||
|
||||
# ############################################################################ #
|
||||
# Public Properties
|
||||
# ############################################################################ #
|
||||
|
||||
var editor_interface: InkEditorInterface
|
||||
var configuration: InkConfiguration
|
||||
var progress_texture: AnimatedTexture
|
||||
|
||||
# ############################################################################ #
|
||||
# Private Properties
|
||||
# ############################################################################ #
|
||||
|
||||
var _scrollbar_max_value = -1
|
||||
var _current_story_index = -1
|
||||
|
||||
var _custom_stories: Array = []
|
||||
var _available_stories: Array = []
|
||||
|
||||
var _file_dialog = EditorFileDialog.new()
|
||||
|
||||
var _ink_player = InkPlayerFactory.create()
|
||||
|
||||
# ############################################################################ #
|
||||
# On Ready | Private Properties
|
||||
# ############################################################################ #
|
||||
|
||||
@onready var _play_icon = get_theme_icon("Play", "EditorIcons")
|
||||
|
||||
# ############################################################################ #
|
||||
# On Ready | Private Nodes
|
||||
# ############################################################################
|
||||
|
||||
@onready var _command_strip = find_child("CommandStripHBoxContainer")
|
||||
|
||||
@onready var _pick_story_button = _command_strip.get_node("PickStoryOptionButton")
|
||||
@onready var _load_story_button = _command_strip.get_node("LoadStoryButton")
|
||||
@onready var _start_button = _command_strip.get_node("StartButton")
|
||||
@onready var _stop_button = _command_strip.get_node("StopButton")
|
||||
@onready var _clear_button = _command_strip.get_node("ClearButton")
|
||||
|
||||
@onready var _scroll_container = find_child("ScrollContainer")
|
||||
@onready var _story_container = _scroll_container.get_node("MarginContainer/StoryVBoxContainer")
|
||||
|
||||
@onready var _choice_area_container = find_child("ChoicesAreaVBoxContainer")
|
||||
@onready var _choices_container = _choice_area_container.get_node("ChoicesVBoxContainer")
|
||||
|
||||
# ############################################################################ #
|
||||
# Overrides
|
||||
# ############################################################################ #
|
||||
|
||||
func _ready():
|
||||
# FIXME: This needs investigating.
|
||||
# Sanity check. It seems the editor instantiates tools script on their
|
||||
# own, probably to add them to its tree. In that case, they won't have
|
||||
# their dependencies injected, so we're not doing anything.
|
||||
if editor_interface == null || configuration == null || progress_texture == null:
|
||||
print("[inkgd] [INFO] Ink Preview Tab: dependencies not met, ignoring.")
|
||||
return
|
||||
|
||||
add_child(_ink_player)
|
||||
|
||||
_connect_signals()
|
||||
_apply_configuration()
|
||||
_update_story_picker()
|
||||
|
||||
var load_icon = get_theme_icon("Load", "EditorIcons")
|
||||
var stop_icon = get_theme_icon("Stop", "EditorIcons")
|
||||
var clear_icon = get_theme_icon("Clear", "EditorIcons")
|
||||
|
||||
_start_button.icon = _play_icon
|
||||
_load_story_button.icon = load_icon
|
||||
_stop_button.icon = stop_icon
|
||||
_clear_button.icon = clear_icon
|
||||
|
||||
_stop_button.visible = false
|
||||
|
||||
_choice_area_container.custom_minimum_size = Vector2(200, 0) * editor_interface.scale
|
||||
_choice_area_container.visible = false
|
||||
|
||||
_file_dialog.connect("file_selected", Callable(self, "_on_file_selected"))
|
||||
add_child(_file_dialog)
|
||||
|
||||
# ############################################################################ #
|
||||
# Signal Receivers
|
||||
# ############################################################################ #
|
||||
|
||||
func _start_button_pressed():
|
||||
var file_path = _get_current_story_file_path()
|
||||
if file_path == null:
|
||||
return
|
||||
|
||||
print("[inkgd] [INFO] Previewing %s" % file_path)
|
||||
_clear_content()
|
||||
|
||||
_ink_player.destroy()
|
||||
_ink_player.ink_file = load(file_path)
|
||||
|
||||
_start_button.icon = progress_texture
|
||||
_disable_command_strip(true)
|
||||
|
||||
_ink_player.create_story()
|
||||
|
||||
|
||||
func _stop_button_pressed():
|
||||
_start_button.visible = true
|
||||
_stop_button.visible = false
|
||||
_choice_area_container.visible = false
|
||||
|
||||
_ink_player.destroy()
|
||||
|
||||
_clear_choices()
|
||||
_clear_content()
|
||||
|
||||
|
||||
func _story_loaded(successfully: bool):
|
||||
_disable_command_strip(false)
|
||||
_start_button.icon = _play_icon
|
||||
|
||||
if !successfully:
|
||||
return
|
||||
|
||||
_start_button.visible = false
|
||||
_stop_button.visible = true
|
||||
|
||||
_ink_player.allow_external_function_fallbacks = true
|
||||
_continue_story()
|
||||
|
||||
func _pick_story_button_selected(index):
|
||||
if _current_story_index != index:
|
||||
_stop_button_pressed()
|
||||
|
||||
_current_story_index = index
|
||||
|
||||
|
||||
func _load_story_button_pressed():
|
||||
_file_dialog.file_mode = FileDialog.FILE_MODE_OPEN_FILE
|
||||
_file_dialog.access = FileDialog.ACCESS_RESOURCES
|
||||
_file_dialog.add_filter("*.json;Compiled Ink story")
|
||||
_file_dialog.popup_centered(Vector2(1280, 800) * editor_interface.scale)
|
||||
|
||||
|
||||
func _choice_button_pressed(index):
|
||||
_clear_choices()
|
||||
|
||||
_ink_player.choose_choice_index(index)
|
||||
_continue_story()
|
||||
|
||||
|
||||
func _on_file_selected(path: String):
|
||||
if _custom_stories.has(path):
|
||||
return
|
||||
|
||||
for story_configuration in self.configuration.stories:
|
||||
var target_file_path = self.configuration.get_target_file_path(story_configuration)
|
||||
if target_file_path == path:
|
||||
return
|
||||
|
||||
_custom_stories.append(path)
|
||||
_apply_configuration()
|
||||
_update_story_picker(path)
|
||||
|
||||
|
||||
func _scrollbar_changed():
|
||||
var max_value = _scroll_container.get_v_scroll_bar().max_value
|
||||
|
||||
if _scrollbar_max_value == max_value && _scrollbar_max_value != -1:
|
||||
return
|
||||
|
||||
_scrollbar_max_value = max_value
|
||||
_scroll_container.scroll_vertical = max_value
|
||||
|
||||
|
||||
func _configuration_changed():
|
||||
# Cleaning everything on configuration change may end up being frustrating.
|
||||
# But for now, it's going to make everybody's life easier.
|
||||
_stop_button_pressed()
|
||||
_apply_configuration()
|
||||
_update_story_picker()
|
||||
|
||||
|
||||
# ############################################################################ #
|
||||
# Private Methods
|
||||
# ############################################################################ #
|
||||
|
||||
func _apply_configuration():
|
||||
_available_stories.clear()
|
||||
|
||||
_current_story_index = -1
|
||||
_pick_story_button.selected = _current_story_index
|
||||
|
||||
var i = 0
|
||||
for story_configuration in self.configuration.stories:
|
||||
var target_file_path = self.configuration.get_target_file_path(story_configuration)
|
||||
if target_file_path != null && !target_file_path.is_empty():
|
||||
_available_stories.append({
|
||||
NAME: "Story %d - %s" % [i + 1, target_file_path.get_file()],
|
||||
FILE_PATH: target_file_path,
|
||||
STORY_ORIGIN: StoryOrigin.CONFIGURATION
|
||||
})
|
||||
i += 1
|
||||
|
||||
var j = 0
|
||||
for custom_story_path in _custom_stories:
|
||||
if custom_story_path != null && !custom_story_path.is_empty():
|
||||
_available_stories.append({
|
||||
NAME: custom_story_path.get_file(),
|
||||
FILE_PATH: ProjectSettings.localize_path(custom_story_path),
|
||||
STORY_ORIGIN: StoryOrigin.FILE
|
||||
})
|
||||
j += 1
|
||||
|
||||
|
||||
func _update_story_picker(selected_path = null):
|
||||
_pick_story_button.clear()
|
||||
|
||||
var i = 0
|
||||
for story in _available_stories:
|
||||
_pick_story_button.add_item(story[NAME], i)
|
||||
if selected_path != null && story[FILE_PATH] == selected_path:
|
||||
_current_story_index = i
|
||||
_pick_story_button.selected = _current_story_index
|
||||
i += 1
|
||||
|
||||
if _available_stories.size() > 0:
|
||||
if _current_story_index == -1:
|
||||
_current_story_index = 0
|
||||
_pick_story_button.selected = _current_story_index
|
||||
|
||||
_pick_story_button.visible = true
|
||||
else:
|
||||
_current_story_index = -1
|
||||
_pick_story_button.selected = _current_story_index
|
||||
|
||||
_pick_story_button.visible = false
|
||||
|
||||
|
||||
func _continue_story():
|
||||
while _ink_player.can_continue:
|
||||
var text = _ink_player.continue_story()
|
||||
|
||||
if text.right(text.length() - 1) == "\n":
|
||||
text.erase(text.length() - 1, 1)
|
||||
|
||||
var text_label = Label.new()
|
||||
text_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
text_label.text = text
|
||||
|
||||
_story_container.add_child(text_label)
|
||||
|
||||
var tags = _ink_player.current_tags
|
||||
if !tags.is_empty():
|
||||
var tag_label = Label.new()
|
||||
tag_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
tag_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
tag_label.text = "# " + ", ".join(PackedStringArray(tags))
|
||||
tag_label.add_theme_color_override("font_color", Color(1, 1, 1, 0.4))
|
||||
|
||||
_story_container.add_child(tag_label)
|
||||
|
||||
var separator = HSeparator.new()
|
||||
_story_container.add_child(separator)
|
||||
|
||||
if _ink_player.current_choices.size() > 0:
|
||||
var i = 0
|
||||
for choice in _ink_player.current_choices:
|
||||
var button = Button.new()
|
||||
button.text = choice.text
|
||||
button.connect("pressed", Callable(self, "_choice_button_pressed").bind(i))
|
||||
|
||||
_choices_container.add_child(button)
|
||||
i += 1
|
||||
|
||||
_choice_area_container.visible = true
|
||||
else:
|
||||
var label = Label.new()
|
||||
label.text = "End of the story."
|
||||
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
|
||||
|
||||
_story_container.add_child(label)
|
||||
|
||||
_choice_area_container.visible = false
|
||||
|
||||
|
||||
func _get_current_story_file_path():
|
||||
if _current_story_index >= 0 && _current_story_index < _available_stories.size():
|
||||
return _available_stories[_current_story_index][FILE_PATH]
|
||||
else:
|
||||
return null
|
||||
|
||||
|
||||
func _clear_content():
|
||||
for child in _story_container.get_children():
|
||||
_story_container.remove_child(child)
|
||||
child.queue_free()
|
||||
|
||||
|
||||
func _clear_choices():
|
||||
for child in _choices_container.get_children():
|
||||
_choices_container.remove_child(child)
|
||||
child.queue_free()
|
||||
|
||||
func _disable_command_strip(disabled: bool):
|
||||
_pick_story_button.disabled = disabled
|
||||
_load_story_button.disabled = disabled
|
||||
_start_button.disabled = disabled
|
||||
_stop_button.disabled = disabled
|
||||
_clear_button.disabled = disabled
|
||||
|
||||
func _connect_signals():
|
||||
if configuration != null:
|
||||
var is_signal_connected := configuration.is_connected(
|
||||
"story_configuration_changed",
|
||||
Callable(self, "_configuration_changed")
|
||||
)
|
||||
|
||||
if !is_signal_connected:
|
||||
configuration.connect(
|
||||
"story_configuration_changed",
|
||||
Callable(self, "_configuration_changed")
|
||||
)
|
||||
|
||||
_ink_player.connect("loaded", Callable(self, "_story_loaded"))
|
||||
_pick_story_button.connect("item_selected", Callable(self, "_pick_story_button_selected"))
|
||||
_load_story_button.connect("pressed", Callable(self, "_load_story_button_pressed"))
|
||||
_start_button.connect("pressed", Callable(self, "_start_button_pressed"))
|
||||
_stop_button.connect("pressed", Callable(self, "_stop_button_pressed"))
|
||||
_clear_button.connect("pressed", Callable(self, "_clear_content"))
|
||||
|
||||
_scroll_container.get_v_scroll_bar().connect("changed", Callable(self, "_scrollbar_changed"))
|
119
addons/inkgd/editor/panel/preview/ink_preview_panel.tscn
Normal file
119
addons/inkgd/editor/panel/preview/ink_preview_panel.tscn
Normal file
|
@ -0,0 +1,119 @@
|
|||
[gd_scene load_steps=4 format=3 uid="uid://0do8xgmkscjn"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/inkgd/editor/panel/preview/ink_preview_panel.gd" id="2"]
|
||||
|
||||
[sub_resource type="Image" id="Image_nl482"]
|
||||
data = {
|
||||
"data": PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
|
||||
"format": "LumAlpha8",
|
||||
"height": 16,
|
||||
"mipmaps": false,
|
||||
"width": 16
|
||||
}
|
||||
|
||||
[sub_resource type="ImageTexture" id="2"]
|
||||
image = SubResource("Image_nl482")
|
||||
|
||||
[node name="Preview" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
script = ExtResource("2")
|
||||
|
||||
[node name="MarginContainer" type="MarginContainer" parent="."]
|
||||
layout_mode = 0
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="HSplitContainer" type="HSplitContainer" parent="MarginContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="StoryVBoxContainer" type="VBoxContainer" parent="MarginContainer/HSplitContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
theme_override_constants/separation = 5
|
||||
|
||||
[node name="CommandStripHBoxContainer" type="HBoxContainer" parent="MarginContainer/HSplitContainer/StoryVBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_override_constants/separation = 10
|
||||
|
||||
[node name="LoadStoryButton" type="Button" parent="MarginContainer/HSplitContainer/StoryVBoxContainer/CommandStripHBoxContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/h_separation = 8
|
||||
text = "Load new…"
|
||||
icon = SubResource("2")
|
||||
|
||||
[node name="PickStoryOptionButton" type="OptionButton" parent="MarginContainer/HSplitContainer/StoryVBoxContainer/CommandStripHBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 0
|
||||
|
||||
[node name="Control" type="Control" parent="MarginContainer/HSplitContainer/StoryVBoxContainer/CommandStripHBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="StartButton" type="Button" parent="MarginContainer/HSplitContainer/StoryVBoxContainer/CommandStripHBoxContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/h_separation = 8
|
||||
text = "Start"
|
||||
icon = SubResource("2")
|
||||
|
||||
[node name="StopButton" type="Button" parent="MarginContainer/HSplitContainer/StoryVBoxContainer/CommandStripHBoxContainer"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
theme_override_constants/h_separation = 10
|
||||
text = "Stop"
|
||||
icon = SubResource("2")
|
||||
|
||||
[node name="ClearButton" type="Button" parent="MarginContainer/HSplitContainer/StoryVBoxContainer/CommandStripHBoxContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/h_separation = 8
|
||||
text = "Clear"
|
||||
icon = SubResource("2")
|
||||
|
||||
[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/HSplitContainer/StoryVBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="Panel" type="Panel" parent="MarginContainer/HSplitContainer/StoryVBoxContainer/MarginContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="ScrollContainer" type="ScrollContainer" parent="MarginContainer/HSplitContainer/StoryVBoxContainer/MarginContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/HSplitContainer/StoryVBoxContainer/MarginContainer/ScrollContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_override_constants/margin_left = 10
|
||||
theme_override_constants/margin_top = 10
|
||||
theme_override_constants/margin_right = 10
|
||||
theme_override_constants/margin_bottom = 10
|
||||
|
||||
[node name="StoryVBoxContainer" type="VBoxContainer" parent="MarginContainer/HSplitContainer/StoryVBoxContainer/MarginContainer/ScrollContainer/MarginContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="ChoicesAreaVBoxContainer" type="VBoxContainer" parent="MarginContainer/HSplitContainer"]
|
||||
custom_minimum_size = Vector2(300, 0)
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 5
|
||||
|
||||
[node name="Button" type="Button" parent="MarginContainer/HSplitContainer/ChoicesAreaVBoxContainer"]
|
||||
layout_mode = 2
|
||||
mouse_filter = 2
|
||||
text = "Choices"
|
||||
flat = true
|
||||
|
||||
[node name="ChoicesVBoxContainer" type="VBoxContainer" parent="MarginContainer/HSplitContainer/ChoicesAreaVBoxContainer"]
|
||||
layout_mode = 2
|
13
addons/inkgd/editor/panel/stories/empty_state_container.tscn
Normal file
13
addons/inkgd/editor/panel/stories/empty_state_container.tscn
Normal file
|
@ -0,0 +1,13 @@
|
|||
[gd_scene format=2]
|
||||
|
||||
[node name="EmptyStateContainer" type="CenterContainer"]
|
||||
offset_right = 1902.0
|
||||
offset_bottom = 100.0
|
||||
custom_minimum_size = Vector2( 0, 100 )
|
||||
|
||||
[node name="Label" type="Label" parent="."]
|
||||
offset_left = 879.0
|
||||
offset_top = 43.0
|
||||
offset_right = 1022.0
|
||||
offset_bottom = 57.0
|
||||
text = "No stories to compile."
|
153
addons/inkgd/editor/panel/stories/ink_story_configuration.gd
Normal file
153
addons/inkgd/editor/panel/stories/ink_story_configuration.gd
Normal file
|
@ -0,0 +1,153 @@
|
|||
@tool
|
||||
# ############################################################################ #
|
||||
# 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 VBoxContainer
|
||||
|
||||
# Hiding this type to prevent registration of "private" nodes.
|
||||
# See https://github.com/godotengine/godot-proposals/issues/1047
|
||||
# class_name InkStoryConfiguration
|
||||
|
||||
# ############################################################################ #
|
||||
# Signals
|
||||
# ############################################################################ #
|
||||
|
||||
signal configuration_changed(story_configuration)
|
||||
|
||||
signal remove_button_pressed(story_configuration)
|
||||
|
||||
signal build_button_pressed(story_configuration)
|
||||
|
||||
signal source_file_button_pressed(story_configuration)
|
||||
|
||||
signal target_file_button_pressed(story_configuration)
|
||||
|
||||
signal watched_folder_button_pressed(story_configuration)
|
||||
|
||||
# ############################################################################ #
|
||||
# Properties
|
||||
# ############################################################################ #
|
||||
|
||||
var editor_interface: InkEditorInterface = null
|
||||
|
||||
# ############################################################################ #
|
||||
# Nodes
|
||||
# ############################################################################ #
|
||||
|
||||
@onready var story_label = find_child("StoryLabel")
|
||||
|
||||
@onready var remove_button = find_child("RemoveButton")
|
||||
@onready var build_button = find_child("BuildButton")
|
||||
|
||||
@onready var source_file_line_edit = find_child("SourceFileLineEdit")
|
||||
@onready var source_file_dialog_button = find_child("SourceFileDialogButton")
|
||||
|
||||
@onready var target_file_line_edit = find_child("TargetFileLineEdit")
|
||||
@onready var target_file_dialog_button = find_child("TargetFileDialogButton")
|
||||
|
||||
@onready var watched_folder_label = find_child("WatchedFolderLabel")
|
||||
@onready var watched_folder_container = find_child("WatchedFolderHBoxContainer")
|
||||
@onready var watched_folder_line_edit = find_child("WatchedFolderLineEdit")
|
||||
@onready var watched_folder_dialog_button = find_child("WatchedFolderDialogButton")
|
||||
|
||||
@onready var background_color_rect = find_child("BackgroundColorRect")
|
||||
|
||||
# ############################################################################ #
|
||||
# Overrides
|
||||
# ############################################################################ #
|
||||
|
||||
func _ready():
|
||||
# FIXME: This needs investigating.
|
||||
# Sanity check. It seems the editor instantiates tools script on their
|
||||
# own, probably to add them to its tree. In that case, they won't have
|
||||
# their dependencies injected, so we're not doing anything.
|
||||
if editor_interface == null:
|
||||
return
|
||||
|
||||
_apply_custom_header_color()
|
||||
_set_button_icons()
|
||||
_connect_signals()
|
||||
|
||||
show_watched_folder(false)
|
||||
|
||||
# ############################################################################ #
|
||||
# Signals
|
||||
# ############################################################################ #
|
||||
|
||||
func _configuration_entered(_new_text: String):
|
||||
_configuration_focus_exited()
|
||||
|
||||
|
||||
func _configuration_focus_exited():
|
||||
emit_signal("configuration_changed", self)
|
||||
|
||||
|
||||
func _remove_button_pressed():
|
||||
emit_signal("remove_button_pressed", self)
|
||||
|
||||
|
||||
func _build_button_pressed():
|
||||
emit_signal("build_button_pressed", self)
|
||||
|
||||
|
||||
func _source_file_button_pressed():
|
||||
emit_signal("source_file_button_pressed", self)
|
||||
|
||||
|
||||
func _target_file_button_pressed():
|
||||
emit_signal("target_file_button_pressed", self)
|
||||
|
||||
|
||||
func _watched_folder_button_pressed():
|
||||
emit_signal("watched_folder_button_pressed", self)
|
||||
|
||||
# ############################################################################ #
|
||||
# Public Methods
|
||||
# ############################################################################ #
|
||||
|
||||
@warning_ignore("shadowed_variable")
|
||||
func show_watched_folder(show: bool):
|
||||
watched_folder_label.visible = show
|
||||
watched_folder_container.visible = show
|
||||
|
||||
func disable_all_buttons(disable: bool):
|
||||
remove_button.disabled = disable
|
||||
build_button.disabled = disable
|
||||
|
||||
|
||||
# ############################################################################ #
|
||||
# Private Methods
|
||||
# ############################################################################ #
|
||||
|
||||
func _apply_custom_header_color():
|
||||
var header_color = editor_interface.get_custom_header_color()
|
||||
if header_color != Color.TRANSPARENT:
|
||||
background_color_rect.color = header_color
|
||||
|
||||
|
||||
func _set_button_icons():
|
||||
var folder_icon = get_theme_icon("Folder", "EditorIcons")
|
||||
source_file_dialog_button.icon = folder_icon
|
||||
target_file_dialog_button.icon = folder_icon
|
||||
watched_folder_dialog_button.icon = folder_icon
|
||||
|
||||
var trash_icon = get_theme_icon("Remove", "EditorIcons")
|
||||
remove_button.icon = trash_icon
|
||||
|
||||
|
||||
func _connect_signals():
|
||||
source_file_line_edit.connect("text_submitted", Callable(self, "_configuration_entered"))
|
||||
source_file_line_edit.connect("focus_exited", Callable(self, "_configuration_focus_exited"))
|
||||
|
||||
target_file_line_edit.connect("text_submitted", Callable(self, "_configuration_entered"))
|
||||
target_file_line_edit.connect("focus_exited", Callable(self, "_configuration_focus_exited"))
|
||||
|
||||
source_file_dialog_button.connect("pressed", Callable(self, "_source_file_button_pressed"))
|
||||
target_file_dialog_button.connect("pressed", Callable(self, "_target_file_button_pressed"))
|
||||
watched_folder_dialog_button.connect("pressed", Callable(self, "_watched_folder_button_pressed"))
|
||||
|
||||
remove_button.connect("pressed", Callable(self, "_remove_button_pressed"))
|
||||
build_button.connect("pressed", Callable(self, "_build_button_pressed"))
|
189
addons/inkgd/editor/panel/stories/ink_story_configuration.tscn
Normal file
189
addons/inkgd/editor/panel/stories/ink_story_configuration.tscn
Normal file
|
@ -0,0 +1,189 @@
|
|||
[gd_scene load_steps=4 format=2]
|
||||
|
||||
[ext_resource path="res://addons/inkgd/editor/panel/stories/ink_story_configuration.gd" type="Script" id=1]
|
||||
|
||||
[sub_resource type="Image" id=3]
|
||||
data = {
|
||||
"data": PackedByteArray( 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ),
|
||||
"format": "LumAlpha8",
|
||||
"height": 16,
|
||||
"mipmaps": false,
|
||||
"width": 16
|
||||
}
|
||||
|
||||
[sub_resource type="ImageTexture" id=2]
|
||||
flags = 4
|
||||
flags = 4
|
||||
image = SubResource( 3 )
|
||||
size = Vector2( 16, 16 )
|
||||
|
||||
[node name="StoryConfiguration" type="VBoxContainer"]
|
||||
offset_right = 1902.0
|
||||
offset_bottom = 87.0
|
||||
theme_override_constants/separation = 5
|
||||
script = ExtResource( 1 )
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="HeaderMarginContainer" type="MarginContainer" parent="."]
|
||||
offset_right = 1902.0
|
||||
offset_bottom = 32.0
|
||||
theme_override_constants/margin_right = 0
|
||||
theme_override_constants/margin_top = 0
|
||||
theme_override_constants/margin_left = 0
|
||||
theme_override_constants/margin_bottom = 0
|
||||
|
||||
[node name="BackgroundColorRect" type="ColorRect" parent="HeaderMarginContainer"]
|
||||
offset_right = 1902.0
|
||||
offset_bottom = 32.0
|
||||
color = Color( 0.268314, 0.291712, 0.340784, 1 )
|
||||
|
||||
[node name="MarginContainer" type="MarginContainer" parent="HeaderMarginContainer"]
|
||||
offset_right = 1902.0
|
||||
offset_bottom = 32.0
|
||||
theme_override_constants/margin_right = 5
|
||||
theme_override_constants/margin_top = 5
|
||||
theme_override_constants/margin_left = 25
|
||||
theme_override_constants/margin_bottom = 5
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="HeaderMarginContainer/MarginContainer"]
|
||||
offset_left = 25.0
|
||||
offset_top = 5.0
|
||||
offset_right = 1897.0
|
||||
offset_bottom = 27.0
|
||||
|
||||
[node name="StoryLabel" type="Label" parent="HeaderMarginContainer/MarginContainer/HBoxContainer"]
|
||||
offset_top = 4.0
|
||||
offset_right = 1711.0
|
||||
offset_bottom = 18.0
|
||||
size_flags_horizontal = 3
|
||||
text = "0"
|
||||
|
||||
[node name="RemoveButton" type="Button" parent="HeaderMarginContainer/MarginContainer/HBoxContainer"]
|
||||
offset_left = 1715.0
|
||||
offset_right = 1803.0
|
||||
offset_bottom = 22.0
|
||||
mouse_filter = 1
|
||||
theme_override_constants/h_separation = 8
|
||||
text = "Remove"
|
||||
icon = SubResource( 2 )
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false,
|
||||
"_editor_description_": "Compile the story manually, based on the configuration below."
|
||||
}
|
||||
|
||||
[node name="BuildButton" type="Button" parent="HeaderMarginContainer/MarginContainer/HBoxContainer"]
|
||||
offset_left = 1807.0
|
||||
offset_right = 1872.0
|
||||
offset_bottom = 22.0
|
||||
mouse_filter = 1
|
||||
theme_override_constants/h_separation = 8
|
||||
text = "Compile"
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false,
|
||||
"_editor_description_": "Compile the story manually, based on the configuration below."
|
||||
}
|
||||
|
||||
[node name="ConfigurationMarginContainer" type="MarginContainer" parent="."]
|
||||
offset_top = 37.0
|
||||
offset_right = 1902.0
|
||||
offset_bottom = 117.0
|
||||
theme_override_constants/margin_right = 0
|
||||
theme_override_constants/margin_top = 0
|
||||
theme_override_constants/margin_left = 5
|
||||
theme_override_constants/margin_bottom = 0
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="GridContainer" type="GridContainer" parent="ConfigurationMarginContainer"]
|
||||
offset_left = 5.0
|
||||
offset_right = 1902.0
|
||||
offset_bottom = 80.0
|
||||
size_flags_horizontal = 3
|
||||
columns = 2
|
||||
|
||||
[node name="SourceFileLabel" type="Label" parent="ConfigurationMarginContainer/GridContainer"]
|
||||
offset_right = 946.0
|
||||
offset_bottom = 24.0
|
||||
tooltip_text = "The input path, a.k.a the original ink story."
|
||||
mouse_filter = 1
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 1
|
||||
text = "Source File (*.ink)"
|
||||
|
||||
[node name="SourceFileHBoxContainer" type="HBoxContainer" parent="ConfigurationMarginContainer/GridContainer"]
|
||||
offset_left = 950.0
|
||||
offset_right = 1896.0
|
||||
offset_bottom = 24.0
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="SourceFileLineEdit" type="LineEdit" parent="ConfigurationMarginContainer/GridContainer/SourceFileHBoxContainer"]
|
||||
offset_right = 914.0
|
||||
offset_bottom = 24.0
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="SourceFileDialogButton" type="Button" parent="ConfigurationMarginContainer/GridContainer/SourceFileHBoxContainer"]
|
||||
offset_left = 918.0
|
||||
offset_right = 946.0
|
||||
offset_bottom = 24.0
|
||||
icon = SubResource( 2 )
|
||||
|
||||
[node name="TargetFileLabel" type="Label" parent="ConfigurationMarginContainer/GridContainer"]
|
||||
offset_top = 28.0
|
||||
offset_right = 946.0
|
||||
offset_bottom = 52.0
|
||||
tooltip_text = "The output path, a.k.a the JSON file compiled from the original ink story."
|
||||
mouse_filter = 1
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 1
|
||||
text = "Target File (*.json)"
|
||||
|
||||
[node name="TargetFileHBoxContainer" type="HBoxContainer" parent="ConfigurationMarginContainer/GridContainer"]
|
||||
offset_left = 950.0
|
||||
offset_top = 28.0
|
||||
offset_right = 1896.0
|
||||
offset_bottom = 52.0
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="TargetFileLineEdit" type="LineEdit" parent="ConfigurationMarginContainer/GridContainer/TargetFileHBoxContainer"]
|
||||
offset_right = 914.0
|
||||
offset_bottom = 24.0
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="TargetFileDialogButton" type="Button" parent="ConfigurationMarginContainer/GridContainer/TargetFileHBoxContainer"]
|
||||
offset_left = 918.0
|
||||
offset_right = 946.0
|
||||
offset_bottom = 24.0
|
||||
icon = SubResource( 2 )
|
||||
|
||||
[node name="WatchedFolderLabel" type="Label" parent="ConfigurationMarginContainer/GridContainer"]
|
||||
offset_top = 56.0
|
||||
offset_right = 946.0
|
||||
offset_bottom = 80.0
|
||||
tooltip_text = "The directory to watch for changes in Ink sources.
|
||||
|
||||
This path is optional, leave it blank to disable automatic recompilation for that story."
|
||||
mouse_filter = 1
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 1
|
||||
text = "Watched Folder"
|
||||
|
||||
[node name="WatchedFolderHBoxContainer" type="HBoxContainer" parent="ConfigurationMarginContainer/GridContainer"]
|
||||
offset_left = 950.0
|
||||
offset_top = 56.0
|
||||
offset_right = 1896.0
|
||||
offset_bottom = 80.0
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="WatchedFolderLineEdit" type="LineEdit" parent="ConfigurationMarginContainer/GridContainer/WatchedFolderHBoxContainer"]
|
||||
offset_right = 914.0
|
||||
offset_bottom = 24.0
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="WatchedFolderDialogButton" type="Button" parent="ConfigurationMarginContainer/GridContainer/WatchedFolderHBoxContainer"]
|
||||
offset_left = 918.0
|
||||
offset_right = 946.0
|
||||
offset_bottom = 24.0
|
||||
icon = SubResource( 2 )
|
524
addons/inkgd/editor/panel/stories/ink_story_panel.gd
Normal file
524
addons/inkgd/editor/panel/stories/ink_story_panel.gd
Normal file
|
@ -0,0 +1,524 @@
|
|||
@tool
|
||||
# ############################################################################ #
|
||||
# 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 Control
|
||||
|
||||
# Hiding this type to prevent registration of "private" nodes.
|
||||
# See https://github.com/godotengine/godot-proposals/issues/1047
|
||||
# class_name InkStoryPanel
|
||||
|
||||
# ############################################################################ #
|
||||
# Imports
|
||||
# ############################################################################ #
|
||||
|
||||
var InkConfiguration = load("res://addons/inkgd/editor/common/ink_configuration.gd")
|
||||
|
||||
var InkCompilationConfiguration = load("res://addons/inkgd/editor/common/executors/structures/ink_compilation_configuration.gd")
|
||||
var InkCompiler = load("res://addons/inkgd/editor/common/executors/ink_compiler.gd")
|
||||
|
||||
var InkRichDialog = load("res://addons/inkgd/editor/panel/common/ink_rich_dialog.tscn")
|
||||
var InkProgressDialog = load("res://addons/inkgd/editor/panel/common/ink_progress_dialog.tscn")
|
||||
var InkStoryConfigurationScene = load("res://addons/inkgd/editor/panel/stories/ink_story_configuration.tscn")
|
||||
var EmptyStateContainerScene = load("res://addons/inkgd/editor/panel/stories/empty_state_container.tscn")
|
||||
|
||||
# ############################################################################ #
|
||||
# Signals
|
||||
# ############################################################################ #
|
||||
|
||||
signal _compiled()
|
||||
|
||||
# ############################################################################ #
|
||||
# Enums
|
||||
# ############################################################################ #
|
||||
|
||||
enum FileDialogSelection {
|
||||
UNKNOWN,
|
||||
SOURCE_FILE,
|
||||
TARGET_FILE,
|
||||
WATCHED_FOLDER
|
||||
}
|
||||
|
||||
|
||||
# ############################################################################ #
|
||||
# Properties
|
||||
# ############################################################################ #
|
||||
|
||||
var editor_interface: InkEditorInterface
|
||||
var configuration: InkConfiguration
|
||||
var progress_texture: AnimatedTexture
|
||||
|
||||
|
||||
# ############################################################################ #
|
||||
# Private Properties
|
||||
# ############################################################################ #
|
||||
|
||||
var _scrollbar_max_value = -1
|
||||
|
||||
var _compilers: Dictionary = {}
|
||||
|
||||
var _file_dialog = EditorFileDialog.new()
|
||||
|
||||
## Configuration item for which the FileDialog is currently shown.
|
||||
##
|
||||
## Unknown by default.
|
||||
var _file_dialog_selection = FileDialogSelection.UNKNOWN
|
||||
|
||||
## The story index for which the FileDialog is currenlty shown.
|
||||
##
|
||||
## -1 by default or when the file dialog currently displayed doesn't
|
||||
## concern the stories source/target files.
|
||||
var _file_dialog_selection_story_index = -1
|
||||
|
||||
var _current_story_node = null
|
||||
|
||||
var _progress_dialog = null
|
||||
|
||||
# ############################################################################ #
|
||||
# Nodes
|
||||
# ############################################################################ #
|
||||
|
||||
@onready var _empty_state_container = EmptyStateContainerScene.instantiate()
|
||||
|
||||
@onready var _build_all_button = find_child("BuildAllButton")
|
||||
@onready var _add_new_story_button = find_child("AddNewStoryButton")
|
||||
|
||||
@onready var _story_configuration_container = find_child("StoryConfigurationVBoxContainer")
|
||||
@onready var _scroll_container = find_child("ScrollContainer")
|
||||
|
||||
|
||||
# ############################################################################ #
|
||||
# Overrides
|
||||
# ############################################################################ #
|
||||
|
||||
func _ready():
|
||||
# FIXME: This needs investigating.
|
||||
# Sanity check. It seems the editor instantiates tools script on their
|
||||
# own, probably to add them to its tree. In that case, they won't have
|
||||
# their dependencies injected, so we're not doing anything.
|
||||
if editor_interface == null || configuration == null || progress_texture == null:
|
||||
print("[inkgd] [INFO] Ink Stories Tab: dependencies not met, ignoring.")
|
||||
return
|
||||
|
||||
configuration.connect("compilation_mode_changed", Callable(self, "_compilation_mode_changed"))
|
||||
|
||||
editor_interface.editor_filesystem.connect("resources_reimported", Callable(self, "_resources_reimported"))
|
||||
|
||||
_story_configuration_container.add_child(_empty_state_container)
|
||||
add_child(_file_dialog)
|
||||
|
||||
var add_icon = get_theme_icon("Add", "EditorIcons")
|
||||
_add_new_story_button.icon = add_icon
|
||||
|
||||
_load_story_configurations()
|
||||
_connect_signals()
|
||||
|
||||
_compilation_mode_changed(configuration.compilation_mode)
|
||||
|
||||
|
||||
# ############################################################################ #
|
||||
# Signal Receivers
|
||||
# ############################################################################ #
|
||||
|
||||
func _resources_reimported(resources):
|
||||
call_deferred("_recompile_if_necessary", resources)
|
||||
|
||||
func _compilation_mode_changed(compilation_mode: int):
|
||||
var show_folder = (compilation_mode == InkConfiguration.BuildMode.AFTER_CHANGE)
|
||||
|
||||
for child in _story_configuration_container.get_children():
|
||||
child.show_watched_folder(show_folder)
|
||||
|
||||
func _source_file_button_pressed(node):
|
||||
_reset_file_dialog()
|
||||
|
||||
var index = _get_story_configuration_index(node)
|
||||
|
||||
_file_dialog_selection = FileDialogSelection.SOURCE_FILE
|
||||
_file_dialog_selection_story_index = index
|
||||
|
||||
var story_configuration = _get_story_configuration_at_index(index)
|
||||
var path = story_configuration.source_file_line_edit.text
|
||||
|
||||
_file_dialog.current_path = path
|
||||
_file_dialog.current_dir = path.get_base_dir()
|
||||
_file_dialog.current_file = path.get_file()
|
||||
|
||||
_file_dialog.file_mode = FileDialog.FILE_MODE_OPEN_FILE
|
||||
_file_dialog.access = FileDialog.ACCESS_FILESYSTEM
|
||||
_file_dialog.add_filter("*.ink;Ink source file")
|
||||
_file_dialog.popup_centered(Vector2(1280, 800) * editor_interface.scale)
|
||||
|
||||
|
||||
func _target_file_button_pressed(node):
|
||||
_reset_file_dialog()
|
||||
|
||||
var index = _get_story_configuration_index(node)
|
||||
|
||||
_file_dialog_selection = FileDialogSelection.TARGET_FILE
|
||||
_file_dialog_selection_story_index = index
|
||||
|
||||
var story_configuration = _get_story_configuration_at_index(index)
|
||||
var path = story_configuration.target_file_line_edit.text
|
||||
|
||||
_file_dialog.current_path = path
|
||||
_file_dialog.current_dir = path.get_base_dir()
|
||||
_file_dialog.current_file = path.get_file()
|
||||
|
||||
_file_dialog.file_mode = FileDialog.FILE_MODE_SAVE_FILE
|
||||
_file_dialog.access = FileDialog.ACCESS_FILESYSTEM
|
||||
_file_dialog.add_filter("*.json;Compiled Ink story")
|
||||
_file_dialog.popup_centered(Vector2(1280, 800) * editor_interface.scale)
|
||||
|
||||
func _watched_folder_button_pressed(node):
|
||||
_reset_file_dialog()
|
||||
|
||||
var index = _get_story_configuration_index(node)
|
||||
|
||||
_file_dialog_selection = FileDialogSelection.WATCHED_FOLDER
|
||||
_file_dialog_selection_story_index = index
|
||||
|
||||
var story_configuration = _get_story_configuration_at_index(index)
|
||||
var path = story_configuration.watched_folder_line_edit.text
|
||||
|
||||
_file_dialog.current_path = path
|
||||
_file_dialog.current_dir = path.get_base_dir()
|
||||
_file_dialog.current_file = path.get_file()
|
||||
|
||||
_file_dialog.file_mode = FileDialog.FILE_MODE_OPEN_DIR
|
||||
_file_dialog.access = FileDialog.ACCESS_FILESYSTEM
|
||||
_file_dialog.popup_centered(Vector2(1280, 800) * editor_interface.scale)
|
||||
|
||||
|
||||
func _build_all_button_pressed():
|
||||
_compile_all_stories()
|
||||
|
||||
|
||||
func _add_new_story_button_pressed():
|
||||
_add_new_story_configuration()
|
||||
|
||||
|
||||
func _configuration_changed(_node):
|
||||
_persist_configuration()
|
||||
|
||||
|
||||
func _remove_button_pressed(node):
|
||||
var index = _get_story_configuration_index(node)
|
||||
configuration.remove_story_configuration_at_index(index)
|
||||
|
||||
# TODO: Rebuild from scratch instead.
|
||||
var parent = node.get_parent()
|
||||
if parent != null:
|
||||
parent.remove_child(node)
|
||||
node.queue_free()
|
||||
|
||||
if _story_configuration_container.get_child_count() == 0:
|
||||
_story_configuration_container.add_child(_empty_state_container)
|
||||
else:
|
||||
var i = 0
|
||||
for child in _story_configuration_container.get_children():
|
||||
# Not using "is InkStoryConfiguration", because it requires a type
|
||||
# declaration. Node Types register in the editor and we don't want
|
||||
# that. This is a bit hacky, but until the proposal is accepted,
|
||||
# it prevents cluttering the "Create new node" list.
|
||||
if "story_label" in child:
|
||||
child.story_label.text = "Story %d" % (i + 1)
|
||||
i += 1
|
||||
|
||||
_persist_configuration()
|
||||
|
||||
|
||||
func _build_button_pressed(node):
|
||||
var index = _get_story_configuration_index(node)
|
||||
var story_configuration = configuration.get_story_configuration_at_index(index)
|
||||
|
||||
if story_configuration == null:
|
||||
printerr("[inkgd] [ERROR] No configurations found for Story %d" % (index + 1))
|
||||
return
|
||||
|
||||
_compile_story(story_configuration, node)
|
||||
|
||||
|
||||
func _compile_all_stories():
|
||||
_disable_all_buttons(true)
|
||||
var number_of_stories = configuration.stories.size()
|
||||
var current_story_index = 0
|
||||
|
||||
_progress_dialog = InkProgressDialog.instantiate()
|
||||
add_child(_progress_dialog)
|
||||
|
||||
_progress_dialog.update_layout(editor_interface.scale)
|
||||
_progress_dialog.popup_centered(Vector2(600, 100) * editor_interface.scale)
|
||||
|
||||
for story_configuration in configuration.stories:
|
||||
var source_file_path: String = configuration.get_source_file_path(story_configuration)
|
||||
_progress_dialog.current_step_name = source_file_path.get_file()
|
||||
|
||||
_compile_story(story_configuration)
|
||||
await self._compiled
|
||||
|
||||
@warning_ignore("integer_division")
|
||||
_progress_dialog.progress = float(100 * (current_story_index + 1) / number_of_stories)
|
||||
current_story_index += 1
|
||||
|
||||
remove_child(_progress_dialog)
|
||||
_progress_dialog.queue_free()
|
||||
_progress_dialog = null
|
||||
_disable_all_buttons(false)
|
||||
|
||||
func _compile_story(story_configuration, node = null):
|
||||
var source_file_path = configuration.get_source_file_path(story_configuration)
|
||||
var target_file_path = configuration.get_target_file_path(story_configuration)
|
||||
|
||||
if node != null:
|
||||
_current_story_node = node
|
||||
|
||||
node.build_button.icon = progress_texture
|
||||
_disable_all_buttons(true)
|
||||
|
||||
var compiler_configuration = InkCompilationConfiguration.new(
|
||||
configuration,
|
||||
true,
|
||||
node != null,
|
||||
source_file_path,
|
||||
target_file_path
|
||||
)
|
||||
var compiler = InkCompiler.new(compiler_configuration)
|
||||
|
||||
_compilers[compiler.identifier] = compiler
|
||||
compiler.connect("story_compiled", Callable(self, "_handle_compilation"))
|
||||
compiler.compile_story()
|
||||
|
||||
|
||||
func _handle_compilation(result):
|
||||
if _current_story_node != null:
|
||||
var button = _current_story_node.build_button
|
||||
button.icon = null
|
||||
_disable_all_buttons(false)
|
||||
_current_story_node = null
|
||||
|
||||
if result.user_triggered:
|
||||
if result.success:
|
||||
if result.output && !result.output.is_empty():
|
||||
var dialog = InkRichDialog.instantiate()
|
||||
add_child(dialog)
|
||||
|
||||
dialog.title = "Success!"
|
||||
dialog.message_text = "The story was successfully compiled."
|
||||
dialog.output_text = result.output
|
||||
dialog.update_layout(editor_interface.scale)
|
||||
|
||||
dialog.popup_centered(Vector2(700, 400) * editor_interface.scale)
|
||||
else:
|
||||
var dialog = AcceptDialog.new()
|
||||
add_child(dialog)
|
||||
|
||||
dialog.title = "Success!"
|
||||
dialog.dialog_text = "The story was successfully compiled."
|
||||
|
||||
dialog.popup_centered()
|
||||
|
||||
_reimport_compiled_stories()
|
||||
else:
|
||||
var dialog = InkRichDialog.instantiate()
|
||||
add_child(dialog)
|
||||
|
||||
dialog.title = "Error"
|
||||
dialog.message_text = "The story could not be compiled. See inklecate's output below."
|
||||
dialog.output_text = result.output
|
||||
dialog.update_layout(editor_interface.scale)
|
||||
|
||||
dialog.popup_centered(Vector2(700, 400) * editor_interface.scale)
|
||||
else:
|
||||
_reimport_compiled_stories()
|
||||
|
||||
if _compilers.has(result.identifier):
|
||||
_compilers.erase(result.identifier)
|
||||
|
||||
emit_signal("_compiled")
|
||||
|
||||
|
||||
func _on_file_selected(path: String):
|
||||
var index = _file_dialog_selection_story_index
|
||||
|
||||
match _file_dialog_selection:
|
||||
FileDialogSelection.SOURCE_FILE:
|
||||
var story_configuration = _get_story_configuration_at_index(index)
|
||||
if story_configuration == null:
|
||||
return
|
||||
|
||||
var localized_path = ProjectSettings.localize_path(path)
|
||||
var source_line_edit = story_configuration.source_file_line_edit
|
||||
|
||||
source_line_edit.text = localized_path
|
||||
source_line_edit.queue_redraw()
|
||||
|
||||
if story_configuration.target_file_line_edit.text.is_empty():
|
||||
var target_line_edit = story_configuration.target_file_line_edit
|
||||
target_line_edit.text = localized_path + ".json"
|
||||
target_line_edit.queue_redraw()
|
||||
|
||||
if story_configuration.watched_folder_line_edit.text.is_empty():
|
||||
var watched_folder_line_edit = story_configuration.watched_folder_line_edit
|
||||
watched_folder_line_edit.text = localized_path.get_base_dir()
|
||||
watched_folder_line_edit.queue_redraw()
|
||||
|
||||
_persist_configuration()
|
||||
|
||||
FileDialogSelection.TARGET_FILE:
|
||||
var story_configuration = _get_story_configuration_at_index(index)
|
||||
if story_configuration == null:
|
||||
return
|
||||
|
||||
var localized_path = ProjectSettings.localize_path(path)
|
||||
var line_edit = story_configuration.target_file_line_edit
|
||||
|
||||
line_edit.text = localized_path
|
||||
line_edit.queue_redraw()
|
||||
_persist_configuration()
|
||||
|
||||
FileDialogSelection.WATCHED_FOLDER:
|
||||
var story_configuration = _get_story_configuration_at_index(index)
|
||||
if story_configuration == null:
|
||||
return
|
||||
|
||||
var localized_path = ProjectSettings.localize_path(path)
|
||||
var line_edit = story_configuration.watched_folder_line_edit
|
||||
|
||||
line_edit.text = localized_path
|
||||
line_edit.queue_redraw()
|
||||
_persist_configuration()
|
||||
|
||||
_:
|
||||
printerr("[inkgd] [ERROR] Unknown FileDialogSelection, failed to save FileDialog file.")
|
||||
|
||||
_file_dialog_selection = FileDialogSelection.UNKNOWN
|
||||
|
||||
|
||||
func _scrollbar_changed():
|
||||
var max_value = _scroll_container.get_v_scroll_bar().max_value
|
||||
|
||||
if _scrollbar_max_value == max_value && _scrollbar_max_value != -1:
|
||||
return
|
||||
|
||||
_scrollbar_max_value = max_value
|
||||
_scroll_container.scroll_vertical = max_value
|
||||
|
||||
|
||||
# ############################################################################ #
|
||||
# Private helpers
|
||||
# ############################################################################ #
|
||||
|
||||
func _reset_file_dialog():
|
||||
_file_dialog.current_path = "res://"
|
||||
_file_dialog.current_dir = "res://"
|
||||
_file_dialog.current_file = ""
|
||||
_file_dialog.clear_filters()
|
||||
|
||||
|
||||
func _persist_configuration():
|
||||
configuration.stories.clear()
|
||||
|
||||
if _empty_state_container.get_parent() == null:
|
||||
configuration.stories.clear()
|
||||
for node in _story_configuration_container.get_children():
|
||||
# Not using "is InkStoryConfiguration", because it requires a type
|
||||
# declaration. Node Types register in the editor and we don't want
|
||||
# that. This is a bit hacky, but until the proposal is accepted,
|
||||
# it prevents cluttering the "Create new node" list.
|
||||
if !("story_label" in node):
|
||||
continue
|
||||
|
||||
configuration.append_new_story_configuration(
|
||||
node.source_file_line_edit.text,
|
||||
node.target_file_line_edit.text,
|
||||
node.watched_folder_line_edit.text
|
||||
)
|
||||
|
||||
configuration.persist()
|
||||
|
||||
|
||||
func _load_story_configurations():
|
||||
for story_configuration in configuration.stories:
|
||||
var node = _add_new_story_configuration()
|
||||
|
||||
node.source_file_line_edit.text = configuration.get_source_file_path(story_configuration)
|
||||
node.target_file_line_edit.text = configuration.get_target_file_path(story_configuration)
|
||||
node.watched_folder_line_edit.text = configuration.get_watched_folder_path(story_configuration)
|
||||
|
||||
|
||||
func _add_new_story_configuration():
|
||||
var story_configuration = InkStoryConfigurationScene.instantiate()
|
||||
|
||||
story_configuration.editor_interface = editor_interface
|
||||
|
||||
story_configuration.connect("configuration_changed", Callable(self, "_configuration_changed"))
|
||||
story_configuration.connect("remove_button_pressed", Callable(self, "_remove_button_pressed"))
|
||||
story_configuration.connect("build_button_pressed", Callable(self, "_build_button_pressed"))
|
||||
story_configuration.connect("source_file_button_pressed", Callable(self, "_source_file_button_pressed"))
|
||||
story_configuration.connect("target_file_button_pressed", Callable(self, "_target_file_button_pressed"))
|
||||
story_configuration.connect("watched_folder_button_pressed", Callable(self, "_watched_folder_button_pressed"))
|
||||
|
||||
if _empty_state_container.get_parent() != null:
|
||||
_story_configuration_container.remove_child(_empty_state_container)
|
||||
|
||||
_story_configuration_container.add_child(story_configuration)
|
||||
|
||||
var count = _story_configuration_container.get_child_count()
|
||||
story_configuration.story_label.text = "Story %d" % count
|
||||
|
||||
var show_folder = (configuration.compilation_mode == InkConfiguration.BuildMode.AFTER_CHANGE)
|
||||
story_configuration.show_watched_folder(show_folder)
|
||||
|
||||
return story_configuration
|
||||
|
||||
|
||||
func _reimport_compiled_stories():
|
||||
editor_interface.scan_file_system()
|
||||
|
||||
|
||||
func _get_story_configuration_index(node) -> int:
|
||||
return _story_configuration_container.get_children().find(node)
|
||||
|
||||
|
||||
func _get_story_configuration_at_index(index: int):
|
||||
if index >= 0 && _story_configuration_container.get_child_count():
|
||||
return _story_configuration_container.get_children()[index]
|
||||
|
||||
return null
|
||||
|
||||
|
||||
func _recompile_if_necessary(resources: PackedStringArray):
|
||||
# Making sure the resources have been imported before recompiling.
|
||||
await get_tree().create_timer(0.5).timeout
|
||||
|
||||
for story_configuration in configuration.stories:
|
||||
var watched_folder_path: String = configuration.get_watched_folder_path(story_configuration)
|
||||
|
||||
if watched_folder_path.is_empty():
|
||||
return
|
||||
|
||||
for resource in resources:
|
||||
if resource.begins_with(watched_folder_path):
|
||||
_compile_story(story_configuration)
|
||||
break
|
||||
|
||||
|
||||
func _disable_all_buttons(disable: bool):
|
||||
_add_new_story_button.disabled = disable
|
||||
_build_all_button.disabled = disable
|
||||
for child in _story_configuration_container.get_children():
|
||||
child.disable_all_buttons(disable)
|
||||
|
||||
|
||||
func _connect_signals():
|
||||
_build_all_button.connect("pressed", Callable(self, "_build_all_button_pressed"))
|
||||
_add_new_story_button.connect("pressed", Callable(self, "_add_new_story_button_pressed"))
|
||||
|
||||
_file_dialog.connect("file_selected", Callable(self, "_on_file_selected"))
|
||||
_file_dialog.connect("dir_selected", Callable(self, "_on_file_selected"))
|
||||
|
||||
_scroll_container.get_v_scroll_bar().connect("changed", Callable(self, "_scrollbar_changed"))
|
75
addons/inkgd/editor/panel/stories/ink_story_panel.tscn
Normal file
75
addons/inkgd/editor/panel/stories/ink_story_panel.tscn
Normal file
|
@ -0,0 +1,75 @@
|
|||
[gd_scene load_steps=4 format=3 uid="uid://b16uj28y2mqmk"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/inkgd/editor/panel/stories/ink_story_panel.gd" id="1"]
|
||||
|
||||
[sub_resource type="Image" id="Image_3omov"]
|
||||
data = {
|
||||
"data": PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
|
||||
"format": "LumAlpha8",
|
||||
"height": 16,
|
||||
"mipmaps": false,
|
||||
"width": 16
|
||||
}
|
||||
|
||||
[sub_resource type="ImageTexture" id="2"]
|
||||
image = SubResource("Image_3omov")
|
||||
|
||||
[node name="Story" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
script = ExtResource("1")
|
||||
|
||||
[node name="ScrollContainer" type="ScrollContainer" parent="."]
|
||||
layout_mode = 0
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
|
||||
[node name="StoriesVBoxContainer" type="VBoxContainer" parent="ScrollContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="ScrollContainer/StoriesVBoxContainer"]
|
||||
layout_mode = 2
|
||||
alignment = 2
|
||||
|
||||
[node name="Label" type="Label" parent="ScrollContainer/StoriesVBoxContainer/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "Managed Stories"
|
||||
|
||||
[node name="AddNewStoryButton" type="Button" parent="ScrollContainer/StoriesVBoxContainer/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
mouse_filter = 1
|
||||
theme_override_constants/h_separation = 8
|
||||
text = "New story"
|
||||
icon = SubResource("2")
|
||||
|
||||
[node name="VSeparator" type="VSeparator" parent="ScrollContainer/StoriesVBoxContainer/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="BuildAllButton" type="Button" parent="ScrollContainer/StoriesVBoxContainer/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
mouse_filter = 1
|
||||
theme_override_constants/h_separation = 10
|
||||
text = "Compile All"
|
||||
|
||||
[node name="MarginContainer" type="MarginContainer" parent="ScrollContainer/StoriesVBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Panel" type="Panel" parent="ScrollContainer/StoriesVBoxContainer/MarginContainer"]
|
||||
self_modulate = Color(1, 1, 1, 0.686275)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="MarginContainer" type="MarginContainer" parent="ScrollContainer/StoriesVBoxContainer/MarginContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/margin_left = 5
|
||||
theme_override_constants/margin_top = 5
|
||||
theme_override_constants/margin_right = 5
|
||||
theme_override_constants/margin_bottom = 5
|
||||
|
||||
[node name="StoryConfigurationVBoxContainer" type="VBoxContainer" parent="ScrollContainer/StoriesVBoxContainer/MarginContainer/MarginContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_override_constants/separation = 15
|
0
addons/inkgd/editor/templates/.gdignore
Normal file
0
addons/inkgd/editor/templates/.gdignore
Normal file
101
addons/inkgd/editor/templates/ink_template.gd
Normal file
101
addons/inkgd/editor/templates/ink_template.gd
Normal file
|
@ -0,0 +1,101 @@
|
|||
# warning-ignore-all:return_value_discarded
|
||||
|
||||
extends %BASE%
|
||||
|
||||
# ############################################################################ #
|
||||
# Imports
|
||||
# ############################################################################ #
|
||||
|
||||
var InkPlayer = load("res://addons/inkgd/ink_player.gd")
|
||||
|
||||
# ############################################################################ #
|
||||
# Public Nodes
|
||||
# ############################################################################ #
|
||||
|
||||
# Alternatively, it could also be retrieved from the tree.
|
||||
# onready var _ink_player = $InkPlayer
|
||||
@onready var _ink_player = InkPlayer.new()
|
||||
|
||||
# ############################################################################ #
|
||||
# Lifecycle
|
||||
# ############################################################################ #
|
||||
|
||||
func _ready():
|
||||
%TS%# Adds the player to the tree.
|
||||
%TS%add_child(_ink_player)
|
||||
|
||||
%TS%# Replace the example path with the path to your story.
|
||||
%TS%# Remove this line if you set 'ink_file' in the inspector.
|
||||
%TS%_ink_player.ink_file = load("res://path/to/file.ink.json")
|
||||
|
||||
%TS%# It's recommended to load the story in the background. On platforms that
|
||||
%TS%# don't support threads, the value of this variable is ignored.
|
||||
%TS%_ink_player.loads_in_background = true
|
||||
|
||||
%TS%_ink_player.connect("loaded", Callable(self, "_story_loaded"))
|
||||
|
||||
%TS%# Creates the story. 'loaded' will be emitted once Ink is ready
|
||||
%TS%# continue the story.
|
||||
%TS%_ink_player.create_story()
|
||||
|
||||
|
||||
# ############################################################################ #
|
||||
# Signal Receivers
|
||||
# ############################################################################ #
|
||||
|
||||
func _story_loaded(successfully: bool):
|
||||
%TS%if !successfully:
|
||||
%TS%%TS%return
|
||||
|
||||
%TS%# _observe_variables()
|
||||
%TS%# _bind_externals()
|
||||
|
||||
%TS%_continue_story()
|
||||
|
||||
|
||||
# ############################################################################ #
|
||||
# Private Methods
|
||||
# ############################################################################ #
|
||||
|
||||
func _continue_story():
|
||||
%TS%while _ink_player.can_continue:
|
||||
%TS%%TS%var text = _ink_player.continue_story()
|
||||
%TS%%TS%# This text is a line of text from the ink story.
|
||||
%TS%%TS%# Set the text of a Label to this value to display it in your game.
|
||||
%TS%%TS%print(text)
|
||||
%TS%if _ink_player.has_choices:
|
||||
%TS%%TS%# 'current_choices' contains a list of the choices, as strings.
|
||||
%TS%%TS%for choice in _ink_player.current_choices:
|
||||
%TS%%TS%%TS%print(choice.text)
|
||||
%TS%%TS%%TS%print(choice.tags)
|
||||
%TS%%TS%# '_select_choice' is a function that will take the index of
|
||||
%TS%%TS%# your selection and continue the story.
|
||||
%TS%%TS%_select_choice(0)
|
||||
%TS%else:
|
||||
%TS%%TS%# This code runs when the story reaches it's end.
|
||||
%TS%%TS%print("The End")
|
||||
|
||||
|
||||
func _select_choice(index):
|
||||
%TS%_ink_player.choose_choice_index(index)
|
||||
%TS%_continue_story()
|
||||
|
||||
|
||||
# Uncomment to bind an external function.
|
||||
#
|
||||
# func _bind_externals():
|
||||
# %TS%_ink_player.bind_external_function("<function_name>", self, "_external_function")
|
||||
#
|
||||
#
|
||||
# func _external_function(arg1, arg2):
|
||||
# %TS%pass
|
||||
|
||||
|
||||
# Uncomment to observe the variables from your ink story.
|
||||
# You can observe multiple variables by putting adding them in the array.
|
||||
# func _observe_variables():
|
||||
# %TS%_ink_player.observe_variables(["var1", "var2"], self, "_variable_changed")
|
||||
#
|
||||
#
|
||||
# func _variable_changed(variable_name, new_value):
|
||||
# %TS%print("Variable '%s' changed to: %s" %[variable_name, new_value])
|
107
addons/inkgd/editor/templates/ink_template_signals.gd
Normal file
107
addons/inkgd/editor/templates/ink_template_signals.gd
Normal file
|
@ -0,0 +1,107 @@
|
|||
# warning-ignore-all:return_value_discarded
|
||||
|
||||
extends %BASE%
|
||||
|
||||
# ############################################################################ #
|
||||
# Imports
|
||||
# ############################################################################ #
|
||||
|
||||
var InkPlayer = load("res://addons/inkgd/ink_player.gd")
|
||||
|
||||
|
||||
# ############################################################################ #
|
||||
# Public Nodes
|
||||
# ############################################################################ #
|
||||
|
||||
# Alternatively, it could also be retrieved from the tree.
|
||||
# onready var _ink_player = $InkPlayer
|
||||
@onready var _ink_player = InkPlayer.new()
|
||||
|
||||
# ############################################################################ #
|
||||
# Lifecycle
|
||||
# ############################################################################ #
|
||||
|
||||
func _ready():
|
||||
%TS%# Adds the player to the tree.
|
||||
%TS%add_child(_ink_player)
|
||||
|
||||
%TS%# Replace the example path with the path to your story.
|
||||
%TS%# Remove this line if you set 'ink_file' in the inspector.
|
||||
%TS%_ink_player.ink_file = load("res://path/to/file.ink.json")
|
||||
|
||||
%TS%# It's recommended to load the story in the background. On platforms that
|
||||
%TS%# don't support threads, the value of this variable is ignored.
|
||||
%TS%_ink_player.loads_in_background = true
|
||||
|
||||
%TS%_ink_player.connect("loaded", Callable(self, "_story_loaded"))
|
||||
%TS%_ink_player.connect("continued", Callable(self, "_continued"))
|
||||
%TS%_ink_player.connect("prompt_choices", Callable(self, "_prompt_choices"))
|
||||
%TS%_ink_player.connect("ended", Callable(self, "_ended"))
|
||||
|
||||
%TS%# Creates the story. 'loaded' will be emitted once Ink is ready
|
||||
%TS%# continue the story.
|
||||
%TS%_ink_player.create_story()
|
||||
|
||||
# ############################################################################ #
|
||||
# Signal Receivers
|
||||
# ############################################################################ #
|
||||
|
||||
func _story_loaded(successfully: bool):
|
||||
%TS%if !successfully:
|
||||
%TS%%TS%return
|
||||
|
||||
%TS%# _observe_variables()
|
||||
%TS%# _bind_externals()
|
||||
|
||||
%TS%# Here, the story is started immediately, but it could be started
|
||||
%TS%# at a later time.
|
||||
%TS%_ink_player.continue_story()
|
||||
|
||||
|
||||
func _continued(text, tags):
|
||||
%TS%print(text)
|
||||
%TS%# Here you could yield for an hypothetical signal, before continuing.
|
||||
%TS%# await self.event
|
||||
%TS%_ink_player.continue_story()
|
||||
|
||||
|
||||
# ############################################################################ #
|
||||
# Private Methods
|
||||
# ############################################################################ #
|
||||
|
||||
func _prompt_choices(choices):
|
||||
%TS%if !choices.is_empty():
|
||||
%TS%%TS%print(choices)
|
||||
|
||||
%TS%%TS%# In a real world scenario, _select_choice' could be
|
||||
%TS%%TS%# connected to a signal, like 'Button.pressed'.
|
||||
%TS%%TS%_select_choice(0)
|
||||
|
||||
|
||||
func _ended():
|
||||
%TS%print("The End")
|
||||
|
||||
|
||||
func _select_choice(index):
|
||||
%TS%_ink_player.choose_choice_index(index)
|
||||
%TS%_ink_player.continue_story()
|
||||
|
||||
|
||||
# Uncomment to bind an external function.
|
||||
#
|
||||
# func _bind_externals():
|
||||
# %TS%_ink_player.bind_external_function("<function_name>", self, "_external_function")
|
||||
#
|
||||
#
|
||||
# func _external_function(arg1, arg2):
|
||||
# %TS%pass
|
||||
|
||||
|
||||
# Uncomment to observe the variables from your ink story.
|
||||
# You can observe multiple variables by putting adding them in the array.
|
||||
# func _observe_variables():
|
||||
# %TS%_ink_player.observe_variables(["var1", "var2"], self, "_variable_changed")
|
||||
#
|
||||
#
|
||||
# func _variable_changed(variable_name, new_value):
|
||||
# %TS%print("Variable '%s' changed to: %s" %[variable_name, new_value])
|
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)
|
49
addons/inkgd/ink_player_factory.gd
Normal file
49
addons/inkgd/ink_player_factory.gd
Normal file
|
@ -0,0 +1,49 @@
|
|||
@tool
|
||||
# ############################################################################ #
|
||||
# Copyright © 2018-2021 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 RefCounted
|
||||
|
||||
class_name InkPlayerFactory
|
||||
|
||||
const DO_NOT_USE_MONO_RUNTIME_SETTING = "inkgd/do_not_use_mono_runtime"
|
||||
|
||||
# ############################################################################ #
|
||||
# Methods
|
||||
# ############################################################################ #
|
||||
|
||||
static func create():
|
||||
if _should_use_mono():
|
||||
var InkPlayer = load("res://addons/inkgd/mono/InkPlayer.cs")
|
||||
if InkPlayer.can_instantiate():
|
||||
return InkPlayer.new()
|
||||
else:
|
||||
printerr(
|
||||
"[inkgd] [ERROR] InkPlayer can't be instantiated. Make sure that a suitable " +
|
||||
"copy of 'ink-runtime-engine.dll' can be found in project and double check " +
|
||||
"that the .csproj file contains a <RefCounted> item pointing to it. " +
|
||||
"If everything is configured correctly, you may need to rebuild " +
|
||||
"the C# solution. Please refer to [TO BE ADDED] for additional help."
|
||||
)
|
||||
print("[inkgd] [INFO] Falling back to the GDScript runtime.")
|
||||
|
||||
# Falling back to GDscript.
|
||||
return load("res://addons/inkgd/ink_player.gd").new()
|
||||
|
||||
|
||||
static func _should_use_mono() -> bool:
|
||||
if ProjectSettings.has_setting(DO_NOT_USE_MONO_RUNTIME_SETTING):
|
||||
var do_not_use_mono = ProjectSettings.get_setting(DO_NOT_USE_MONO_RUNTIME_SETTING)
|
||||
if do_not_use_mono == null:
|
||||
do_not_use_mono = false
|
||||
|
||||
return _can_run_mono() && !do_not_use_mono
|
||||
else:
|
||||
return _can_run_mono()
|
||||
|
||||
static func _can_run_mono() -> bool:
|
||||
return type_exists("_GodotSharp")
|
182
addons/inkgd/mono/InkBridger.cs
Normal file
182
addons/inkgd/mono/InkBridger.cs
Normal file
|
@ -0,0 +1,182 @@
|
|||
// /////////////////////////////////////////////////////////////////////////// /
|
||||
// Copyright © 2019-2022 Frédéric Maquin <fred@ephread.com>
|
||||
// Licensed under the MIT License.
|
||||
// See LICENSE in the project root for license information.
|
||||
// /////////////////////////////////////////////////////////////////////////// /
|
||||
|
||||
using Godot;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
|
||||
public partial class InkBridger : Node
|
||||
{
|
||||
#region Imports
|
||||
private readonly GDScript InkPath =
|
||||
(GDScript) ResourceLoader.Load("res://addons/inkgd/runtime/ink_path.gd");
|
||||
|
||||
private readonly GDScript InkList =
|
||||
(GDScript) ResourceLoader.Load("res://addons/inkgd/runtime/lists/ink_list.gd");
|
||||
|
||||
private readonly GDScript InkListDefinition =
|
||||
(GDScript) ResourceLoader.Load("res://addons/inkgd/runtime/lists/list_definition.gd");
|
||||
|
||||
private readonly GDScript InkListItem =
|
||||
(GDScript) ResourceLoader.Load("res://addons/inkgd/runtime/lists/structs/ink_list_item.gd");
|
||||
|
||||
private readonly GDScript InkFunctionResult =
|
||||
(GDScript) ResourceLoader.Load("res://addons/inkgd/runtime/extra/function_result.gd");
|
||||
#endregion
|
||||
|
||||
#region Methods | Helpers
|
||||
public bool IsInkObjectOfType(Godot.Object inkObject, string name)
|
||||
{
|
||||
return inkObject.HasMethod("is_ink_class") && (bool)inkObject.Call("is_ink_class", new object[] { name });
|
||||
}
|
||||
|
||||
public Godot.Object MakeFunctionResult(string textOutput, object returnValue)
|
||||
{
|
||||
var parameters = new object[] { textOutput ?? "", returnValue };
|
||||
return (Godot.Object) InkFunctionResult.New(parameters);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Methods | Conversion -> (GDScript -> C#)
|
||||
public Godot.Object MakeGDInkPath(Ink.Runtime.Path3D path) {
|
||||
var inkPath = (Godot.Object) InkPath.New();
|
||||
inkPath.Call("_init_with_components_string", path.componentsString);
|
||||
return inkPath;
|
||||
}
|
||||
|
||||
public Godot.Object MakeGDInkList(Ink.Runtime.InkList list)
|
||||
{
|
||||
var inkListBase = new Godot.Collections.Dictionary<string, int>();
|
||||
|
||||
foreach(KeyValuePair<Ink.Runtime.InkListItem, int> kv in list) {
|
||||
inkListBase.Add(MakeGDInkListItem(kv.Key).Call("serialized") as string, kv.Value);
|
||||
}
|
||||
|
||||
object[] inkListParams = new object[] {
|
||||
inkListBase,
|
||||
list.originNames.ToArray(),
|
||||
MakeGDInkListOrigins(list.origins)
|
||||
};
|
||||
|
||||
var inkList = (Godot.Object) InkList.New();
|
||||
inkList.Call("_init_from_csharp", inkListParams);
|
||||
|
||||
return inkList;
|
||||
}
|
||||
|
||||
public Ink.Runtime.Path3D MakeSharpInkPath(Godot.Object path) {
|
||||
if (!IsInkObjectOfType(path, "InkPath"))
|
||||
{
|
||||
throw new ArgumentException("Expected a 'Godot.Object' of class 'InkPath'");
|
||||
}
|
||||
|
||||
return new Ink.Runtime.Path3D((string)path.Get("components_string"));
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Methods | Conversion (GDScript -> C#)
|
||||
public Ink.Runtime.InkList MakeSharpInkList(Godot.Object list, Ink.Runtime.Story story)
|
||||
{
|
||||
if (!IsInkObjectOfType(list, "InkList"))
|
||||
{
|
||||
throw new ArgumentException("Expected a 'Godot.Object' of class 'InkList'");
|
||||
}
|
||||
|
||||
var underlyingDictionary = new Godot.Collections.Dictionary<string, int>(
|
||||
(Godot.Collections.Dictionary)list.Get("_dictionary"));
|
||||
|
||||
var originNames = new Godot.Collections.Array<string>(
|
||||
(Godot.Collections.Array)list.Get("origin_names"));
|
||||
|
||||
var inkList = new Ink.Runtime.InkList();
|
||||
inkList.origins = new List<Ink.Runtime.ListDefinition>();
|
||||
|
||||
inkList.SetInitialOriginNames(originNames.ToList());
|
||||
|
||||
foreach(string originName in originNames)
|
||||
{
|
||||
if (story.listDefinitions.TryListGetDefinition (originName, out Ink.Runtime.ListDefinition definition))
|
||||
{
|
||||
if (!inkList.origins.Contains(definition)) {
|
||||
inkList.origins.Add(definition);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception (
|
||||
$"InkList origin could not be found in story when reconstructing list: {originName}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
foreach(KeyValuePair<string, int> kv in underlyingDictionary)
|
||||
{
|
||||
inkList[MakeSharpInkListItem(kv.Key)] = kv.Value;
|
||||
}
|
||||
|
||||
return inkList;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Private Methods | Conversion (C# -> GDScript)
|
||||
private Godot.Collections.Array<Godot.Object> MakeGDInkListOrigins(
|
||||
List<Ink.Runtime.ListDefinition> listDefinitions)
|
||||
{
|
||||
var inkListDefinitions = new Godot.Collections.Array<Godot.Object>();
|
||||
|
||||
foreach(Ink.Runtime.ListDefinition listDefinition in listDefinitions) {
|
||||
var inkListDefinition = MakeGDListDefinition(listDefinition);
|
||||
inkListDefinitions.Add(inkListDefinition);
|
||||
}
|
||||
|
||||
return inkListDefinitions;
|
||||
}
|
||||
|
||||
private Godot.Object MakeGDListDefinition(Ink.Runtime.ListDefinition listDefinition)
|
||||
{
|
||||
var items = new Godot.Collections.Dictionary<Godot.Object, int>();
|
||||
|
||||
foreach(KeyValuePair<Ink.Runtime.InkListItem, int> kv in listDefinition.items) {
|
||||
var inkListItem = MakeGDInkListItem(kv.Key);
|
||||
items.Add(inkListItem, kv.Value);
|
||||
}
|
||||
|
||||
var definitionParams = new object[] { listDefinition.name, items };
|
||||
var inkListDefinition = (Godot.Object) InkListDefinition.New(definitionParams);
|
||||
|
||||
return inkListDefinition;
|
||||
}
|
||||
|
||||
private Godot.Object MakeGDInkListItem(Ink.Runtime.InkListItem listItem)
|
||||
{
|
||||
object[] itemParams = new object[] { listItem.fullName };
|
||||
|
||||
var inkListItem = (Godot.Object) InkListItem.New();
|
||||
inkListItem.Call("_init_with_full_name", itemParams);
|
||||
|
||||
return inkListItem;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Private Methods | Conversion (GDScript -> C#)
|
||||
private Ink.Runtime.InkListItem MakeSharpInkListItem(string listItemKey)
|
||||
{
|
||||
|
||||
var listItem = (Godot.Object) InkListItem.Call("from_serialized_key", new object[] { listItemKey });
|
||||
|
||||
if (!IsInkObjectOfType(listItem, "InkListItem")) {
|
||||
throw new ArgumentException("Expected a 'Godot.Object' of class 'InkListItem'");
|
||||
}
|
||||
|
||||
return new Ink.Runtime.InkListItem(
|
||||
listItem.Get("origin_name") as string,
|
||||
listItem.Get("item_name") as string
|
||||
);
|
||||
}
|
||||
#endregion
|
||||
}
|
1271
addons/inkgd/mono/InkPlayer.cs
Normal file
1271
addons/inkgd/mono/InkPlayer.cs
Normal file
File diff suppressed because it is too large
Load diff
0
addons/inkgd/mono/assemblies/.gitkeep
Normal file
0
addons/inkgd/mono/assemblies/.gitkeep
Normal file
7
addons/inkgd/plugin.cfg
Normal file
7
addons/inkgd/plugin.cfg
Normal file
|
@ -0,0 +1,7 @@
|
|||
[plugin]
|
||||
|
||||
name="InkGD"
|
||||
description="Full implementation of inkle's Ink narrative language in pure GDScript, with editor support."
|
||||
author="Frédéric Maquin"
|
||||
version="0.6.0"
|
||||
script="editor/ink_editor_plugin.gd"
|
35
addons/inkgd/runtime.gd
Normal file
35
addons/inkgd/runtime.gd
Normal file
|
@ -0,0 +1,35 @@
|
|||
# ############################################################################ #
|
||||
# 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 InkRuntime
|
||||
|
||||
static func init(root_node, stop_on_error = true):
|
||||
if root_node.has_node("__InkRuntime"):
|
||||
var _ink_runtime = root_node.get_node("__InkRuntime")
|
||||
_ink_runtime.stop_execution_on_exception = stop_on_error
|
||||
_ink_runtime.stop_execution_on_error = stop_on_error
|
||||
|
||||
return _ink_runtime
|
||||
|
||||
var _ink_runtime = load("res://addons/inkgd/runtime/static/ink_runtime.gd").new()
|
||||
|
||||
_ink_runtime.stop_execution_on_exception = stop_on_error
|
||||
_ink_runtime.stop_execution_on_error = stop_on_error
|
||||
|
||||
root_node.add_child(_ink_runtime)
|
||||
|
||||
return _ink_runtime
|
||||
|
||||
static func deinit(root_node):
|
||||
var _ink_runtime = root_node.get_node("__InkRuntime")
|
||||
root_node.remove_child(_ink_runtime)
|
||||
_ink_runtime.queue_free()
|
457
addons/inkgd/runtime/callstack.gd
Normal file
457
addons/inkgd/runtime/callstack.gd
Normal 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)
|
24
addons/inkgd/runtime/common/ink.gd
Normal file
24
addons/inkgd/runtime/common/ink.gd
Normal 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
|
||||
}
|
31
addons/inkgd/runtime/common/ink_base.gd
Normal file
31
addons/inkgd/runtime/common/ink_base.gd
Normal 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"
|
212
addons/inkgd/runtime/common/ink_object.gd
Normal file
212
addons/inkgd/runtime/common/ink_object.gd
Normal 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"
|
58
addons/inkgd/runtime/content/choices/ink_choice.gd
Normal file
58
addons/inkgd/runtime/content/choices/ink_choice.gd
Normal 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"
|
122
addons/inkgd/runtime/content/choices/ink_choice_point.gd
Normal file
122
addons/inkgd/runtime/content/choices/ink_choice_point.gd
Normal 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"
|
333
addons/inkgd/runtime/content/ink_container.gd
Normal file
333
addons/inkgd/runtime/content/ink_container.gd
Normal 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"
|
213
addons/inkgd/runtime/content/ink_control_command.gd
Normal file
213
addons/inkgd/runtime/content/ink_control_command.gd
Normal 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"
|
145
addons/inkgd/runtime/content/ink_divert.gd
Normal file
145
addons/inkgd/runtime/content/ink_divert.gd
Normal 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"
|
29
addons/inkgd/runtime/content/ink_glue.gd
Normal file
29
addons/inkgd/runtime/content/ink_glue.gd
Normal 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"
|
372
addons/inkgd/runtime/content/ink_native_function_call.gd
Normal file
372
addons/inkgd/runtime/content/ink_native_function_call.gd
Normal 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
|
36
addons/inkgd/runtime/content/ink_tag.gd
Normal file
36
addons/inkgd/runtime/content/ink_tag.gd
Normal 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"
|
60
addons/inkgd/runtime/content/ink_variable_assignment.gd
Normal file
60
addons/inkgd/runtime/content/ink_variable_assignment.gd
Normal 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
|
69
addons/inkgd/runtime/content/ink_variable_reference.gd
Normal file
69
addons/inkgd/runtime/content/ink_variable_reference.gd
Normal 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"
|
27
addons/inkgd/runtime/content/ink_void.gd
Normal file
27
addons/inkgd/runtime/content/ink_void.gd
Normal 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"
|
76
addons/inkgd/runtime/debug_metadata.gd
Normal file
76
addons/inkgd/runtime/debug_metadata.gd
Normal 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")
|
24
addons/inkgd/runtime/extra/function_result.gd
Normal file
24
addons/inkgd/runtime/extra/function_result.gd
Normal 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
|
39
addons/inkgd/runtime/extra/ink_key_value_pair.gd
Normal file
39
addons/inkgd/runtime/extra/ink_key_value_pair.gd
Normal 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
|
50
addons/inkgd/runtime/extra/state_element.gd
Normal file
50
addons/inkgd/runtime/extra/state_element.gd
Normal 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"
|
34
addons/inkgd/runtime/extra/stopwatch.gd
Normal file
34
addons/inkgd/runtime/extra/stopwatch.gd
Normal 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
|
31
addons/inkgd/runtime/extra/story_error.gd
Normal file
31
addons/inkgd/runtime/extra/story_error.gd
Normal 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
|
30
addons/inkgd/runtime/extra/story_error_metadata.gd
Normal file
30
addons/inkgd/runtime/extra/story_error_metadata.gd
Normal 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
|
63
addons/inkgd/runtime/extra/string_set.gd
Normal file
63
addons/inkgd/runtime/extra/string_set.gd
Normal 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)
|
33
addons/inkgd/runtime/extra/string_writer.gd
Normal file
33
addons/inkgd/runtime/extra/string_writer.gd
Normal 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
|
25
addons/inkgd/runtime/extra/try_get_result.gd
Normal file
25
addons/inkgd/runtime/extra/try_get_result.gd
Normal 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
|
272
addons/inkgd/runtime/extra/utils.gd
Normal file
272
addons/inkgd/runtime/extra/utils.gd
Normal 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")
|
154
addons/inkgd/runtime/flow.gd
Normal file
154
addons/inkgd/runtime/flow.gd
Normal 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)
|
294
addons/inkgd/runtime/ink_path.gd
Normal file
294
addons/inkgd/runtime/ink_path.gd
Normal 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")
|
522
addons/inkgd/runtime/lists/ink_list.gd
Normal file
522
addons/inkgd/runtime/lists/ink_list.gd
Normal 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"
|
||||
|
102
addons/inkgd/runtime/lists/list_definition.gd
Normal file
102
addons/inkgd/runtime/lists/list_definition.gd
Normal 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()
|
86
addons/inkgd/runtime/lists/list_definitions_origin.gd
Normal file
86
addons/inkgd/runtime/lists/list_definitions_origin.gd
Normal 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"
|
159
addons/inkgd/runtime/lists/structs/ink_list_item.gd
Normal file
159
addons/inkgd/runtime/lists/structs/ink_list_item.gd
Normal 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
|
57
addons/inkgd/runtime/profiler.gd
Normal file
57
addons/inkgd/runtime/profiler.gd
Normal 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 ""
|
41
addons/inkgd/runtime/search_result.gd
Normal file
41
addons/inkgd/runtime/search_result.gd
Normal 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"
|
588
addons/inkgd/runtime/simple_json.gd
Normal file
588
addons/inkgd/runtime/simple_json.gd
Normal 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
|
105
addons/inkgd/runtime/state_patch.gd
Normal file
105
addons/inkgd/runtime/state_patch.gd
Normal 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"
|
200
addons/inkgd/runtime/static/ink_runtime.gd
Normal file
200
addons/inkgd/runtime/static/ink_runtime.gd
Normal 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
|
623
addons/inkgd/runtime/static/json.gd
Normal file
623
addons/inkgd/runtime/static/json.gd
Normal 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
|
284
addons/inkgd/runtime/static/native_function_call.gd
Normal file
284
addons/inkgd/runtime/static/native_function_call.gd
Normal 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)
|
2095
addons/inkgd/runtime/story.gd
Normal file
2095
addons/inkgd/runtime/story.gd
Normal file
File diff suppressed because it is too large
Load diff
1144
addons/inkgd/runtime/story_state.gd
Normal file
1144
addons/inkgd/runtime/story_state.gd
Normal file
File diff suppressed because it is too large
Load diff
125
addons/inkgd/runtime/structs/pointer.gd
Normal file
125
addons/inkgd/runtime/structs/pointer.gd
Normal 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)
|
62
addons/inkgd/runtime/values/bool_value.gd
Normal file
62
addons/inkgd/runtime/values/bool_value.gd
Normal 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
|
60
addons/inkgd/runtime/values/divert_target_value.gd
Normal file
60
addons/inkgd/runtime/values/divert_target_value.gd
Normal 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
|
59
addons/inkgd/runtime/values/float_value.gd
Normal file
59
addons/inkgd/runtime/values/float_value.gd
Normal 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
|
59
addons/inkgd/runtime/values/int_value.gd
Normal file
59
addons/inkgd/runtime/values/int_value.gd
Normal 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
|
90
addons/inkgd/runtime/values/list_value.gd
Normal file
90
addons/inkgd/runtime/values/list_value.gd
Normal 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
|
82
addons/inkgd/runtime/values/string_value.gd
Normal file
82
addons/inkgd/runtime/values/string_value.gd
Normal 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
|
131
addons/inkgd/runtime/values/value.gd
Normal file
131
addons/inkgd/runtime/values/value.gd
Normal 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
|
23
addons/inkgd/runtime/values/value_type.gd
Normal file
23
addons/inkgd/runtime/values/value_type.gd
Normal 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
|
||||
}
|
69
addons/inkgd/runtime/values/variable_pointer_value.gd
Normal file
69
addons/inkgd/runtime/values/variable_pointer_value.gd
Normal 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
|
373
addons/inkgd/runtime/variables_state.gd
Normal file
373
addons/inkgd/runtime/variables_state.gd
Normal 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)
|
BIN
audio/Ambiente.ogg
(Stored with Git LFS)
Normal file
BIN
audio/Ambiente.ogg
(Stored with Git LFS)
Normal file
Binary file not shown.
19
audio/Ambiente.ogg.import
Normal file
19
audio/Ambiente.ogg.import
Normal file
|
@ -0,0 +1,19 @@
|
|||
[remap]
|
||||
|
||||
importer="oggvorbisstr"
|
||||
type="AudioStreamOggVorbis"
|
||||
uid="uid://3hulnd5c5me5"
|
||||
path="res://.godot/imported/Ambiente.ogg-d8268c600c8aca0de710e563231cd1c8.oggvorbisstr"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://audio/Ambiente.ogg"
|
||||
dest_files=["res://.godot/imported/Ambiente.ogg-d8268c600c8aca0de710e563231cd1c8.oggvorbisstr"]
|
||||
|
||||
[params]
|
||||
|
||||
loop=true
|
||||
loop_offset=0.0
|
||||
bpm=0.0
|
||||
beat_count=0
|
||||
bar_beats=4
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue