Updated escoria-demo-game

This commit is contained in:
2023-06-06 19:28:49 +02:00
parent 6d3bd3511c
commit 0d38a8646a
116 changed files with 1670 additions and 1291 deletions

View File

@@ -0,0 +1,62 @@
# `block_say`
#
# `say` commands called subsequent to using the `block_say` command will reuse the
# dialog box type of the previous `say` command if both dialog box types between the two `say`
# commands match.
#
# Different dialog box types can be used across multiple `say` commands, with the latest one
# used being preserved for reuse by the next `say` command should the dialog box type specified by
# both `say` commands match.
#
# This reuse will continue until a call to `end_block_say` is made.
#
# Using `block_say` more than once prior to calling `end_block_say` is idempotent and has the
# following behaviour:
#
# - If no `say` command has yet been encountered since the first use of `block_say`,
# the result of using this command will be as described above.
# - If a `say` command has been encountered since the previous use of `block_say`,
# the dialog box used with that `say` command will continue to be reused for subsequent
# `say` commands should the dialog box type requested match. Note that the dialog box used with
# the next `say` command may be different than the one currently being reused.
#
# Example:
# `block say`
# `say player "Picture's looking good."`
# `say player "And so am I."`
# `end_block_say`
#
# This example will reuse the same dialog box type since they are the same between both `say` calls.
#
# @ESC
extends ESCBaseCommand
class_name BlockSayCommand
# Constructor
func _init() -> void:
pass
# Return the descriptor of the arguments of this command
func configure() -> ESCCommandArgumentDescriptor:
return ESCCommandArgumentDescriptor.new(0)
# Validate whether the given arguments match the command descriptor
func validate(arguments: Array):
return true
# Run the command
func run(command_params: Array) -> int:
escoria.dialog_player.enable_preserve_dialog_box()
return ESCExecution.RC_OK
# Function called when the command is interrupted.
func interrupt():
escoria.logger.debug(
self,
"[%s] interrupt() function not implemented." % get_command_name()
)

View File

@@ -0,0 +1,47 @@
# `end_block_say`
#
# `say` commands used subsequent to using the `end_block_say` command will no longer
# reuse the dialog box type used by the previous `say` command(s) encountered.
#
# Using `end_block_say` more than once is safe and idempotent.
#
# Example:
# `block say`
# `say player "Picture's looking good."`
# `say player "And so am I."`
# `end_block_say`
#
# This example will reuse the same dialog box type since they are the same between both `say` calls.
#
# @ESC
extends ESCBaseCommand
class_name EndBlockSayCommand
# Constructor
func _init() -> void:
pass
# Return the descriptor of the arguments of this command
func configure() -> ESCCommandArgumentDescriptor:
return ESCCommandArgumentDescriptor.new(0)
# Validate whether the given arguments match the command descriptor
func validate(arguments: Array):
return true
# Run the command
func run(command_params: Array) -> int:
escoria.dialog_player.disable_preserve_dialog_box()
return ESCExecution.RC_OK
# Function called when the command is interrupted.
func interrupt():
escoria.logger.debug(
self,
"[%s] interrupt() function not implemented." % get_command_name()
)

View File

