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 index a5a3c260..518413d7 100644 --- a/addons/escoria-core/game/core-scripts/esc/commands/change_scene.gd +++ b/addons/escoria-core/game/core-scripts/esc/commands/change_scene.gd @@ -61,7 +61,7 @@ func run(command_params: Array) -> int: var exited_previous_room = false if command_params[1] \ - and escoria.event_manager._running_event.name \ + and escoria.event_manager.get_running_event("_front").name \ in ["exit_scene", "room_selector"]: exited_previous_room = true escoria.main.scene_transition.transition( @@ -111,7 +111,7 @@ func run(command_params: Array) -> int: var room_scene = res_room.instance() if room_scene: if command_params[1] \ - and escoria.event_manager._running_event.name \ + and escoria.event_manager.get_running_event("_front").name \ == "room_selector": room_scene.enabled_automatic_transitions = true else: diff --git a/addons/escoria-core/game/core-scripts/esc/commands/queue_event.gd b/addons/escoria-core/game/core-scripts/esc/commands/queue_event.gd new file mode 100644 index 00000000..aed9709e --- /dev/null +++ b/addons/escoria-core/game/core-scripts/esc/commands/queue_event.gd @@ -0,0 +1,99 @@ +# `queue_event object event [channel] [block]` +# +# Queue another event to run +# +# **Parameters** +# +# - object: Object that holds the ESC script with the event +# - event: Name of the event to queue +# - channel: Channel to run the event on (default: `_front`) +# - block: Whether to wait for the queue to finish. This is only possible, if +# the queued event is not to be run on the same event as this command +# (default: `false`) +# +# @ESC +extends ESCBaseCommand +class_name QueueEventCommand + + +# Return the descriptor of the arguments of this command +func configure() -> ESCCommandArgumentDescriptor: + return ESCCommandArgumentDescriptor.new( + 2, + [TYPE_STRING, TYPE_STRING, TYPE_STRING, TYPE_BOOL], + [null, null, "_front", 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( + "queue_event.gd:validate", + [ + "Object with global id %s not found" % arguments[0] + ] + ) + return false + var node = escoria.object_manager.objects.get( + arguments[0] + ).node + if not "esc_script" in node or node.esc_script == "": + escoria.logger.report_errors( + "queue_event.gd:validate", + [ + "Object with global id %s has no ESC script" % arguments[0] + ] + ) + return false + var esc_script = escoria.esc_compiler.load_esc_file(node.esc_script) + if not arguments[1] in esc_script.events: + escoria.logger.report_errors( + "queue_event.gd:validate", + [ + "Event with name %s not found" % arguments[1] + ] + ) + return false + if arguments[3] and not escoria.event_manager.is_channel_free(arguments[2]): + escoria.logger.report_errors( + "queue_event.gd:validate", + [ + "The queue %s doesn't accept a new event." % arguments[2] + ] + ) + return false + return .validate(arguments) + + +# Run the command +func run(arguments: Array) -> int: + var node = escoria.object_manager.objects.get( + arguments[0] + ).node + var esc_script = escoria.esc_compiler.load_esc_file(node.esc_script) + if arguments[2] == "_front": + escoria.event_manager.queue_event(esc_script.events[arguments[1]]) + else: + escoria.event_manager.queue_background_event( + arguments[2], + esc_script.events[arguments[1]] + ) + if arguments[3]: + if arguments[2] == "_front": + var rc = yield(escoria.event_manager, "event_finished") + while rc[1] != arguments[1]: + rc = yield(escoria.event_manager, "event_finished") + return rc + else: + var rc = yield( + escoria.event_manager, + "background_event_finished" + ) + while rc[1] != arguments[1] and rc[2] != arguments[2]: + rc = yield( + escoria.event_manager, + "background_event_finished" + ) + return rc + return ESCExecution.RC_OK 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 index 4397797e..75f36d59 100644 --- a/addons/escoria-core/game/core-scripts/esc/esc_event_manager.gd +++ b/addons/escoria-core/game/core-scripts/esc/esc_event_manager.gd @@ -1,106 +1,194 @@ # A manager for running events +# There are different "channels" an event can run on. +# The usual events happen in the foreground channel _front, but +# additional event queues can be added as required. +# Additionally, events can be scheduled to be queued in the future extends Node class_name ESCEventManager + # Emitted when the event started execution signal event_started(event_name) +# Emitted when an event is started in a channel of the background queue +signal background_event_started(channel_name, event_name) + # Emitted when the event did finish running -signal event_finished(event_name, return_code) +signal event_finished(return_code, event_name) -# Emitted when the event was interrupted -signal event_interrupted(event_name, return_code) +# Emitted when a background event was finished +signal background_event_finished(return_code, event_name, channel_name) -# A queue of events to run -var events_queue: Array = [] - # A list of currently scheduled events var scheduled_events: Array = [] -# Currently running event -var _running_event: ESCEvent +# A list of constantly running events in multiple background channels +var events_queue: Dictionary = { + "_front": [] +} -# Whether the event manager is allowed to proceed with next event. -var can_process_next_event = true +# Currently running event in background channels +var _running_events: Dictionary = {} + +# Wether an event can be played on a specific channel +var _channels_state: Dictionary = {} +# Make sure to stop when pausing the game func _ready(): self.pause_mode = Node.PAUSE_MODE_STOP # Handle the events queue and scheduled events +# +# #### Parameters +# - delta: Time passed since the last process call func _process(delta: float) -> void: - if events_queue.size() > 0 and can_process_next_event: - can_process_next_event = false - _running_event = events_queue.pop_front() - escoria.logger.debug( - "esc_event_manager", - [ - "Popping event %s from event_queue" \ - % _running_event.name if _running_event.get("name") != null \ - else str(_running_event) - ] - ) - if not _running_event.is_connected( - "finished", self, "_on_event_finished" - ): - _running_event.connect( - "finished", - self, - "_on_event_finished", - [_running_event] + for channel_name in events_queue.keys(): + if events_queue[channel_name].size() == 0: + continue + if is_channel_free(channel_name): + _channels_state[channel_name] = false + _running_events[channel_name] = \ + events_queue[channel_name].pop_front() + escoria.logger.debug( + "esc_event_manager", + [ + "Popping event %s from background queue %s" % [ + _running_events[channel_name].name, + channel_name + ] + ] ) - if not _running_event.is_connected( - "interrupted", self, "_on_event_finished" - ): - _running_event.connect( - "interrupted", - self, - "_on_event_finished", - [_running_event] - ) - - emit_signal("event_started", _running_event.name) - _running_event.run() + if not _running_events[channel_name].is_connected( + "finished", self, "_on_event_finished" + ): + _running_events[channel_name].connect( + "finished", + self, + "_on_event_finished", + [channel_name] + ) + if not _running_events[channel_name].is_connected( + "interrupted", self, "_on_event_finished" + ): + _running_events[channel_name].connect( + "interrupted", + self, + "_on_event_finished", + [channel_name] + ) + + if channel_name == "_front": + emit_signal( + "event_started", + _running_events[channel_name].name + ) + else: + emit_signal( + "background_event_started", + channel_name, + _running_events[channel_name].name + ) + _running_events[channel_name].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.event) + self.events_queue['_front'].append(event.event) -# Queue a new event to run +# Queue a new event to run in the foreground +# +# #### Parameters +# - event: Event to run func queue_event(event: ESCEvent) -> void: - events_queue.append(event) + self.events_queue['_front'].append(event) # Schedule an event to run after a timeout +# +# #### Parameters +# - event: Event to run +# - timeout: Number of seconds to wait before adding the event to the +# front queue func schedule_event(event: ESCEvent, timeout: float) -> void: scheduled_events.append(ESCScheduledEvent.new(event, timeout)) +# Queue the run of an event in a background channel +# +# #### Parameters +# - channel_name: Name of the channel to use +# - event: Event to run +func queue_background_event(channel_name: String, event: ESCEvent) -> void: + if not channel_name in events_queue: + events_queue[channel_name] = [] + + events_queue[channel_name].append(event) + + +# Interrupt the events currently running. +func interrupt_running_event(): + for channel_name in _running_events.keys(): + if _running_events[channel_name] != null: + _running_events[channel_name].interrupt() + + +# Clears the event queues. +func clear_event_queue(): + for channel_name in events_queue.keys(): + events_queue[channel_name].clear() + + +# Check wether a channel is free to run more events +# +# #### Parameters +# - name: Name of the channel to test +# **Returns** Wether the channel can currently accept a new event +func is_channel_free(name: String) -> bool: + return _channels_state[name] if name in _channels_state else true + + +# Get the currently running event in a channel +# +# #### Parameters +# - name: Name of the channel +# **Returns** The currently running event or null +func get_running_event(name: String) -> ESCEvent: + return _running_events[name] if name in _running_events else null + + # The event finished running -func _on_event_finished(return_code: int, event: ESCEvent) -> void: +# +# #### Parameters +# - return_code: Return code of the finished event +# - channel_name: Name of the channel that the event came from +func _on_event_finished(return_code: int, channel_name: String) -> void: + var event = _running_events[channel_name] escoria.logger.debug( "Event %s ended with return code %d" % [event.name, return_code] ) event.disconnect("finished", self, "_on_event_finished") event.disconnect("interrupted", self, "_on_event_finished") - _running_event = null - can_process_next_event = true - match(return_code): - ESCExecution.RC_CANCEL: - return_code = ESCExecution.RC_OK - emit_signal("event_finished", return_code, event.name) + + if return_code == ESCExecution.RC_CANCEL: + return_code = ESCExecution.RC_OK - -# Interrupt the event currently running. -func interrupt_running_event(): - if _running_event == null: - return - _running_event.interrupt() - -# Clears the event queue. -func clear_event_queue(): - events_queue.clear() + _running_events[channel_name] = null + _channels_state[channel_name] = true + + if channel_name == "_front": + emit_signal( + "event_finished", + return_code, + event.name + ) + else: + emit_signal( + "background_event_finished", + return_code, + event.name, + channel_name + ) 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 index ce1c50f1..2aab2db5 100644 --- 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 @@ -37,7 +37,8 @@ func prepare_arguments(arguments: Array) -> Array: for index in range(arguments.size()): complete_arguments[index] = escoria.utils.get_typed_value( - arguments[index] + arguments[index], + types[index] ) var strip = strip_quotes[0] if strip_quotes.size() == complete_arguments.size(): diff --git a/addons/escoria-core/game/core-scripts/esc_room.gd b/addons/escoria-core/game/core-scripts/esc_room.gd index eb1f0b64..7006f56c 100644 --- a/addons/escoria-core/game/core-scripts/esc_room.gd +++ b/addons/escoria-core/game/core-scripts/esc_room.gd @@ -118,9 +118,13 @@ func _ready(): # Performs the ESC script events "setup" and "ready", in this order, if they are # present. Also manages automatic transitions. func perform_script_events(): - if esc_script and escoria.event_manager._running_event == null \ - or (escoria.event_manager._running_event != null \ - and escoria.event_manager._running_event.name != "load"): + if esc_script and escoria.event_manager.is_channel_free("_front") \ + or ( + not escoria.event_manager.is_channel_free("_front") and \ + not escoria.event_manager.get_running_event( + "_front" + ).name == "load" + ): # If the room was loaded from change_scene and automatic transitions # are not disabled, do the transition out now @@ -159,10 +163,7 @@ func perform_script_events(): var ready_event_added: bool = false # Run the ready event, if there is one. - if escoria.event_manager._running_event == null \ - or (escoria.event_manager._running_event != null \ - and escoria.event_manager._running_event.name != "load"): - ready_event_added = _run_script_event("ready") + ready_event_added = _run_script_event("ready") if ready_event_added: # Wait for ready event to be done diff --git a/addons/escoria-core/game/core-scripts/esc_tooltip.gd b/addons/escoria-core/game/core-scripts/esc_tooltip.gd index 201f08a2..063a2e1a 100644 --- a/addons/escoria-core/game/core-scripts/esc_tooltip.gd +++ b/addons/escoria-core/game/core-scripts/esc_tooltip.gd @@ -1,9 +1,32 @@ # A tooltip displaying [] - tool extends RichTextLabel class_name ESCTooltip + +# Maximum width of the label +const MAX_WIDTH = 200 + +# Minimum height of the label +const MIN_HEIGHT = 30 + +# Maximum height of the label +const MAX_HEIGHT = 500 + +# Height of one line in the label +const ONE_LINE_HEIGHT = 16 + + +# Color of the label +export(Color) var color setget set_color + +# Vector2 defining the offset from the cursor +export(Vector2) var offset_from_cursor = Vector2(10,0) + +# Activates debug mode. If enabled, shows the label with a white background. +export(bool) var debug_mode = false setget set_debug_mode + + # Infinitive verb var current_action: String @@ -19,32 +42,11 @@ var current_target2: String # True if tooltip is waiting for a click on second target (use x with y) var waiting_for_target2 = false -# Color of the label -export(Color) var color setget set_color - -# Vector2 defining the offset from the cursor -export(Vector2) var offset_from_cursor = Vector2(10,0) - -# Activates debug mode. If enabled, shows the label with a white background. -export(bool) var debug_mode = false setget set_debug_mode - # Node containing the debug white background var debug_texturerect_node: TextureRect -# Maximum width of the label -const MAX_WIDTH = 200 -# Minimum height of the label -const MIN_HEIGHT = 30 - -# Maximum height of the label -const MAX_HEIGHT = 500 - -# Height of one line in the label -const ONE_LINE_HEIGHT = 16 - - -# Ready function +# Connect relevant functions func _ready(): escoria.main.connect("room_ready", self, "_on_room_ready") escoria.action_manager.connect("action_changed", self, "_on_action_selected") @@ -84,12 +86,6 @@ func set_debug_mode(p_debug_mode: bool): if debug_texturerect_node: remove_child(debug_texturerect_node) - -# Called when an action is selected in Escoria -func _on_action_selected() -> void: - current_action = escoria.action_manager.current_action - update_tooltip_text() - # Set the first target of the label. # @@ -122,22 +118,19 @@ func update_tooltip_text(): # Update the tooltip size according to the text. func update_size(): - ## RECT_SIZE ## + if not get_tree(): + # We're not in the tree anymore. Return + return + var rtl_width = rect_size.x var rtl_height = rect_size.y var content_height = get_content_height() var nb_visible_characters = visible_characters var nb_visible_lines = get_visible_line_count() -# printt("BEFORE", "text_height", content_height, "rtl_height", rect_size.y) - # if text is too long and is wrapped -# var nblines = float(get_content_height()) / float(ONE_LINE_HEIGHT) var nblines = nb_visible_lines if nblines >= 1: - - yield(get_tree(), "idle_frame") - yield(get_tree(), "idle_frame") var text_height = get_content_height() if text_height > MAX_HEIGHT: text_height = MAX_HEIGHT @@ -151,7 +144,6 @@ func update_size(): rect_size.x += 1 parent_width = rect_size.x - rect_size.y = text_height if rect_size.x >= MAX_WIDTH: @@ -162,7 +154,6 @@ func update_size(): anchor_right = 0.0 anchor_bottom = 0.0 anchor_left = 0.0 -# printt("AFTER", "text_height", get_content_height(), "rtl_height", rect_size.y) # Calculate the offset of the label depending on its position. @@ -235,3 +226,9 @@ func clear(): # Called when the room is loaded to setup the label. func _on_room_ready(): escoria.main.current_scene.game.tooltip_node = self + + +# Called when an action is selected in Escoria +func _on_action_selected() -> void: + current_action = escoria.action_manager.current_action + update_tooltip_text() diff --git a/addons/escoria-core/game/core-scripts/utils/esc_utils.gd b/addons/escoria-core/game/core-scripts/utils/esc_utils.gd index 6fa09c95..145718e5 100644 --- a/addons/escoria-core/game/core-scripts/utils/esc_utils.gd +++ b/addons/escoria-core/game/core-scripts/utils/esc_utils.gd @@ -37,8 +37,9 @@ func get_re_group(re_match: RegExMatch, group: String) -> String: # #### Parameters # # - value: The original value +# - type_hint: The type it should be # **Returns** The typed value according to the type inference -func get_typed_value(value: String): +func get_typed_value(value: String, type_hint = []): var regex_bool = RegEx.new() regex_bool.compile("^true|false$") var regex_float = RegEx.new() @@ -52,7 +53,9 @@ func get_typed_value(value: String): return int(value) elif regex_bool.search(value.to_lower()): return true if value.to_lower() == "true" else false - elif "," in value: + elif (typeof(type_hint) != TYPE_ARRAY and type_hint == TYPE_ARRAY) or \ + (typeof(type_hint) == TYPE_ARRAY and TYPE_ARRAY in type_hint) \ + and "," in value: return value.split(",") else: return str(value) diff --git a/game/rooms/room06/room06.tscn b/game/rooms/room06/room06.tscn index 1c710532..42add933 100644 --- a/game/rooms/room06/room06.tscn +++ b/game/rooms/room06/room06.tscn @@ -82,7 +82,6 @@ __meta__ = { } [node name="worker" parent="Hotspots" instance=ExtResource( 7 )] -pause_mode = 1 position = Vector2( 480, 430 ) esc_script = "res://game/rooms/room06/esc/worker.esc" interaction_direction = 2 diff --git a/game/rooms/room14/esc/room14.esc b/game/rooms/room14/esc/room14.esc index 3fa40dbf..15d298d5 100644 --- a/game/rooms/room14/esc/room14.esc +++ b/game/rooms/room14/esc/room14.esc @@ -27,3 +27,4 @@ :ready + queue_event worker moveworker diff --git a/game/rooms/room14/esc/worker.esc b/game/rooms/room14/esc/worker.esc new file mode 100644 index 00000000..abc1b11d --- /dev/null +++ b/game/rooms/room14/esc/worker.esc @@ -0,0 +1,2 @@ +:moveworker + walk worker worker_target diff --git a/game/rooms/room14/room14.tscn b/game/rooms/room14/room14.tscn index dc6810fd..1500bfe2 100644 --- a/game/rooms/room14/room14.tscn +++ b/game/rooms/room14/room14.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=11 format=2] +[gd_scene load_steps=12 format=2] [ext_resource path="res://addons/escoria-core/game/core-scripts/esc_terrain.gd" type="Script" id=1] [ext_resource path="res://game/rooms/room14/background.tscn" type="PackedScene" id=2] @@ -9,6 +9,7 @@ [ext_resource path="res://addons/escoria-core/game/core-scripts/esc_item.gd" type="Script" id=7] [ext_resource path="res://game/rooms/room14/r_door.tscn" type="PackedScene" id=8] [ext_resource path="res://game/items/escitems/button.tscn" type="PackedScene" id=9] +[ext_resource path="res://game/characters/worker/worker.tscn" type="PackedScene" id=10] [sub_resource type="NavigationPolygon" id=1] vertices = PoolVector2Array( 1168.92, 640.557, 1182.53, 588.863, 1269.59, 622.872, 1275.03, 799.721, 864.626, 613.518, 1143.08, 613.35, -9.16094, 803.802, 386.666, 618.012, 129.634, 615.792, 84.5821, 654.06, -6.44019, 711.297, 3.15687, 646.051, 59.2201, 628.698 ) @@ -149,3 +150,12 @@ align = 1 __meta__ = { "_edit_use_anchors_": false } + +[node name="worker" parent="." instance=ExtResource( 10 )] +position = Vector2( 204.268, 376.233 ) +esc_script = "res://game/rooms/room14/esc/worker.esc" + +[node name="worker_target" type="Position2D" parent="."] +position = Vector2( 970.51, 374.808 ) +script = ExtResource( 5 ) +global_id = "worker_target" diff --git a/project.godot b/project.godot index 921c7b32..f91ff660 100644 --- a/project.godot +++ b/project.godot @@ -390,6 +390,11 @@ _global_script_classes=[ { "path": "res://addons/escoria-core/game/core-scripts/esc/commands/play_snd.gd" }, { "base": "ESCBaseCommand", +"class": "QueueEventCommand", +"language": "GDScript", +"path": "res://addons/escoria-core/game/core-scripts/esc/commands/queue_event.gd" +}, { +"base": "ESCBaseCommand", "class": "QueueResourceCommand", "language": "GDScript", "path": "res://addons/escoria-core/game/core-scripts/esc/commands/queue_resource.gd" @@ -611,6 +616,7 @@ _global_script_class_icons={ "InventoryAddCommand": "", "InventoryRemoveCommand": "", "PlaySndCommand": "", +"QueueEventCommand": "", "QueueResourceCommand": "", "RandGlobalCommand": "", "RepeatCommand": "",