diff --git a/addons/escoria-core/editor/plugin_escoria.gd b/addons/escoria-core/editor/plugin_escoria.gd index 989deeb1..d2bb1ba4 100644 --- a/addons/escoria-core/editor/plugin_escoria.gd +++ b/addons/escoria-core/editor/plugin_escoria.gd @@ -133,6 +133,16 @@ func set_escoria_main_settings(): ProjectSettings.set_setting("application/run/main_scene", "res://addons/escoria-core/game/main_scene.tscn") + if not ProjectSettings.has_setting("escoria/main/command_directories"): + ProjectSettings.set_setting("escoria/main/command_directories", [ + "res://addons/escoria-core/game/core-scripts/esc/commands" + ]) + ProjectSettings.add_property_info({ + "name": "escoria/main/command_directories", + "type": TYPE_ARRAY, + }) + + if !ProjectSettings.has_setting("escoria/main/text_lang"): ProjectSettings.set_setting("escoria/main/text_lang", TranslationServer.get_locale()) var text_lang_property_info = { @@ -168,6 +178,17 @@ func set_escoria_debug_settings(): if !ProjectSettings.has_setting("escoria/debug/development_lang"): ProjectSettings.set_setting("escoria/debug/development_lang", "en") + # Assure log level preference + if not ProjectSettings.has_setting("escoria/debug/log_level"): + ProjectSettings.set_setting("escoria/debug/log_level", "ERROR") + var property_info = { + "name": "escoria/debug/log_level", + "type": TYPE_STRING, + "hint": PROPERTY_HINT_ENUM, + "hint_string": "ERROR,WARNING,INFO,DEBUG" + } + ProjectSettings.add_property_info(property_info) + func set_escoria_internal_settings(): if !ProjectSettings.has_setting("escoria/internals/save_data"): diff --git a/addons/escoria-core/game/core-scripts/behaviors/movable.gd b/addons/escoria-core/game/core-scripts/behaviors/movable.gd index f157360b..a5c01938 100644 --- a/addons/escoria-core/game/core-scripts/behaviors/movable.gd +++ b/addons/escoria-core/game/core-scripts/behaviors/movable.gd @@ -198,7 +198,9 @@ func walk_stop(pos): # orient towards the defined interaction direction set on the object (if any) if walk_context.has("target_object") \ and walk_context.target_object.player_orients_on_arrival \ - and escoria.esc_runner.get_interactive(walk_context.target_object.global_id): + and escoria.object_manager.get_object( + walk_context.target_object.global_id + ).interactive: var orientation = walk_context["target_object"].interaction_direction last_dir = orientation parent.animation_sprite.play(parent.animations.idles[orientation][0]) @@ -209,11 +211,6 @@ func walk_stop(pos): pose_scale = parent.animations.idles[last_dir][1] update_terrain() - if walk_context != null: - escoria.esc_level_runner.finished(walk_context) - escoria.esc_level_runner.finished() -# walk_context = null - yield(parent.animation_sprite, "animation_finished") escoria.logger.info(parent.global_id + " arrived at " + str(walk_context)) parent.emit_signal("arrived", walk_context) diff --git a/addons/escoria-core/game/core-scripts/esc/_test/test_esc_compiler.gd b/addons/escoria-core/game/core-scripts/esc/_test/test_esc_compiler.gd new file mode 100644 index 00000000..89702c15 --- /dev/null +++ b/addons/escoria-core/game/core-scripts/esc/_test/test_esc_compiler.gd @@ -0,0 +1,295 @@ +extends Control + + +func _test_basic() -> bool: + var esc = """ +:test +# first group +> + say player "Test" + # Second group + > [test] + say player "Test2 BLANK" + say player "Test3" [test2] + # Third group + > + say player "Test4" +# Fourth group +> + say player "Test5" + say player "Test 6" + """ + var script = escoria.esc_compiler.compile(esc.split("\n")) + + var subject = script + assert(subject is ESCScript) + + subject = script.events.keys() + assert(subject.size() == 1) + assert(subject[0] == "test") + + subject = script.events["test"].statements + assert(subject.size() == 2) + + subject = script.events["test"].statements[0] + assert(subject is ESCGroup) + assert(subject.statements.size() == 4) + + subject = script.events["test"].statements[0].statements[0] + assert(subject is ESCCommand) + assert(subject.name == "say") + assert(subject.parameters.size() == 2) + assert(subject.parameters[0] == "player") + assert(subject.parameters[1] == "Test") + + subject = script.events["test"].statements[0].statements[1] + assert(subject is ESCGroup) + assert(subject.conditions.size() == 1) + assert(subject.conditions[0] is ESCCondition) + assert(subject.conditions[0].flag == "test") + + subject = script.events["test"].statements[0].statements[1].statements[0] + assert(subject is ESCCommand) + assert(subject.name == "say") + assert(subject.parameters.size() == 2) + assert(subject.parameters[0] == "player") + assert(subject.parameters[1] == "Test2 BLANK") + + subject = script.events["test"].statements[0].statements[2] + assert(subject is ESCCommand) + assert(subject.name == "say") + assert(subject.parameters.size() == 2) + assert(subject.parameters[0] == "player") + assert(subject.parameters[1] == "Test3") + assert(subject.conditions.size() == 1) + assert(subject.conditions[0].flag == "test2") + + subject = script.events["test"].statements[0].statements[3] + assert(subject is ESCGroup) + assert(subject.statements.size() == 1) + + subject = script.events["test"].statements[1] + assert(subject is ESCGroup) + assert(subject.statements.size() == 2) + + subject = script.events["test"].statements[1].statements[1] + assert(subject is ESCCommand) + assert(subject.name == "say") + assert(subject.parameters[1] == "Test 6") + + return true + + +func _test_conditions() -> bool: + var esc = """ +:test +say player "Test" [flag] +say player "Test" [flag1,flag2] +say player "Test" [!flag] +say player "Test" [i/flag] +say player "Test" [i/flag,flag] +say player "Test" [i/flag,flag,!flag2] +say player "Test" [eq flag 3] +say player "Test" [eq flag 3,gt flag 5] +say player "Test" [!eq flag 3] + """ + var script = escoria.esc_compiler.compile(esc.split("\n")) + + var subject = script.events["test"].statements[0] + assert(subject is ESCCommand) + assert(subject.conditions.size() == 1) + + subject = script.events["test"].statements[0].conditions[0] + assert(subject.flag == "flag") + assert(not subject.negated) + assert(not subject.inventory) + assert(subject.comparison == ESCCondition.COMPARISON_NONE) + + subject = script.events["test"].statements[1].conditions + assert(subject.size() == 2) + assert(subject[0].flag == "flag1") + assert(subject[1].flag == "flag2") + + subject = script.events["test"].statements[2].conditions + assert(subject.size() == 1) + assert(subject[0].flag == "flag") + assert(subject[0].negated) + + subject = script.events["test"].statements[3].conditions + assert(subject.size() == 1) + assert(subject[0].flag == "flag") + assert(subject[0].inventory) + + subject = script.events["test"].statements[4].conditions + assert(subject.size() == 2) + assert(subject[0].flag == "flag") + assert(subject[0].inventory) + assert(subject[1].flag == "flag") + assert(not subject[1].inventory) + + subject = script.events["test"].statements[5].conditions + assert(subject.size() == 3) + assert(subject[0].flag == "flag") + assert(subject[0].inventory) + assert(subject[1].flag == "flag") + assert(not subject[1].inventory) + assert(subject[2].flag == "flag2") + assert(not subject[2].inventory) + assert(subject[2].negated) + + subject = script.events["test"].statements[6].conditions + assert(subject.size() == 1) + assert(subject[0].flag == "flag") + assert(subject[0].comparison == ESCCondition.COMPARISON_EQ) + assert(subject[0].comparison_value == 3) + + subject = script.events["test"].statements[7].conditions + assert(subject.size() == 2) + assert(subject[0].flag == "flag") + assert(subject[0].comparison == ESCCondition.COMPARISON_EQ) + assert(subject[0].comparison_value == 3) + assert(subject[1].flag == "flag") + assert(subject[1].comparison == ESCCondition.COMPARISON_GT) + assert(subject[1].comparison_value == 5) + + subject = script.events["test"].statements[8].conditions + assert(subject.size() == 1) + assert(subject[0].flag == "flag") + assert(subject[0].comparison == ESCCondition.COMPARISON_EQ) + assert(subject[0].comparison_value == 3) + assert(subject[0].negated) + + return true + + +func _test_event_flags() -> bool: + var esc = """ +:test | TK +:test2 | TK NO_TT +:test3 | TK NO_TT NO_HUD +:test4 | TK NO_TT CUT_BLACK + """ + var script = escoria.esc_compiler.compile(esc.split("\n")) + + var subject = script.events + assert(subject.keys().size() == 4) + assert("test" in subject.keys()) + assert("test2" in subject.keys()) + assert("test3" in subject.keys()) + assert("test4" in subject.keys()) + + subject = script.events["test"] + assert(subject.name == "test") + assert(subject.flags & ESCEvent.FLAG_TK != 0) + assert(subject.flags & ESCEvent.FLAG_NO_TT == 0) + + subject = script.events["test2"] + assert(subject.name == "test2") + assert(subject.flags & ESCEvent.FLAG_TK != 0) + assert(subject.flags & ESCEvent.FLAG_NO_TT != 0) + + subject = script.events["test3"] + assert(subject.name == "test3") + assert(subject.flags & ESCEvent.FLAG_TK != 0) + assert(subject.flags & ESCEvent.FLAG_NO_TT != 0) + assert(subject.flags & ESCEvent.FLAG_NO_HUD != 0) + + subject = script.events["test4"] + assert(subject.name == "test4") + assert(subject.flags & ESCEvent.FLAG_TK != 0) + assert(subject.flags & ESCEvent.FLAG_NO_TT != 0) + assert(subject.flags & ESCEvent.FLAG_NO_HUD == 0) + assert(subject.flags & ESCEvent.FLAG_CUT_BLACK != 0) + return true + + +func _test_dialog() -> bool: + var esc = """ +:test +? + - "Option 1" + say player "test" + say player "testb" + say player "testb?" + - "Option 2" [flag] + say player "test2" + ? + - "Suboption 1" + say player "test21" + - "Suboption 2" + say player "test22" + ! + - "Option 3" + > + say player "test3" +! + """ + var script = escoria.esc_compiler.compile(esc.split("\n")) + + var subject = script.events["test"].statements + assert(subject.size() == 1) + + assert(subject[0] is ESCDialog) + assert(subject[0].options.size() == 3) + + subject = script.events["test"].statements[0].options[0] + assert(subject is ESCDialogOption) + assert(subject.option == "Option 1") + + subject = script.events["test"].statements[0].options[0].statements + assert(subject.size() == 3) + assert(subject[0] is ESCCommand) + assert(subject[0].name == "say") + assert(subject[0].parameters.size() == 2) + assert(subject[1] is ESCCommand) + assert(subject[1].name == "say") + assert(subject[1].parameters.size() == 2) + assert(subject[1].parameters[1] == "testb") + assert(subject[2] is ESCCommand) + assert(subject[2].name == "say") + assert(subject[2].parameters.size() == 2) + assert(subject[2].parameters[1] == "testb?") + + subject = script.events["test"].statements[0].options[1] + assert(subject is ESCDialogOption) + assert(subject.option == "Option 2") + assert(subject.conditions.size() == 1) + assert(subject.conditions[0].flag == "flag") + + subject = script.events["test"].statements[0].options[1].statements + assert(subject.size() == 2) + assert(subject[0] is ESCCommand) + assert(subject[0].name == "say") + assert(subject[0].parameters.size() == 2) + + assert(subject[1] is ESCDialog) + assert(subject[1].options.size() == 2) + + subject = script.events["test"].statements[0].options[2] + assert(subject is ESCDialogOption) + assert(subject.option == "Option 3") + + subject = script.events["test"].statements[0].options[2].statements + assert(subject.size() == 1) + assert(subject[0] is ESCGroup) + assert(subject[0].statements.size() == 1) + assert(subject[0].statements[0] is ESCCommand) + assert(subject[0].statements[0].parameters.size() == 2) + + return true + + +func _on_BasicFunctionality_pressed(): + $VBoxContainer/VBoxContainer/BasicFunctionality.pressed = self._test_basic() + + +func _on_Conditions_pressed(): + $VBoxContainer/VBoxContainer/Conditions.pressed = self._test_conditions() + + +func _on_EventFlags_pressed(): + $VBoxContainer/VBoxContainer/EventFlags.pressed = self._test_event_flags() + + +func _on_Dialog_pressed(): + $VBoxContainer/VBoxContainer/Dialog.pressed = self._test_dialog() diff --git a/addons/escoria-core/game/core-scripts/esc/_test/test_esc_compiler.tscn b/addons/escoria-core/game/core-scripts/esc/_test/test_esc_compiler.tscn new file mode 100644 index 00000000..dda5bef1 --- /dev/null +++ b/addons/escoria-core/game/core-scripts/esc/_test/test_esc_compiler.tscn @@ -0,0 +1,58 @@ +[gd_scene load_steps=2 format=2] + +[ext_resource path="res://addons/escoria-core/game/core-scripts/esc/_test/test_esc_compiler.gd" type="Script" id=1] + +[node name="Testsuite" type="Control"] +anchor_right = 1.0 +anchor_bottom = 1.0 +script = ExtResource( 1 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="VBoxContainer" type="VBoxContainer" parent="."] +anchor_right = 1.0 +anchor_bottom = 1.0 +margin_right = 1.0 +margin_bottom = 1.0 +size_flags_horizontal = 3 +size_flags_vertical = 3 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer"] +margin_right = 1281.0 +margin_bottom = 172.0 + +[node name="BasicFunctionality" type="CheckButton" parent="VBoxContainer/VBoxContainer"] +margin_right = 1281.0 +margin_bottom = 40.0 +text = "Basic Functionality" +align = 1 + +[node name="Conditions" type="CheckButton" parent="VBoxContainer/VBoxContainer"] +margin_top = 44.0 +margin_right = 1281.0 +margin_bottom = 84.0 +text = "Check conditions" +align = 1 + +[node name="EventFlags" type="CheckButton" parent="VBoxContainer/VBoxContainer"] +margin_top = 88.0 +margin_right = 1281.0 +margin_bottom = 128.0 +text = "Check event flags" +align = 1 + +[node name="Dialog" type="CheckButton" parent="VBoxContainer/VBoxContainer"] +margin_top = 132.0 +margin_right = 1281.0 +margin_bottom = 172.0 +text = "Check dialogs" +align = 1 + +[connection signal="pressed" from="VBoxContainer/VBoxContainer/BasicFunctionality" to="." method="_on_BasicFunctionality_pressed"] +[connection signal="pressed" from="VBoxContainer/VBoxContainer/Conditions" to="." method="_on_Conditions_pressed"] +[connection signal="pressed" from="VBoxContainer/VBoxContainer/EventFlags" to="." method="_on_EventFlags_pressed"] +[connection signal="pressed" from="VBoxContainer/VBoxContainer/Dialog" to="." method="_on_Dialog_pressed"] diff --git a/addons/escoria-core/game/core-scripts/esc/commands/accept_input.gd b/addons/escoria-core/game/core-scripts/esc/commands/accept_input.gd new file mode 100644 index 00000000..a5857aae --- /dev/null +++ b/addons/escoria-core/game/core-scripts/esc/commands/accept_input.gd @@ -0,0 +1,47 @@ +# `accept_input [ALL|NONE|SKIP]` +# +# What type of input does the game accept. ALL is the default, SKIP allows +# skipping of dialog but nothing else, NONE denies all input. Including opening +# the menu etc. SKIP and NONE also disable autosaves. +# +# *Note* that SKIP gets reset to ALL when the event is done, but NONE persists. +# This allows you to create cut scenes with SKIP where the dialog can be +# skipped, but also initiate locked#### down cutscenes with accept_input +# NONE in :setup and accept_input ALL later in :ready. +# +# @STUB +# @ESC +extends ESCBaseCommand +class_name AcceptInputCommand + + +# Return the descriptor of the arguments of this command +func configure() -> ESCCommandArgumentDescriptor: + return ESCCommandArgumentDescriptor.new( + 1, + [TYPE_STRING], + ["ALL"] + ) + + +# Validate wether the given arguments match the command descriptor +func validate(arguments: Array): + if not arguments[0] in ["ALL", "NONE", "SKIP"]: + escoria.logger.report_errors( + "accept_input: invalid parameter", + [ + "%s is not a valid parameter value (ALL, NONE, SKIP)" %\ + arguments[0] + ] + ) + return false + return .validate(arguments) + + +# Run the command +func run(command_params: Array) -> int: + escoria.logger.report_errors( + "accept_input: command not implemented", + [] + ) + return ESCExecution.RC_ERROR diff --git a/addons/escoria-core/game/core-scripts/esc/commands/anim.gd b/addons/escoria-core/game/core-scripts/esc/commands/anim.gd new file mode 100644 index 00000000..e2689f24 --- /dev/null +++ b/addons/escoria-core/game/core-scripts/esc/commands/anim.gd @@ -0,0 +1,46 @@ +# `anim object name [reverse]` +# +# Executes the animation specificed with the "name" parameter on the object, +# without blocking. The next command in the event will be executed immediately +# after. Optional parameters: +# +# * reverse: plays the animation in reverse when true +# +# @ESC +extends ESCBaseCommand +class_name AnimCommand + + +# Return the descriptor of the arguments of this command +func configure() -> ESCCommandArgumentDescriptor: + return ESCCommandArgumentDescriptor.new( + 2, + [TYPE_STRING, TYPE_STRING, TYPE_BOOL], + [null, null, false] + ) + + +# Validate wether the given arguments match the command descriptor +func validate(arguments: Array): + if not escoria.object_manager.objects.has(arguments[0]): + escoria.logger.report_errors( + "anim: invalid object", + [ + "Object with global id %s not found." % arguments[0] + ] + ) + return false + return .validate(arguments) + + +# Run the command +func run(command_params: Array) -> int: + var obj = escoria.object_manager.objects[command_params[0]] + var anim_id = command_params[1] + var reverse = command_params[2] + var animator = (obj.node as ESCItem).get_animation_player() + if reverse: + animator.play_backwards(anim_id) + else: + animator.play(anim_id) + return ESCExecution.RC_OK diff --git a/addons/escoria-core/game/core-scripts/esc/commands/camera_push.gd b/addons/escoria-core/game/core-scripts/esc/commands/camera_push.gd new file mode 100644 index 00000000..a2dcdeed --- /dev/null +++ b/addons/escoria-core/game/core-scripts/esc/commands/camera_push.gd @@ -0,0 +1,44 @@ +# `camera_push target [time] [type]` +# +# Push camera to `target`. Target must have camera_pos set. If it's of type +# Camera2D, its zoom will be used as well as position. `type` is any of the +# Tween.TransitionType values without the prefix, eg. LINEAR, QUART or CIRC; +# defaults to QUART. A `time` value of 0 will set the camera immediately. +# +# @ESC +extends ESCBaseCommand +class_name CameraPushCommand + + +# Return the descriptor of the arguments of this command +func configure() -> ESCCommandArgumentDescriptor: + return ESCCommandArgumentDescriptor.new( + 1, + [TYPE_STRING, [TYPE_REAL, TYPE_INT], TYPE_STRING], + [null, 1, "QUAD"] + ) + + +# Validate wether the given arguments match the command descriptor +func validate(arguments: Array): + if not escoria.object_manager.objects.has(arguments[0]): + escoria.logger.report_errors( + "camera_push: invalid object", + [ + "Object global id %s not found" % arguments[0] + ] + ) + return false + + return .validate(arguments) + + +# Run the command +func run(command_params: Array) -> int: + (escoria.object_manager.get_object("camera").node as ESCCamera)\ + .push( + escoria.object_manager.get_object(command_params[0]).node, + command_params[1], + command_params[2] + ) + return ESCExecution.RC_OK diff --git a/addons/escoria-core/game/core-scripts/esc/commands/camera_set_limits.gd b/addons/escoria-core/game/core-scripts/esc/commands/camera_set_limits.gd new file mode 100644 index 00000000..eb816fc1 --- /dev/null +++ b/addons/escoria-core/game/core-scripts/esc/commands/camera_set_limits.gd @@ -0,0 +1,40 @@ +# `camera_set_limits camlimits_id` +# Sets the camera limits to the one defined under `camlimits_id` in ESCRoom's +# camera_limits array. +# - camlimits_id : int : id of the camera limits to apply (defined in ESCRoom's +# camera_limits array) +# @ESC +extends ESCBaseCommand +class_name CameraSetLimitsCommand + + +# Return the descriptor of the arguments of this command +func configure() -> ESCCommandArgumentDescriptor: + return ESCCommandArgumentDescriptor.new( + 1, + [TYPE_INT], + [null] + ) + + +# Validate wether the given arguments match the command descriptor +func validate(arguments: Array): + if not escoria.main.current_scene.camera_limits.size() < arguments[0]: + escoria.logger.report_errors( + "camera_set_limits: invalid limits id", + [ + "Limit id %d is bigger than limits array size %d" % [ + arguments[0], + escoria.main.current_scene.camera_limits.size() + ] + ] + ) + return false + + return .validate(arguments) + + +# Run the command +func run(command_params: Array) -> int: + escoria.main.set_camera_limits(command_params[0]) + return ESCExecution.RC_OK diff --git a/addons/escoria-core/game/core-scripts/esc/commands/camera_set_pos.gd b/addons/escoria-core/game/core-scripts/esc/commands/camera_set_pos.gd new file mode 100644 index 00000000..4f998596 --- /dev/null +++ b/addons/escoria-core/game/core-scripts/esc/commands/camera_set_pos.gd @@ -0,0 +1,28 @@ +# `camera_set_pos speed x y` +# +# Moves the camera to a position defined by "x" and "y", at the speed defined +# by "speed" in pixels per second. If speed is 0, camera is teleported to the +# position. +# +# @ESC +extends ESCBaseCommand +class_name CameraSetPosCommand + + +# Return the descriptor of the arguments of this command +func configure() -> ESCCommandArgumentDescriptor: + return ESCCommandArgumentDescriptor.new( + 3, + [[TYPE_REAL, TYPE_INT], TYPE_INT, TYPE_INT], + [null, null, null] + ) + + +# Run the command +func run(command_params: Array) -> int: + (escoria.object_manager.get_object("camera").node as ESCCamera)\ + .set_target( + Vector2(command_params[1], command_params[2]), + command_params[0] + ) + return ESCExecution.RC_OK diff --git a/addons/escoria-core/game/core-scripts/esc/commands/camera_set_target.gd b/addons/escoria-core/game/core-scripts/esc/commands/camera_set_target.gd new file mode 100644 index 00000000..d53bd7b1 --- /dev/null +++ b/addons/escoria-core/game/core-scripts/esc/commands/camera_set_target.gd @@ -0,0 +1,42 @@ +# `camera_set_target speed object` +# +# Configures the camera to set the target to the given `object`using `speed` +# as speed limit. +# This is the default behavior (default follow object is "player"). +# +# @ESC +extends ESCBaseCommand +class_name CameraSetTargetCommand + + +# Return the descriptor of the arguments of this command +func configure() -> ESCCommandArgumentDescriptor: + return ESCCommandArgumentDescriptor.new( + 2, + [[TYPE_REAL, TYPE_INT], TYPE_STRING], + [null, null] + ) + + +# Validate wether the given arguments match the command descriptor +func validate(arguments: Array): + if not escoria.object_manager.objects.has(arguments[1]): + escoria.logger.report_errors( + "camera_set_pos: invalid object", + [ + "Object with global id %s not found" % arguments[1] + ] + ) + return false + + return .validate(arguments) + + +# Run the command +func run(command_params: Array) -> int: + (escoria.object_manager.get_object("camera").node as ESCCamera)\ + .set_target( + escoria.object_manager.get_object(command_params[1]).node, + command_params[0] + ) + return ESCExecution.RC_OK diff --git a/addons/escoria-core/game/core-scripts/esc/commands/camera_set_zoom.gd b/addons/escoria-core/game/core-scripts/esc/commands/camera_set_zoom.gd new file mode 100644 index 00000000..cd19be04 --- /dev/null +++ b/addons/escoria-core/game/core-scripts/esc/commands/camera_set_zoom.gd @@ -0,0 +1,29 @@ +# `camera_set_zoom magnitude [time]` +# +# Zooms the camera in/out to the desired `magnitude`. Values larger than 1 zooms +# the camera out, and smaller values zooms in, relative to the default value +# of 1. An optional `time` in seconds controls how long it takes for the camera +# to zoom into position. +# +# @ESC +extends ESCBaseCommand +class_name CameraSetZoomCommand + + +# Return the descriptor of the arguments of this command +func configure() -> ESCCommandArgumentDescriptor: + return ESCCommandArgumentDescriptor.new( + 1, + [[TYPE_REAL, TYPE_INT], [TYPE_REAL, TYPE_INT]], + [null, 0.0] + ) + + +# Run the command +func run(command_params: Array) -> int: + (escoria.object_manager.get_object("camera").node as ESCCamera)\ + .set_camera_zoom( + command_params[0], + command_params[1] + ) + return ESCExecution.RC_OK diff --git a/addons/escoria-core/game/core-scripts/esc/commands/camera_set_zoom_height.gd b/addons/escoria-core/game/core-scripts/esc/commands/camera_set_zoom_height.gd new file mode 100644 index 00000000..8a51b29e --- /dev/null +++ b/addons/escoria-core/game/core-scripts/esc/commands/camera_set_zoom_height.gd @@ -0,0 +1,42 @@ +# `camera_set_zoom_height pixels [time] +# +# Zooms the camera in/out to the desired `pixels` height. +# An optional `time` in seconds controls how long it takes for the camera +# to zoom into position. +# +# @ESC +extends ESCBaseCommand +class_name CameraSetZoomHeightCommand + + +# Return the descriptor of the arguments of this command +func configure() -> ESCCommandArgumentDescriptor: + return ESCCommandArgumentDescriptor.new( + 1, + [TYPE_INT, [TYPE_INT, TYPE_REAL]], + [null, 0.0] + ) + + +# Validate wether the given arguments match the command descriptor +func validate(arguments: Array): + if arguments[0] < 0: + escoria.logger.report_errors( + "camera_set_zoom_height: invalid height", + [ + "Can't zoom to a negative height %d" % arguments[0] + ] + ) + return false + + return .validate(arguments) + + +# Run the command +func run(command_params: Array) -> int: + (escoria.object_manager.get_object("camera").node as ESCCamera)\ + .set_camera_zoom( + command_params[0] / escoria.game_size.y, + command_params[1] + ) + return ESCExecution.RC_OK diff --git a/addons/escoria-core/game/core-scripts/esc/commands/camera_shift.gd b/addons/escoria-core/game/core-scripts/esc/commands/camera_shift.gd new file mode 100644 index 00000000..08de4154 --- /dev/null +++ b/addons/escoria-core/game/core-scripts/esc/commands/camera_shift.gd @@ -0,0 +1,30 @@ +# `camera_shift x y [time] [type]` +# +# Shift camera by `x` and `y` pixels over `time` seconds. `type` is any of the +# Tween.TransitionType values without the prefix, eg. LINEAR, QUART or CIRC; +# defaults to QUART. +# +# @ESC +extends ESCBaseCommand +class_name CameraShiftCommand + + +# Return the descriptor of the arguments of this command +func configure() -> ESCCommandArgumentDescriptor: + return ESCCommandArgumentDescriptor.new( + 2, + [TYPE_INT, TYPE_INT, [TYPE_INT, TYPE_REAL], TYPE_STRING], + [null, null, 1, "QUAD"] + ) + + +# Run the command +func run(command_params: Array) -> int: + (escoria.object_manager.get_object("camera").node as ESCCamera)\ + .shift( + command_params[0], + command_params[1], + command_params[2], + command_params[3] + ) + return ESCExecution.RC_OK diff --git a/addons/escoria-core/game/core-scripts/esc/commands/change_scene.gd b/addons/escoria-core/game/core-scripts/esc/commands/change_scene.gd new file mode 100644 index 00000000..71ffc151 --- /dev/null +++ b/addons/escoria-core/game/core-scripts/esc/commands/change_scene.gd @@ -0,0 +1,132 @@ +# `change_scene path run_events` +# +# Loads a new scene, specified by "path". The `run_events` variable is a +# boolean (default true) which you never want to set manually! It's there only +# to benefit save games, so they don't conflict with the scene's events. +# +# @ESC +extends ESCBaseCommand +class_name ChangeSceneCommand + + +# An array of scenes that have already been loaded +var readied_scenes: Array = [] + + +# Return the descriptor of the arguments of this command +func configure() -> ESCCommandArgumentDescriptor: + return ESCCommandArgumentDescriptor.new( + 1, + [TYPE_STRING, TYPE_BOOL], + [null, true] + ) + + +# Validate wether the given arguments match the command descriptor +func validate(arguments: Array) -> bool: + if not ResourceLoader.exists(arguments[0]): + escoria.logger.report_errors( + "change_scene: Invalid scene", + ["Scene %s was not found" % arguments[0]] + ) + return false + if not ResourceLoader.exists( + ProjectSettings.get_setting("escoria/ui/game_scene") + ): + escoria.logger.report_errors( + "change_scene: Game scene not found", + [ + "The path set in 'ui/game_scene' was not found: %s" % \ + ProjectSettings.get_setting("escoria/ui/game_scene") + ] + ) + return false + return .validate(arguments) + + +# Run the command +func run(command_params: Array) -> int: + escoria.logger.info("Changing scene to %s (run_events = %s)" % [ + command_params[0], + command_params[1] + ]) + + if escoria.main.current_scene: + escoria.globals_manager.set_global( + "ESC_LAST_SCENE", + escoria.main.current_scene.global_id, + true + ) + + escoria.main.scene_transition.fade_out() + yield(escoria.main.scene_transition, "transition_done") + + escoria.main_menu_instance.hide() + + var res_room = escoria.resource_cache.get_resource(command_params[0]) + var res_game = escoria.resource_cache.get_resource( + ProjectSettings.get_setting("escoria/ui/game_scene") + ) + + # Load game scene + var game_scene = res_game.instance() + if not game_scene: + escoria.logger.report_errors( + "ChangeSceneCommand.run: Failed loading game scene", + [ + "Failed loading scene %s" % \ + ProjectSettings.get_setting("escoria/ui/game_scene") + ] + ) + + # Load room scene + var room_scene = res_room.instance() + if room_scene: + room_scene.add_child(game_scene) + room_scene.move_child(game_scene, 0) + escoria.main.set_scene(room_scene) + + if "esc_script" in room_scene and room_scene.esc_script \ + and command_params[1]: + + var script = escoria.esc_compiler.load_esc_file( + room_scene.esc_script + ) + + if script.events.has("setup"): + escoria.event_manager.queue_event(script.events["setup"]) + var rc = yield(escoria.event_manager, "event_finished") + while rc[1] != "setup": + rc = yield(escoria.event_manager, "event_finished") + if rc[0] != ESCExecution.RC_OK: + return rc[0] + + # If scene was never visited, add "ready" event to the events stack + if not command_params[0] in self.readied_scenes \ + and script.events.has("ready"): + escoria.event_manager.queue_event(script.events["ready"]) + var rc = yield(escoria.event_manager, "event_finished") + while rc[1] != "ready": + rc = yield(escoria.event_manager, "event_finished") + if rc[0] != ESCExecution.RC_OK: + return rc[0] + + escoria.main.scene_transition.fade_in() + yield(escoria.main.scene_transition, "transition_done") + + self.readied_scenes.append(command_params[0]) + + # Clear queued resources + escoria.resource_cache.clear() + + escoria.inputs_manager.hotspot_focused = "" + else: + escoria.logger.report_errors( + "ChangeSceneCommand.run: Failed loading room scene", + [ + "Failed loading scene %s" % command_params[0] + ] + ) + return ESCExecution.RC_ERROR + + return ESCExecution.RC_OK diff --git a/addons/escoria-core/game/core-scripts/esc/commands/custom.gd b/addons/escoria-core/game/core-scripts/esc/commands/custom.gd new file mode 100644 index 00000000..93a9941f --- /dev/null +++ b/addons/escoria-core/game/core-scripts/esc/commands/custom.gd @@ -0,0 +1,73 @@ +# `custom object node func_name [params]` +# +# Calls the function `func_name` of the node `node` of object `object` with +# the optional `params`. This is a blocking function +# +# @ESC +extends ESCBaseCommand +class_name CustomCommand + + +# Return the descriptor of the arguments of this command +func configure() -> ESCCommandArgumentDescriptor: + return ESCCommandArgumentDescriptor.new( + 3, + [TYPE_STRING, TYPE_STRING, TYPE_STRING, TYPE_ARRAY], + [null, null, null, []] + ) + + +# Validate wether the given arguments match the command descriptor +func validate(arguments: Array): + if not escoria.object_manager.objects.has(arguments[0]): + escoria.logger.report_errors( + "custom: invalid object", + [ + "Object with global id %s not found" % arguments[0] + ] + ) + return false + elif not escoria.object_manager.get_object(arguments[0]).node.has_node( + arguments[1] + ): + escoria.logger.report_errors( + "custom: invalid node", + [ + "Object with global id %s has no node %s" % [ + arguments[0], + arguments[1], + ] + ] + ) + return false + elif not escoria.object_manager.get_object(arguments[0]).node\ + .get_node( + arguments[1] + )\ + .has_method( + arguments[2] + ): + escoria.logger.report_errors( + "custom: invalid function", + [ + "Object with global id %s and node %s has no function %s" % [ + arguments[0], + arguments[1], + arguments[2], + ] + ] + ) + return false + return .validate(arguments) + + +# Run the command +func run(command_params: Array) -> int: + var object = escoria.object_manager.get_object( + command_params[0] + ) + object.node.get_node(command_params[1]).call( + command_params[2], + command_params[3] + ) + return ESCExecution.RC_OK diff --git a/addons/escoria-core/game/core-scripts/esc/commands/cut_scene.gd b/addons/escoria-core/game/core-scripts/esc/commands/cut_scene.gd new file mode 100644 index 00000000..246ccebf --- /dev/null +++ b/addons/escoria-core/game/core-scripts/esc/commands/cut_scene.gd @@ -0,0 +1,49 @@ +# `cut_scene object name [reverse]` +# +# Executes the animation specificed with the "name" parameter on the object, +# blocking. The next command in the event will be executed when the animation +# is finished playing. Optional parameters: +# +# * reverse plays the animation in reverse when true +# +# @ESC +extends ESCBaseCommand +class_name CutSceneCommand + + +# Return the descriptor of the arguments of this command +func configure() -> ESCCommandArgumentDescriptor: + return ESCCommandArgumentDescriptor.new( + 2, + [TYPE_STRING, TYPE_STRING, TYPE_BOOL], + [null, null, false] + ) + + +# Validate wether the given arguments match the command descriptor +func validate(arguments: Array): + if not escoria.object_manager.objects.has(arguments[0]): + escoria.logger.report_errors( + "anim: invalid object", + [ + "Object with global id %s not found." % arguments[0] + ] + ) + return false + return .validate(arguments) + + +# Run the command +func run(command_params: Array) -> int: + var obj = escoria.object_manager.objects[command_params[0]] + var anim_id = command_params[1] + var reverse = command_params[2] + var animator = (obj.node as ESCItem).get_animation_player() + if reverse: + animator.play_backwards(anim_id) + else: + animator.play(anim_id) + var animation_finished = yield(animator, "animation_finished") + while animation_finished != anim_id: + animation_finished = yield(animator, "animation_finished") + return ESCExecution.RC_OK diff --git a/addons/escoria-core/game/core-scripts/esc/commands/debug.gd b/addons/escoria-core/game/core-scripts/esc/commands/debug.gd new file mode 100644 index 00000000..b65466d5 --- /dev/null +++ b/addons/escoria-core/game/core-scripts/esc/commands/debug.gd @@ -0,0 +1,22 @@ +# `debug string [string2 ...]` +# +# Takes 1 or more strings, prints them to the console. +# +# @ESC +extends ESCBaseCommand +class_name DebugCommand + + +# Return the descriptor of the arguments of this command +func configure() -> ESCCommandArgumentDescriptor: + return ESCCommandArgumentDescriptor.new( + 1, + [TYPE_STRING], + [""] + ) + + +# Run the command +func run(command_params: Array) -> int: + escoria.logger.debug("debug command issued", command_params) + return ESCExecution.RC_OK diff --git a/addons/escoria-core/game/core-scripts/esc/commands/dec_global.gd b/addons/escoria-core/game/core-scripts/esc/commands/dec_global.gd new file mode 100644 index 00000000..e9503ecc --- /dev/null +++ b/addons/escoria-core/game/core-scripts/esc/commands/dec_global.gd @@ -0,0 +1,40 @@ +# `dec_global name value` +# +# Subtracts the value from global with given "name". Value and global must +# both be integers. +# +# @ESC +extends ESCBaseCommand +class_name DecGlobalCommand + + +# Return the descriptor of the arguments of this command +func configure() -> ESCCommandArgumentDescriptor: + return ESCCommandArgumentDescriptor.new( + 2, + [TYPE_STRING, TYPE_INT], + [null, 0] + ) + + +# Validate wether the given arguments match the command descriptor +func validate(arguments: Array): + if not escoria.globals_manager.get(arguments[0]) is int: + escoria.logger.report_errors( + "dec_global: invalid global", + [ + "Global %s didn't have an integer value." % arguments[0] + ] + ) + return false + return .validate(arguments) + + +# Run the command +func run(command_params: Array) -> int: + escoria.globals_manager.set_global( + command_params[0], + escoria.globals_manager.get_global(command_params[0]) - \ + command_params[1] + ) + return ESCExecution.RC_OK diff --git a/addons/escoria-core/game/core-scripts/esc/commands/enable_terrain.gd b/addons/escoria-core/game/core-scripts/esc/commands/enable_terrain.gd new file mode 100644 index 00000000..aa9d71c1 --- /dev/null +++ b/addons/escoria-core/game/core-scripts/esc/commands/enable_terrain.gd @@ -0,0 +1,36 @@ +# `enable_terrain node_name` +# Enable the ESCTerrain's NavigationPolygonInstance defined by given node name. +# Disables previously activated NavigationPolygonInstance. +# @ESC +extends ESCBaseCommand +class_name EnableTerrainCommand + + +# Return the descriptor of the arguments of this command +func configure() -> ESCCommandArgumentDescriptor: + return ESCCommandArgumentDescriptor.new( + 1, + [TYPE_STRING], + [null] + ) + + +# Run the command +func run(command_params: Array) -> int: + var name : String = command_params[0] + if escoria.room_terrain.has_node(name): + var new_active_navigation_instance = \ + escoria.room_terrain.get_node(name) + escoria.room_terrain.current_active_navigation_instance.enabled = false + escoria.room_terrain.current_active_navigation_instance = \ + new_active_navigation_instance + escoria.room_terrain.current_active_navigation_instance.enabled = true + return ESCExecution.RC_OK + else: + escoria.logger.report_errors( + "EnableTerrainCommand.run: Can not find terrain node", + [ + "Terrain node %s could not be found" % name + ] + ) + return ESCExecution.RC_ERROR diff --git a/addons/escoria-core/game/core-scripts/esc/commands/game_over.gd b/addons/escoria-core/game/core-scripts/esc/commands/game_over.gd new file mode 100644 index 00000000..f47fe680 --- /dev/null +++ b/addons/escoria-core/game/core-scripts/esc/commands/game_over.gd @@ -0,0 +1,29 @@ +# `game_over continue_enabled show_credits` +# +# Ends the game. Use the "continue_enabled" parameter to enable or disable the +# continue button in the main menu afterwards. The "show_credits" parameter +# loads the ui/end_credits scene if true. You can configure it to your regular +# credits scene if you want. +# +# @STUB +# @ESC +extends ESCBaseCommand +class_name GameOverCommand + + +# Return the descriptor of the arguments of this command +func configure() -> ESCCommandArgumentDescriptor: + return ESCCommandArgumentDescriptor.new( + 0, + [TYPE_BOOL, TYPE_BOOL], + [false, true] + ) + + +# Run the command +func run(command_params: Array) -> int: + escoria.logger.report_errors( + "game_over: command not implemented", + [] + ) + return ESCExecution.RC_ERROR diff --git a/addons/escoria-core/game/core-scripts/esc/commands/inc_global.gd b/addons/escoria-core/game/core-scripts/esc/commands/inc_global.gd new file mode 100644 index 00000000..41697618 --- /dev/null +++ b/addons/escoria-core/game/core-scripts/esc/commands/inc_global.gd @@ -0,0 +1,48 @@ +# `inc_global name value` +# +# Adds the value to global with given "name". Value and global must both be +# integers. +# +# @ESC +extends ESCBaseCommand +class_name IncGlobalCommand + + +# Return the descriptor of the arguments of this command +func configure() -> ESCCommandArgumentDescriptor: + return ESCCommandArgumentDescriptor.new( + 2, + [TYPE_STRING, TYPE_INT], + [null, 0] + ) + + +# Validate wether the given arguments match the command descriptor +func validate(arguments: Array): + if not escoria.globals_manager.has(arguments[0]): + escoria.logger.report_errors( + "inc_global: invalid global", + [ + "Global %s does not exist." % arguments[0] + ] + ) + return false + if not escoria.globals_manager.get(arguments[0]) is int: + escoria.logger.report_errors( + "inc_global: invalid global", + [ + "Global %s didn't have an integer value." % arguments[0] + ] + ) + return false + return .validate(arguments) + + +# Run the command +func run(command_params: Array) -> int: + escoria.globals_manager.set_global( + command_params[0], + escoria.globals_manager.get_global(command_params[0]) +\ + command_params[1] + ) + return ESCExecution.RC_OK diff --git a/addons/escoria-core/game/core-scripts/esc/commands/inventory_add.gd b/addons/escoria-core/game/core-scripts/esc/commands/inventory_add.gd new file mode 100644 index 00000000..ce6e3066 --- /dev/null +++ b/addons/escoria-core/game/core-scripts/esc/commands/inventory_add.gd @@ -0,0 +1,22 @@ +# `inventory_add item` +# +# Add an item to the inventory +# +# @ESC +extends ESCBaseCommand +class_name InventoryAddCommand + + +# Return the descriptor of the arguments of this command +func configure() -> ESCCommandArgumentDescriptor: + return ESCCommandArgumentDescriptor.new( + 1, + [TYPE_STRING], + [null] + ) + + +# Run the command +func run(command_params: Array) -> int: + escoria.inventory_manager.add_item(command_params[0]) + return ESCExecution.RC_OK diff --git a/addons/escoria-core/game/core-scripts/esc/commands/inventory_remove.gd b/addons/escoria-core/game/core-scripts/esc/commands/inventory_remove.gd new file mode 100644 index 00000000..0bfee535 --- /dev/null +++ b/addons/escoria-core/game/core-scripts/esc/commands/inventory_remove.gd @@ -0,0 +1,22 @@ +# `inventory_remove item` +# +# Remove an item from the inventory. +# +# @ESC +extends ESCBaseCommand +class_name InventoryRemoveCommand + + +# Return the descriptor of the arguments of this command +func configure() -> ESCCommandArgumentDescriptor: + return ESCCommandArgumentDescriptor.new( + 1, + [TYPE_STRING], + [null] + ) + + +# Run the command +func run(command_params: Array) -> int: + escoria.inventory_manager.remove_item(command_params[0]) + return ESCExecution.RC_OK diff --git a/addons/escoria-core/game/core-scripts/esc/commands/play_snd.gd b/addons/escoria-core/game/core-scripts/esc/commands/play_snd.gd new file mode 100644 index 00000000..54281022 --- /dev/null +++ b/addons/escoria-core/game/core-scripts/esc/commands/play_snd.gd @@ -0,0 +1,28 @@ +# `play_snd object file [loop]` +# +# Plays the sound specificed with the "file" parameter on the object, without +# blocking. You can play background sounds, eg. during scene changes, with +# `play_snd bg_snd res://...` +# +# @STUB +# @ESC +extends ESCBaseCommand +class_name PlaySndCommand + + +# Return the descriptor of the arguments of this command +func configure() -> ESCCommandArgumentDescriptor: + return ESCCommandArgumentDescriptor.new( + 2, + [TYPE_STRING, TYPE_STRING, TYPE_BOOL], + [null, null, false] + ) + + +# Run the command +func run(command_params: Array) -> int: + escoria.logger.report_errors( + "play_snd: command not implemented", + [] + ) + return ESCExecution.RC_ERROR diff --git a/addons/escoria-core/game/core-scripts/esc/commands/queue_animation.gd b/addons/escoria-core/game/core-scripts/esc/commands/queue_animation.gd new file mode 100644 index 00000000..9fe6ca0d --- /dev/null +++ b/addons/escoria-core/game/core-scripts/esc/commands/queue_animation.gd @@ -0,0 +1,42 @@ +# `queue_animation object animation` +# +# Similar to queue_resource, queues the resources necessary to have an +# animation loaded on an item. The resource paths are taken from the item +# placeholders. +# +# @STUB +# @ESC +extends ESCBaseCommand +class_name QueueAnimationCommand + + +# Return the descriptor of the arguments of this command +func configure() -> ESCCommandArgumentDescriptor: + return ESCCommandArgumentDescriptor.new( + 2, + [TYPE_STRING, TYPE_STRING], + [null, null] + ) + + +# Validate wether the given arguments match the command descriptor +func validate(arguments: Array): + if not escoria.object_manager.objects.has(arguments[0]): + escoria.logger.report_errors( + "queue_animation: invalid first object", + [ + "Object with global id %s not found" % arguments[0] + ] + ) + return false + # TODO: Check if animation is valid + return .validate(arguments) + + +# Run the command +func run(command_params: Array) -> int: + escoria.logger.report_errors( + "queue_animation: command not implemented", + [] + ) + return ESCExecution.RC_ERROR diff --git a/addons/escoria-core/game/core-scripts/esc/commands/queue_resource.gd b/addons/escoria-core/game/core-scripts/esc/commands/queue_resource.gd new file mode 100644 index 00000000..971234ee --- /dev/null +++ b/addons/escoria-core/game/core-scripts/esc/commands/queue_resource.gd @@ -0,0 +1,41 @@ +# `queue_resource path [front_of_queue]` +# +# Queues the load of a resource in a background thread. The `path` must be a +# full path inside your game, for example "res://scenes/next_scene.tscn". The +# "front_of_queue" parameter is optional (default value false), to put the +# resource in the front of the queue. Queued resources are cleared when a +# change scene happens (but after the scene is loaded, meaning you can queue +# resources that belong to the next scene). +# +# @ESC +extends ESCBaseCommand +class_name QueueResourceCommand + + +# Return the descriptor of the arguments of this command +func configure() -> ESCCommandArgumentDescriptor: + return ESCCommandArgumentDescriptor.new( + 1, + [], + [null, false] + ) + + +# Validate wether the given arguments match the command descriptor +func validate(arguments: Array) -> bool: + if not ResourceLoader.exists(arguments[0]): + escoria.logger.report_errors( + "queue_resource: Invalid resource", + ["Resource %s was not found" % arguments[0]] + ) + return false + return .validate(arguments) + + +# Run the command +func run(command_params: Array) -> int: + escoria.resource_cache.queue_resource( + command_params[0], + command_params[1] + ) + return ESCExecution.RC_OK diff --git a/addons/escoria-core/game/core-scripts/esc/commands/repeat.gd b/addons/escoria-core/game/core-scripts/esc/commands/repeat.gd new file mode 100644 index 00000000..b4705b3d --- /dev/null +++ b/addons/escoria-core/game/core-scripts/esc/commands/repeat.gd @@ -0,0 +1,22 @@ +# `repeat` +# +# Restarts the execution of the current scope at the start. A scope can be a +# group or an event. +# +# @ESC +extends ESCBaseCommand +class_name RepeatCommand + + +# Return the descriptor of the arguments of this command +func configure() -> ESCCommandArgumentDescriptor: + return ESCCommandArgumentDescriptor.new( + 0, + [], + [] + ) + + +# Run the command +func run(command_params: Array) -> int: + return ESCExecution.RC_CANCEL diff --git a/addons/escoria-core/game/core-scripts/esc/commands/say.gd b/addons/escoria-core/game/core-scripts/esc/commands/say.gd new file mode 100644 index 00000000..9b157917 --- /dev/null +++ b/addons/escoria-core/game/core-scripts/esc/commands/say.gd @@ -0,0 +1,85 @@ +# `say object text [type] [avatar]` +# +# Runs the specified string as a dialog said by the object. Blocks execution +# until the dialog finishes playing. Optional parameters: +# +# * "type" determines the type of dialog UI to use. Default value is "default" +# * "avatar" determines the avatar to use for the dialog. Default value is +# "default" +# +# @ESC +extends ESCBaseCommand +class_name SayCommand + + +# Return the descriptor of the arguments of this command +func configure() -> ESCCommandArgumentDescriptor: + return ESCCommandArgumentDescriptor.new( + 2, + [TYPE_STRING, TYPE_STRING, TYPE_STRING, TYPE_STRING], + [ + null, + null, + ProjectSettings.get_setting("escoria/ui/default_dialog_scene")\ + .get_file().get_basename(), + "default" + ] + ) + + +# Run the command +func run(command_params: Array) -> int: + + var dict : Dictionary + var dialog_scene_name = ProjectSettings.get_setting( + "escoria/ui/default_dialog_scene" + ) + + if dialog_scene_name.empty(): + escoria.logger.report_errors( + "say()", + [ + "Project setting 'escoria/ui/default_dialog_scene' is not set.", + "Please set a default dialog scene." + ] + ) + return ESCExecution.RC_ERROR + + var file = dialog_scene_name.get_file() + var extension = dialog_scene_name.get_extension() + dialog_scene_name = file.rstrip("." + extension) + + # Manage specific dialog scene + if command_params.size() > 2: + dialog_scene_name = command_params[2] + + # Manage translation/voice lines keys in the form of: + # line_key:"Default line text" + # If a line_key exists, we'll set it a label as it will automatically be + # translated + var dialog_key_line = command_params[1].split(":", true, 1) + if dialog_key_line.size() > 1: + dialog_key_line[1] = dialog_key_line[1].trim_prefix("\"") + + dict = { + "key": dialog_key_line[0], + "line": dialog_key_line[1] if dialog_key_line.size() > 1 \ + else dialog_key_line[0], + "ui": dialog_scene_name + } + + escoria.current_state = escoria.GAME_STATE.DIALOG + + if !escoria.dialog_player: + escoria.logger.report_errors( + "No dialog player registered", + [ + "No dialog player was registered and the say command was" + + "encountered." + ] + ) + return ESCExecution.RC_ERROR + + escoria.dialog_player.say(command_params[0], dict) + yield(escoria.dialog_player, "dialog_line_finished") + return ESCExecution.RC_OK diff --git a/addons/escoria-core/game/core-scripts/esc/commands/sched_event.gd b/addons/escoria-core/game/core-scripts/esc/commands/sched_event.gd new file mode 100644 index 00000000..405e6801 --- /dev/null +++ b/addons/escoria-core/game/core-scripts/esc/commands/sched_event.gd @@ -0,0 +1,53 @@ +# `sched_event time object event` +# +# Schedules the execution of an "event" found in "object" in a time in seconds. +# If another event is running at the time, execution starts when the running +# event ends. +# +# @ESC +extends ESCBaseCommand +class_name SchedEventCommand + + +# Return the descriptor of the arguments of this command +func configure() -> ESCCommandArgumentDescriptor: + return ESCCommandArgumentDescriptor.new( + 3, + [TYPE_INT, TYPE_STRING, TYPE_STRING], + [null, null, null] + ) + + +# Validate wether the given arguments match the command descriptor +func validate(arguments: Array): + if not escoria.object_manager.objects.has(arguments[1]): + escoria.logger.report_errors( + "sched_event: invalid object", + [ + "Object with global id %s not found" % arguments[1] + ] + ) + return false + elif not escoria.object_manager.get_object(arguments[1]).events\ + .has(arguments[2]): + escoria.logger.report_errors( + "sched_event: invalid object event", + [ + "Object with global id %s has no event %s" % [ + arguments[1], + arguments[2], + ] + ] + ) + return false + return .validate(arguments) + + +# Run the command +func run(command_params: Array) -> int: + escoria.event_manager.schedule_event( + escoria.object_manager.get_object(command_params[1])\ + .events[command_params[2]], + command_params[0] + ) + return ESCExecution.RC_OK diff --git a/addons/escoria-core/game/core-scripts/esc/commands/set_active.gd b/addons/escoria-core/game/core-scripts/esc/commands/set_active.gd new file mode 100644 index 00000000..6c9d3c80 --- /dev/null +++ b/addons/escoria-core/game/core-scripts/esc/commands/set_active.gd @@ -0,0 +1,37 @@ +# `set_active object value` +# +# Changes the "active" state of the object, value can be true or false. +# Inactive objects are hidden in the scene. +# +# @ESC +extends ESCBaseCommand +class_name SetActiveCommand + + +# Return the descriptor of the arguments of this command +func configure() -> ESCCommandArgumentDescriptor: + return ESCCommandArgumentDescriptor.new( + 2, + [TYPE_STRING, TYPE_BOOL], + [null, null] + ) + + +# Validate wether the given arguments match the command descriptor +func validate(arguments: Array): + if not escoria.object_manager.objects.has(arguments[0]): + escoria.logger.report_errors( + "set_active: invalid object", + [ + "Object with global id %s not found" % arguments[0] + ] + ) + return false + return .validate(arguments) + + +# Run the command +func run(command_params: Array) -> int: + escoria.object_manager.get_object(command_params[0]).active = \ + command_params[1] + return ESCExecution.RC_OK diff --git a/addons/escoria-core/game/core-scripts/esc/commands/set_angle.gd b/addons/escoria-core/game/core-scripts/esc/commands/set_angle.gd new file mode 100644 index 00000000..2e7b494a --- /dev/null +++ b/addons/escoria-core/game/core-scripts/esc/commands/set_angle.gd @@ -0,0 +1,45 @@ +# `set_angle object degrees` +# +# Turns object to a degrees angle without animations. 0 sets object facing +# forward, 90 sets it 90 degrees clockwise ("east") etc. When turning to the +# destination angle, animations are played if they're defined in animations. +# +# object must be player or interactive. degrees must be between [0, 360] or an +# error is reported. +# +# @ESC +extends ESCBaseCommand +class_name SetAngleCommand + + +# Return the descriptor of the arguments of this command +func configure() -> ESCCommandArgumentDescriptor: + return ESCCommandArgumentDescriptor.new( + 2, + [TYPE_STRING, TYPE_INT], + [null, true] + ) + + +# Validate wether the given arguments match the command descriptor +func validate(arguments: Array): + if not escoria.object_manager.objects.has(arguments[0]): + escoria.logger.report_errors( + "set_angle: invalid object", + [ + "Object with global id %s not found" % arguments[0] + ] + ) + return false + return .validate(arguments) + + +# Run the command +func run(command_params: Array) -> int: + # HACK Countering the fact that angle_to_point() function gives + # angle against X axis not Y, we need to check direction using (angle-90°). + # Since the ESC command already gives the right angle, we add 90. + escoria.object_manager.get_object(command_params[0]).node\ + .set_angle(int(command_params[1] + 90)) + return ESCExecution.RC_OK + diff --git a/addons/escoria-core/game/core-scripts/esc/commands/set_global.gd b/addons/escoria-core/game/core-scripts/esc/commands/set_global.gd new file mode 100644 index 00000000..a08225fe --- /dev/null +++ b/addons/escoria-core/game/core-scripts/esc/commands/set_global.gd @@ -0,0 +1,23 @@ +# `set_global name value` +# +# Changes the value of the global "name" with the value. Value can be "true", +# "false" or an integer. +# +# @ESC +extends ESCBaseCommand +class_name SetGlobalCommand + + +# Return the descriptor of the arguments of this command +func configure() -> ESCCommandArgumentDescriptor: + return ESCCommandArgumentDescriptor.new( + 2, + [TYPE_STRING, [TYPE_INT, TYPE_BOOL, TYPE_STRING]], + [null, null] + ) + + +# Run the command +func run(command_params: Array) -> int: + escoria.globals_manager.set_global(command_params[0], command_params[1]) + return ESCExecution.RC_OK diff --git a/addons/escoria-core/game/core-scripts/esc/commands/set_globals.gd b/addons/escoria-core/game/core-scripts/esc/commands/set_globals.gd new file mode 100644 index 00000000..c50a67bb --- /dev/null +++ b/addons/escoria-core/game/core-scripts/esc/commands/set_globals.gd @@ -0,0 +1,27 @@ +# `set_globals pattern value` +# +# Changes the value of multiple globals using a wildcard pattern, where "*" +# matches zero or more arbitrary characters and "?" matches any single +# character except a period ("."). +# +# @ESC +extends ESCBaseCommand +class_name SetGlobalsCommand + + +# Return the descriptor of the arguments of this command +func configure() -> ESCCommandArgumentDescriptor: + return ESCCommandArgumentDescriptor.new( + 2, + [TYPE_STRING, [TYPE_BOOL, TYPE_STRING, TYPE_INT]], + [null, null] + ) + + +# Run the command +func run(command_params: Array) -> int: + escoria.globals_manager.set_global_wildcard( + command_params[0], + command_params[1] + ) + return ESCExecution.RC_OK diff --git a/addons/escoria-core/game/core-scripts/esc/commands/set_hud_visible.gd b/addons/escoria-core/game/core-scripts/esc/commands/set_hud_visible.gd new file mode 100644 index 00000000..5db05d5e --- /dev/null +++ b/addons/escoria-core/game/core-scripts/esc/commands/set_hud_visible.gd @@ -0,0 +1,28 @@ +# `set_hud_visible visible` +# +# If you have a cutscene like sequence where the player doesn't have control, +# and you also have HUD elements visible, use this to hide the HUD. You want +# to do that because it explicitly signals the player that there is no control +# over the game at the moment. "visible" is true or false. +# +# @ESC +extends ESCBaseCommand +class_name SetHudVisibleCommand + + +# Return the descriptor of the arguments of this command +func configure() -> ESCCommandArgumentDescriptor: + return ESCCommandArgumentDescriptor.new( + 1, + [TYPE_BOOL], + [null] + ) + + +# Run the command +func run(command_params: Array) -> int: + if command_params[0]: + escoria.main.current_scene.game.show_ui() + else: + escoria.main.current_scene.game.hide_ui() + return ESCExecution.RC_OK diff --git a/addons/escoria-core/game/core-scripts/esc/commands/set_interactive.gd b/addons/escoria-core/game/core-scripts/esc/commands/set_interactive.gd new file mode 100644 index 00000000..237ab7b8 --- /dev/null +++ b/addons/escoria-core/game/core-scripts/esc/commands/set_interactive.gd @@ -0,0 +1,36 @@ +# `set_interactive object value` +# +# Sets whether or not an object should be interactive. +# +# @ESC +extends ESCBaseCommand +class_name SetInteractiveCommand + + +# Return the descriptor of the arguments of this command +func configure() -> ESCCommandArgumentDescriptor: + return ESCCommandArgumentDescriptor.new( + 2, + [TYPE_STRING, TYPE_BOOL], + [null, null] + ) + + +# Validate wether the given arguments match the command descriptor +func validate(arguments: Array): + if not escoria.object_manager.objects.has(arguments[0]): + escoria.logger.report_errors( + "set_interactive: invalid object", + [ + "Object with global id %s not found" % arguments[0] + ] + ) + return false + return .validate(arguments) + + +# Run the command +func run(command_params: Array) -> int: + escoria.object_manager.get_object(command_params[0]).interactive = \ + command_params[1] + return ESCExecution.RC_OK diff --git a/addons/escoria-core/game/core-scripts/esc/commands/set_sound_state.gd b/addons/escoria-core/game/core-scripts/esc/commands/set_sound_state.gd new file mode 100644 index 00000000..89e07fdd --- /dev/null +++ b/addons/escoria-core/game/core-scripts/esc/commands/set_sound_state.gd @@ -0,0 +1,50 @@ +# `set_sound_state player sound loop` +# +# Change the sound playing on `player` to `sound` with optional looping if +# `loop` is true. +# Valid players are "bg_music" and "bg_sound". +# Aside from paths to sound or music files, the values *off* and *default*. +# *default* is the default value. +# are also valid for `sound` +# +# @ESC +extends ESCBaseCommand +class_name SetSoundStateCommand + + +# Return the descriptor of the arguments of this command +func configure() -> ESCCommandArgumentDescriptor: + return ESCCommandArgumentDescriptor.new( + 1, + [TYPE_STRING, TYPE_STRING, TYPE_BOOL], + [null, "default", false] + ) + + +# Validate wether the given arguments match the command descriptor +func validate(arguments: Array): + if not arguments[0] in ["bg_music", "bg_sound"]: + escoria.logger.report_errors( + "SetSoundStateCommand.validate: invalid player", + [ + "Player %s is invalid found" % arguments[0] + ] + ) + return false + if not arguments[1] in ["default", "off"] \ + or not ResourceLoader.exists(arguments[1]): + escoria.logger.report_errors( + "SetSoundStateCommand.validate: invalid sound", + [ + "Sound %s is invalid or not found" % arguments[1] + ] + ) + return false + return .validate(arguments) + + +# Run the command +func run(command_params: Array) -> int: + escoria.main.get_node(command_params[0])\ + .set_state(command_params[1], command_params[2]) + return ESCExecution.RC_OK diff --git a/addons/escoria-core/game/core-scripts/esc/commands/set_speed.gd b/addons/escoria-core/game/core-scripts/esc/commands/set_speed.gd new file mode 100644 index 00000000..f6840e31 --- /dev/null +++ b/addons/escoria-core/game/core-scripts/esc/commands/set_speed.gd @@ -0,0 +1,34 @@ +# `set_speed object speed` +# +# Sets how fast object moves. Speed is an integer. +# +# @ESC +extends ESCBaseCommand +class_name SetSpeedCommand + + +# Return the descriptor of the arguments of this command +func configure() -> ESCCommandArgumentDescriptor: + return ESCCommandArgumentDescriptor.new( + 2, + [TYPE_STRING, TYPE_INT], + [null, null] + ) + +# Validate wether the given arguments match the command descriptor +func validate(arguments: Array): + if not escoria.object_manager.objects.has(arguments[0]): + escoria.logger.report_errors( + "set_speed: invalid object", + [ + "Object with global id %s not found" % arguments[0] + ] + ) + return false + return .validate(arguments) + +# Run the command +func run(command_params: Array) -> int: + (escoria.object_manager.objects[command_params[0]].node as ESCItem).\ + set_speed(command_params[1]) + return ESCExecution.RC_OK diff --git a/addons/escoria-core/game/core-scripts/esc/commands/set_state.gd b/addons/escoria-core/game/core-scripts/esc/commands/set_state.gd new file mode 100644 index 00000000..a2bf8bd5 --- /dev/null +++ b/addons/escoria-core/game/core-scripts/esc/commands/set_state.gd @@ -0,0 +1,41 @@ +# `set_state object state [immediate]` +# +# Changes the state of an object, and executes the state animation if present. +# The command can be used to change the appearance of an item or a player +# character. +# If `immediate` is set to true, the animation is run directly +# +# @ESC +extends ESCBaseCommand +class_name SetStateCommand + + +# Return the descriptor of the arguments of this command +func configure() -> ESCCommandArgumentDescriptor: + return ESCCommandArgumentDescriptor.new( + 2, + [TYPE_STRING, TYPE_STRING, TYPE_BOOL], + [null, null, false] + ) + + +# Validate wether the given arguments match the command descriptor +func validate(arguments: Array): + if not escoria.object_manager.objects.has(arguments[0]): + escoria.logger.report_errors( + "set_state: invalid object", + [ + "Object %s not found." % arguments[0] + ] + ) + return false + return .validate(arguments) + + +# Run the command +func run(command_params: Array) -> int: + (escoria.object_manager.objects[command_params[0]] as ESCObject).set_state( + command_params[1], + command_params[2] + ) + return ESCExecution.RC_OK diff --git a/addons/escoria-core/game/core-scripts/esc/commands/slide.gd b/addons/escoria-core/game/core-scripts/esc/commands/slide.gd new file mode 100644 index 00000000..f765ea73 --- /dev/null +++ b/addons/escoria-core/game/core-scripts/esc/commands/slide.gd @@ -0,0 +1,50 @@ +# `slide object1 object2 [speed]` +# +# Moves object1 towards the position of object2, at the speed determined by +# object1's "speed" property, unless overridden. This command is non-blocking. +# It does not respect the room's navigation polygons, so you can move items +# where the player can't walk. +# +# @STUB +# @ESC +extends ESCBaseCommand +class_name SlideCommand + + +# Return the descriptor of the arguments of this command +func configure() -> ESCCommandArgumentDescriptor: + return ESCCommandArgumentDescriptor.new( + 2, + [TYPE_STRING, TYPE_STRING, TYPE_INT], + [null, null, null] + ) + + +# Validate wether the given arguments match the command descriptor +func validate(arguments: Array): + if not escoria.object_manager.objects.has(arguments[0]): + escoria.logger.report_errors( + "slide: invalid first object", + [ + "Object with global id %s not found" % arguments[0] + ] + ) + return false + if not escoria.object_manager.objects.has(arguments[1]): + escoria.logger.report_errors( + "slide: invalid second object", + [ + "Object with global id %s not found" % arguments[0] + ] + ) + return false + return .validate(arguments) + + +# Run the command +func run(command_params: Array) -> int: + escoria.logger.report_errors( + "slide: command not implemented", + [] + ) + return ESCExecution.RC_ERROR diff --git a/addons/escoria-core/game/core-scripts/esc/commands/slide_block.gd b/addons/escoria-core/game/core-scripts/esc/commands/slide_block.gd new file mode 100644 index 00000000..ef1f6d45 --- /dev/null +++ b/addons/escoria-core/game/core-scripts/esc/commands/slide_block.gd @@ -0,0 +1,50 @@ +# `slide_block object1 object2 [speed]` +# +# Moves object1 towards the position of object2, at the speed determined by +# object1's "speed" property, unless overridden. This command is blocking. +# It does not respect the room's navigation polygons, so you can move items +# where the player can't walk. +# +# @STUB +# @ESC +extends ESCBaseCommand +class_name SlideBlockCommand + + +# Return the descriptor of the arguments of this command +func configure() -> ESCCommandArgumentDescriptor: + return ESCCommandArgumentDescriptor.new( + 2, + [TYPE_STRING, TYPE_STRING, TYPE_INT], + [null, null, null] + ) + + +# Validate wether the given arguments match the command descriptor +func validate(arguments: Array): + if not escoria.object_manager.objects.has(arguments[0]): + escoria.logger.report_errors( + "slide_block: invalid first object", + [ + "Object with global id %s not found" % arguments[0] + ] + ) + return false + if not escoria.object_manager.objects.has(arguments[1]): + escoria.logger.report_errors( + "slide_block: invalid second object", + [ + "Object with global id %s not found" % arguments[0] + ] + ) + return false + return .validate(arguments) + + +# Run the command +func run(command_params: Array) -> int: + escoria.logger.report_errors( + "slide_block: command not implemented", + [] + ) + return ESCExecution.RC_ERROR diff --git a/addons/escoria-core/game/core-scripts/esc/commands/spawn.gd b/addons/escoria-core/game/core-scripts/esc/commands/spawn.gd new file mode 100644 index 00000000..ebf9f392 --- /dev/null +++ b/addons/escoria-core/game/core-scripts/esc/commands/spawn.gd @@ -0,0 +1,61 @@ +# `spawn path [object2]` +# +# Instances a scene determined by "path", and places in the position of +# object2 (object2 is optional) +# +# @ESC +extends ESCBaseCommand +class_name SpawnCommand + + +# Return the descriptor of the arguments of this command +func configure() -> ESCCommandArgumentDescriptor: + return ESCCommandArgumentDescriptor.new( + 1, + [TYPE_STRING, TYPE_STRING], + [null, null] + ) + + +# Validate wether the given arguments match the command descriptor +func validate(arguments: Array): + if not ResourceLoader.exists(arguments[0]): + escoria.logger.report_errors( + "spawn: invalid scene path", + [ + "Scene with path %s not found" % arguments[0] + ] + ) + return false + if arguments[1] and not escoria.object_manager.objects.has(arguments[1]): + escoria.logger.report_errors( + "spawn: invalid object", + [ + "Object with global id %s not found" % arguments[1] + ] + ) + return false + return .validate(arguments) + + +# Run the command +func run(command_params: Array) -> int: + var res_scene = escoria.resource_cache.get_resource(command_params[0]) + + # Load room scene + var scene = res_scene.instance() + if scene: + escoria.main.get_node("/root").add_child(scene) + if command_params[1]: + var obj = escoria.object_manager.get_object(command_params[1]) + scene.set_position(obj.get_global_position()) + escoria.inputs_manager.hotspot_focused = false + else: + escoria.logger.report_errors( + "spawn: Invalid scene", + [ + "Failed loading scene %s" % command_params[0] + ] + ) + + return ESCExecution.RC_OK diff --git a/addons/escoria-core/game/core-scripts/esc/commands/stop.gd b/addons/escoria-core/game/core-scripts/esc/commands/stop.gd new file mode 100644 index 00000000..a389c099 --- /dev/null +++ b/addons/escoria-core/game/core-scripts/esc/commands/stop.gd @@ -0,0 +1,21 @@ +# `stop` +# +# Stops the event's execution. +# +# @ESC +extends ESCBaseCommand +class_name StopCommand + + +# Return the descriptor of the arguments of this command +func configure() -> ESCCommandArgumentDescriptor: + return ESCCommandArgumentDescriptor.new( + 0, + [], + [] + ) + + +# Run the command +func run(command_params: Array) -> int: + return ESCExecution.RC_CANCEL diff --git a/addons/escoria-core/game/core-scripts/esc/commands/teleport.gd b/addons/escoria-core/game/core-scripts/esc/commands/teleport.gd new file mode 100644 index 00000000..54fd0a10 --- /dev/null +++ b/addons/escoria-core/game/core-scripts/esc/commands/teleport.gd @@ -0,0 +1,50 @@ +# `teleport object1 object2 [angle]` +# +# Sets the position of object1 to the position of object2. By default, +# object2's interact_angle is used to turn object1, but angle will override +# this. Useful for doors and such with an interact_angle you don't always want +# to adhere to when re-entering a room. +# +# @ESC +extends ESCBaseCommand +class_name TeleportCommand + + +# Return the descriptor of the arguments of this command +func configure() -> ESCCommandArgumentDescriptor: + return ESCCommandArgumentDescriptor.new( + 2, + [TYPE_STRING, TYPE_STRING, TYPE_INT], + [null, null, null] + ) + + +# Validate wether the given arguments match the command descriptor +func validate(arguments: Array): + if not escoria.object_manager.objects.has(arguments[0]): + escoria.logger.report_errors( + "teleport: invalid first object", + [ + "Object with global id %s not found" % arguments[0] + ] + ) + return false + if not escoria.object_manager.objects.has(arguments[1]): + escoria.logger.report_errors( + "teleport: invalid second object", + [ + "Object with global id %s not found" % arguments[0] + ] + ) + return false + return .validate(arguments) + + +# Run the command +func run(command_params: Array) -> int: + escoria.object_manager.get_object(command_params[0]).node\ + .teleport( + escoria.object_manager.get_object(command_params[1]).node, + command_params[2] + ) + return ESCExecution.RC_OK diff --git a/addons/escoria-core/game/core-scripts/esc/commands/turn_to.gd b/addons/escoria-core/game/core-scripts/esc/commands/turn_to.gd new file mode 100644 index 00000000..5cba63ac --- /dev/null +++ b/addons/escoria-core/game/core-scripts/esc/commands/turn_to.gd @@ -0,0 +1,44 @@ +# `turn_to object degrees` +# +# Turns object to a degrees angle with a directions animation. +# +# 0 sets object facing forward, 90 sets it 90 degrees clockwise ("east") etc. +# When turning to the destination angle, animations are played if they're +# defined in animations. object must be player or interactive. degrees must +# be between [0, 360] or an error is reported. +# +# @STUB +# @ESC +extends ESCBaseCommand +class_name TurnToCommand + + +# Return the descriptor of the arguments of this command +func configure() -> ESCCommandArgumentDescriptor: + return ESCCommandArgumentDescriptor.new( + 2, + [TYPE_STRING, TYPE_INT], + [null, true] + ) + + +# Validate wether the given arguments match the command descriptor +func validate(arguments: Array): + if not escoria.object_manager.objects.has(arguments[0]): + escoria.logger.report_errors( + "turn_to: invalid object", + [ + "Object with global id %s not found" % arguments[0] + ] + ) + return false + return .validate(arguments) + + +# Run the command +func run(command_params: Array) -> int: + escoria.logger.report_errors( + "turn_to: command not implemented", + [] + ) + return ESCExecution.RC_ERROR diff --git a/addons/escoria-core/game/core-scripts/esc/commands/wait.gd b/addons/escoria-core/game/core-scripts/esc/commands/wait.gd new file mode 100644 index 00000000..6ad40186 --- /dev/null +++ b/addons/escoria-core/game/core-scripts/esc/commands/wait.gd @@ -0,0 +1,22 @@ +# `wait seconds` +# +# Blocks execution of the current script for a number of seconds specified by the "seconds" parameter. +# +# @ESC +extends ESCBaseCommand +class_name WaitCommand + + +# Return the descriptor of the arguments of this command +func configure() -> ESCCommandArgumentDescriptor: + return ESCCommandArgumentDescriptor.new( + 1, + [TYPE_INT], + [null] + ) + + +# Run the command +func run(command_params: Array) -> int: + yield(escoria.get_tree().create_timer(command_params[0]), "timeout") + return ESCExecution.RC_OK diff --git a/addons/escoria-core/game/core-scripts/esc/commands/walk.gd b/addons/escoria-core/game/core-scripts/esc/commands/walk.gd new file mode 100644 index 00000000..0ecc75c6 --- /dev/null +++ b/addons/escoria-core/game/core-scripts/esc/commands/walk.gd @@ -0,0 +1,45 @@ +# `walk object1 object2 [speed]` +# +# Walks, using the walk animation, object1 towards the position of object2, +# at the speed determined by object1's "speed" property, +# unless overridden. This command is non-blocking. +# +# @ESC +extends ESCBaseCommand +class_name WalkCommand + + +# Return the descriptor of the arguments of this command +func configure() -> ESCCommandArgumentDescriptor: + return ESCCommandArgumentDescriptor.new( + 2, + [TYPE_STRING, TYPE_STRING, TYPE_INT], + [null, null, null] + ) + + +# Validate wether the given arguments match the command descriptor +func validate(arguments: Array): + if not escoria.object_manager.objects.has(arguments[0]): + escoria.logger.report_errors( + "walk: invalid first object", + [ + "Object with global id %s not found" % arguments[0] + ] + ) + return false + if not escoria.object_manager.objects.has(arguments[1]): + escoria.logger.report_errors( + "walk: invalid second object", + [ + "Object with global id %s not found" % arguments[0] + ] + ) + return false + return .validate(arguments) + + +# Run the command +func run(command_params: Array) -> int: + escoria.do("walk", command_params) + return ESCExecution.RC_OK diff --git a/addons/escoria-core/game/core-scripts/esc/commands/walk_block.gd b/addons/escoria-core/game/core-scripts/esc/commands/walk_block.gd new file mode 100644 index 00000000..35dea689 --- /dev/null +++ b/addons/escoria-core/game/core-scripts/esc/commands/walk_block.gd @@ -0,0 +1,49 @@ +# `walk_block object1 object2 [speed]` +# +# Walks, using the walk animation, object1 towards the position of object2, +# at the speed determined by object1's "speed" property, +# unless overridden. This command is blocking. +# +# @ESC +extends ESCBaseCommand +class_name WalkBlockCommand + + +# Return the descriptor of the arguments of this command +func configure() -> ESCCommandArgumentDescriptor: + return ESCCommandArgumentDescriptor.new( + 2, + [TYPE_STRING, TYPE_STRING, TYPE_INT], + [null, null, null] + ) + + +# Validate wether the given arguments match the command descriptor +func validate(arguments: Array): + if not escoria.object_manager.objects.has(arguments[0]): + escoria.logger.report_errors( + "walk_block: invalid first object", + [ + "Object with global id %s not found" % arguments[0] + ] + ) + return false + if not escoria.object_manager.objects.has(arguments[1]): + escoria.logger.report_errors( + "walk_block: invalid second object", + [ + "Object with global id %s not found" % arguments[0] + ] + ) + return false + return .validate(arguments) + + +# Run the command +func run(command_params: Array) -> int: + escoria.do("walk", command_params) + yield( + (escoria.object_manager.objects[command_params[0]].node as ESCItem), + "arrived" + ) + return ESCExecution.RC_OK diff --git a/addons/escoria-core/game/core-scripts/esc/commands/walk_to_pos.gd b/addons/escoria-core/game/core-scripts/esc/commands/walk_to_pos.gd new file mode 100644 index 00000000..8634fc51 --- /dev/null +++ b/addons/escoria-core/game/core-scripts/esc/commands/walk_to_pos.gd @@ -0,0 +1,38 @@ +# `walk_to_pos player x y` +# +# Makes the `player` walk to the position `x`/`y`. +# +# @ESC +extends ESCBaseCommand +class_name WalkToPosCommand + + +# Return the descriptor of the arguments of this command +func configure() -> ESCCommandArgumentDescriptor: + return ESCCommandArgumentDescriptor.new( + 3, + [TYPE_STRING, TYPE_INT, TYPE_INT], + [null, null, null] + ) + + +# Validate wether the given arguments match the command descriptor +func validate(arguments: Array): + if not escoria.object_manager.objects.has(arguments[0]): + escoria.logger.report_errors( + "walk_to_pos: invalid first object", + [ + "Object with global id %s not found" % arguments[0] + ] + ) + return false + return .validate(arguments) + + +# Run the command +func run(command_params: Array) -> int: + escoria.do("walk", [ + command_params[0], + Vector2(command_params[1], command_params[2]) + ]) + return ESCExecution.RC_OK diff --git a/addons/escoria-core/game/core-scripts/esc/commands/walk_to_pos_block.gd b/addons/escoria-core/game/core-scripts/esc/commands/walk_to_pos_block.gd new file mode 100644 index 00000000..c21492ae --- /dev/null +++ b/addons/escoria-core/game/core-scripts/esc/commands/walk_to_pos_block.gd @@ -0,0 +1,42 @@ +# `walk_to_pos_block player x y` +# +# Makes the `player` walk to the position `x`/`y`. This is a blocking command. +# +# @ESC +extends ESCBaseCommand +class_name WalkToPosBlockCommand + + +# Return the descriptor of the arguments of this command +func configure() -> ESCCommandArgumentDescriptor: + return ESCCommandArgumentDescriptor.new( + 3, + [TYPE_STRING, TYPE_INT, TYPE_INT], + [null, null, null] + ) + + +# Validate wether the given arguments match the command descriptor +func validate(arguments: Array): + if not escoria.object_manager.objects.has(arguments[0]): + escoria.logger.report_errors( + "walk_to_pos_block: invalid first object", + [ + "Object with global id %s not found" % arguments[0] + ] + ) + return false + return .validate(arguments) + + +# Run the command +func run(command_params: Array) -> int: + escoria.do("walk", [ + command_params[0], + Vector2(command_params[1], command_params[2]) + ]) + yield( + (escoria.object_manager.objects[command_params[0]].node as ESCItem), + "arrived" + ) + return ESCExecution.RC_OK diff --git a/addons/escoria-core/game/core-scripts/esc/esc_action_manager.gd b/addons/escoria-core/game/core-scripts/esc/esc_action_manager.gd new file mode 100644 index 00000000..1268b5c1 --- /dev/null +++ b/addons/escoria-core/game/core-scripts/esc/esc_action_manager.gd @@ -0,0 +1,188 @@ +# Manages currently carried out actions +extends Object +class_name ESCActionManager + + +# The current action was changed +signal action_changed + + +# Current verb used +var current_action : String = "" setget set_current_action + +# Current tool (ESCItem/ESCInventoryItem) used +var current_tool: ESCObject + + +# Set the current action +func set_current_action(action : String): + if action != current_action: + clear_current_tool() + + current_action = action + emit_signal("action_changed") + + +# Clear the current action +func clear_current_action(): + set_current_action("") + + +# Clear the current tool +func clear_current_tool(): + current_tool = null + + +# Activates the action for given params +# +# #### Parameters +# +# - action String Action to execute (defined in attached ESC file and in +# action verbs UI) eg: arrived, use, look, pickup... +# - target: Target ESC object +# - combine_with: ESC object to combine with +func activate( + action: String, + target: ESCObject, + combine_with: ESCObject = null +) -> int: + escoria.logger.info("Activated action %s on %s" % [action, target]) + + # If we're using an action which item requires to combine + if target.node is ESCItem\ + and action in target.node.combine_if_action_used_among: + # Check if object must be in inventory to be used + if target.node.use_from_inventory_only: + if escoria.inventory_manager.inventory_has(target.global_id): + # Player has item in inventory, we check the element to use on + if combine_with: + var do_combine = true + if combine_with.node is ESCItem \ + and combine_with.node.use_from_inventory_only\ + and not escoria.inventory_manager.inventory_has( + combine_with.global_id + ): + do_combine = false + + if do_combine: + var target_event = "%s %s" % [ + action, + combine_with.global_id + ] + var combine_with_event = "%s %s" % [ + action, + target.global_id + ] + if target.events.has(target_event): + escoria.event_manager.queue_event(target.events[ + target_event + ]) + var event_returned = yield( + escoria.event_manager, + "event_finished" + ) + while event_returned[1] != target_event: + event_returned = yield( + escoria.event_manager, + "event_finished" + ) + if event_returned[0] == ESCExecution.RC_OK: + escoria.action_manager\ + .clear_current_action() + return event_returned[0] + elif combine_with.events.has(combine_with_event)\ + and not combine_with.node.combine_is_one_way: + escoria.event_manager.queue_event( + combine_with.events[ + combine_with_event + ] + ) + var event_returned = yield( + escoria.event_manager, + "event_finished" + ) + while event_returned[1] != combine_with_event: + event_returned = yield( + escoria.event_manager, + "event_finished" + ) + if event_returned[0] == ESCExecution.RC_OK: + escoria.action_manager\ + .clear_current_action() + return event_returned[0] + else: + var errors = [ + "Attempted to execute action %s between item %s and item %s" % [ + action, + target.global_id, + combine_with.global_id + ] + ] + if combine_with.node.combine_is_one_way: + errors.append( + "Reason: %s's item interaction " +\ + "is one-way." % combine_with.global_id + ) + escoria.logger.report_warnings( + "ESCActionManager.activate: Invalid action", + errors + ) + + return ESCExecution.RC_ERROR + else: + escoria.logger.report_warnings( + "ESCActionManager.activate: Invalid action on item", + [ + "Trying to combine object %s with %s, "+ + "but %s is not in inventory." % [ + target.global_id, + combine_with.global_id, + combine_with.global_id + ] + ] + ) + return ESCExecution.RC_ERROR + else: + # We're missing a target here. + # Tell the Label to add a conjunction and wait for another + # click to add the target to p_param. Until then, return + current_tool = target + return ESCExecution.RC_OK + else: + escoria.logger.report_warnings( + "ESCActionManager.activate: Invalid action on item", + [ + "Trying to run %s on object %s, "+ + "but item must be in inventory." % [ + action, + target.global_id + ] + ] + ) + + + if target.events.has(action): + escoria.event_manager.queue_event(target.events[action]) + var event_returned = yield( + escoria.event_manager, + "event_finished" + ) + while event_returned[1] != action: + event_returned = yield( + escoria.event_manager, + "event_finished" + ) + if event_returned[0] == ESCExecution.RC_OK: + escoria.action_manager.clear_current_action() + return event_returned[0] + else: + escoria.logger.report_warnings( + "ESCActionManager.activate: Invalid action", + [ + "Event for action %s on object %s not found." % [ + action, + target.global_id + ] + ] + ) + return ESCExecution.RC_ERROR diff --git a/addons/escoria-core/game/core-scripts/esc/esc_command_registry.gd b/addons/escoria-core/game/core-scripts/esc/esc_command_registry.gd new file mode 100644 index 00000000..2e984a1e --- /dev/null +++ b/addons/escoria-core/game/core-scripts/esc/esc_command_registry.gd @@ -0,0 +1,47 @@ +# A registry of ESC command objects +extends Object +class_name ESCCommandRegistry + + +# The registry of registered commands +var registry: Dictionary = {} + + +# Load a command by its name +# +# #### Parameters +# +# - command_name: Name of command to load +# **Returns** The command object +func load_command(command_name: String) -> ESCBaseCommand: + for command_directory in ProjectSettings.get( + "escoria/main/command_directories" + ): + if ResourceLoader.exists("%s/%s.gd" % [command_directory, command_name]): + registry[command_name] = load( + "%s/%s.gd" % [command_directory, command_name] + ).new() + return registry[command_name] + + escoria.logger.report_errors( + "ESCCommandRegistry.load_command: Command not found", + [ + "No command class could be found for command %s" % + command_name + ] + ) + + return null + + +# Retrieve a command from the command registry +# +# #### Parameters +# +# - command_name: The name of the command +# **Returns** The command object +func get_command(command_name: String) -> ESCBaseCommand: + if self.registry.has(command_name): + return self.registry[command_name] + else: + return self.load_command(command_name) diff --git a/addons/escoria-core/game/core-scripts/esc/esc_compiler.gd b/addons/escoria-core/game/core-scripts/esc/esc_compiler.gd index ba193209..b30e9ce6 100644 --- a/addons/escoria-core/game/core-scripts/esc/esc_compiler.gd +++ b/addons/escoria-core/game/core-scripts/esc/esc_compiler.gd @@ -1,545 +1,220 @@ -extends Node -""" -ESC files: -Lines beginning with ":" such as :push, :say are EVENTS. -Lines in between are usually the ESC API functions calls. They are called COMMANDS. +# Compiler of the ESC language +extends Object +class_name ESCCompiler -Steps - compile_script(path/to/esc) : called once - > compile(path/to/esc, errors) : called once - > read_events() : called once - > create an ESCState, initialized with 1st line - > for each line in ESCState that corresponds to an event (:event), create a new level - > add_level(state, level, errors) - > for each state.line that belongs to same group (same indentation), create a command - > read_cmd(state, level, errors) - > get the token in state.line : this is the actual command (say, teleport, etc.) - > get the parameters next to the token - > create an ESCCommand, check it and push it into level array - > create an ESCEvent with the level created - > add it to the returned Dictionary of events -In the end, the ESCState has read all lines in the file and is deleted -Returned value is a Dictionary { event name : ESCEvent} -And ESCEvent.level is an array of ESCCommand -""" +# A RegEx for comment lines +const COMMENT_REGEX = '^\\s*#.*$' + +# A RegEx for empty lines +const EMPTY_REGEX = '^\\s*$' + +# A RegEx for finding out the indent of a line +const INDENT_REGEX = '^(?\\s*)' +# The currently compiled event +var _current_event = null -var commands = { - "accept_input": { "min_args": 1, "types": [TYPE_STRING] }, - "autosave": { "min_args": 0 }, - "anim": { "min_args": 2, "types": [TYPE_STRING, TYPE_STRING, TYPE_BOOL, TYPE_BOOL, TYPE_BOOL] }, - "camera_push": { "min_args": 1, "types": [TYPE_STRING] }, - "camera_set_drag_margin_enabled": { "min_args": 2, "types": [TYPE_BOOL, TYPE_BOOL] }, - "camera_set_limits": { "min_args": 1, "types": [TYPE_INT]}, - "camera_set_pos": { "min_args": 3, "types": [TYPE_REAL, TYPE_INT, TYPE_INT] }, - "camera_set_target": { "min_args": 1, "types": [TYPE_REAL] }, - "camera_set_zoom": { "min_args": 1, "types": [TYPE_REAL] }, - "camera_set_zoom_height": { "min_args": 1, "types": [TYPE_INT] }, - "camera_shift": { "min_args": 2, "types": [TYPE_INT, TYPE_INT] }, - "change_scene": { "min_args": 1, "types": [TYPE_STRING, TYPE_BOOL] }, - "custom": { "min_args": 2, "types": [TYPE_STRING, TYPE_STRING] }, - "cut_scene": { "min_args": 2, "types": [TYPE_STRING, TYPE_STRING, TYPE_BOOL, TYPE_BOOL, TYPE_BOOL] }, - "debug": { "min_args": 1 }, - "dec_global": { "min_args": 2, "types": [TYPE_STRING, TYPE_INT] }, -# "dialog_config": { "min_args": 3, "types": [TYPE_STRING, TYPE_BOOL, TYPE_BOOL] }, - "enable_terrain": { "min_args": 1, "types": [TYPE_STRING]}, - "game_over": { "min_args": 1, "types": [TYPE_BOOL] }, - "inc_global": { "min_args": 2, "types": [TYPE_STRING, TYPE_INT] }, - "inventory_add": { "min_args": 1 }, - "inventory_remove": { "min_args": 1 }, - "inventory_display": { "min_args": 1, "types": [TYPE_BOOL] }, - "jump": { "min_args": 1 }, - "label": { "min_args": 1 }, - "set_sound_state": { "min_args": 2, "types": [TYPE_STRING, TYPE_STRING, TYPE_BOOL] }, - "queue_animation": { "min_args": 2, "types": [TYPE_STRING, TYPE_STRING, TYPE_BOOL] }, - "queue_resource": { "min_args": 1, "types": [TYPE_STRING, TYPE_BOOL] }, - "repeat": true, - "set_state": { "min_args": 2, "types": [TYPE_STRING, TYPE_STRING, TYPE_BOOL] }, - "set_hud_visible": { "min_args": 1, "types": [TYPE_BOOL]}, - "say": { "min_args": 2 }, - "sched_event": { "min_args": 3, "types": [TYPE_REAL, TYPE_STRING, TYPE_STRING] }, - "set_active": { "min_args": 2, "types": [TYPE_STRING, TYPE_BOOL] }, - "set_angle": { "min_args": 2, "types": [TYPE_STRING, TYPE_INT] }, - "set_global": { "min_args": 2, "types": [TYPE_STRING, TYPE_STRING] }, - "set_globals": { "min_args": 2, "types": [TYPE_STRING, TYPE_BOOL] }, - "set_interactive": { "min_args": 2, "types": [TYPE_STRING, TYPE_BOOL] }, - "set_speed": { "min_args": 2, "types": [TYPE_STRING, TYPE_INT] }, - "slide": { "min_args": 2 }, - "slide_block": { "min_args": 2 }, - "spawn": { "min_args": 1 }, - "stop": true, - "superpose_scene": { "min_args": 1, "types": [TYPE_STRING, TYPE_BOOL] }, - "teleport": { "min_args": 2, "types": [TYPE_STRING, TYPE_STRING, TYPE_INT] }, - "teleport_pos": { "min_args": 3 }, - "turn_to": { "min_args": 2 }, - "wait": true, - "walk": { "min_args": 2 }, - "walk_block": { "min_args": 2 }, - "walk_to_pos": { "min_args": 3}, - "walk_to_pos_block": { "min_args": 3}, - - "%": { "alias": "label", "min_args": 1}, - "?": { "alias": "dialog"}, - "!": { "alias": "end_dialog", "min_args": 0 }, - ">": { "alias": "branch"}, -} +# A stack of groups currently compiling +var _groups_stack = [] -# Commands that can be called only by the ESC debug prompt -var debug_commands = { - "get_active": { "min_args": 1, "types": [TYPE_STRING] }, - "get_global": { "min_args": 1, "types": [TYPE_STRING] }, - "get_interactive": { "min_args": 1, "types": [TYPE_STRING] }, - "get_state": { "min_args": 1, "types": [TYPE_STRING] }, -} +# A stack of dialogs currently compiling +var _dialogs_stack = [] -# Loads a Dictionary of actions from a file, given its path. -func load_esc_file(esc_file_path : String) -> Dictionary: - var f = File.new() - if !f.file_exists(esc_file_path): - escoria.logger.report_errors("esc_compiler.gd:load_esc_file()", ["File " + esc_file_path + " not found."]) - return {} - return compile_script(esc_file_path) +# A stack of dialog options currently compiling +var _dialogs_option_stack = [] -# Loads the parameter script file. Can be either GDScript of ESC type. -# Returns the Dictionary of actions loaded from the file. -func compile_script(p_path : String) -> Dictionary: - var ev_table - # Script is GDScript - if p_path.find(".gd") != -1: - var res = ResourceLoader.load(p_path) - if res == null: - return {} - ev_table = res.new().get_events() - else: # Script is ESC - var errors = [] - ev_table = compile(p_path, errors) - if errors.size() > 0: - escoria.logger.call_deferred("report_errors", p_path, errors) - return ev_table - -func check_command(commands_list : Dictionary, cmd : esctypes.ESCCommand, state : esctypes.ESCState, errors : Array): - if !(cmd.name in commands_list): - errors.push_back("line "+str(state.line_count)+": command "+cmd.name+" not valid.") - return false +# A pointer to the current container (group, dialog option) +# that should get the current command +var _command_container = [] - var cmd_data = commands_list[cmd.name] - if typeof(cmd_data) == TYPE_BOOL: - return true +# The currently identified indent +var _current_indent = 0 - if "alias" in cmd_data: - cmd.name = cmd_data.alias - if "min_args" in cmd_data: - if cmd.params.size() < cmd_data.min_args: - errors.push_back("line "+str(state.line_count)+": command "+cmd.name+" takes "+str(cmd_data.min_args)+" parameters ("+str(cmd.params.size())+" were given).") - return false - - var ret = true - if "types" in cmd_data: - var i = 0 - for t in cmd_data.types: - if i >= cmd.params.size(): - break - if t == TYPE_BOOL: - if cmd.params[i] == "true": - cmd.params[i] = true - elif cmd.params[i] == "false": - cmd.params[i] = false - else: - errors.push_back("line " + str(state.line_count) + ": Invalid parameter " + cmd.params[i] + " for command " + cmd.name + ". Must be 'true' or 'false'.") - ret = false - if t == TYPE_INT: - if not cmd.params[i].is_valid_integer(): - errors.push_back("line " + str(state.line_count) + ": Invalid parameter " + cmd.params[i] + " for command " + cmd.name + ". Expected integer.") - cmd.params[i] = int(cmd.params[i]) - if t == TYPE_REAL: - if not cmd.params[i].is_valid_float(): - errors.push_back("line " + str(state.line_count) + ": Invalid parameter " + cmd.params[i] + " for command " + cmd.name + ". Expected float.") - cmd.params[i] = float(cmd.params[i]) - i+=1 - return ret - -# Check that the given command exists and respects the right number of parameters -func check_normal_command(cmd : esctypes.ESCCommand, state : esctypes.ESCState, errors : Array): - return check_command(commands, cmd, state, errors) - -func check_debug_command(cmd : esctypes.ESCCommand, state : esctypes.ESCState, errors : Array): - return check_command(debug_commands, cmd, state, errors) - -# Fills the given "state" with the next line read from the file -func read_line(state : esctypes.ESCState) -> void: - while true: - if _eof_reached(state.file): - state.line = null - return - else: - state.line = _get_line(state.file) - state.line_count += 1 - if !is_comment(state.line): - return - -# Returns true if line is a comment (starting with #) -func is_comment(line : String) -> bool: - for i in range(0, line.length()): - var c = line[i] - if c == "#": - return true - if c != " " && c != "\t": - return false - return true - -# Returns the position of the first non-blank character in given line string -func get_indent(line : String): - for i in range(0, line.length()): - if line[i] != " " && line[i] != "\t": - return i - -# If the given line string is a event (begins with ":"), returns its name -# Else, return false -func is_event(line : String): - var trimmed = trim(line) - if trimmed.find(":") == 0: - return trimmed.substr(1, trimmed.length()-1) - return false - -# Returns true if the given string is a flag (ie. "[.+]") -func is_flags(tk : String) -> bool: - var trimmed = trim(tk) - if trimmed.find("[") == 0 && trimmed.find("]") == trimmed.length()-1: - return true - return false - -# Reads each line contained in the state (ESCState) (updates state.line) -# While the new line belongs to the same group, creates an ESCCommand from the current state -func add_level(state : esctypes.ESCState, level : Array, errors : Array): - read_line(state) - while state.line != null: - if is_event(state.line): - return - var ind_level = get_indent(state.line) - if ind_level < state.indent: - return - if ind_level > state.indent: - errors.push_back("line "+str(state.line_count)+": invalid indentation for group") - read_line(state) - continue - - read_cmd(state, level, errors) - -func add_dialog(state : esctypes.ESCState, level : Array, errors : Array): - read_line(state) - - while typeof(state.line) != typeof(null): - if is_event(state.line): - return - - var ind_level = get_indent(state.line) - - if ind_level < state.indent: - return - - if ind_level > state.indent: - errors.push_back("line "+str(state.line_count)+": invalid indentation for dialog") - read_line(state) - continue - - read_dialog_option(state, level, errors) - -func get_token(line : String, p_from : int, line_count : int, errors : Array) -> int: - while p_from < line.length(): - if line[p_from] == " " || line[p_from] == "\t": - p_from += 1 - else: - break - if p_from >= line.length(): - return -1 - var tk_end - if line[p_from] == "[": - tk_end = line.find("]", p_from) - if tk_end == -1: - errors.push_back("line "+str(line_count)+": unterminated flags") - tk_end += 1 - elif line[p_from] == "\"": - tk_end = line.find("\"", p_from+1) - if tk_end == -1: - errors.push_back("line "+str(line_count)+": unterminated quotes, line '"+line+"'") - else: - tk_end = p_from - while tk_end < line.length(): - if line[tk_end] == ":": - var ntk = get_token(line, tk_end+1, line_count, errors) - tk_end = ntk - break - if line[tk_end] == " " || line[tk_end] == "\t": - break - tk_end += 1 - return tk_end - -# Remove blank characters around p_str -func trim(p_str : String) -> String: - while p_str.length() && (p_str[0] == " " || p_str[0] == "\t"): - p_str = p_str.substr(1, p_str.length()-1) - while p_str.length() && p_str[p_str.length()-1] == " " || p_str[p_str.length()-1] == "\t": - p_str = p_str.substr(0, p_str.length()-1) - - if p_str[0] == "\"": - p_str = p_str.substr(1, p_str.length()-1) - if p_str[p_str.length()-1] == "\"": - p_str = p_str.substr(0, p_str.length()-1) - return p_str - -# Parses a flags string (usually defined by '[.*]') and fills the flags_list array -# and ifs variable (Dictionary containing all ifs conditions) -func parse_flags(p_flags : String, flags_list : Array, ifs : Dictionary): - var from = 1 - while true: - var next = p_flags.find(",", from) - var flag - if next == -1: - flag = p_flags.substr(from, (p_flags.length()-1) - from) - else: - flag = p_flags.substr(from, next - from) - flag = trim(flag) - var list = [] - - if flag[0] == "!": - list.push_back(true) - flag = trim(flag.substr(1, flag.length()-1)) - if flag.find("inv-") == 0: - ifs["if_not_inv"].push_back(trim(flag).substr(4, flag.length()-1)) - elif flag.find("a/") == 0: - ifs["if_not_active"].push_back(trim(flag).substr(2, flag.length() - 1)) - elif flag.substr(0, 3) in ["eq ", "gt ", "lt "]: - var elems = flag.split(" ", true, 2) - var comparison = "ne" if elems[0] == "eq" else "le" if elems[0] == "gt" else "ge" - ifs["if_" + comparison].push_back([elems[1], elems[2]]) - else: - ifs["if_false"].push_back(trim(flag)) - else: - list.push_back(false) - if flag.find("inv-") == 0: - ifs["if_inv"].push_back(trim(flag).substr(4, flag.length()-1)) - elif flag.find("a/") == 0: - ifs["if_active"].push_back(trim(flag).substr(2, flag.length() - 1)) - elif flag.substr(0, 3) in ["eq ", "gt ", "lt "]: - var elems = flag.split(" ", true, 2) - ifs["if_" + elems[0]].push_back([elems[1], elems[2]]) - else: - ifs["if_true"].push_back(trim(flag)) - - if flag.find(":") >= 0: - var pos = flag.substr(0, flag.find(":")) - var inv = flag.substr(0, pos) - inv = trim(inv) - list.push_back(inv) - flag = flag.substr(pos, flag.length() - pos) - elif flag.find("inv-") == 0: - flag = trim(flag).substr(4, flag.length()-1) - list.push_back("i") - else: - list.push_back("g") - - list.push_back(trim(flag)) - # printt("adding flag ", list) - flags_list.push_back(list) - if next == -1: - return - from = next+1 - -func read_dialog_option(state : esctypes.ESCState, level : Array, errors : Array): - var tk_end = get_token(state.line, 0, state.line_count, errors) - var tk = trim(state.line.substr(0, tk_end)) - if tk != "*" && tk != "-": - errors.append("line "+str(state.line_count)+": Invalid dialog option") - read_line(state) - return - - # Remove inline comments - var comment_idx = state.line.find("#") - if comment_idx > -1: - state.line = state.line.substr(0, comment_idx) - - tk_end += 1 - # var c_start = state.line.find("\"", 0) - var c_end = state.line.find_last("\"") - var q_end = state.line.find("[", c_end) - var q_flags = null - #printt("flags before", q_flags) - if q_end == -1: - q_end = state.line.length() - else: - var f_end = state.line.find("]", q_end) - if f_end == -1: - errors.append("line "+str(state.line_count)+": unterminated flags") - else: - f_end += 1 - q_flags = state.line.substr(q_end, f_end - q_end) - var question = trim(state.line.substr(tk_end, q_end - tk_end)) - var cmd = { "name": "*", "params": [question, []] } - - if q_flags: - var ifs = { - "if_true": [], "if_false": [], "if_inv": [], "if_not_inv": [], - "if_active": [], "if_not_active": [], - "if_eq": [], "if_ne": [], # string and integer comparison - "if_gt": [], "if_ge": [], "if_lt": [], "if_le": [] # integer comparison +func _init(): + # Assure command list preference + if not ProjectSettings.has_setting("escoria/esc/command_paths"): + ProjectSettings.set_setting("escoria/esc/command_paths", [ + "res://addons/escoria-core/game/core-scripts/esc/commands" + ]) + var property_info = { + "name": "escoria/esc/command_paths", + "type": TYPE_STRING_ARRAY } - var flag_list = [] - parse_flags(q_flags, flag_list, ifs) - for key in ifs: - if ifs[key].size(): - cmd.conditions[key] = ifs[key] - if flag_list.size(): - cmd.flags = flag_list - - state.indent += 1 - add_level(state, cmd.params[1], errors) - state.indent -= 1 - - level.push_back(cmd) - -# Read an ESCState and converts it to ESCCommand -# then puts it into level (Array of ESCCommand) -func read_cmd(state : esctypes.ESCState, level : Array, errors : Array): - var params = [] - var from = 0 - var tk_end = get_token(state.line, from, state.line_count, errors) - var ifs = { - "if_true": [], "if_false": [], "if_inv": [], "if_not_inv": [], - "if_active": [], "if_not_active": [], - "if_eq": [], "if_ne": [], # string and integer comparison - "if_gt": [], "if_ge": [], "if_lt": [], "if_le": [] # integer comparison - } - var flags = [] - while tk_end != -1: - var tk = trim(state.line.substr(from, tk_end - from)) - from = tk_end + 1 - if is_flags(tk): - parse_flags(tk, flags, ifs) - else: - params.push_back(tk) - tk_end = get_token(state.line, from, state.line_count, errors) - - if params.size() == 0: - errors.append("line "+str(state.line_count)+": Invalid command.") - read_line(state) - return - - var cmd = esctypes.ESCCommand.new(params[0]) - - if params[0] == ">": - cmd.params = [] - state.indent += 1 - add_level(state, cmd.params, errors) - state.indent -= 1 - elif params[0] == "?": - params.remove(0) - var dialog_params = [] - state.indent += 1 - add_dialog(state, dialog_params, errors) - cmd.params = params - cmd.params.insert(0, dialog_params) - state.indent -= 1 - elif params[0] == "*": - errors.push_back("line "+str(state.line_count)+": Invalid command: dialog option outside dialog") - read_line(state) - return - else: - params.remove(0) - - # Remove inline comments - var comment_idx = params.find("#") - if comment_idx > -1: - params.resize(comment_idx) - - cmd.params = params - read_line(state) - - for key in ifs: - if ifs[key].size(): - cmd.conditions[key] = ifs[key] - - if flags.size(): - cmd.flags = flags + ProjectSettings.add_property_info(property_info) - var errors_before = errors.duplicate() - var valid = check_normal_command(cmd, state, errors) - if valid: - level.push_back(cmd) + +# Load an ESC file from a file resource +func load_esc_file(path: String) -> ESCScript: + escoria.logger.debug("Parsing file %s" % path) + if File.new().file_exists(path): + var file = File.new() + file.open(path, File.READ) + var lines = [] + while not file.eof_reached(): + lines.append(file.get_line()) + return self.compile(lines) else: - var debug_valid = check_debug_command(cmd, state, errors) - if debug_valid: - errors.clear() - level.push_back(cmd) + escoria.logger.report_errors( + "Can not find ESC file", + [ + "File %s could not be found" % path + ] + ) + return null -# Read events from f (Dictionary or File) into ret Dictionary -func read_events(f, ret : Dictionary, errors : Array): - #var state = { "file": f, "line": _get_line(f), "indent": 0, "line_count": 0 } - var state = esctypes.ESCState.new(f, _get_line(f), 0, 0) - while state.line != null: - if is_comment(state.line): - read_line(state) - continue - var ev = is_event(state.line) - if typeof(ev) != typeof(null): - var level = [] - var abort = add_level(state, level, errors) - var ev_flags = [] - if ev is String: - if "|" in ev: - var ev_split = ev.split("|", true, 1) - ev = ev_split[0] - ev = ev.strip_edges() - if ev_split.size() > 1: - ev_split[1] = ev_split[1].strip_edges() - ev_flags = ev_split[1].split(" ") +# Compiles an array of ESC script strings to an ESCScript +func compile(lines: Array) -> ESCScript: + var script = ESCScript.new() + if lines.size() > 0: + var events = self._compile(lines) + for event in events: + script.events[event.name] = event + + return script + + +# Compile an array of ESC script lines into an array of ESC objects +func _compile(lines: Array) -> Array: + + var comment_regex = RegEx.new() + comment_regex.compile(COMMENT_REGEX) + var empty_regex = RegEx.new() + empty_regex.compile(EMPTY_REGEX) + var indent_regex = RegEx.new() + indent_regex.compile(INDENT_REGEX) - ret[ev] = esctypes.ESCEvent.new(ev, level, Array(ev_flags)) - if abort: - return abort - -# If f is a File, returns the next line as String (or null) -# If f is a Dictionary, returns the next line from f.lines -func _get_line(f): - if f is Dictionary: - if f.line >= f.lines.size(): - return null - var line = f.lines[f.line] - f.line += 1 - #printt("reading line ", line) - return line - else: - return f.get_line() - -func _eof_reached(f): - if typeof(f) == typeof({}): - return f.line >= f.lines.size() - else: - return f.eof_reached() - -func compile_str(p_str : String, errors : Array): - var f = { "line": 0, "lines": p_str.split("\n") } - - #printt("esc compile str ", f) - - var ret = {} - read_events(f, ret, errors) - - #printt("returning ", p_fname, ret) - return ret - -# Returns a Dictionary of events read from p_fname filename -func compile(p_fname : String, errors : Array) -> Dictionary: - var f = File.new() - f.open(p_fname, File.READ) - if !f.is_open(): - return {} - - var ret = {} - read_events(f, ret, errors) - - #printt("returning ", p_fname, ret) - return ret + var event_regex = RegEx.new() + event_regex.compile(ESCEvent.REGEX) + var command_regex = RegEx.new() + command_regex.compile(ESCCommand.REGEX) + var dialog_regex = RegEx.new() + dialog_regex.compile(ESCDialog.REGEX) + var dialog_end_regex = RegEx.new() + dialog_end_regex.compile(ESCDialog.END_REGEX) + var dialog_option_regex = RegEx.new() + dialog_option_regex.compile(ESCDialogOption.REGEX) + var group_regex = RegEx.new() + group_regex.compile(ESCGroup.REGEX) + + var returned = [] + + while lines.size() > 0: + var line = lines.pop_front() + escoria.logger.debug("Parsing line %s" % line) + if comment_regex.search(line) or empty_regex.search(line): + # Ignore comments and empty lines + escoria.logger.debug("Line is empty or a comment. Skipping.") + continue + var indent = \ + escoria.utils.get_re_group( + indent_regex.search(line), + "indent" + ).length() + + if event_regex.search(line): + var event = ESCEvent.new(line) + escoria.logger.debug("Line is the event %s" % event.name) + var event_lines = [] + while lines.size() > 0: + var next_line = lines.pop_front() + if not event_regex.search(next_line): + event_lines.append(next_line) + else: + lines.push_front(next_line) + break + if event_lines.size() > 0: + escoria.logger.debug( + "Compiling the next %d lines into the event" % \ + event_lines.size() + ) + event.statements = self._compile(event_lines) + returned.append(event) + elif group_regex.search(line): + var group = ESCGroup.new(line) + escoria.logger.debug("Line is a group") + var group_lines = [] + while lines.size() > 0: + var next_line = lines.pop_front() + var next_line_indent = \ + escoria.utils.get_re_group( + indent_regex.search(next_line), + "indent" + ).length() + if next_line_indent > indent: + group_lines.append(next_line) + else: + lines.push_front(next_line) + break + if group_lines.size() > 0: + escoria.logger.debug( + "Compiling the next %d lines into the group" % \ + group_lines.size() + ) + group.statements = self._compile(group_lines) + returned.append(group) + elif dialog_regex.search(line): + var dialog = ESCDialog.new(line) + escoria.logger.debug("Line is a dialog") + var dialog_lines = [] + while lines.size() > 0: + var next_line = lines.pop_front() + var end_line = dialog_end_regex.search(next_line) + if end_line and \ + escoria.utils.get_re_group( + end_line, + "indent" + ).length() == indent: + break + else: + dialog_lines.append(next_line) + if dialog_lines.size() > 0: + escoria.logger.debug( + "Compiling the next %d lines into the dialog" % \ + dialog_lines.size() + ) + dialog.options = self._compile(dialog_lines) + # Remove the end line from the stack + lines.pop_front() + returned.append(dialog) + elif dialog_option_regex.search(line): + var dialog_option = ESCDialogOption.new(line) + escoria.logger.debug( + "Line is the dialog option %s" % \ + dialog_option.option + ) + var dialog_option_lines = [] + while lines.size() > 0: + var next_line = lines.pop_front() + var next_line_indent = \ + escoria.utils.get_re_group( + indent_regex.search(next_line), + "indent" + ).length() + if next_line_indent > indent: + dialog_option_lines.append(next_line) + else: + lines.push_front(next_line) + break + if dialog_option_lines.size() > 0: + escoria.logger.debug( + "Compiling the next %d lines into the event" % \ + dialog_option_lines.size() + ) + dialog_option.statements = self._compile(dialog_option_lines) + returned.append(dialog_option) + elif command_regex.search(line): + var command = ESCCommand.new(line) + escoria.logger.debug("Line is the command %s" % command.name) + returned.append(command) + else: + escoria.logger.report_errors( + "Invalid ESC line detected", + [ + "Line couldn't be compiled: %s" % line + ] + ) + return returned diff --git a/addons/escoria-core/game/core-scripts/esc/esc_event_manager.gd b/addons/escoria-core/game/core-scripts/esc/esc_event_manager.gd new file mode 100644 index 00000000..c2fef33a --- /dev/null +++ b/addons/escoria-core/game/core-scripts/esc/esc_event_manager.gd @@ -0,0 +1,60 @@ +# A manager for running events +extends Node +class_name ESCEventManager + + +# Emitted when the event did finish running +signal event_finished(event_name, return_code) + + +# A queue of events to run +var events_queue: Array = [] + +# A list of currently scheduled events +var scheduled_events: Array = [] + + +# Handle the events queue and scheduled events +func _process(delta: float) -> void: + if events_queue.size() > 0: + var running_event = events_queue.pop_front() + # TODO: Handle event flags + if not running_event.is_connected( + "finished", self, "_on_event_finished" + ): + running_event.connect( + "finished", + self, + "_on_event_finished", + [running_event] + ) + running_event.run() + for event in self.scheduled_events: + (event as ESCScheduledEvent).timeout -= delta + if (event as ESCScheduledEvent).timeout <= 0: + self.scheduled_events.erase(event) + self.events_queue.append(event) + + +# Queue a new event to run +func queue_event(event: ESCEvent) -> void: + events_queue.append(event) + + +# Schedule an event to run after a timeout +func schedule_event(event: ESCEvent, timeout: float) -> void: + scheduled_events.append(ESCScheduledEvent.new(event, timeout)) + + +# The event finished running +func _on_event_finished(return_code: int, event: ESCEvent) -> void: + escoria.logger.debug( + "Event %s ended with return code %d" % [event.name, return_code] + ) + event.disconnect("finished", self, "_on_event_finished") + match(return_code): + ESCExecution.RC_CANCEL: + self.scheduled_events = [] + self.events_queue = [] + return_code = ESCExecution.RC_OK + emit_signal("event_finished", return_code, event.name) diff --git a/addons/escoria-core/game/core-scripts/esc/esc_globals_manager.gd b/addons/escoria-core/game/core-scripts/esc/esc_globals_manager.gd new file mode 100644 index 00000000..7986da45 --- /dev/null +++ b/addons/escoria-core/game/core-scripts/esc/esc_globals_manager.gd @@ -0,0 +1,90 @@ +# A resource that manages the ESC global states +# The ESC global state is basically simply a dictionary of keys with +# values. Values can be bool, integer or strings +extends Resource +class_name ESCGlobalsManager + + +# Emitted when a global is changed +signal global_changed(global, old_value, new_value) + + +# A list of reserved globals which can not be overridden +const RESERVED_GLOBALS = [ + "ESC_LAST_SCENE" +] + + +# The globals registry +export(Dictionary) var _globals = {} + + +# Check if a global was registered +# +# #### Parameters +# +# - key: The global key to check +# **Returns** Wether the global was registered +func has(key: String) -> bool: + return _globals.has(key) + + +# Get the current value of a global +# +# #### Parameters +# +# - key: The key of the global to return the value +# **Returns** The value of the global +func get_global(key: String): + if _globals.has(key): + return _globals[key] + return null + + +# Filter the globals and return all matching keys and their values as +# a dictionary +# Check out [the Godot docs](https://docs.godotengine.org/en/stable/classes/class_string.html#class-string-method-match) +# for the pattern format +# +# #### Parameters +# +# - pattern: The pattern that the keys have to match +# **Returns** A dictionary of matching keys and their values +func filter(pattern: String) -> Dictionary: + var ret = {} + for global_key in _globals.keys(): + if global_key.match(pattern): + ret[global_key] = _globals[global_key] + return ret + + +# Set the value of a global +# +# #### Parameters +# +# - key: The key of the global to modify +# - value: The new value +func set_global(key: String, value, ignore_reserved: bool = false) -> void: + if key in RESERVED_GLOBALS and not ignore_reserved: + escoria.logger.report_errors( + "ESCGlobalsManager.set_global: Can not override reserved global", + [ + "Global key %s is reserved and can not be overridden" % key + ] + ) + _globals[key] = value + emit_signal("global_changed", key, _globals[key], value) + + +# Set all globals that match the pattern to the value +# Check out [the Godot docs](https://docs.godotengine.org/en/stable/classes/class_string.html#class-string-method-match) +# for the pattern format +# +# #### Parameters +# +# - pattern: The wildcard pattern to match +# - value: The new value +func set_global_wildcard(pattern: String, value) -> void: + for global_key in _globals.keys: + if global_key.match(pattern): + self.set_global(global_key, value) diff --git a/addons/escoria-core/game/core-scripts/esc/esc_inventory_manager.gd b/addons/escoria-core/game/core-scripts/esc/esc_inventory_manager.gd new file mode 100644 index 00000000..13c0dce2 --- /dev/null +++ b/addons/escoria-core/game/core-scripts/esc/esc_inventory_manager.gd @@ -0,0 +1,50 @@ +# A manager for inventory objects +extends Object +class_name ESCInventoryManager + + +# Check if the player has an inventory item +# +# #### Parameters +# +# - item: Inventory item id +# **Returns** Wether the player has the inventory +func inventory_has(item: String) -> bool: + return escoria.globals_manager.has("i/%s" % item) + + +# Get all inventory items +# **Returns** The items in the inventory +func items_in_inventory() -> Array: + var items = [] + var filtered = escoria.globals_manager.filter("i/*") + for glob in filtered.keys(): + if filtered[glob]: + items.append(glob.rsplit("i/", false)[0]) + return items + + +# Remove an item from the inventory +# +# #### Parameters +# +# - item: Inventory item id +func remove_item(item: String): + if not inventory_has(item): + self.logger.report_errors( + "ESCInventoryManager.remove_item: Error removing inventory item", + [ + "Trying to remove non-existent item %s" % item + ] + ) + else: + escoria.globals_manager.set_global("i/%s" % item, false) + + +# Add an item to the inventory +# +# #### Parameters +# +# - item: Inventory item id +func add_item(item: String): + escoria.globals_manager.set_global("i/%s" % item, true) diff --git a/addons/escoria-core/game/core-scripts/esc/esc_object_manager.gd b/addons/escoria-core/game/core-scripts/esc/esc_object_manager.gd new file mode 100644 index 00000000..dc995bc0 --- /dev/null +++ b/addons/escoria-core/game/core-scripts/esc/esc_object_manager.gd @@ -0,0 +1,95 @@ +# A manager for ESC objects +extends Node +class_name ESCObjectManager + + +const RESERVED_OBJECTS = [ + "bg_music" +] + + +# The hash of registered objects (the global id is the key) +var objects: Dictionary = {} + + +# Make active objects visible +func _process(_delta): + for object in objects: + if (object as ESCObject).node: + (object as ESCObject).node.visible = (object as ESCObject).active + + +# Register the object in the manager +# +# #### Parameters +# +# - object: Obejct to register +# - force: Register the object, even if it has already been registered +func register_object(object: ESCObject, force: bool = false) -> void: + if objects.has(object.global_id) and not force: + escoria.logger.report_errors( + "ESCObjectManager.register_object: Object already registered", + [ + "Object with global id %s already registered" % + object.global_id + ] + ) + else: + if not object.node.is_connected( + "tree_exited", + self, + "unregister_object" + ): + object.node.connect( + "tree_exited", + self, + "unregister_object", + [object] + ) + + if "is_interactive" in object.node and object.node.is_interactive: + object.interactive = true + + if "esc_script" in object.node and not object.node.esc_script.empty(): + var script = escoria.esc_compiler.load_esc_file( + object.node.esc_script + ) + object.events = script.events + + objects[object.global_id] = object + + +# Check wether an object was registered +# +# #### Parameters +# +# - global_id: Global ID of object +# **Returns** Wether the object exists in the object registry +func has(global_id: String) -> bool: + return objects.has(global_id) + + +# Get the object from the object registry +func get_object(global_id: String) -> ESCObject: + if objects.has(global_id): + return objects[global_id] + else: + escoria.logger.report_warnings( + "Invalid object retrieved", + [ + "Object with global id %s not found" % global_id + ] + ) + return null + + +# Remove an object from the registry +# +# #### Parameters +# +# - object: The object to unregister +func unregister_object(object: ESCObject) -> void: + if not escoria.inventory_manager.inventory_has(object.global_id) \ + and not object.global_id in RESERVED_OBJECTS: + + objects.erase(object.global_id) diff --git a/addons/escoria-core/game/core-scripts/esc/esc_runner.gd b/addons/escoria-core/game/core-scripts/esc/esc_runner.gd deleted file mode 100644 index 8830e47d..00000000 --- a/addons/escoria-core/game/core-scripts/esc/esc_runner.gd +++ /dev/null @@ -1,747 +0,0 @@ -extends Node - -""" -This is the script that runs in background, checking for events in the events -stack and executing them. -Events are managed using 2 structures: -- event_queue: a queue for scheduled events. On each iteration, every event - in the queue is updated according time delta. If an event time occurs, it is run - (see check_event_queue()). -- levels_stack: stack of events to be run immediately (from an event in ESC file - usually) -""" - -signal global_changed(global_name) -signal inventory_changed -signal open_inventory -signal saved -signal run_event(ev_name, ev_data) -signal event_done(ev_name) -signal music_volume_changed -signal action_changed -signal paused(p_paused) - -onready var resource_cache = load("res://addons/escoria-core/game/core-scripts/resource_queue.gd").new() -onready var save_data = load("res://addons/escoria-core/game/core-scripts/save_data/save_data.gd").new() - -# Cached scenes -var scenes_cache_list : Array = [] -var scenes_cache : Dictionary = {} # this will eventually have everything in scenes_cache_list forever - -# Event currently running -var running_event -# Events queue for timed events -var event_queue : Array = [] -# Events stack -var levels_stack : Array = [] - -var reserved_globals = [ - "ESC_LAST_SCENE" -] -# Dictionary of global variables -var globals : Dictionary = {} - -# Dictionary of active objects -## Active == visible to the player -## Inactive == invisible to the player -var actives : Dictionary = {} -# Dictionary of all objects -## in the form : { "object global_id" : object } -var objects : Dictionary = {} -# Dictionary of all objects' states -## in the form : { "object_name" : "state_name" } -var states : Dictionary = {} -var interactives : Dictionary = {} - -# Array containing the event table for each registered object -var objects_events_table : Dictionary - -var continue_enabled : bool = true -var loading_game : bool = false -var game - -# VERBS & TOOLS -# to be used like this: -# with (target item or hotspot) -# eg: "use" "wrench" (with) (target item or hotspot) - -# Current verb used -var current_action : String = "" setget set_current_action -# Current tool (ESCItem/ESCInventoryItem) used -var current_tool - -## If true, we are accepting inputs -#var accept_input : bool -#enum ACCEPTABLE_INPUT { -# INPUT_NONE -#} - -func _ready(): - save_data.start() - - get_tree().set_auto_accept_quit(ProjectSettings.get('escoria/main/force_quit')) - randomize() - save_data.check_settings() - var settings = save_data.load_settings(null) - escoria.settings = parse_json(settings) - escoria._on_settings_loaded(escoria.settings) - - printt("Calling resource cache start") - resource_cache.start() - -# if !ProjectSettings.get_setting("escoria/platform/skip_cache"): -# scenes_cache_list.push_back(ProjectSettings.get_setting("escoria/main/curtain")) -# scenes_cache_list.push_back(ProjectSettings.get_setting("escoria/main/hud")) -# -# printt("Cache list ", [scenes_cache_list]) -# for s in scenes_cache_list: -# if s != null: -# resource_cache.queue_resource(s, false, true) - set_process(true) - -func _process(delta : float): - check_event_queue(delta) - run() - check_autosave() - -# Process the event queue -func check_event_queue(delta : float): - # Update every event's time in the queue - for e in event_queue: - if e.qe_time > 0: - e.qe_time -= delta - -# if !can_interact() or running_event: -# return - - var i = event_queue.size() - while i: - i -= 1 - var queued_next = event_queue[i] - print(queued_next) -# if queued_next.qe_time <= 0: -# var obj = get_object(queued_next.qe_objname) -# run_event(obj.event_table[queued_next.qe_event]) -# event_queue.remove(i) -# break - -func is_debug_command(p_event): - if p_event["ev_name"] != "debug": - return false - if p_event["ev_level"].size() != 1: - return false - if !(p_event["ev_level"][0].name in escoria.esc_compiler.debug_commands): - return false - return true - -# Called by run_game() -func run_event(p_event): - """ - Run an event - """ - if is_debug_command(p_event): - return run_debug_command(p_event) - else: - running_event = p_event - add_level(p_event, true) - -func run_debug_command(p_event): - return callv(p_event["ev_level"][0].name, p_event["ev_level"][0].params) - -func add_level(p_event, p_root : bool): - """ - Add an ESCEvent to events stack - p_event: the ESCEvent - p_root: bool - """ - levels_stack.push_back(instance_level(p_event, p_root)) - return esctypes.EVENT_LEVEL_STATE.CALL - -func instance_level(p_event : esctypes.ESCEvent, p_root : bool): - var new_level = { - "ip": 0, # Current instruction id - "instructions": p_event.ev_level, # List of instructions (commands) - "waiting": false, # If true, wait for current command to be finished (esc_runner_level.finished()) - "break_stop": p_root, - "labels": {}, - "flags": p_event.ev_flags - } - - for i in range(p_event.ev_level.size()): - if p_event.ev_level[i].name == "label": - var lname = p_event.ev_level[i].params[0] - new_level.labels[lname] = i - return new_level - -func run(): - if levels_stack.size() == 0: - # Constantly run in _process: we may have an empty levels_stack and no event - if running_event: - emit_signal("event_done", running_event.ev_name) - running_event = null - return - - while levels_stack.size() > 0: - var ret = run_top() - if ret == esctypes.EVENT_LEVEL_STATE.YIELD: - return - if ret == esctypes.EVENT_LEVEL_STATE.BREAK: - while levels_stack.size() > 0 && !(levels_stack[levels_stack.size()-1].break_stop): - levels_stack.remove(levels_stack.size()-1) - levels_stack.remove(levels_stack.size()-1) - -func run_top(): - var top = levels_stack[levels_stack.size()-1] -# printt("-----> TOP:", top) - var ret = $esc_level_runner.resume(top) - if ret == esctypes.EVENT_LEVEL_STATE.RETURN || ret == esctypes.EVENT_LEVEL_STATE.BREAK: - levels_stack.remove(levels_stack.size()-1) - return ret - -func test(cmd): - if "if_true" in cmd.conditions.keys(): - for flag in cmd.conditions.if_true: - if !get_global(flag): - return false - if "if_false" in cmd.conditions.keys(): - for flag in cmd.conditions.if_false: - if get_global(flag): - return false - if "if_inv" in cmd.conditions.keys(): - for flag in cmd.conditions.if_inv: - if !inventory_has(flag): - return false - if "if_not_inv" in cmd.conditions.keys(): - for flag in cmd.conditions.if_not_inv: - if inventory_has(flag): - return false - if "if_active" in cmd.conditions.keys(): - for flag in cmd.conditions.if_active: - if not flag in actives or not actives[flag]: - return false - if "if_not_active" in cmd.conditions.keys(): - for flag in cmd.conditions.if_not_active: - if flag in actives and actives[flag]: - return false - if "if_eq" in cmd.conditions.keys(): - for flag in cmd.conditions.if_eq: - if !is_global_equal_to(flag[0], flag[1]): - return false - if "if_ne" in cmd.conditions.keys(): - for flag in cmd.conditions.if_ne: - if is_global_equal_to(flag[0], flag[1]): - return false - if "if_gt" in cmd.conditions.keys(): - for flag in cmd.conditions.if_gt: - if !is_global_greater_than(flag[0], flag[1]): - return false - if "if_ge" in cmd.conditions.keys(): - for flag in cmd.conditions.if_ge: - if is_global_less_than(flag[0], flag[1]): - return false - if "if_lt" in cmd.conditions.keys(): - for flag in cmd.conditions.if_lt: - if !is_global_less_than(flag[0], flag[1]): - return false - if "if_le" in cmd.conditions.keys(): - for flag in cmd.conditions.if_le: - if is_global_greater_than(flag[0], flag[1]): - return false - return true - -func inventory_has(p_obj): - return get_global("i/"+p_obj) - -func items_in_inventory(): - var items = [] - for glob in globals.keys(): - if glob.begins_with("i/") and globals[glob] == "true": - items.push_back(glob.rsplit("i/", false)[0]) - return items - -func get_global(name): - # If no value or looks like boolean, return boolean for backwards compatibility - if not name in globals or globals[name].to_lower() == "false": - return false - if globals[name].to_lower() == "true": - return true - return globals[name] - -# Set global state 'name' to 'value' (can be true or false) -#  -func set_global(name, val, force_change_reserved : bool = false): - if name in reserved_globals and !force_change_reserved: - escoria.logger.report_warnings("esc_runner.gd:set_global()", - ["Global " + name + " is reserved. Value not modified."]) - return - globals[name] = val - # printt("global changed at global_vm, emitting for ", name, val) - emit_signal("global_changed", name) - -func set_globals(pattern : String, val): - for key in globals: - if key.match(pattern): - set_global(key, val) -# globals[key] = val -# emit_signal("global_changed", key) - -func dec_global(name, diff): - var global = get_global(name) - global = int(global) if global else 0 - set_global(name, str(global - diff)) - -func inc_global(name, diff): - var global = get_global(name) - global = int(global) if global else 0 - set_global(name, str(global + diff)) - -func is_global_equal_to(name, val): - var global = get_global(name) - if global and val and global == val: - return true - -func is_global_greater_than(name, val): - var global = get_global(name) - if global and val and int(global) > int(val): - return true - -func is_global_less_than(name, val): - var global = get_global(name) - if global and val and int(global) < int(val): - return true - -func jump(p_label): - while levels_stack.size() > 0: - var top = levels_stack[levels_stack.size()-1] - printt("top labels: ", top.labels, p_label) - if p_label in top.labels: - top.ip = top.labels[p_label] - return - else: - if top.break_stop || levels_stack.size() == 1: - escoria.logger.report_errors("esc_runner.gd:jump()", - ["ESC: Jump to inexisting label: " + p_label]) - levels_stack.remove(levels_stack.size()-1) - break - else: - levels_stack.remove(levels_stack.size()-1) - -func check_autosave(): - pass - -func set_current_action(action : String): - if ! action is String: - escoria.logger.report_errors("esc_runner.gd", - ["Trying to set_current_action: " + str(typeof(action))]) - - if action != current_action: - clear_current_tool() - - current_action = action - emit_signal("action_changed") - -func clear_current_action(): - set_current_action("") - -func clear_current_tool(): - current_tool = null - -func change_scene(params, context, run_events=true): - escoria.logger.info("Change scene to " + params[0] + " with run_events " + str(run_events)) -# check_cache() -# main.clear_scene() -# camera = null - event_queue = [] - - escoria.main.scene_transition.fade_out() - yield(escoria.main.scene_transition, "transition_done") - - # Regular events need to be reset immediately, so we don't - # accidentally `yield()` on them, for performance reasons. - # This does not affect `stack` so execution is fine anyway. - if running_event and running_event.ev_name != "load": - emit_signal("event_done", running_event.ev_name) - running_event = null - - var res_room = resource_cache.get_resource(params[0]) - var res_game = resource_cache.get_resource(ProjectSettings.get_setting("escoria/ui/game_scene")) - if !res_room: - escoria.logger.report_errors("esc_runner.gd:change_scene()", - ["Resource not found: " + params[0]]) - if !res_game: - escoria.logger.report_errors("esc_runner.gd:change_scene()", - ["Resource not found: " + ProjectSettings.get_setting("escoria/ui/game_scene")]) - - resource_cache.clear() - - # Load game scene - var game_scene = res_game.instance() - if !game_scene: - escoria.logger.report_errors("esc_runner.gd:change_scene()", - ["Failed loading scene " + ProjectSettings.get_setting("escoria/ui/game_scene")]) - - # Load room scene - var room_scene = res_room.instance() - if room_scene: - room_scene.add_child(game_scene) - room_scene.move_child(game_scene, 0) - var events = escoria.main.set_scene(room_scene, run_events) - - escoria.main.scene_transition.fade_in() - yield(escoria.main.scene_transition, "transition_done") - - # If scene was never visited, add "ready" event to the events stack - if !scenes_cache.has(room_scene.global_id) \ - and "ready" in events: - run_event(events["ready"]) - - # :setup is pretty much required in the code, but fortunately - # we can help out with cases where one isn't necessary otherwise - if not "setup" in events: - var fake_setup = escoria.esc_compiler.compile_str(":setup\n") - events["setup"] = fake_setup["setup"] - # Finally we add the setup on to of the events stack so that it is ran first - run_event(events["setup"]) - - escoria.inputs_manager.hotspot_focused = "" - if !scenes_cache_list.has(params[0]): - scenes_cache_list.push_back(params[0]) - scenes_cache[room_scene.global_id] = params[0] - else: - escoria.logger.report_errors("esc_runner.gd:change_scene()", - ["Failed loading scene " + params[0]]) - - if context != null: - context.waiting = false - - # Re-apply actives - for active in actives: - set_active(active, actives[active]) - -# cam_target = null -# autosave_pending = true - - -func superpose_scene(params, context, run_events=true): - printt("superposing scene ", params[0], " with run_events ", run_events) -# check_cache() -# main.clear_scene() -# camera = null -# event_queue = [] - - # Regular events need to be reset immediately, so we don't - # accidentally `yield()` on them, for performance reasons. - # This does not affect `stack` so execution is fine anyway. - if running_event and running_event.ev_name != "load": - emit_signal("event_done", running_event.ev_name) - running_event = null - - var res_room = resource_cache.get_resource(params[0]) - var res_game = resource_cache.get_resource(ProjectSettings.get_setting("escoria/ui/game_scene")) - if !res_room: - escoria.logger.report_errors("esc_runner.gd:superpose_scene()", - ["Resource not found: " + params[0]]) - if !res_game: - escoria.logger.report_errors("esc_runner.gd:superpose_scene()", - ["Resource not found: " + ProjectSettings.get_setting("escoria/ui/game_scene")]) - - resource_cache.clear() - - # Load game scene - var game_scene = res_game.instance() - if !game_scene: - escoria.logger.report_errors("esc_runner.gd:superpose_scene()", - ["Failed loading scene " + ProjectSettings.get_setting("escoria/ui/game_scene")]) - - # Load room scene - var room_scene = res_room.instance() - if room_scene: - get_node("/root").add_child(room_scene) - - var target = context.get("set_position") - if target: - if target is Vector2: - room_scene.set_position(target) - if target is String: - var obj = get_object(target) - room_scene.set_position(obj.get_global_position()) - - escoria.inputs_manager.hotspot_focused = false - if !scenes_cache_list.has(params[0]): - scenes_cache_list.push_back(params[0]) - if room_scene.get("global_id"): - scenes_cache[room_scene.global_id] = params[0] - else: - scenes_cache[room_scene.name] = params[0] - else: - escoria.logger.report_errors("esc_runner.gd:superpose_scene()", - ["Failed loading scene " + params[0]]) - - if context != null: - context.waiting = false - - # Re-apply actives - for active in actives: - set_active(active, actives[active]) - -# cam_target = null -# autosave_pending = true - - -func run_game(actions : Dictionary): - set_process(true) - # `load` and `ready` are exclusive because you probably don't want to - # reset the game state when a scene becomes ready, and `ready` is - # redundant when `load`ing state anyway. - # `start` is used only in your `escoria/platform/game_start_script` .esc - # file to start the game. - if "start" in actions: - clear() - run_event(actions["start"]) - escoria.main_menu_instance.hide() - elif "load" in actions: - clear() - run_event(actions["load"]) - elif "ready" in actions: - run_event(actions["ready"]) - -func clear(): - get_tree().call_group_flags(SceneTree.GROUP_CALL_DEFAULT, "game", "game_cleared") - levels_stack = [] - globals = {} - objects = {} - states = {} - actives = {} - interactives = {} - event_queue = [] - continue_enabled = true - loading_game = false - - -func register_object(name : String, val : Object, force : bool = false): - if !name: - escoria.logger.report_errors("esc_runner.gd:register_object()", - ["global_id not given for " + val.get_class() + " " + val.name]) - - if name in objects and not force: - escoria.logger.report_errors("esc_runner.gd:register_object()", - ["Trying to register already registered object " + name + ": " \ - + val.get_class() + " (" + val.name + ")"]) - - objects[name] = val - - if not val.is_connected("tree_exited", self, "object_exit_scene"): - val.connect("tree_exited", self, "object_exit_scene", [name]) - - # Most objects have states/animations, but don't count on it -# if val.has_method("set_state"): - if val is ESCItem or val is ESCPlayer: - if name in states: - set_state(name, [states[name], true]) - else: - set_state(name, [esctypes.OBJ_DEFAULT_STATE]) - - if val is ESCItem: - if val.is_interactive: - set_interactive(name, true) - - if val.get("esc_script") != null and !val.get("esc_script").empty(): - objects_events_table[name] = escoria.esc_compiler.load_esc_file(val.esc_script) - - -func get_object(name): - if !(name in objects): - return null - return objects[name] - - -# Activates the action for given params -# p_action String Action to execute (defined in attached ESC file and in action verbs UI) -# - eg: arrived, use, look, pickup... -# p_params Array -# - 0 Object Target object -func activate(p_action : String, p_param : Array): - escoria.logger.info("Action " + p_action + " with params ", [p_param]) -# if p_param[0].global_id: -# printt("("+p_param[0].global_id+")") - var what = p_param[0] - - # If we're using an action which item requires to combine - if what is ESCItem and p_action in what.combine_if_action_used_among: - # Check if object must be in inventory to be used - if what is ESCItem and what.use_from_inventory_only: - if !inventory_has(what.global_id): - # TODO Either use fallback here, or run pickup action before use - escoria.logger.report_warnings("esc_runner.gd:activate()", - ["Trying to " + p_action + " on object " + what.global_id - + " but item must be in inventory."]) - return esctypes.EVENT_LEVEL_STATE.YIELD - else: - - # Player has item in inventory, we check the element to use on - if p_param.size() > 1: - var combine_with = p_param[1] - - var do_combine = false - if combine_with is ESCItem and combine_with.use_from_inventory_only: - if inventory_has(combine_with.global_id): - do_combine = true - else: - do_combine = true - - if do_combine: - if objects_events_table[what.global_id].has(p_action + " " + combine_with.global_id): - run_event(objects_events_table[what.global_id][p_action + " " + combine_with.global_id]) - return esctypes.EVENT_LEVEL_STATE.RETURN - elif objects_events_table[combine_with.global_id].has(p_action + " " + what.global_id) \ - and !combine_with.combine_is_one_way: - run_event(objects_events_table[combine_with.global_id][p_action + " " + what.global_id]) - return esctypes.EVENT_LEVEL_STATE.RETURN - else: - var errors = ["Attempted to execute inexisting action " + \ - p_action + " between item " + combine_with.global_id + " and item " + what.global_id] - if combine_with.get("combine_is_one_way") != null \ - and combine_with.combine_is_one_way: - errors.append("Reason: " + combine_with.global_id + "'s item interaction is one-way.") - escoria.logger.report_warnings("esc_runner.gd:activate()", errors) - - return esctypes.EVENT_LEVEL_STATE.YIELD - else: - # TODO Use fallback here - pass - - else: - # We're missing a target here. - # Tell the Label to add a conjunction and wait for another click - # to add the target to p_param. Until then, return false. - current_tool = what - return esctypes.EVENT_LEVEL_STATE.YIELD - - if what.global_id in objects_events_table: - if p_action in objects_events_table[what.global_id]: - run_event(objects_events_table[what.global_id][p_action]) - else: - escoria.logger.report_warnings("esc_runner.gd:activate()", - ["Action '" + p_action + "' requested on object '" \ - + what.global_id + "' but action doesn't exist in attached ESC file.", - "TODO: manage fallbacks."]) - return esctypes.EVENT_LEVEL_STATE.RETURN - else: - escoria.logger.report_warnings("esc_runner.gd:activate()", - ["Action '" + p_action + "' requested on object '" + what.global_id \ - + "' but object does not exist in objects_events_table.", \ - "Does object " + what.global_id + " have an attached ESC file?"]) - return esctypes.EVENT_LEVEL_STATE.RETURN - return esctypes.EVENT_LEVEL_STATE.RETURN - - -func get_state(name : String): - return states[name] - - -func get_active(name : String) -> bool: - if actives.has(name): - return actives[name] - return false - -""" -Return the interactive object given its global_id. -""" -func get_interactive(global_id : String): - if interactives.has(global_id): - return interactives[global_id] - return false - - -""" -Change an object state and play its animation (if it has one) -p_params[] : - - String state : the state name - - bool immediate (default=false) : if true, the animation is not played and immediately goes to the last frame -""" -func set_state(global_id : String, p_params : Array): - var obj = get_object(global_id) - states[global_id] = p_params[0] - var immediate : bool = false - if p_params.size() > 1: - immediate = p_params[1] - - # A Hotspot can have a child item, if this item has an empty sprite - # (the hotspot is there to get the user input) - var animation_node - if obj is ESCItem: - #if obj.get("animation") != null: - # animation_node = obj.get("animation") - if obj.get_animation_player() != null: - animation_node = obj.get_animation_player() - - if animation_node: - animation_node.stop() - var actual_animator - if animation_node is AnimationPlayer: - actual_animator = animation_node - elif animation_node is AnimatedSprite: - actual_animator = animation_node.frames - - if actual_animator.has_animation(p_params[0]): - if !immediate: - animation_node.play(p_params[0]) - else: - # The animation is not played, we directly set it at its last frame - if animation_node is AnimatedSprite: - animation_node.animation = p_params[0] - else: - animation_node.current_animation = p_params[0] - var animation = actual_animator.get_animation(p_params[0]) - var animation_length = animation.length - animation_node.seek(animation_length) - escoria.logger.info("Item " + obj.global_id + " changed state to: " + p_params[0]) - -""" -When object is active, it is VISIBLE. -When object is inactive, it is HIDDEN. -""" -func set_active(name : String, active): - actives[name] = active - if objects.has(name) and is_instance_valid(objects[name]): - if objects[name] is ESCInventoryItem: - return - - if active: - objects[name].show() - else: - objects[name].hide() - -""" -When object is interactive, it can be focused -When object is not interactive, it cannot be focused and used -""" -func set_interactive(name : String, active): - interactives[name] = active - - - -""" -Callback called by ESCItems when it emits "tree_exit", ie. removed from scene. -Item is kept in objects[] array if it is in inventory. -""" -func object_exit_scene(name : String): - # If object is in inventory, save it before it's destroyed so we still have - # its data in objects[] - if inventory_has(name): - objects[name] = objects[name].duplicate() - else: - if name != "bg_music": - escoria.logger.info("Object " + name + " removed from scene.") - objects.erase(name) - - -func check_obj(name, cmd): - var obj = escoria.esc_runner.get_object(name) - if obj == null: - escoria.logger.report_errors("esc_runner.gd:check_obj()", - ["Global id "+name+" not found for " + cmd]) - return false - return true - - diff --git a/addons/escoria-core/game/core-scripts/esc/esc_runner_level.gd b/addons/escoria-core/game/core-scripts/esc/esc_runner_level.gd deleted file mode 100644 index e423d371..00000000 --- a/addons/escoria-core/game/core-scripts/esc/esc_runner_level.gd +++ /dev/null @@ -1,773 +0,0 @@ -extends Node - -# This script implements the ESCCommands contained in the ESCEvent. -# TODO : this script should use "Command" pattern. - -var current_context - - -func finished(context = null): - if context != null: - context.waiting = false - else: - current_context.waiting = false - - if escoria.current_state == escoria.GAME_STATE.WAIT: - escoria.current_state = escoria.GAME_STATE.DEFAULT - - -func resume(context): - current_context = context - if context.waiting: - return esctypes.EVENT_LEVEL_STATE.YIELD - var count = context.instructions.size() - while context.ip < count: - var top = escoria.esc_runner.levels_stack.size() - var ret = run(context) - context.ip += 1 - if top < escoria.esc_runner.levels_stack.size(): - return esctypes.EVENT_LEVEL_STATE.CALL - if ret == esctypes.EVENT_LEVEL_STATE.YIELD: - return esctypes.EVENT_LEVEL_STATE.YIELD - if ret == esctypes.EVENT_LEVEL_STATE.CALL: - return esctypes.EVENT_LEVEL_STATE.CALL - if ret == esctypes.EVENT_LEVEL_STATE.BREAK: - if context.break_stop: - break - else: - return esctypes.EVENT_LEVEL_STATE.BREAK - if ret == esctypes.EVENT_LEVEL_STATE.REPEAT: - context.ip = 0 - if ret == esctypes.EVENT_LEVEL_STATE.JUMP: - return esctypes.EVENT_LEVEL_STATE.JUMP - context.ip = 0 - return esctypes.EVENT_LEVEL_STATE.RETURN - - -func run(context): - var cmd = context.instructions[context.ip] - if cmd.name == "label": - return esctypes.EVENT_LEVEL_STATE.RETURN - if !escoria.esc_runner.test(cmd): - return esctypes.EVENT_LEVEL_STATE.RETURN - #print("name is ", cmd.name) - #if !(cmd.name in self): - # escoria.logger.report_errors("", ["Unexisting command "+cmd.name]) - return call(cmd.name, cmd.params) - - -""" -Automatically called when a dialog line is said. -""" -func dialog_line_finished() -> void: -# escoria.esc_runner.get_node("esc_level_runner").finished() - finished() - escoria.dialog_player.is_speaking = false - escoria.current_state = escoria.GAME_STATE.DEFAULT - - -""" -accept_input [ALL|NONE|SKIP] -What type of input does the game accept. ALL is the default, SKIP allows skipping -of dialog but nothing else, NONE denies all input. Including opening the menu etc. -SKIP and NONE also disable autosaves. Note that SKIP gets reset to ALL when the -event is done, but NONE persists. This allows you to create cut scenes with SKIP -where the dialog can be skipped, but also initiate locked-down cutscenes with -accept_input NONE in :setup and accept_input ALL later in :ready. -""" -func accept_input(command_params : Array): -# var p_input = command_params[0] -# var input = escoria.esc_runner.acceptable_inputs["INPUT_" + p_input] -# escoria.esc_runner.set_accept_input(input) - pass - - -""" -""" -func autosave(): -# escoria.request_autosave() - pass - - -""" -anim object name [reverse] [flip_x] [flip_y] -Executes the animation specificed with the "name" parameter on the object, -without blocking. The next command in the event will be executed immediately after. -Optional parameters: - reverse plays the animation in reverse when true - flip_x flips the x axis of the object's sprites when true (object's root node needs to be Node2D) - flip_y flips the y axis of the object's sprites when true (object's root node needs to be Node2D) -""" -func anim(command_params : Array): - if !escoria.esc_runner.check_obj(command_params[0], "anim"): - return esctypes.EVENT_LEVEL_STATE.RETURN - private_play_animation(command_params) - return esctypes.EVENT_LEVEL_STATE.RETURN - - -""" -Groups Commands can be grouped using the character ">" to start a group, and -incrementing the indentation of the commands that belong to the group. Example: - > - set_global door_open true - animation player pick_up - # end of group -""" -func branch(command_params : Array): - var branch_ev = esctypes.ESCEvent.new("branch", command_params, []) - return escoria.esc_runner.add_level(branch_ev, false) - - -""" -camera_push target [time] [type] -Push camera to target. Target must have camera_pos set. -If it's of type Camera2D, its zoom will be used as well as position. -- A time value of 0 will set the camera immediately. -- type is any of the Tween.TransitionType values without the prefix, eg. LINEAR, - QUART or CIRC; defaults to QUART. -""" -func camera_push(command_params : Array): - var target = escoria.esc_runner.get_object(command_params[0]) - var time = command_params[1] if command_params.size() > 1 else 1 - var type = command_params[2] if command_params.size() > 2 else "QUAD" - escoria.esc_runner.get_object("camera").push(target, time, type) - - -""" -camera_set_limits camlimits_id -Sets the camera limits to the one defined under "camlimits_id" in ESCRoom's -camera_limits array. -- camlimits_id : int : id of the camera limits to apply (defined in ESCRoom's -camera_limits array) -""" -func camera_set_limits(command_params : Array): - escoria.main.set_camera_limits(command_params[0]) - - -""" -camera_set_drag_margin_enabled horizontal_enabled vertical_enabled -- horizontal_enabled : bool -- vertical_enabled : bool are booleans for whether or not horizontal and vertical drag -margins are enabled. You will likely want to set them false for advanced camera -motions and true for regular gameplay and/or tracking NPCs. -""" -func camera_set_drag_margin_enabled(command_params : Array): - var horizontal_enabled = command_params[0] - var vertical_enabled = command_params[1] - escoria.esc_runner.get_object("camera").set_drag_margin_enabled(horizontal_enabled, vertical_enabled) - - -""" -camera_set_pos speed x y -Moves the camera to a position defined by "x" and "y", at the speed defined by -"speed" in pixels per second. If speed is 0, camera is teleported to the position. -""" -func camera_set_pos(command_params : Array): - var speed = command_params[0] - var pos = Vector2(command_params[1], command_params[2]) - escoria.esc_runner.get_object("camera").set_target(pos, speed) - - -""" -camera_set_target speed object [object2 object3 ...] -Configures the camera to follow 1 or more objects, using "speed" as speed limit. -This is the default behavior (default follow object is "player"). -If there's more than 1 object, the camera follows the average position of all -the objects specified. -""" -func camera_set_target(command_params : Array): - var speed = command_params[0] - var target = escoria.esc_runner.get_object(command_params[1]) - escoria.esc_runner.get_object("camera").set_target(target, speed) - - -""" -camera_set_zoom magnitude [time] -Zooms the camera in/out to the desired magnitude. Values larger than 1 zooms -the camera out, and smaller values zooms in, relative to the default value of 1. -An optional time in seconds controls how long it takes for the camera to zoom -into position. -""" -func camera_set_zoom(command_params : Array): - var zoom_level = command_params[0] - var speed = command_params[0] if command_params.size() > 1 else 0 - escoria.esc_runner.get_object("camera").set_camera_zoom(zoom_level, speed) - - -""" -camera_set_zoom_height pixels [time] -Similar to the command above, but uses pixel height instead of magnitude to zoom. -""" -func camera_set_zoom_height(command_params : Array): - var magnitude = command_params[0] / escoria.game_size.y - var time = command_params[1] if command_params.size() > 1 else 0 - escoria.esc_runner.get_object("camera").set_camera_zoom(magnitude, float(time)) - - -""" -camera_shift x y [time] [type] -Shift camera by x and y pixels over time seconds. -- type is any of the Tween.TransitionType values without the prefix, eg. LINEAR, - QUART or CIRC; defaults to QUART. -""" -func camera_shift(command_params : Array): - var x = command_params[0] - var y = command_params[1] - var time = command_params[2] - var type = command_params[3] if command_params.size() > 3 else "QUAD" - escoria.esc_runner.get_object("camera").shift(x, y, time, type) - - -""" -change_scene path [run_events] -Loads a new scene, specified by "path". -The run_events variable is a boolean (default true) which you never want to set -manually! It's there only to benefit save games, so they don't conflict with the -scene's events. -""" -func change_scene(command_params : Array): - # Savegames must have events disabled, so saving the game adds a false to params - var run_events = true - if command_params.size() == 2: - run_events = bool(command_params[1]) - - # looking for localized string format in scene. this should be somewhere else - var sep = command_params[0].find(":\"") - if sep >= 0: - var path = command_params[0].substr(sep + 2, command_params[0].length() - (sep + 2)) - escoria.esc_runner.call_deferred("change_scene", [path], current_context, run_events) - else: - escoria.esc_runner.call_deferred("change_scene", command_params, current_context, run_events) - - current_context.waiting = true - return esctypes.EVENT_LEVEL_STATE.YIELD - - -""" -""" -func custom(): - pass - - -""" -cut_scene object name [reverse] [flip_x] [flip_y] -Executes the animation specificed with the "name" parameter on the object, BLOCKING. -The next command in the event will be executed when the animation is finished -playing. -Optional parameters: -- reverse plays the animation in reverse when true -- flip_x flips the x axis of the object's sprites when true - (object's root node needs to be Node2D) -- flip_y flips the y axis of the object's sprites when true - (object's root node needs to be Node2D) -""" -func cut_scene(command_params : Array): - if !escoria.esc_runner.check_obj(command_params[0], "cut_scene"): - return esctypes.EVENT_LEVEL_STATE.RETURN - private_play_animation(command_params) - return esctypes.EVENT_LEVEL_STATE.YIELD - - -""" -PRIVATE -Play animation using parameters. -Used by commands anim() and cut_scene() -""" -func private_play_animation(command_params : Array): - var obj = escoria.esc_runner.get_object(command_params[0]) - var anim_id = command_params[1] - var reverse = false - if command_params.size() > 2: - reverse = command_params[2] - var flip = Vector2(1, 1) - if command_params.size() > 3 && command_params[3]: - flip.x = -1 - if command_params.size() > 4 && command_params[4]: - flip.y = -1 - current_context.waiting = true - obj.play_anim(anim_id, current_context, reverse, flip) - - -""" -debug string [string2 ...] - Takes 1 or more strings, prints them to the console. -""" -func debug(command_params : Array): - for p in command_params: - printt(p) - return esctypes.EVENT_LEVEL_STATE.RETURN - - -""" -dec_global name value - Subtracts the value from global with given "name". - Value and global must both be integers. -""" -func dec_global(command_params : Array): - escoria.esc_runner.dec_global(command_params[0], command_params[1]) - return esctypes.EVENT_LEVEL_STATE.RETURN - - -""" -inc_global name value - Adds the value to global with given "name". - Value and global must both be integers. -""" -func inc_global(command_params : Array): - escoria.esc_runner.inc_global(command_params[0], command_params[1]) - return esctypes.EVENT_LEVEL_STATE.RETURN - - -""" -Start a dialog choice. -""" -func dialog(command_params : Array): - current_context.waiting = true - current_context.in_dialog = true - escoria.current_state = escoria.GAME_STATE.DIALOG - if !escoria.dialog_player: - escoria.dialog_player = escoria.main.current_scene.get_node("game/ui/dialog_layer/dialog_player") - var options = command_params.slice(1, command_params.size()) - escoria.dialog_player.start_dialog_choices(command_params[0], options) - return esctypes.EVENT_LEVEL_STATE.YIELD - - -#func dialog_config(): -## escoria.esc_runner.dialog_config(params) -## return esctypes.EVENT_LEVEL_STATE.RETURN -# pass - - -""" -enable_terrain node_name -Enable the ESCTerrain's NavigationPolygonInstance defined by given node name. -Disables previously activated NavigationPolygonInstance. -""" -func enable_terrain(command_params : Array): - var name : String = command_params[0] - if escoria.room_terrain.has_node(name): - var new_active_navigation_instance = escoria.room_terrain.get_node(name) - escoria.room_terrain.current_active_navigation_instance.enabled = false - escoria.room_terrain.current_active_navigation_instance = new_active_navigation_instance - escoria.room_terrain.current_active_navigation_instance.enabled = true - -""" -""" -func game_over(command_params : Array): - pass - - -""" -Adds element in inventory. -Usage: inventory_add my_item -equivalent to: set_global i/my_item true -""" -func inventory_add(command_params : Array): - set_global(["i/"+command_params[0], "true"]) - - -""" -Removes element from inventory. -Usage: inventory_remove my_item -equivalent to: set_global i/my_item false -""" -func inventory_remove(command_params : Array): - set_global(["i/"+command_params[0], "false"]) - - -""" -inventory_open true/false -Shows or hides inventory. Uses code in game.tscn scene, thus developed outside of Escoria. -""" -func inventory_display(command_params : Array): - var display : bool = bool(command_params[0]) - if display: - escoria.main.current_scene.game.open_inventory() - else: - escoria.main.current_scene.game.close_inventory() - - -""" -jump label_name -Jump to label_name block. This is used to build more complex dialog trees. -Labels must be defined at the same level as the 'jump' command, using either -'label label_name' command or '% label_name'. -""" -func jump(command_params : Array): - escoria.esc_runner.jump(command_params[0]) - return esctypes.EVENT_LEVEL_STATE.JUMP - - -""" -set_sound_state bg_music|bg_sound off|default| true|false -""" -func set_sound_state(command_params : Array): - var snd_player = command_params[0] - var snd_id = command_params[1] - var loop = false - if command_params.size() == 3 and command_params[2]: - loop = true - escoria.main.get_node(snd_player).set_state(snd_id, loop) - return esctypes.EVENT_LEVEL_STATE.RETURN - - -""" -queue_animation object animation -""" -func queue_animation(command_params : Array): - pass - - -""" -queue_resource path [front_of_queue] -Queues the load of a resource in a background thread. The path must be a full -path inside your game, for example "res://scenes/next_scene.tscn". -The "front_of_queue" parameter is optional (default value false), to put the -resource in the front of the queue. Queued resources are cleared when a change -scene happens (but after the scene is loaded, meaning you can queue resources -that belong to the next scene). -""" -func queue_resource(command_params : Array): - var path : String = command_params[0] - var in_front : bool = command_params[1] if command_params.size() > 1 else false - escoria.esc_runner.resource_cache.queue_resource(path, in_front) - - -""" -repeat -Restarts the execution of the current scope at the start. A scope can be a group -or an event. -""" -func repeat(command_params : Array): - return esctypes.EVENT_LEVEL_STATE.REPEAT - - -""" -say speaker_id "text_to_say" [dialog_ui_name] -Make a character say one line. -- dialog_ui_name String if set, uses the dialog UI by its name as defined - in game.tscn/dialog_layer/dialog_player -""" -func say(command_params : Array) -> esctypes: - current_context.waiting = true - - var dict : Dictionary - var dialog_scene_name = ProjectSettings.get_setting("escoria/ui/default_dialog_scene") - - if dialog_scene_name.empty(): - escoria.logger.report_errors("level_esc_runners.gd:say()", - ["Project setting 'escoria/ui/default_dialog_scene' is not set.", - "Please set a default dialog scene."]) - var file = dialog_scene_name.get_file() - var extension = dialog_scene_name.get_extension() - dialog_scene_name = file.rstrip("." + extension) - - # Manage specific dialog scene - if command_params.size() > 2: - dialog_scene_name = command_params[2] - - # Manage translation/voice lines keys in the form of : - # line_key:"Default line text" - # If a line_key exists, we'll set it a label as it will automatically be - # translated - var dialog_key_line = command_params[1].split(":", true, 1) - if dialog_key_line.size() > 1: - dialog_key_line[1] = dialog_key_line[1].trim_prefix("\"") - - dict = { - "key": dialog_key_line[0], - "line": dialog_key_line[1] if dialog_key_line.size() > 1 else dialog_key_line[0], - "ui": dialog_scene_name - #"ui": "dialog_label" - #"ui": "dialog_box_inset" - } - escoria.current_state = escoria.GAME_STATE.DIALOG - if !escoria.dialog_player: - escoria.dialog_player = escoria.main.current_scene.get_node("game/ui/dialog_layer/dialog_player") - escoria.dialog_player.say(command_params[0], dict) - return esctypes.EVENT_LEVEL_STATE.YIELD - - -""" -set_active global_id true/false -Sets object as active or inactive. Active objects are displayed in scene and -respond to inputs. Inactives are hidden. -""" -func set_active(command_params : Array): - if !escoria.esc_runner.check_obj(command_params[0], "set_active"): - return esctypes.EVENT_LEVEL_STATE.RETURN - var name : String = command_params[0] - var value = command_params[1] - escoria.esc_runner.set_active(name, value) - -""" -set_angle object_id angle_degrees -Set the angle of an object. -""" -func set_angle(command_params : Array): - if !escoria.esc_runner.check_obj(command_params[0], "set_angle"): - return esctypes.EVENT_LEVEL_STATE.RETURN - var obj = escoria.esc_runner.get_object(command_params[0]) - # HACK Countering the fact that angle_to_point() function gives - # angle against X axis not Y, we need to check direction using (angle-90°). - # Since the ESC command already gives the right angle, we add 90. - obj.set_angle(int(command_params[1] + 90)) - return esctypes.EVENT_LEVEL_STATE.RETURN - - -""" -set_state object_id state_name -Set object state to state_name. States are recorded in escoria.states[]. -If the object has an animation named 'state_name', Escoria plays it. -""" -func set_state(command_params : Array): - var global_id : String = command_params[0] - var p_params : Array = command_params.slice(1, command_params.size()) - escoria.esc_runner.set_state(global_id, p_params) - - -""" -set_hud_visible true/false -Hides the UI. Uses code in game.tscn scene, thus developed outside of Escoria. -""" -func set_hud_visible(command_params : Array): - if command_params[0]: - escoria.main.current_scene.game.show_ui() - else: - escoria.main.current_scene.game.hide_ui() - - -""" -""" -func sched_event(command_params : Array): - pass - - -""" -set_global global_variable value -Sets a global variable value. Value can be string, integer or boolean. -""" -func set_global(command_params : Array): - var name : String = command_params[0] - var value = command_params[1] - escoria.esc_runner.set_global(name, value) - - -""" -set_globals pattern value -Changes the value of multiple globals using a wildcard pattern. -Example: - # clears the inventory - set_globals i/* false -""" -func set_globals(command_params : Array): - var pattern : String = command_params[0] - var val = command_params[1] - escoria.esc_runner.set_globals(pattern, val) - - -""" -set_interactive global_id true/false -Sets object as interactive or not. Interactive objects can be pointed and -interacted with. -""" -func set_interactive(command_params : Array): - var global_id : String = command_params[0] - var value = command_params[1] - escoria.esc_runner.set_interactive(global_id, value) - - -""" -set_speed global_id speed_value -Sets the speed of object defined by 'global_id' to 'speed_value' -""" -func set_speed(command_params : Array): - if !escoria.esc_runner.check_obj(command_params[0], "set_speed"): - return esctypes.EVENT_LEVEL_STATE.RETURN - - var global_id : String = command_params[0] - var speed_value = command_params[1] - escoria.get_object(global_id).set_speed(speed_value) - - -""" -""" -func slide(command_params : Array): - pass - - -""" -""" -func slide_to_pos(command_params : Array): - pass - - -""" -""" -func slide_block(command_params : Array): - pass - - -""" -""" -func slide_to_pos_block(command_params : Array): - pass - - -""" -spawn path [object2] -Instances a scene determined by "path", and places in the position of object2 -(object2 is optional) -""" -func spawn(command_params : Array): - superpose_scene(command_params) - -""" -stop -Stops the execution of the current script when it reaches the 'stop' instruction. -Usually used in the end of commands blocks. -""" -func stop(command_params : Array): - return esctypes.EVENT_LEVEL_STATE.BREAK - - -""" -superpose_scene path [run_events] -Loads a new scene, specified by "path" and displays it OVER the current one. -This is useful to display puzzle scenes over the current room, so that you don't -loose any progression and continuity. -- path String Path to the scene to superpose. -- run_events Boolean (default true) which you never want to set -manually! It's there only to benefit save games, so they don't conflict with the -scene's events. -""" -func superpose_scene(command_params : Array): - # Savegames must have events disabled, so saving the game adds a false to params - var run_events = true - if command_params.size() == 2: - if command_params[1] in ["true", "false"]: - run_events = true if command_params[1] == "true" else false - else: - current_context["set_position"] = command_params[1] - - # looking for localized string format in scene. this should be somewhere else - var sep = command_params[0].find(":\"") - if sep >= 0: - var path = command_params[0].substr(sep + 2, command_params[0].length() - (sep + 2)) - escoria.esc_runner.call_deferred("superpose_scene", [path], current_context, run_events) - else: - escoria.esc_runner.call_deferred("superpose_scene", command_params, current_context, run_events) - - current_context.waiting = true - return esctypes.EVENT_LEVEL_STATE.YIELD - - -""" -teleport obj1 obj2 [angle_degrees] -Teleports obj1 at obj2's position. If angle_degrees is set (int), sets obj1's -angle to angle_degrees. -""" -func teleport(command_params : Array): - if !escoria.esc_runner.check_obj(command_params[0], "teleport"): - return esctypes.EVENT_LEVEL_STATE.RETURN - if !escoria.esc_runner.check_obj(command_params[1], "teleport"): - return esctypes.EVENT_LEVEL_STATE.RETURN - - var angle - if command_params.size() > 2: - angle = int(command_params[2]) - - escoria.esc_runner.get_object(command_params[0]) \ - .teleport(escoria.esc_runner.get_object(command_params[1]), angle) - return esctypes.EVENT_LEVEL_STATE.RETURN - - -""" -teleport obj1 x y [angle_degrees] -Teleports obj1 at (x,y). If angle_degrees is set (int), sets obj1's -angle to angle_degrees. -""" -func teleport_pos(command_params : Array): - if !escoria.esc_runner.check_obj(command_params[0], "teleport"): - return esctypes.EVENT_LEVEL_STATE.RETURN - var x = command_params[1] - var y = command_params[2] - - var angle - if command_params.size() > 2: - angle = int(command_params[3]) - - escoria.esc_runner.get_object(command_params[0]).teleport(Vector2(x,y), angle) - return esctypes.EVENT_LEVEL_STATE.RETURN - - -""" -""" -func turn_to(command_params : Array): - pass - - -""" -Wait for given time in seconds. -Usage: wait time_in_seconds -""" -func wait(command_params : Array): - escoria.current_state = escoria.GAME_STATE.WAIT - var time = float(command_params[0]) - if time <= 0: - return esctypes.EVENT_LEVEL_STATE.RETURN -# get_tree().call_group_flags(SceneTree.GROUP_CALL_DEFAULT, "game", "wait", time, p_level) - escoria.main.wait(command_params, current_context) - current_context.waiting = true - return esctypes.EVENT_LEVEL_STATE.YIELD - - -""" -walk object_id1 object_id2 -Make object1 walk towards object2. This command is not blocking (user input not disabled) -""" -func walk(command_params : Array): - current_context.waiting = false - escoria.do("walk", command_params) - return esctypes.EVENT_LEVEL_STATE.RETURN - - -""" -walk_block object_id1 object_id2 -Make object1 walk towards object2. This command is blocking (user input disabled) -""" -func walk_block(command_params : Array): - current_context.waiting = true - escoria.do("walk", command_params) - return esctypes.EVENT_LEVEL_STATE.YIELD - - -""" -walk_to_pos object_id1 pos_x pos_y -Make object1 walk towards object2. This command is not blocking (user input not disabled) -""" -func walk_to_pos(command_params : Array): - current_context.waiting = false - var destination_pos = Vector2(command_params[1], command_params[2]) - escoria.do("walk", [command_params[0], destination_pos]) - return esctypes.EVENT_LEVEL_STATE.RETURN - -""" -walk_to_pos_block object_id1 pos_x pos_y -Make object1 walk towards object2. This command is blocking (user input disabled) -""" -func walk_to_pos_block(command_params : Array): - current_context.waiting = true - var destination_pos = Vector2(command_params[1], command_params[2]) - escoria.do("walk", [command_params[0], destination_pos]) - return esctypes.EVENT_LEVEL_STATE.YIELD - - - - - - - - - - diff --git a/addons/escoria-core/game/core-scripts/esc/types/esc_base_command.gd b/addons/escoria-core/game/core-scripts/esc/types/esc_base_command.gd new file mode 100644 index 00000000..811f1118 --- /dev/null +++ b/addons/escoria-core/game/core-scripts/esc/types/esc_base_command.gd @@ -0,0 +1,21 @@ +# A base class for every ESC command. +# Extending classes have to override the configure and run function +extends Node +class_name ESCBaseCommand + + +# Return the descriptor of the arguments of this command +func configure() -> ESCCommandArgumentDescriptor: + escoria.logger.error("Command %s did not override configure." % get_class()) + return ESCCommandArgumentDescriptor.new() + + +# Validate wether the given arguments match the command descriptor +func validate(arguments: Array) -> bool: + return self.configure().validate(get_class(), arguments) + + +# Run the command +func run(command_params: Array) -> int: + escoria.logger.error("Command %s did not override run." % get_class()) + return 0 diff --git a/addons/escoria-core/game/core-scripts/esc/types/esc_command.gd b/addons/escoria-core/game/core-scripts/esc/types/esc_command.gd new file mode 100644 index 00000000..a983308b --- /dev/null +++ b/addons/escoria-core/game/core-scripts/esc/types/esc_command.gd @@ -0,0 +1,124 @@ +# An ESC command +extends ESCStatement +class_name ESCCommand + + +# Regex matching command lines +const REGEX = \ + '^(\\s*)(?[^\\s]+)(\\s(?([^\\[]|$)+))?' +\ + '(\\[(?[^\\]]+)\\])?' + + +# The name of this command +var name: String + +# Parameters of this command +var parameters: Array = [] + +# A list of ESCConditions to run this command. +# Conditions are combined using logical AND +var conditions: Array = [] + + +# Create a command from a command string +func _init(command_string): + var command_regex = RegEx.new() + command_regex.compile(REGEX) + + if command_regex.search(command_string): + for result in command_regex.search_all(command_string): + if "name" in result.names: + self.name = escoria.utils.get_re_group(result, "name") + if "parameters" in result.names: + # Split parameters by whitespace but allow quoted + # parameters + var quote_open = false + var parameter_values = PoolStringArray([]) + var parsed_parameters = \ + escoria.utils.sanitize_whitespace( + escoria.utils.get_re_group( + result, + "parameters" + ).strip_edges() + ) + for parameter in parsed_parameters.split(" "): + if parameter.begins_with('"') and parameter.ends_with('"'): + parameters.append( + parameter.substr(1, parameter.length() - 2) + ) + elif parameter.begins_with('"'): + quote_open = true + parameter_values.append(parameter.substr(1)) + elif parameter.ends_with('"'): + quote_open = false + parameter_values.append( + parameter.substr(0, len(parameter) - 1) + ) + parameters.append(parameter_values.join(" ")) + parameter_values.resize(0) + elif quote_open: + parameter_values.append(parameter) + else: + parameters.append(parameter) + if "conditions" in result.names: + for condition in escoria.utils.get_re_group( + result, + "conditions" + ).split(","): + self.conditions.append( + ESCCondition.new(condition.strip_edges()) + ) + else: + escoria.logger.report_errors( + "Invalid command detected: %s" % command_string, + [ + "Command regexp didn't match" + ] + ) + + +# Check, if conditions match +func is_valid() -> bool: + var command_found = false + for base_path in ProjectSettings.get("escoria/esc/command_directories"): + var command_path = "%s/%s.gd" % [ + base_path, + self.name + ] + if ResourceLoader.exists(command_path): + command_found = true + + if not command_found: + escoria.logger.report_errors( + "Invalid command detected: %s" % self.name, + [ + "Command implementation not found in any command directory" + ] + ) + return false + + return .is_valid() + + +# Run this command +func run() -> int: + var command_object = escoria.command_registry.get_command(self.name) + if command_object == null: + return ESCExecution.RC_ERROR + else: + var argument_descriptor = command_object.configure() + var prepared_arguments = argument_descriptor.prepare_arguments( + self.parameters + ) + if argument_descriptor.validate(self.name, prepared_arguments): + escoria.logger.debug("Running command %s with parameters %s" % [ + self.name, + prepared_arguments + ]) + var rc = command_object.run(prepared_arguments) + if rc is GDScriptFunctionState: + rc = yield(rc, "completed") + escoria.logger.debug("[%s] Return code: %d" % [self.name, rc]) + return rc + else: + return ESCExecution.RC_ERROR diff --git a/addons/escoria-core/game/core-scripts/esc/types/esc_command_argument_descriptor.gd b/addons/escoria-core/game/core-scripts/esc/types/esc_command_argument_descriptor.gd new file mode 100644 index 00000000..b3713d5b --- /dev/null +++ b/addons/escoria-core/game/core-scripts/esc/types/esc_command_argument_descriptor.gd @@ -0,0 +1,90 @@ +# The descriptor of the arguments of an ESC command +extends Object +class_name ESCCommandArgumentDescriptor + + +# Number of arguments the command expects +var min_args: int = 0 + +# The types the arguments as TYPE_ constants. If the command is called with +# more arguments than there are entries in the types array, the additional +# arguments will be checked against the last entry of the types array. +var types: Array = [] + +# The default values for the arguments +var defaults: Array = [] + + +# Initialize the descriptor +func _init(p_min_args: int = 0, p_types: Array = [], p_defaults: Array = []): + min_args = p_min_args + types = p_types + defaults = p_defaults + + +# Combine the default argument values with the given arguments +func prepare_arguments(arguments: Array) -> Array: + var complete_arguments = defaults + + for index in range(arguments.size()): + complete_arguments[index] = escoria.utils.get_typed_value( + arguments[index] + ) + + return complete_arguments + + +# Validate wether the given arguments match the command descriptor +func validate(command: String, arguments: Array) -> bool: + if arguments.size() < self.min_args: + escoria.logger.report_errors( + "Invalid command arguments for command %s" % command, + [ + "Arguments didn't match minimum size %d: %s" % + self.min_args, + arguments + ] + ) + + for index in range(arguments.size()): + if arguments[index] == null: + # No type checking for null values + continue + var correct = false + var types_index = index + if types_index > types.size(): + types_index = types.size() - 1 + if not self.types[types_index] is Array: + self.types[types_index] = [self.types[index]] + for type in self.types[types_index]: + if not correct: + correct = self._is_type(arguments[index], type) + + if not correct: + escoria.logger.report_errors( + "Argument type did not match descriptor for command %s" % + command, + [ + "Argument %d is of type %d. Expected %s" % [ + index, + typeof(arguments[index]), + PoolStringArray( + self.types[types_index] + ).join(",") + ] + ] + ) + + return true + + +# Check wether the given argument is of the given type +# +# #### Parameters +# +# - argument: Argument to test +# - type: Type to check +# *Returns* Wether the argument is of the given type +func _is_type(argument, type: int) -> bool: + return typeof(argument) == type + diff --git a/addons/escoria-core/game/core-scripts/esc/types/esc_condition.gd b/addons/escoria-core/game/core-scripts/esc/types/esc_condition.gd new file mode 100644 index 00000000..bda2401b --- /dev/null +++ b/addons/escoria-core/game/core-scripts/esc/types/esc_condition.gd @@ -0,0 +1,130 @@ +# A condition to run a command +extends Object +class_name ESCCondition + + +# Valid comparison types +enum {COMPARISON_NONE, COMPARISON_EQ, COMPARISON_GT, COMPARISON_LT} + + +# Regex that matches condition lines +const REGEX = \ + '^(?!)?(?eq|gt|lt)? ?' +\ + '(?i\/)?(?[^ ]+)( (?.+))?$' + + +const COMPARISON_DESCRIPTION = [ + "Checking if %s %s %s true%s", + "Checking if %s %s %s equals %s", + "Checking if %s %s %s greater than %s", + "Checking if %s %s %s less than %s" +] + + +# Name of the flag compared +var flag: String + +# Wether this condition is negated +var negated: bool = false + +# Wether this condition is regarding an inventory item ("i/...") +var inventory: bool = false + +# An optional comparison type. Use the COMPARISON-Enum +var comparison: int = COMPARISON_NONE + +# The value used together with the comparison type +var comparison_value + + +# Create a new condition from an ESC condition string +func _init(comparison_string: String): + var comparison_regex = RegEx.new() + comparison_regex.compile( + REGEX + ) + + if comparison_regex.search(comparison_string): + for result in comparison_regex.search_all(comparison_string): + if "is_negated" in result.names: + self.negated = true + if "comparison" in result.names: + match escoria.utils.get_re_group(result, "comparison"): + "eq": self.comparison = COMPARISON_EQ + "gt": self.comparison = COMPARISON_GT + "lt": self.comparison = COMPARISON_LT + _: escoria.logger.report_errors( + "Invalid comparison type detected: %s" % + comparison_string, + [ + "Comparison type %s unknown" % + escoria.utils.get_re_group( + result, + "comparison" + ) + ] + ) + if "comparison_value" in result.names: + self.comparison_value = escoria.utils.get_typed_value( + escoria.utils.get_re_group( + result, + "comparison_value" + ) + ) + if "is_inventory" in result.names: + self.inventory = true + if "flag" in result.names: + self.flag = escoria.utils.get_re_group(result, "flag") + else: + escoria.logger.report_errors( + "Invalid comparison detected: %s" % comparison_string, + [ + "Comparison regexp didn't match" + ] + ) + + +# Run this comparison against the globals +func run() -> bool: + var global_name = self.flag + + escoria.logger.debug( + COMPARISON_DESCRIPTION[self.comparison] % [ + "inventory item" if self.inventory else "global value", + self.flag, + "is not" if self.negated else "is", + "" if self.comparison == COMPARISON_NONE else self.comparison_value + ] + ) + + if self.inventory: + global_name = "i/%s" % flag + + var return_value = false + + if self.comparison == COMPARISON_NONE and \ + escoria.globals_manager.has(global_name) and \ + escoria.globals_manager.get_global(global_name) is bool and \ + escoria.globals_manager.get_global(global_name): + return_value = true + elif self.comparison == COMPARISON_EQ and \ + escoria.globals_manager.get_global(global_name) == \ + self.comparison_value: + return_value = true + elif self.comparison == COMPARISON_GT and \ + escoria.globals_manager.get_global(global_name) > \ + self.comparison_value: + return_value = true + elif self.comparison == COMPARISON_LT and \ + escoria.globals_manager.get_global(global_name) < \ + self.comparison_value: + return_value = true + + if self.negated: + return_value = not return_value + + escoria.logger.debug( + "It is" if return_value else "It isn't" + ) + + return return_value diff --git a/addons/escoria-core/game/core-scripts/esc/types/esc_dialog.gd b/addons/escoria-core/game/core-scripts/esc/types/esc_dialog.gd new file mode 100644 index 00000000..66a45902 --- /dev/null +++ b/addons/escoria-core/game/core-scripts/esc/types/esc_dialog.gd @@ -0,0 +1,84 @@ +# An ESC dialog +extends ESCStatement +class_name ESCDialog + + +# Regex that matches dialog lines +const REGEX = \ + '^(\\s*)\\?( (?[^ ]+))?( (?[^ ]+))?' +\ + '( (?[^ ]+))?( (?.+))?$' + + +# A Regex that matches the end of a dialog +const END_REGEX = \ + '^(?\\s*)!.*$' + + +# Dialog type +var type: String = "" + +# Avatar used in the dialog +var avatar: String = "" + +# Timeout until the timeout_option option is selected. Use 0 for no timeout +var timeout: int = 0 + +# The dialog option to select when timeout is reached +var timeout_option: int = 0 + +# A list of ESCDialogOptions +var options: Array + + +# Construct a dialog from a dialog string +func _init(dialog_string: String): + var dialog_regex = RegEx.new() + dialog_regex.compile(REGEX) + + if dialog_regex.search(dialog_string): + for result in dialog_regex.search_all(dialog_string): + if "type" in result.names: + self.type = escoria.utils.get_re_group(result, "type") + if "avatar" in result.names: + self.avatar = escoria.utils.get_re_group(result, "avatar") + if "timeout" in result.names: + self.timeout = int( + escoria.utils.get_re_group(result, "timeout") + ) + if "timeout_option" in result.names: + self.timeout_option = int( + escoria.utils.get_re_group(result, "timeout_option") + ) + else: + escoria.logger.report_errors( + "Invalid dialog detected: %s" % dialog_string, + [ + "Dialog regexp didn't match" + ] + ) + + +# Dialogs have no conditions, just return true +func is_valid() -> bool: + return true + + +# Run this dialog +func run(): + escoria.logger.debug("Starting dialog") + escoria.current_state = escoria.GAME_STATE.DIALOG + if !escoria.dialog_player: + escoria.dialog_player = escoria.main.current_scene.get_node( + "game/ui/dialog_layer/dialog_player" + ) + escoria.dialog_player.start_dialog_choices(self) + var option = yield( + escoria.dialog_player, + "option_chosen" + ) as ESCDialogOption + var rc = option.run() + if rc is GDScriptFunctionState: + rc = yield(rc, "completed") + if rc != ESCExecution.RC_CANCEL: + return self.run() + return rc diff --git a/addons/escoria-core/game/core-scripts/esc/types/esc_dialog_option.gd b/addons/escoria-core/game/core-scripts/esc/types/esc_dialog_option.gd new file mode 100644 index 00000000..f481be2b --- /dev/null +++ b/addons/escoria-core/game/core-scripts/esc/types/esc_dialog_option.gd @@ -0,0 +1,49 @@ +# An option of an ESC dialog +extends ESCStatement +class_name ESCDialogOption + + +# Regex that matches dialog option lines +const REGEX = \ + '^[^-]*- "(?