@@ -1,18 +1,11 @@
# `hide_menu menu_type [enable_automatic_transition]`
# `hide_menu menu_type`
#
# Hides either the main menu or the pause menu. The enable_automatic_transition
# parameter can be used to specify if Escoria manages the graphical transition
# for you or not.
# Setting `enable_automatic_transition` to false allows you to manage the
# transition effect for your room as it transitions in and out. Place a
# `transition` command in the room's `setup` event to manage the look of the
# transition in, and in the room's `exit_scene` event to manage the look of the
# transition out.
# Hides either the main menu or the pause menu. Transitions from the menu using
# the default transition type (set in the Escoria project settings).
#
# **Parameters**
#
# - *menu_type*: Which menu to hide. Can be either `main` or `pause` (default: `main`)
# - *enable_automatic_transition*: Whether to automatically transition from the menu (default: `false`)
#
# @ESC
extends ESCBaseCommand
@@ -23,8 +16,8 @@ class_name HideMenuCommand
func configure() -> ESCCommandArgumentDescriptor:
return ESCCommandArgumentDescriptor.new(
0,
[TYPE_STRING, TYPE_BOOL],
["main", false]
[TYPE_STRING],
["main"]
)
@@ -45,26 +38,26 @@ func validate(arguments: Array):
# Run the command
func run(command_params: Array) -> int:
var transition_id: int
if command_params[1]:
# Transition out from menu
transition_id = escoria.main.scene_transition.transition(
"",
ESCTransitionPlayer.TRANSITION_MODE.OUT
)
if transition_id != ESCTransitionPlayer.TRANSITION_ID_INSTANT:
while yield(
escoria.main.scene_transition,
"transition_done"
) != transition_id:
pass
# Transition out from menu
transition_id = escoria.main.scene_transition.transition(
"",
ESCTransitionPlayer.TRANSITION_MODE.OUT
)
if transition_id != ESCTransitionPlayer.TRANSITION_ID_INSTANT:
while yield(
escoria.main.scene_transition,
"transition_done"
) != transition_id:
pass
if command_params[0] == "main":
escoria.game_scene.hide_main_menu()
elif command_params[0] == "pause":
escoria.game_scene.unpause_game()
if command_params[1] and escoria.main.current_scene != null:
if escoria.main.current_scene != null:
transition_id = escoria.main.scene_transition.transition()
if transition_id != ESCTransitionPlayer.TRANSITION_ID_INSTANT:

View File

@@ -1,18 +1,11 @@
# `show_menu menu_type [enable_automatic_transition]`
# `show_menu menu_type`
#
# Shows either the main menu or the pause menu. The enable_automatic_transition
# parameter can be used to specify if Escoria manages the graphical transition to
# the menu or not.
# Setting `enable_automatic_transition` to false allows you to manage the
# transition effect for your menu as it transitions in and out. Place a
# `transition` command in the menu's `setup` event to manage the look of the
# transition in, and in the menu's `exit_scene` event to manage the look of the
# transition out.
# Shows either the main menu or the pause menu. Transitions to the menu using
# the default transition type (set in the Escoria project settings).
#
# **Parameters**
#
# - *menu_type*: Which menu to show. Can be either `main` or `pause` (default: `main`)
# - *enable_automatic_transition*: Whether to automatically transition to the menu (default: `false`)
#
# @ESC
extends ESCBaseCommand
@@ -23,8 +16,8 @@ class_name ShowMenuCommand
func configure() -> ESCCommandArgumentDescriptor:
return ESCCommandArgumentDescriptor.new(
0,
[TYPE_STRING, TYPE_BOOL],
["main", false]
[TYPE_STRING],
["main"]
)
@@ -47,39 +40,33 @@ func run(command_params: Array) -> int:
if not escoria.game_scene.is_inside_tree():
escoria.add_child(escoria.game_scene)
if command_params[1]:
# Transition out from current scene
var transition_id = escoria.main.scene_transition.transition(
"",
ESCTransitionPlayer.TRANSITION_MODE.OUT
)
# Transition out from current scene
var transition_id = escoria.main.scene_transition.transition(
"",
ESCTransitionPlayer.TRANSITION_MODE.OUT
)
if transition_id != ESCTransitionPlayer.TRANSITION_ID_INSTANT:
while yield(
escoria.main.scene_transition,
"transition_done"
) != transition_id:
pass
if transition_id != ESCTransitionPlayer.TRANSITION_ID_INSTANT:
while yield(
escoria.main.scene_transition,
"transition_done"
) != transition_id:
pass
if command_params[0] == "main":
escoria.game_scene.show_main_menu()
elif command_params[0] == "pause":
escoria.game_scene.pause_game()
if command_params[0] == "main":
escoria.game_scene.show_main_menu()
elif command_params[0] == "pause":
escoria.game_scene.pause_game()
# Transition in to menu
transition_id = escoria.main.scene_transition.transition()
# Transition in to menu
transition_id = escoria.main.scene_transition.transition()
if transition_id != ESCTransitionPlayer.TRANSITION_ID_INSTANT:
while yield(
escoria.main.scene_transition,
"transition_done"
) != transition_id:
pass
else:
if command_params[0] == "main":
escoria.game_scene.show_main_menu()
elif command_params[0] == "pause":
escoria.game_scene.pause_game()
if transition_id != ESCTransitionPlayer.TRANSITION_ID_INSTANT:
while yield(
escoria.main.scene_transition,
"transition_done"
) != transition_id:
pass
return ESCExecution.RC_OK

