Fix when ESCPlayer is not selectable or interactive manage click through

This commit is contained in:
Julian Murgia
2022-11-21 13:58:27 +01:00
parent 23b5de6ac2
commit a131efcfc2
21 changed files with 958 additions and 363 deletions

View File

@@ -100,7 +100,17 @@ func run(command_params: Array) -> int:
"[%s]: No dialog player was registered and the say command was encountered."
% get_command_name()
)
escoria.current_state = escoria.GAME_STATE.DEFAULT
return ESCExecution.RC_ERROR
if not escoria.main.current_scene.player:
escoria.logger.warn(
self,
"[%s]: No player item in the current scene was registered and the say command was encountered."
% get_command_name()
)
escoria.current_state = escoria.GAME_STATE.DEFAULT
return ESCExecution.RC_CANCEL
# Replace the names of any globals in "{ }" with their value
command_params[1] = escoria.globals_manager.replace_globals(command_params[1])

View File

@@ -748,13 +748,13 @@ func _is_object_actionable(obj: ESCObject) -> bool:
return false
if not obj.active:
escoria.logger.debug(
escoria.logger.trace(
self,
"Item %s is not active." % obj.global_id
)
object_is_actionable = false
elif not obj.interactive:
escoria.logger.debug(
escoria.logger.trace(
self,
"Item %s is not interactive." % obj.global_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.clear_stack()
escoria.inputs_manager.hover_stack_clear()
# Check if game scene was loaded
if not escoria.game_scene:

View File

@@ -97,6 +97,9 @@ func _get_interactive() -> bool:
func _set_interactive(value: bool):
if "is_interactive" in self.node:
self.node.is_interactive = value
if not value:
escoria.game_scene.clear_tooltip()
escoria.inputs_manager.on_item_non_interactive(self.node)
# Return the data of the object to be inserted in a savegame file.

View File

@@ -111,6 +111,12 @@ func _draw():
draw_rect(mouse_limits, ColorN("red"), false, 10.0)
# Clears the tooltip content (if an ESCTooltip node exists in UI)
func clear_tooltip():
if tooltip_node != null:
(tooltip_node as ESCTooltip).clear()
# Sets up and performs default walking action
#
# #### Parameters

View File

@@ -188,8 +188,8 @@ func _ready():
validate_animations(animations)
if not self.is_connected("mouse_entered", self, "_on_mouse_entered"):
connect("mouse_entered", self, "_on_mouse_entered")
if not self.is_connected("input_event", self, "_on_input_event"):
connect("input_event", self, "_on_input_event")
if not self.is_connected("mouse_exited", self, "_on_mouse_exited"):
connect("mouse_exited", self, "_on_mouse_exited")
@@ -294,6 +294,47 @@ func _ready():
_movable.last_scale = scale
_movable.update_terrain()
# Mouse exited happens on any item that mouse cursor exited, even those UNDER
# 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.unset_hovered_node(self)
class HoverStackSorter:
static func sort_ascending_z_index(a, b):
if a.z_index < b.z_index:
return true
return false
# Manage input events on the item
#
# #### Parameters
#
# - _viewport: the viewport node the event entered
# - event: the input event
# - _shape_idx is the child index of the clicked Shape2D.
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(event.global_position, 32, [], 0x7FFFFFFF, true, true)
var colliding_nodes = []
for c in colliding:
if c.collider.get("global_id") \
and escoria.action_manager.is_object_actionable(c.collider.global_id):
colliding_nodes.push_back(c.collider)
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.set_hovered_node(colliding_nodes.back())
# Manage mouse button clicks on this item by sending out signals
#
# #### Parameters
@@ -334,7 +375,7 @@ func _unhandled_input(input_event: InputEvent) -> void:
)
return
var p = get_global_mouse_position()
if _is_in_shape(p):
if _is_in_shape(p) and escoria.action_manager.is_object_actionable(global_id):
if event.doubleclick and event.button_index == BUTTON_LEFT:
emit_signal("mouse_double_left_clicked_item", self, event)
get_tree().set_input_as_handled()
@@ -472,12 +513,13 @@ func get_interact_position() -> Vector2:
# React to the mouse entering the item by emitting the respective signal
func _on_mouse_entered():
emit_signal("mouse_entered_item", self)
func mouse_entered():
if escoria.action_manager.is_object_actionable(global_id):
emit_signal("mouse_entered_item", self)
# React to the mouse exiting the item by emitting the respective signal
func _on_mouse_exited():
func mouse_exited():
emit_signal("mouse_exited_item", self)

View File

@@ -22,5 +22,3 @@ func _ready():
._ready()
else:
tooltip_name = ""
if is_connected("input_event", self, "manage_input"):
disconnect("input_event", self, "manage_input")

View File

@@ -48,6 +48,27 @@ var hotspot_focused: String = ""
# **Returns** Whether the function processed the event.
var custom_input_handler = null
# The currently hovered element. Usually the one on top of the hover stack.
var _hovered_element = null
# Constructor
func _init():
escoria.event_manager.connect("event_finished", self, "_on_event_finished")
# Called when an event is finished, so that the current hotspot is reset
#
# #### Parameters
#
# - return_code: The return code of the event
# - event_name: the name of the event
#
func _on_event_finished(return_code: int, event_name: String):
if _hovered_element == null:
hotspot_focused = ""
# Register core signals (from escoria.gd)
func register_core():
escoria.game_scene.connect(
@@ -154,9 +175,44 @@ func try_custom_input_handler(event: InputEvent, is_default_state: bool) -> bool
return false
# Clear the stack of hovered items
func clear_stack():
hover_stack = []
# Unsets the hovered node.
#
# **Parameters**
#
# - item: the item that was unfocused (mouse_exited)
func unset_hovered_node(item: ESCItem):
if _hovered_element == item:
_hovered_element.mouse_exited()
_hovered_element = null
hotspot_focused = ""
# Sets the hovered node and calls its mouse_entered() method if it was the top
# most item in hover_stack.
#
# #### Parameters
#
# - item: the item that was focused (mouse_entered)
#
# **Returns**
# True if item is the new top hovered object
func set_hovered_node(item: ESCItem) -> bool:
# If tested item was already hovered
# or is not actionable (not selectable for ESCPlayer) then do nothing
if _hovered_element == item \
or not escoria.action_manager.is_object_actionable(item.global_id) \
or (item is ESCPlayer and not (item as ESCPlayer).selectable):
return true
# Else if the tested item is on top of hover stack (or null)
# Set that item as hovered and call that item's mouse_entered()
if _hovered_element == null or hover_stack.back() != item:
_hovered_element = item
_hovered_element.mouse_entered()
return true
# Else, the tested item is currently on top of hover stack, then do nothing
else:
return false
# The background was clicked with the LMB
@@ -165,7 +221,8 @@ func clear_stack():
#
# - position: Position of the click
func _on_left_click_on_bg(position: Vector2) -> void:
if input_mode == INPUT_ALL and hotspot_focused.empty():
if input_mode == INPUT_ALL: # and hotspot_focused.empty():
hotspot_focused = ""
escoria.logger.info(
self,
"Left click on background at %s." % str(position)
@@ -179,7 +236,8 @@ func _on_left_click_on_bg(position: Vector2) -> void:
#
# - position: Position of the click
func _on_double_left_click_on_bg(position: Vector2) -> void:
if input_mode == INPUT_ALL and hotspot_focused.empty():
if input_mode == INPUT_ALL: # and hotspot_focused.empty():
hotspot_focused = ""
escoria.logger.info(
self,
"Double left click on background at %s." % str(position)
@@ -295,31 +353,32 @@ func _on_mouse_exited_inventory_item() -> void:
# - item: The Escoria item hovered
func _on_mouse_entered_item(item: ESCItem) -> void:
if item as ESCPlayer and not (item as ESCPlayer).selectable:
escoria.logger.debug(
self,
"Ignoring mouse entering player %s: Player not selectable." % [item.global_id]
)
return
escoria.logger.trace(
self,
"Ignoring mouse entering player %s: Player not selectable." % [item.global_id]
)
if hover_stack.empty():
hotspot_focused = ""
escoria.main.current_scene.game.element_unfocused()
else:
hotspot_focused = hover_stack.back().global_id
escoria.main.current_scene.game.element_focused(hotspot_focused)
return
if not escoria.action_manager.is_object_actionable(item.global_id):
escoria.logger.debug(
self,
"Ignoring mouse entering item %s." % [item.global_id]
)
return
escoria.logger.info(
self,
"Item focused: %s" % item.global_id
)
_clean_hover_stack()
hover_stack.push_back(item)
hover_stack.sort_custom(HoverStackSorter, "sort_ascending_z_index")
hotspot_focused = hover_stack.back().global_id
escoria.main.current_scene.game.element_focused(hotspot_focused)
hotspot_focused = item.global_id
escoria.main.current_scene.game.element_focused(item.global_id)
# The mouse exited an Escoria item
@@ -328,11 +387,23 @@ func _on_mouse_entered_item(item: ESCItem) -> void:
#
# - item: The Escoria item hovered
func _on_mouse_exited_item(item: ESCItem) -> void:
var object: ESCObject = escoria.object_manager.get_object(item.global_id)
if object and not object.interactive:
hover_stack_erase_item(item)
escoria.main.current_scene.game.element_unfocused()
return
if object and not object.interactive:
return
if object and object.node is ESCPlayer and not (object.node as ESCPlayer).selectable:
hotspot_focused = ""
return
escoria.logger.info(
self,
"Item unfocused: %s" % item.global_id
"Item unfocused: %s" % hotspot_focused
)
_hover_stack_erase_item(item)
if hover_stack.empty():
hotspot_focused = ""
escoria.main.current_scene.game.element_unfocused()
@@ -341,6 +412,25 @@ func _on_mouse_exited_item(item: ESCItem) -> void:
escoria.main.current_scene.game.element_focused(hotspot_focused)
# Function called when the item is set interactive, to re-trigger an input on
# underlying item.
#
# #### Parameters
#
# - item: The ESCCItem that was set non-interactive
func on_item_non_interactive(item: ESCItem) -> void:
var object: ESCObject = escoria.object_manager.get_object(item.global_id)
if object and not object.interactive:
hover_stack_erase_item(item)
escoria.main.current_scene.game.element_unfocused()
hover_stack.sort_custom(HoverStackSorter, "sort_ascending_z_index")
if hover_stack.empty():
return
else:
var new_item = hover_stack.back()
escoria.action_manager.set_action_input_state(ESCActionManager.ACTION_INPUT_STATE.AWAITING_VERB_OR_ITEM)
new_item.mouse_entered()
# An Escoria item was clicked with the LMB
#
# #### Parameters
@@ -349,39 +439,37 @@ func _on_mouse_exited_item(item: ESCItem) -> void:
# - event: The input event from the click
func _on_mouse_left_clicked_item(item: ESCItem, event: InputEvent) -> void:
if input_mode == INPUT_ALL:
# Manage clicking through ESCPlayer (if ESCPlayer.selectable is false)
if item as ESCPlayer and not (item as ESCPlayer).selectable:
escoria.logger.debug(
escoria.logger.trace(
self,
"Ignoring left click on player %s: Player not selectable." % [item.global_id]
"Ignoring left click on player %s: Player not selectable."
% [item.global_id]
)
# Get next object in hover stack and forward event to it
if not hover_stack.empty():
var next_item = hover_stack.pop_back()
_on_mouse_left_clicked_item(next_item, event)
else: # if no next object, consider this click as background click
hotspot_focused = ""
_on_left_click_on_bg(event.position)
return
if not escoria.action_manager.is_object_actionable(item.global_id):
escoria.logger.debug(
self,
"Ignoring left click on %s with event %s." % [item.global_id, event]
)
# Treat this as a background click now
# Clicked object can't be actioned and there is no other object behind
# We consider this click as a background click
if not escoria.action_manager.is_object_actionable(item.global_id) \
and hover_stack.empty():
hotspot_focused = ""
_on_left_click_on_bg(event.position)
return
if hover_stack.empty() or hover_stack.back() == item:
escoria.logger.info(
self,
"Item %s left clicked with event %s." % [item.global_id, event]
)
hotspot_focused = item.global_id
escoria.main.current_scene.game.left_click_on_item(
item.global_id,
event
)
# Finally, execute the action on the ESCItem
hotspot_focused = item.global_id
escoria.main.current_scene.game.left_click_on_item(
item.global_id,
event
)
# An Escoria item was double-clicked with the LMB
@@ -395,33 +483,32 @@ func _on_mouse_left_double_clicked_item(
event: InputEvent
) -> void:
if input_mode == INPUT_ALL:
# Manage clicking through ESCPlayer (if ESCPlayer.selectable is false)
if item as ESCPlayer and not (item as ESCPlayer).selectable:
escoria.logger.debug(
escoria.logger.trace(
self,
"Ignoring double-left click on player %s: Player not selectable." % [item.global_id]
"Ignoring double left click on player %s: Player not selectable."
% [item.global_id]
)
# Get next object in hover stack and forward event to it
if not hover_stack.empty():
var next_item = hover_stack.pop_back()
_on_mouse_left_double_clicked_item(next_item, event)
else: # if no next object, consider this click as background click
hotspot_focused = ""
_on_double_left_click_on_bg(event.position)
return
if not escoria.action_manager.is_object_actionable(item.global_id):
escoria.logger.debug(
self,
"Ignoring double-left click on %s with event %s." % [item.global_id, event]
)
# Treat this as a background click now
# Clicked object can't be actioned and there is no other object behind
# We consider this click as a background click
if not escoria.action_manager.is_object_actionable(item.global_id) \
and hover_stack.empty():
hotspot_focused = ""
_on_double_left_click_on_bg(event.position)
return
escoria.logger.info(
self,
"Item %s left double clicked with event %s." % [item.global_id, event]
)
# Finally, execute the action on the ESCItem
hotspot_focused = item.global_id
escoria.main.current_scene.game.left_double_click_on_item(
item.global_id,
@@ -446,29 +533,46 @@ func _on_mouse_right_clicked_item(item: ESCItem, event: InputEvent) -> void:
if not hover_stack.empty():
var next_item = hover_stack.pop_back()
_on_mouse_right_clicked_item(next_item, event)
return
if not escoria.action_manager.is_object_actionable(item.global_id):
escoria.logger.debug(
if not escoria.action_manager.is_object_actionable(item.global_id) \
and hover_stack.empty():
# Treat this as a background click now
hotspot_focused = ""
_on_right_click_on_bg(event.position)
return
var actual_item
# We check if the clicked object is ESCPlayer and not selectable. If so
# we consider we clicked through it.
var object: ESCObject = escoria.object_manager.get_object(item.global_id)
if object.node is ESCPlayer and not (object.node as ESCPlayer).selectable:
actual_item = hover_stack.back()
else:
actual_item = item
if actual_item == null:
if event.position:
(escoria.main.current_scene.game as ESCGame).right_click_on_bg(event.position)
else:
escoria.logger.info(
self,
"Clicked item %s with event %s cannot be activated (player not selectable or not interactive).\n"
% [item.global_id, event] +
"No valid item found in the items stack. Action cancelled."
)
else:
escoria.logger.info(
self,
"Ignoring right click on %s with event %s." % [item.global_id, event]
"Item %s right clicked with event %s." % [actual_item.global_id, event]
)
hotspot_focused = actual_item.global_id
escoria.main.current_scene.game.right_click_on_item(
actual_item.global_id,
event
)
# Treat this as a background click now
_on_right_click_on_bg(event.position)
return
escoria.logger.info(
self,
"Item %s right clicked with event %s." % [item.global_id, event]
)
hotspot_focused = item.global_id
escoria.main.current_scene.game.right_click_on_item(
item.global_id,
event
)
# The mousewheel was turned
@@ -485,6 +589,27 @@ func _on_pause_menu_requested():
escoria.main.current_scene.game.pause_game()
# Add the given item to the stack if not already in it.
#
# #### Parameters
# - item: the item to add to the hover stack
func hover_stack_add_item(item):
if item is ESCPlayer and not (item as ESCPlayer).selectable:
return
if not hover_stack.has(item):
hover_stack.push_back(item)
hover_stack.sort_custom(HoverStackSorter, "sort_ascending_z_index")
# Add the items contained in given list to the stack if not already in it.
#
# #### Parameters
# - items: the items list (array) to add to the hover stack
func hover_stack_add_items(items: Array):
for item in items:
hover_stack_add_item(item)
# Clean the hover stack
func _clean_hover_stack():
for e in hover_stack:
@@ -496,8 +621,14 @@ func _clean_hover_stack():
#
# #### Parameters
# - item: the item to remove from the hover stack
func _hover_stack_erase_item(item):
func hover_stack_erase_item(item):
hover_stack.erase(item)
hover_stack.sort_custom(HoverStackSorter, "sort_ascending_z_index")
# Clear the stack of hovered items
func hover_stack_clear():
hover_stack = []
class HoverStackSorter: