diff --git a/addons/escoria-core/game/escoria.gd b/addons/escoria-core/game/escoria.gd index 0bfa5d45..45941eca 100644 --- a/addons/escoria-core/game/escoria.gd +++ b/addons/escoria-core/game/escoria.gd @@ -18,7 +18,8 @@ onready var main = $main func _init(): escoria.inventory_manager = ESCInventoryManager.new() - escoria.action_manager = ESCActionManager.new() + # MODIFIED FOR RETURN TO MONKEY UI + escoria.action_manager = ESCActionManagerMonkey.new() escoria.event_manager = ESCEventManager.new() escoria.globals_manager = ESCGlobalsManager.new() add_child(escoria.event_manager) diff --git a/gymkhana/addons/escoria-ui-return-monkey-island/esc/esc_action_manager_monkey.gd b/gymkhana/addons/escoria-ui-return-monkey-island/esc/esc_action_manager_monkey.gd new file mode 100644 index 00000000..67f8d01e --- /dev/null +++ b/gymkhana/addons/escoria-ui-return-monkey-island/esc/esc_action_manager_monkey.gd @@ -0,0 +1,364 @@ +# Manages currently carried out actions +# MODIFIED FOR RETURN TO MONKEY UI +extends ESCActionManager +class_name ESCActionManagerMonkey + +# Set the current action verb +# +# ## Parameters +# - action: The action verb to set +func set_current_action(action: String): + # MODIFIED FOR RETURN TO MONKEY UI + if (action != current_action) && (action_state != ACTION_INPUT_STATE.AWAITING_TARGET_ITEM): + clear_current_tool() + + current_action = action + + if action_state == ACTION_INPUT_STATE.AWAITING_VERB_OR_ITEM: + set_action_input_state(ACTION_INPUT_STATE.AWAITING_ITEM) + elif action_state == ACTION_INPUT_STATE.AWAITING_VERB: + set_action_input_state(ACTION_INPUT_STATE.AWAITING_VERB_CONFIRM) + + emit_signal("action_changed") + + +# 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 +# +# - action: 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 +# +# *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, + target: ESCObject, + combine_with: ESCObject = null +) -> ESCEvent: + + escoria.logger.info( + self, + "Checking if action '%s' on '%s' is valid..." % [action, target] + ) + + var event_to_return: ESCEvent = null + + # If we're using an action which item requires to combine + if target.node is ESCItem \ + and (action in target.node.combine_when_selected_action_is_in + # MODIFIED FOR RETURN TO MONKEY UI + or (combine_with && action in combine_with.node.combine_when_selected_action_is_in)): + + # 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): + event_to_return = target.events[target_event] + elif combine_with.events.has(combine_with_event)\ + and not combine_with.node.combine_is_one_way: + + event_to_return = combine_with.events[combine_with_event] + else: + # Check to see if there isn't a "fallback" action to + # run before we declare this a failure. + if escoria.action_default_script \ + and escoria.action_default_script.events.has(action): + + event_to_return = escoria.action_default_script.events[action] + 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.warn( + self, + "Invalid action: " + str(errors) + ) + else: + escoria.logger.warn( + self, + "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 + ] + ) + else: + escoria.logger.warn( + self, + "Invalid action on item: " + + "Trying to run action %s on object %s, " % + [ + action, + target.node.global_id + ] + + "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 there's a "fallback" action to run, return it + event_to_return = escoria.action_default_script.events[action] + else: + escoria.logger.warn( + self, + "Invalid action: " + + "Event for action %s on object %s not found." % [ + action, + target.global_id + ] + ) + + return event_to_return + + +# Event handler when an object/item was clicked +# +# #### Parameters +# +# - obj: Object that was left clicked +# - event: Input event that was received +# - default_action: if true, run the inventory default action +func perform_inputevent_on_object( + obj: ESCObject, + event: InputEvent, + default_action: bool = false +): + """ + 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 + (if it is located in the room for example) and wait for reaching. + - when reached, performs an action depending on current defined action + * no current action defined: do nothing else + * current action defined: + * item requires no combination: perform the current action + on the item + * item requires combination: check the status of the combination + A combination requires 3 elements to fulfill: + 1/ a verb action + 2/ a first "tool" (item to use) + 3/ a second "tool" (item to use ON) + Whatever the user inputs to fulfill the combination (this is + determined by gamedev in his game.gd script) + - combination not fulfilled: no not perform until fulfilled + - combination fulfilled: perform the combination. + * else do nothing, except if default_action is requested. + In this case, perform the default_action on the item. + """ + + escoria.logger.info( + self, + "%s to perform event %s." % [obj.global_id, event] + ) + + # Don't interact after player movement towards object + # (because object is inactive for example) + var dont_interact = false + + # We need to have the new action input state BEFORE initiating the player + # move so we determine now if the object clicked will require a combination + # depending on the used action verb. + var tool_just_set = _set_tool_and_action(obj, default_action) + # MODIFIED FOR RETURN TO MONKEY UI + var need_combine = _check_item_needs_combine_obj(obj) + + # 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): + current_tool = obj + # Else, if we have a tool and combination required, this is our second item, + # make it the target. + elif need_combine and not tool_just_set: + current_target = obj + + # Update the action input state + if action_state == ACTION_INPUT_STATE.AWAITING_TARGET_ITEM and current_target: + set_action_input_state(ACTION_INPUT_STATE.COMPLETED) + elif action_state == ACTION_INPUT_STATE.AWAITING_ITEM and \ + not need_combine: + set_action_input_state(ACTION_INPUT_STATE.COMPLETED) + 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) + + 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) and \ + need_combine: + + # 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: + var destination_position: Vector2 = escoria.main.current_scene.player \ + .global_position + + # If clicked object not in inventory, player walks towards it + if not obj.node is ESCPlayer and \ + not escoria.inventory_manager.inventory_has(obj.global_id) and \ + not event_flags & ESCEvent.FLAG_TK: + var context = _walk_towards_object( + obj, + event.position, + event.doubleclick + ) + + if context is GDScriptFunctionState: + context = yield(context, "completed") + + # In case of an interrupted walk, we don't want to proceed. + if context == null: + return + + destination_position = context.target_position + dont_interact = context.dont_interact_on_arrival + + var player_global_pos = escoria.main.current_scene.player.global_position + var clicked_position = event.position + + # Using this instead of is_equal_approx due to + # https://github.com/godotengine/godot/issues/65257 + if (player_global_pos - destination_position).length() > 1: + dont_interact = true + escoria.logger.info( + self, + "Player could not reach destination coordinates %s. " % str(destination_position) \ + + "Any requested action for %s will not fire." % obj.global_id + ) + if escoria.event_manager.EVENT_CANT_REACH in obj.events: + escoria.event_manager.queue_event(obj.events[escoria.event_manager.EVENT_CANT_REACH]) + else: + escoria.logger.info( + self, + "%s event not found for object %s so nothing to do." % \ + [escoria.event_manager.EVENT_CANT_REACH, obj.global_id] + ) + + # If no interaction should happen after player has arrived, leave + # immediately. + if not dont_interact and event_to_queue: + _run_event(event_to_queue) + +# Prepare the "obj" object for current_action: if required, set the object as +# current tool. +# +# #### Parameters +# +# - obj: the ESCObject to prepare +# - default_action: if true, the default action set on the item is used +# +# *Returns* True if the tool was set in this function +func _set_tool_and_action(obj: ESCObject, default_action: bool): + var tool_just_set: bool = false + # Check if current_action and current_tool are already set + if current_action and current_tool: + # MODIFIED FOR RETURN TO MONKEY UI + if (not current_action in escoria.action_manager\ + .current_tool.node.combine_when_selected_action_is_in and not current_action in obj.node.combine_when_selected_action_is_in): + current_tool = obj + tool_just_set = true + elif default_action: + if escoria.inventory_manager.inventory_has(obj.global_id): + current_action = obj.node.default_action_inventory + else: + current_action = obj.node.default_action + elif current_action in obj.node.combine_when_selected_action_is_in: + current_tool = obj + tool_just_set = true + return tool_just_set + + +# Checks if object requires a combination with another, according to +# currently selected action verb (or check with default action of the item). +# +# *Returns* True if current action on "obj" requires a combination + # MODIFIED FOR RETURN TO MONKEY UI +func _check_item_needs_combine_obj(obj: ESCObject) -> bool: + return current_action \ + and current_tool \ + and (current_action in current_tool.node.combine_when_selected_action_is_in + # MODIFIED FOR RETURN TO MONKEY UI + or current_action in obj.node.combine_when_selected_action_is_in) diff --git a/gymkhana/rooms/trasera_cocina/trasera_cocina.tscn b/gymkhana/rooms/trasera_cocina/trasera_cocina.tscn index dd2633ca..848582c3 100644 --- a/gymkhana/rooms/trasera_cocina/trasera_cocina.tscn +++ b/gymkhana/rooms/trasera_cocina/trasera_cocina.tscn @@ -65,7 +65,7 @@ global_id = "l_exit" esc_script = "res://gymkhana/rooms/trasera_cocina/esc/entrada_cocina.esc" is_exit = true tooltip_name = "Esto es una puerta y tiene una descripcion mu larga" -combine_when_selected_action_is_in = [ ] +combine_when_selected_action_is_in = [ "action4" ] dialog_color = Color( 1, 1, 1, 1 ) action1_text = "Esto es una puerta y tiene una descripcion mu larga" action2_text = "Entrar! con descricion..." diff --git a/project.godot b/project.godot index e2ba228b..aef80b71 100644 --- a/project.godot +++ b/project.godot @@ -149,6 +149,11 @@ _global_script_classes=[ { "language": "GDScript", "path": "res://addons/escoria-core/game/core-scripts/esc/esc_action_manager.gd" }, { +"base": "ESCActionManager", +"class": "ESCActionManagerMonkey", +"language": "GDScript", +"path": "res://gymkhana/addons/escoria-ui-return-monkey-island/esc/esc_action_manager_monkey.gd" +}, { "base": "Resource", "class": "ESCAnimationName", "language": "GDScript", @@ -733,6 +738,7 @@ _global_script_class_icons={ "DialogSayFinish": "", "DialogVisible": "", "ESCActionManager": "", +"ESCActionManagerMonkey": "", "ESCAnimationName": "", "ESCAnimationPlayer": "", "ESCAnimationResource": "",