View File

@@ -117,5 +117,6 @@ func interrupt():
tween.stop_all()
func _on_tween_completed(tween: Tween):
tween.queue_free()
func _on_tween_completed(tween: Tween, _key: NodePath):
if tween:
tween.queue_free()

View File

@@ -75,11 +75,6 @@ var action_state = ACTION_INPUT_STATE.AWAITING_VERB_OR_ITEM \
#
# - action: type of the action to run
# - params: Parameters for the action
# - BACKGROUND_CLICK: [moving_obj, target, walk_fast]
# - ITEM_LEFT_CLICK: [item, input_event]
# - ITEM_RIGHT_CLICK: [item, input_event]
# - TRIGGER_IN: [trigger_id, object_id, trigger_in_verb]
# - TRIGGER_OUT: [trigger_id, object_id, trigger_out_verb]
# - can_interrupt: if true, this command will interrupt any ongoing event
# before it is finished
func do(action: int, params: Array = [], can_interrupt: bool = false) -> void:

View File

@@ -121,6 +121,12 @@ func register_object(object: ESCObject, room: ESCRoom = null, force: bool = fals
if room == null or room.global_id.empty():
# We duplicate the key so as to not hold a reference when current_room_key
# changes.
if current_room_key.room_global_id.empty():
escoria.logger.error(
self,
"The current room has no Global ID.\n" +
"Please set the ESCRoom's Global ID property."
)
room_key.room_global_id = current_room_key.room_global_id
room_key.room_instance_id = current_room_key.room_instance_id

View File

@@ -89,7 +89,7 @@ func change_scene(room_path: String, enable_automatic_transitions: bool) -> void
if escoria.dialog_player:
escoria.dialog_player.interrupt()
escoria.inputs_manager.hover_stack_clear()
escoria.inputs_manager.hover_stack.clear()
# Check if game scene was loaded
if not escoria.game_scene:

View File

@@ -39,7 +39,7 @@ func run() -> int:
if _is_interrupted:
final_rc = ESCExecution.RC_INTERRUPTED
statement.interrupt()
emit_signal("interrupted", self, final_rc)
emit_signal("interrupted", self, statement, final_rc)
return final_rc
if statement.is_valid():

View File

@@ -74,6 +74,10 @@ func _enter_tree():
func _ready():
mouse_filter = MOUSE_FILTER_IGNORE
# If background has no texture, set its rect size to viewport size
if texture == null and rect_size == Vector2.ZERO:
rect_size = escoria.game_size
if !Engine.is_editor_hint():
escoria.inputs_manager.register_background(self)

View File

@@ -46,6 +46,9 @@ var tooltip_node: Object
# function of game.gd script.
var room_ready_for_inputs: bool = false
# Displayer node for hover stack debugging
var hover_stack_displayer
# Function called when ESCGame enters the scene tree.
func _enter_tree():
@@ -59,13 +62,21 @@ func _enter_tree():
self,
"_on_action_finished"
)
escoria.main.connect(
"room_ready",
self,
"_on_room_ready"
)
# Debug display for hover stack
if ProjectSettings.get_setting(ESCProjectSettingsManager.ENABLE_HOVER_STACK_VIEWER) and \
get_node_or_null("hover_stack_layer") == null:
hover_stack_displayer = preload(
"res://addons/escoria-core/ui_library/tools/hover_stack/hover_stack.tscn"
).instance()
add_child(hover_stack_displayer)
escoria.inputs_manager.hover_stack.connect("hover_stack_changed", hover_stack_displayer, "update")
# Function called when ESCGame exits the scene tree.
func _exit_tree():

View File

