feat: allows for default/fallback actions to be specified

This commit is contained in:
Duncan Brown
2022-08-27 14:08:57 -04:00
parent 6e220e7e64
commit 895700870d
7 changed files with 170 additions and 132 deletions

2
action_defaults.esc Normal file
View File

@@ -0,0 +1,2 @@
:use | TK
say player "I can't use this!"

View File

@@ -230,27 +230,36 @@ func clear_current_tool():
set_action_input_state(ACTION_INPUT_STATE.AWAITING_ITEM) set_action_input_state(ACTION_INPUT_STATE.AWAITING_ITEM)
# Activates the action for given params # Checks if the specified action is valid and returns the associated event;
# otherwise, we see if there's a "fallback" event and use that if necessary and,
# if not, we return no event as there's nothing to do.
# #
# #### Parameters # #### Parameters
# #
# - action String Action to execute (defined in attached ESC file and in # - action: Action to execute (defined in attached ESC file and in
# action verbs UI) eg: arrived, use, look, pickup... # action verbs UI) eg: arrived, use, look, pickup...
# - target: Target ESC object # - target: Target ESC object
# - combine_with: ESC object to combine with # - combine_with: ESC object to combine with
func _activate( #
# *Returns* the appropriate ESCEvent to queue/run, or null if none can be found
# or there's a reason not to run an event.
func _get_event_to_queue(
action: String, action: String,
target: ESCObject, target: ESCObject,
combine_with: ESCObject = null combine_with: ESCObject = null
) -> int: ) -> ESCEvent:
escoria.logger.info( escoria.logger.info(
self, self,
"Activated action %s on %s." % [action, target] "Checking action %s on %s..." % [action, target]
) )
var event_to_return: ESCEvent = null
# If we're using an action which item requires to combine # If we're using an action which item requires to combine
if target.node is ESCItem\ if target.node is ESCItem \
and action in target.node.combine_when_selected_action_is_in: and action in target.node.combine_when_selected_action_is_in:
# Check if object must be in inventory to be used # Check if object must be in inventory to be used
if target.node.use_from_inventory_only: if target.node.use_from_inventory_only:
if escoria.inventory_manager.inventory_has(target.global_id): if escoria.inventory_manager.inventory_has(target.global_id):
@@ -273,64 +282,39 @@ func _activate(
action, action,
target.global_id 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"
)
clear_current_action() if target.events.has(target_event):
emit_signal("action_finished") event_to_return = target.events[target_event]
return event_returned[0]
elif combine_with.events.has(combine_with_event)\ elif combine_with.events.has(combine_with_event)\
and not combine_with.node.combine_is_one_way: 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"
)
clear_current_action() event_to_return = combine_with.events[combine_with_event]
emit_signal("action_finished")
return event_returned[0]
else: else:
var errors = [ # Check to see if there isn't a "fallback" action to
"Attempted to execute action %s between item %s and item %s" % [ # run before we declare this a failure.
action, if escoria.action_default_script \
target.global_id, and escoria.action_default_script.events.has(action):
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.warn(
self,
"Invalid action" + str(errors)
)
clear_current_action() event_to_return = escoria.action_default_script.events[action]
emit_signal("action_finished") else:
return ESCExecution.RC_ERROR 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.warn(
self,
"Invalid action: " + str(errors)
)
else: else:
escoria.logger.warn( escoria.logger.warn(
self, self,
@@ -344,17 +328,6 @@ func _activate(
combine_with.global_id combine_with.global_id
] ]
) )
clear_current_action()
emit_signal("action_finished")
return ESCExecution.RC_ERROR
else:
# We're missing a target here for our tool to be used on
current_tool = target
set_action_input_state(
ACTION_INPUT_STATE.AWAITING_TARGET_ITEM
)
return ESCExecution.RC_OK
else: else:
escoria.logger.warn( escoria.logger.warn(
self, self,
@@ -366,33 +339,45 @@ func _activate(
] ]
+ "but item must be in inventory." + "but item must be in inventory."
) )
else:
if target.events.has(action):
event_to_return = target.events[action]
elif escoria.action_default_script \
and escoria.action_default_script.events.has(action):
if target.events.has(action): # If there's a "fallback" action to run, return it
escoria.event_manager.queue_event(target.events[action]) event_to_return = escoria.action_default_script.events[action]
var event_returned = yield( else:
escoria.logger.warn(
self,
"Invalid action: " +
"Event for action %s on object %s not found." % [
action,
target.global_id
]
)
return event_to_return
func _run_event(event: ESCEvent) -> int:
escoria.event_manager.queue_event(event)
var event_returned = yield(
escoria.event_manager,
"event_finished"
)
while event_returned[1] != event.name:
event_returned = yield(
escoria.event_manager, escoria.event_manager,
"event_finished" "event_finished"
) )
while event_returned[1] != action:
event_returned = yield( clear_current_action()
escoria.event_manager, emit_signal("action_finished")
"event_finished"
) return event_returned[0]
clear_current_action()
emit_signal("action_finished")
return event_returned[0]
else:
escoria.logger.warn(
self,
"Invalid action: " +
"Event for action %s on object %s not found." % [
action,
target.global_id
]
)
clear_current_action()
emit_signal("action_finished")
return ESCExecution.RC_ERROR
# Makes an object walk to a destination. This can be either a 2D position or # Makes an object walk to a destination. This can be either a 2D position or
@@ -460,6 +445,8 @@ func perform_inputevent_on_object(
): ):
""" """
This algorithm: This algorithm:
- validates the requested action
- grabs the corresponding event for the action, if available
- makes the player move to the clicked object location, if needed - makes the player move to the clicked object location, if needed
(if it is located in the room for example) and wait for reaching. (if it is located in the room for example) and wait for reaching.
- when reached, performs an action depending on current defined action - when reached, performs an action depending on current defined action
@@ -485,12 +472,6 @@ func perform_inputevent_on_object(
"%s to perform event %s." % [obj.global_id, event] "%s to perform event %s." % [obj.global_id, event]
) )
var event_flags = 0
var has_current_action: bool = false
if obj.events.has(current_action):
event_flags = obj.events[current_action].flags
has_current_action = true
# Don't interact after player movement towards object # Don't interact after player movement towards object
# (because object is inactive for example) # (because object is inactive for example)
var dont_interact = false var dont_interact = false
@@ -501,11 +482,11 @@ func perform_inputevent_on_object(
var tool_just_set = _set_tool_and_action(obj, default_action) var tool_just_set = _set_tool_and_action(obj, default_action)
var need_combine = _check_item_needs_combine() var need_combine = _check_item_needs_combine()
# If the current tool was not set, this is our first item, make it tool # If the current tool was not set, this is our first item, make it the tool
if not current_tool or (current_tool and not need_combine): if not current_tool or (current_tool and not need_combine):
current_tool = obj current_tool = obj
# Else, if we have a tool an combination required, this is our second item, # Else, if we have a tool and combination required, this is our second item,
# make it target. # make it the target.
elif need_combine and not tool_just_set: elif need_combine and not tool_just_set:
current_target = obj current_target = obj
@@ -518,19 +499,68 @@ func perform_inputevent_on_object(
elif action_state == ACTION_INPUT_STATE.AWAITING_ITEM and need_combine and not tool_just_set: elif action_state == ACTION_INPUT_STATE.AWAITING_ITEM and need_combine and not tool_just_set:
set_action_input_state(ACTION_INPUT_STATE.AWAITING_TARGET_ITEM) set_action_input_state(ACTION_INPUT_STATE.AWAITING_TARGET_ITEM)
var event_to_queue: ESCEvent = null
# Manage exits
if obj.node.is_exit and current_action in ["", ACTION_WALK]:
event_to_queue = _get_event_to_queue(ACTION_EXIT_SCENE, obj)
else:
# Manage movements towards object before activating it
if current_action in ["", ACTION_WALK] and \
not escoria.inventory_manager.inventory_has(obj.global_id):
event_to_queue = _get_event_to_queue(ACTION_ARRIVED, obj)
# Manage action on object
elif not current_action in ["", ACTION_WALK]:
if need_combine and current_target:
event_to_queue = _get_event_to_queue(
current_action,
current_tool,
current_target
)
else:
# Check if object must be in inventory to be used and update
# action state if necessary
if obj.node.use_from_inventory_only and \
escoria.inventory_manager.inventory_has(obj.global_id):
# We're missing a target here for our tool to be used on
current_tool = obj
set_action_input_state(
ACTION_INPUT_STATE.AWAITING_TARGET_ITEM
)
# We need to wait for that target
return
else:
event_to_queue = _get_event_to_queue(
current_action,
obj
)
# Get out of here if there's a specified action but an event couldn't be found.
# Note that `event_to_queue` may still be null, but we do need to start the
# player walking towards the destination.
if current_action and not event_to_queue:
clear_current_action()
emit_signal("action_finished")
return
var event_flags = event_to_queue.flags if event_to_queue else 0
if escoria.main.current_scene.player: if escoria.main.current_scene.player:
var destination_position: Vector2 = escoria.main.current_scene.player.\ var destination_position: Vector2 = escoria.main.current_scene.player \
global_position .global_position
# If clicked object not in inventory, player walks towards it # If clicked object not in inventory, player walks towards it
if not obj.node is ESCPlayer and \ if not obj.node is ESCPlayer and \
not escoria.inventory_manager.inventory_has(obj.global_id) and \ not escoria.inventory_manager.inventory_has(obj.global_id) and \
(not has_current_action or not event_flags & ESCEvent.FLAG_TK): not event_flags & ESCEvent.FLAG_TK:
var context = _walk_towards_object( var context = _walk_towards_object(
obj, obj,
event.position, event.position,
event.doubleclick event.doubleclick
) )
if context is GDScriptFunctionState: if context is GDScriptFunctionState:
context = yield(context, "completed") context = yield(context, "completed")
@@ -562,36 +592,10 @@ func perform_inputevent_on_object(
[escoria.event_manager.EVENT_CANT_REACH, obj.global_id] [escoria.event_manager.EVENT_CANT_REACH, obj.global_id]
) )
# If no interaction should happen after player has arrived, leave # If no interaction should happen after player has arrived, leave
# immediately. # immediately.
if dont_interact: if not dont_interact and event_to_queue:
return _run_event(event_to_queue)
# Manage exits
if obj.node.is_exit and current_action in ["", ACTION_WALK]:
escoria.event_manager.interrupt()
escoria.event_manager.clear_event_queue()
_activate(ACTION_EXIT_SCENE, obj)
else:
# Manage movements towards object before activating it
if current_action in ["", ACTION_WALK] and \
not escoria.inventory_manager.inventory_has(obj.global_id):
_activate(ACTION_ARRIVED, obj)
# Manage action on object
elif not current_action in ["", ACTION_WALK]:
if need_combine and current_target:
_activate(
current_action,
current_tool,
current_target
)
else:
_activate(
current_action,
obj
)
# Determines whether the object in question can be acted upon. # Determines whether the object in question can be acted upon.

View File

@@ -97,6 +97,10 @@ var player_camera: ESCCamera
# escoria/main/game_start_script # escoria/main/game_start_script
var start_script: ESCScript var start_script: ESCScript
# The "fallback" script to use when an action is tried on an item that hasn't
# been explicitly scripted.
var action_default_script: ESCScript
# Whether we ran a room directly from editor, not a full game # Whether we ran a room directly from editor, not a full game
var is_direct_room_run: bool = false var is_direct_room_run: bool = false

View File

@@ -26,6 +26,7 @@ const FORCE_QUIT = "%s/%s/force_quit" % [_ESCORIA_SETTINGS_ROOT, _MAIN_ROOT]
const GAME_MIGRATION_PATH = "%s/%s/game_migration_path" % [_ESCORIA_SETTINGS_ROOT, _MAIN_ROOT] const GAME_MIGRATION_PATH = "%s/%s/game_migration_path" % [_ESCORIA_SETTINGS_ROOT, _MAIN_ROOT]
const GAME_VERSION = "%s/%s/game_version" % [_ESCORIA_SETTINGS_ROOT, _MAIN_ROOT] const GAME_VERSION = "%s/%s/game_version" % [_ESCORIA_SETTINGS_ROOT, _MAIN_ROOT]
const GAME_START_SCRIPT = "%s/%s/game_start_script" % [_ESCORIA_SETTINGS_ROOT, _MAIN_ROOT] const GAME_START_SCRIPT = "%s/%s/game_start_script" % [_ESCORIA_SETTINGS_ROOT, _MAIN_ROOT]
const ACTION_DEFAULT_SCRIPT = "%s/%s/action_default_script" % [_ESCORIA_SETTINGS_ROOT, _MAIN_ROOT]
const SAVEGAMES_PATH = "%s/%s/savegames_path" % [_ESCORIA_SETTINGS_ROOT, _MAIN_ROOT] const SAVEGAMES_PATH = "%s/%s/savegames_path" % [_ESCORIA_SETTINGS_ROOT, _MAIN_ROOT]
const SETTINGS_PATH = "%s/%s/settings_path" % [_ESCORIA_SETTINGS_ROOT, _MAIN_ROOT] const SETTINGS_PATH = "%s/%s/settings_path" % [_ESCORIA_SETTINGS_ROOT, _MAIN_ROOT]
const TEXT_LANG = "%s/%s/text_lang" % [_ESCORIA_SETTINGS_ROOT, _MAIN_ROOT] const TEXT_LANG = "%s/%s/text_lang" % [_ESCORIA_SETTINGS_ROOT, _MAIN_ROOT]

View File

@@ -52,6 +52,7 @@ func _ready():
escoria.room_manager.register_reserved_globals() escoria.room_manager.register_reserved_globals()
escoria.inputs_manager.register_core() escoria.inputs_manager.register_core()
if ESCProjectSettingsManager.get_setting( if ESCProjectSettingsManager.get_setting(
ESCProjectSettingsManager.GAME_START_SCRIPT ESCProjectSettingsManager.GAME_START_SCRIPT
).empty(): ).empty():
@@ -66,6 +67,21 @@ func _ready():
) )
) )
if ESCProjectSettingsManager.get_setting(
ESCProjectSettingsManager.ACTION_DEFAULT_SCRIPT
).empty():
escoria.logger.info(
self,
"Project setting '%s' is not set. No action defaults will be used."
% ESCProjectSettingsManager.ACTION_DEFAULT_SCRIPT
)
else:
escoria.action_default_script = escoria.esc_compiler.load_esc_file(
ESCProjectSettingsManager.get_setting(
ESCProjectSettingsManager.ACTION_DEFAULT_SCRIPT
)
)
escoria.main = main escoria.main = main
_perform_plugins_checks() _perform_plugins_checks()

View File

@@ -156,6 +156,16 @@ func set_escoria_main_settings():
} }
) )
register_setting(
ESCProjectSettingsManager.ACTION_DEFAULT_SCRIPT,
"",
{
"type": TYPE_STRING,
"hint": PROPERTY_HINT_FILE,
"hint_string": "*.esc"
}
)
register_setting( register_setting(
ESCProjectSettingsManager.FORCE_QUIT, ESCProjectSettingsManager.FORCE_QUIT,
true, true,

View File

@@ -792,6 +792,7 @@ dialog_simple/avatars_path="res://game/dialog_avatars/"
dialog_simple/text_speed_per_character=0.1 dialog_simple/text_speed_per_character=0.1
dialog_simple/fast_text_speed_per_character=0.25 dialog_simple/fast_text_speed_per_character=0.25
dialog_simple/max_time_to_disappear=1.0 dialog_simple/max_time_to_disappear=1.0
main/action_default_script="res://action_defaults.esc"
[input] [input]