lib/gamefic/action.rb in gamefic-2.4.0 vs lib/gamefic/action.rb in gamefic-3.0.0

- old
+ new

@@ -1,213 +1,82 @@ +# frozen_string_literal: true + module Gamefic + # The handler for executing responses for a provided actor and array of + # arguments. It's also responsible for executing before_action and + # after_action hooks if necessary. + # class Action - # @return [Gamefic::Actor] - attr_reader :actor + include Logging - # An array of objects on which the action will operate, e.g., an entity - # that is a direct object of a command. + # Hooks are blocks of code that get executed before or after an actor + # performs an action. A before action hook is capable of cancelling the + # action's performance. # - # @return [Array<Object>] + class Hook + attr_reader :verbs + + attr_reader :block + + def initialize *verbs, &block + @verbs = verbs + @block = block + end + + def match?(input) + verbs.empty? || verbs.include?(input) + end + end + + # @return [Active] + attr_reader :actor + + # @return [Array] attr_reader :arguments - alias parameters arguments - # @param actor [Gamefic::Actor] - # @param arguments [Array<Object>] - def initialize actor, arguments, with_callbacks = false + # @return [Response] + attr_reader :response + + # @param actor [Active] + # @param arguments [Array] + # @param response [Response] + def initialize actor, arguments, response @actor = actor @arguments = arguments - @executed = false - @with_callbacks = with_callbacks + @response = response end - # Perform the action. - # def execute - return if @cancelled - run_before_actions - return if @cancelled - self.class.executor.call(@actor, *arguments) unless self.class.executor.nil? + return if cancelled? + @executed = true - run_after_actions + response.execute actor, *arguments end - # Cancel an action. This method can be called in a before_action hook to - # prevent subsequent hooks and the action itself from being executed. - # Cancelling an action in an after_action hook has no effect. + # True if the response has been executed. False typically means that the + # #execute method has not been called or the action was cancelled in a + # before_action hook. # - def cancel - # @todo Emit a warning for attempts to cancel an action after it's been - # executed - @cancelled = true + def executed? + @executed ||= false end - # True if the #execute method has been called for this action. + # Cancel an action. This method can be called in an action hook to + # prevent subsequent hooks and/or the action itself from being executed. # - # @return [Boolean] - def executed? - @executed + def cancel + @cancelled = true end def cancelled? - !@executed && @cancelled + @cancelled ||= false end - # The verb associated with this action. - # - # @return [Symbol] The symbol representing the verb def verb - self.class.verb + response.verb end - def signature - self.class.signature - end - - def rank - self.class.rank - end - - # True if the action is flagged as meta. - # - # @return [Boolean] def meta? - self.class.meta? - end - - # @param verb [Symbol] - # @param queries [Array<Gamefic::Query::Base>] - # @param meta [Boolean] - # @return [Class<Action>] - def self.subclass verb, *queries, meta: false, &block - act = Class.new(self) do - self.verb = verb - self.meta = meta - queries.each do |q| - add_query q - end - on_execute &block - end - if !block.nil? && act.queries.length + 1 != block.arity && block.arity > 0 - raise ArgumentError.new("Number of parameters is not compatible with proc arguments") - end - act - end - - private - - def run_before_actions - return unless @with_callbacks - @actor.playbooks - .flat_map(&:before_actions) - .each do |hook| - next unless hook.verb.nil? || hook.verb == verb - hook.block.call(self) - break if @cancelled - end - end - - def run_after_actions - return unless @with_callbacks - @actor.playbooks - .flat_map(&:after_actions) - .each do |hook| - next unless hook.verb.nil? || hook.verb == verb - hook.block.call(self) - end - end - - class << self - attr_reader :verb - - # The proc to call when the action is executed - # - # @return [Proc] - attr_reader :executor - - def meta? - @meta ||= false - end - - def add_query q - @specificity = nil - queries.push q - end - - def queries - @queries ||= [] - end - - def on_execute &block - @executor = block - end - - def signature - # @todo This is clearly unfinished - "#{verb} #{queries.map {|m| m.signature}.join(', ')}" - end - - # True if this action is not intended to be performed directly by a - # character. - # If the action is hidden, users should not be able to perform it with a - # direct command. By default, any action whose verb starts with an - # underscore is hidden. - # - # @return [Boolean] - def hidden? - verb.to_s.start_with?('_') - end - - # @return [Integer] - def rank - if @rank.nil? - @rank = 0 - queries.each do |q| - @rank += (q.rank + 1) - end - @rank -= 1000 if verb.nil? - end - @rank - end - - def valid? actor, objects - return false if objects.length != queries.length - queries.each_with_index do |p, i| - return false unless p.include?(actor, objects[i]) - end - true - end - - # Return an instance of this Action if the actor can execute it with the - # provided tokens, or nil if the tokens are invalid. - # - # @param action [Gamefic::Entity] - # @param command [Command] - # @return [self, nil] - def attempt actor, command, with_callbacks = false - return nil if command.verb != verb - tokens = command.arguments - result = [] - matches = Gamefic::Query::Matches.new([], '', '') - queries.each_with_index do |p, i| - return nil if tokens[i].nil? && matches.remaining == '' - matches = p.resolve(actor, "#{matches.remaining} #{tokens[i]}".strip, continued: (i < queries.length - 1)) - return nil if matches.objects.empty? - accepted = matches.objects.select { |o| p.accept?(o) } - return nil if accepted.empty? - if p.ambiguous? - result.push accepted - else - return nil if accepted.length != 1 - result.push accepted.first - end - end - new(actor, result, with_callbacks) - end - - protected - - attr_writer :verb - - attr_writer :meta + response.meta? end end end