@@ -300,7 +300,7 @@ func _ready():
# the top level of overlapping stack.
func _on_mouse_exited():
if escoria.inputs_manager.hover_stack.has(self):
escoria.inputs_manager.hover_stack_erase_item(self)
escoria.inputs_manager.hover_stack.erase_item(self)
escoria.inputs_manager.unset_hovered_node(self)
@@ -321,7 +321,7 @@ class HoverStackSorter:
func _on_input_event(_viewport: Object, event: InputEvent, _shape_idx: int):
if event is InputEventMouseMotion:
var physics2d_dss: Physics2DDirectSpaceState = get_world_2d().direct_space_state
var colliding: Array = physics2d_dss.intersect_point(get_global_mouse_position(), 32, [], 0x7FFFFFFF, true, true)
var colliding: Array = physics2d_dss.intersect_point(get_global_mouse_position(), 32, [], 0x7FFFFFFF, true, true)
var colliding_nodes = []
for c in colliding:
if c.collider.get("global_id") \
@@ -331,8 +331,8 @@ func _on_input_event(_viewport: Object, event: InputEvent, _shape_idx: int):
if colliding_nodes.empty():
return
colliding_nodes.sort_custom(HoverStackSorter, "sort_ascending_z_index")
escoria.inputs_manager.hover_stack_clear()
escoria.inputs_manager.hover_stack_add_items(colliding_nodes)
escoria.inputs_manager.hover_stack.clear()
escoria.inputs_manager.hover_stack.add_items(colliding_nodes)
escoria.inputs_manager.set_hovered_node(colliding_nodes.back())
@@ -587,7 +587,7 @@ func teleport_to(target: Vector2) -> void:
escoria.logger.warn(
self,
"Node %s cannot \"teleport_to\". Its \"is_movable\" parameter is false." %self
)
)
# Use the movable node to make the item walk to the given position
@@ -603,7 +603,7 @@ func walk_to(pos: Vector2, p_walk_context: ESCWalkContext = null) -> void:
escoria.logger.warn(
self,
"Node %s cannot use \"walk_to\". Its \"is_movable\" parameter is false." %self
)
)
# Stop the movable node immediately and remain where it is at this moment,
@@ -667,7 +667,7 @@ func set_angle(deg: int, wait: float = 0.0):
escoria.logger.warn(
self,
"Node %s cannot use \"set_angle\". Its \"is_movable\" parameter is false." % self
)
)
# Turn to face another object
@@ -718,13 +718,13 @@ func check_talk_possible():
)
return false
return true
# Play the talking animation
func start_talking():
if not check_talk_possible():
return
var animation_player = get_animation_player()
if animation_player.is_playing():
@@ -744,7 +744,7 @@ func start_talking():
func stop_talking():
if not check_talk_possible():
return
var animation_player = get_animation_player()
if animation_player.is_playing():

View File

@@ -3,15 +3,23 @@
# automatically use an `ESCLocation` that is a child of the destination node.
# Commands like `turn_to`--which are not movement-based--will ignore child
# `ESCLocation`s and refer to the parent node.
tool
extends Position2D
class_name ESCLocation, "res://addons/escoria-core/design/esc_location.svg"
signal is_start_location_set
const MULTIPLE_START_LOCATIONS_WARNING = \
"Only 1 ESCLocation should have is_start_location set to true in an ESCRoom"
# The global ID of this item
export(String) var global_id
# If true, this ESCLocation is considered as a player start location
export(bool) var is_start_location = false
export(bool) var is_start_location = false setget set_is_start_location
# If true, player orients towards 'interaction_direction' as
# player character arrives.
@@ -22,6 +30,9 @@ export(bool) var player_orients_on_arrival = true
export(int) var interaction_direction
var _multiple_start_locations_exist: bool = false setget set_multiple_locations_exist
# Used by "is" keyword to check whether a node's class_name
# is the same as p_classname.
#
@@ -34,10 +45,36 @@ func is_class(p_classname: String) -> bool:
# Ready function
func _ready():
if not self.global_id.empty():
escoria.object_manager.register_object(
ESCObject.new(
self.global_id,
self
if not Engine.is_editor_hint():
if not self.global_id.empty():
escoria.object_manager.register_object(
ESCObject.new(
self.global_id,
self
)
)
)
func _exit_tree():
if Engine.is_editor_hint():
if is_start_location:
emit_signal("is_start_location_set", self)
func _get_configuration_warning():
if _multiple_start_locations_exist:
return MULTIPLE_START_LOCATIONS_WARNING
return ""
func set_multiple_locations_exist(value: bool) -> void:
_multiple_start_locations_exist = value
update_configuration_warning()
func set_is_start_location(value: bool) -> void:
is_start_location = value
if Engine.is_editor_hint() and is_instance_valid(get_owner()):
emit_signal("is_start_location_set")

View File

@@ -1,47 +1,24 @@
# A cache for resources
extends Reference
extends Node
class_name ESCResourceCache
var thread: Thread
var mutex: Mutex
var sem: Semaphore
var queue: Array = []
var pending: Dictionary = {}
signal resource_loading_progress(path, progress)
signal resource_loading_done(path)
signal resource_queue_progress(queue_size)
#warning-ignore:unused_argument
func _lock(caller):
mutex.lock()
#warning-ignore:unused_argument
func _unlock(caller):
mutex.unlock()
#warning-ignore:unused_argument
func _post(caller):
sem.post()
#warning-ignore:unused_argument
func _wait(caller):
sem.wait()
var queue: Array = []
var pending: Dictionary = {}
func queue_resource(path: String, p_in_front: bool = false, p_permanent: bool = false):
_lock("queue_resource")
if path in pending:
_unlock("queue_resource")
return
elif ResourceLoader.has(path):
var res = ResourceLoader.load(path)
pending[path] = ESCResourceDescriptor.new(res, p_permanent)
_unlock("queue_resource")
return
else:
var res = ResourceLoader.load_interactive(path)
res.set_meta("path", path)
@@ -50,21 +27,16 @@ func queue_resource(path: String, p_in_front: bool = false, p_permanent: bool =
else:
queue.push_back(res)
pending[path] = ESCResourceDescriptor.new(res, p_permanent)
_post("queue_resource")
_unlock("queue_resource")
return
func cancel_resource(path):
_lock("cancel_resource")
if path in pending:
if pending[path].res is ResourceInteractiveLoader:
queue.erase(pending[path].res)
pending.erase(path)
_unlock("cancel_resource")
func clear():
_lock("clear")
for p in pending.keys():
if pending[p].permanent:
continue
@@ -72,11 +44,8 @@ func clear():
#queue = []
#pending = {}
_unlock("clear")
func get_progress(path):
_lock("get_progress")
var ret = -1
if path in pending:
if pending[path].res is ResourceInteractiveLoader:
@@ -85,36 +54,32 @@ func get_progress(path):
ret = 1.0
emit_signal("resource_loading_done", path)
emit_signal("resource_loading_progress", path, ret)
_unlock("get_progress")
return ret
func is_ready(path):
var ret
_lock("is_ready")
if path in pending:
ret = !(pending[path].res is ResourceInteractiveLoader)
else:
ret = false
_unlock("is_ready")
return ret
func _wait_for_resource(res, path):
_unlock("wait_for_resource")
while true:
#VisualServer.call("sync") # workaround because sync is a keyword
VisualServer.force_sync()
OS.delay_usec(16000) # wait 1 frame
_lock("wait_for_resource")
if queue.size() == 0 || queue[0] != res:
return pending[path].res
_unlock("wait_for_resource")
func get_resource(path):
_lock("get_resource")
if path in pending:
if pending[path].res is ResourceInteractiveLoader:
var res = pending[path].res
@@ -127,17 +92,16 @@ func get_resource(path):
if !pending[path].permanent:
pending.erase(path)
_unlock("return")
return res
else:
var res = pending[path].res
if !pending[path].permanent:
pending.erase(path)
_unlock("return")
return res
else:
_unlock("return")
# We can't use ESCProjectSettingsManager here since this method
# can be called from escoria._init()
if not ProjectSettings.get_setting("escoria/platform/skip_cache"):
@@ -146,17 +110,32 @@ func get_resource(path):
return res
return ResourceLoader.load(path)
func thread_process():
_wait("thread_process")
_lock("process")
func print_progress(p_path, p_progress):
printt(p_path, "loading", round(p_progress * 100), "%")
func res_loaded(p_path):
printt("loaded resource", p_path)
func print_queue_progress(p_queue_size):
printt("queue size:", p_queue_size)
func start():
pass
## Uncomment these for debug, or wait for someone to implement log levels
# connect("resource_loading_progress", self, "print_progress")
# connect("resource_loading_done", self, "res_loaded")
# connect("resource_queue_progress", self, "print_queue_progress")
func _process(_delta) -> void:
while queue.size() > 0:
var res = queue[0]
_unlock("process_poll")
var ret = res.poll()
_lock("process_check_queue")
var path = res.get_meta("path")
if ret == ERR_FILE_EOF || ret != OK:
@@ -166,33 +145,3 @@ func thread_process():
queue.erase(res) # something might have been put at the front of the queue while we polled, so use erase instead of remove
emit_signal("resource_queue_progress", queue.size())
get_progress(path)
_unlock("process")
#warning-ignore:unused_argument
func thread_func(u):
while true:
thread_process()
func print_progress(p_path, p_progress):
printt(p_path, "loading", round(p_progress * 100), "%")
func res_loaded(p_path):
printt("loaded resource", p_path)
func print_queue_progress(p_queue_size):
printt("queue size:", p_queue_size)
func start():
mutex = Mutex.new()
sem = Semaphore.new()
thread = Thread.new()
thread.start(self, "thread_func", 0)
## Uncomment these for debug, or wait for someone to implement log levels
# connect("resource_loading_progress", self, "print_progress")
# connect("resource_loading_done", self, "res_loaded")
# connect("resource_queue_progress", self, "print_queue_progress")

View File

@@ -12,6 +12,8 @@ enum EditorRoomDebugDisplay {
CAMERA_LIMITS
}
const ESC_BACKGROUND_NAME = "escbackground"
# The global id of this room
export(String) var global_id = ""
@@ -66,8 +68,26 @@ func _ready():
) != self.filename:
is_run_directly = true
if not Engine.is_editor_hint():
escoria.room_manager.init_room(self)
if Engine.is_editor_hint():
_connect_location_nodes()
_validate_start_locations()
return
# If room has no ESCBackground child, add one
var found_escbackground: bool = false
for child in get_children():
if child is ESCBackground:
found_escbackground = true
move_child(child, 0)
if not found_escbackground:
var esc_bg = ESCBackground.new()
esc_bg.name = ESC_BACKGROUND_NAME
if not camera_limits.empty():
esc_bg.set_size(camera_limits.front().size)
add_child(esc_bg)
move_child(esc_bg, 0)
escoria.room_manager.init_room(self)
# Draw the camera limits visualization if enabled
@@ -97,6 +117,56 @@ func _draw():
camera_limits[i].position.y + 30), str(i), camera_limits_colors[i])
# Listen for any signals from ESCLocation indicating that the is_start_location attribute
# has been set/unset in order to update start location validation.
func _connect_location_nodes() -> void:
_connect_location_nodes_in_tree(self)
func _connect_location_nodes_in_tree(node: Node):
for n in node.get_children():
if n is ESCLocation:
if not n.is_connected("is_start_location_set", self, "_validate_start_locations"):
n.connect("is_start_location_set", self, "_validate_start_locations")
if n.get_child_count() > 0:
_connect_location_nodes_in_tree(n)
# Validate that we only have one start location for this scene. If we don't, call it out in the
# scene tree via configuration warnings.
#
# We may have to ignore a node if it's being removed/deleted from the scene tree.
func _validate_start_locations(to_ignore: ESCLocation = null):
var esc_locations: Array = _find_esc_locations(self)
var num_start_locations: int = 0
for n in esc_locations:
if n == to_ignore:
continue
num_start_locations += 1 if n.is_start_location else 0
for n in esc_locations:
if n == to_ignore:
continue
n.set_multiple_locations_exist(n.is_start_location and num_start_locations > 1)
func _find_esc_locations(node: Node) -> Array:
var esc_locations: Array = []
for n in node.get_children():
if n is ESCLocation:
esc_locations.append(n)
if n.get_child_count() > 0:
esc_locations.append_array(_find_esc_locations(n))
return esc_locations
# Set the camera limits
#
# #### Parameters

View File

@@ -4,12 +4,12 @@ class_name ESCResourceDescriptor
# The resource being described
var res: Resource
var res
# Whether the resource is permanent
var permanent: bool
func _init(res_in: Resource, permanent_in: bool) -> void:
func _init(res_in, permanent_in: bool) -> void:
res = res_in
permanent = permanent_in