lib/gamefic/character.rb in gamefic-1.5.1 vs lib/gamefic/character.rb in gamefic-1.6.0

- old
+ new

@@ -1,25 +1,31 @@ -require 'gamefic/director' +#require 'gamefic/director' -class NotConclusionError < Exception -end module Gamefic + class NotConclusionError < Exception + end + class Character < Entity + autoload :State, 'gamefic/character/state' + attr_reader :queue, :user - # @return [Gamefic::Director::Order] - attr_reader :last_order + # @return [Gamefic::Action] + attr_reader :last_action # @return [Entity,nil] attr_reader :last_object attr_accessor :object_of_pronoun attr_reader :scene attr_reader :next_scene attr_accessor :playbook + + include Character::State def initialize(args = {}) - @queue = Array.new super + @queue = Array.new + @messages = '' @buffer_stack = 0 @buffer = "" end # Connect a User. @@ -32,11 +38,36 @@ # Disconnect the current User. # def disconnect @user = nil end - + + # Send a message to the entity. + # This method will automatically wrap the message in HTML paragraphs. + # To send a message without paragraph formatting, use #stream instead. + # + # @param message [String] + def tell(message) + if @buffer_stack > 0 + @buffer += message + else + super + end + end + + # Send a message to the Character as raw text. + # Unlike #tell, this method will not wrap the message in HTML paragraphs. + # + # @param message [String] + def stream(message) + if @buffer_stack > 0 + @buffer += message + else + super + end + end + # Perform a command. # The command can be specified as a String or a set of tokens. Either form # should yield the same result, but using tokens can yield better # performance since it bypasses the parser. # @@ -47,11 +78,27 @@ # # @example Send a command as a set of tokens # character.perform :take, @key # def perform(*command) - Director.dispatch(self, *command) + #Director.dispatch(self, *command) + actions = playbook.dispatch(self, *command) + a = actions.first + okay = true + unless a.meta? + playbook.validators.each { |v| + result = v.call(self, a.verb, a.parameters) + okay = (result != false) + break if not okay + } + end + if okay + performance_stack.push actions + proceed + performance_stack.pop + end + a end # Quietly perform a command. # This method executes the command exactly as #perform does, except it # buffers the resulting output instead of sending it to the user. @@ -64,38 +111,10 @@ @buffer_stack += 1 self.perform *command @buffer_stack -= 1 @buffer end - - # Send a message to the Character. - # This method will automatically wrap the message in HTML paragraphs. - # To send a message without paragraph formatting, use #stream instead. - # - # @param message [String] - def tell(message) - if user != nil and message.to_s != '' - if @buffer_stack > 0 - @buffer += message - else - message = "<p>#{message.strip}</p>" - # This method uses String#gsub instead of String#gsub! for - # compatibility with Opal. - message = message.gsub(/[ \t\r]*\n[ \t\r]*\n[ \t\r]*/, '</p><p>') - message = message.gsub(/[ \t]*\n[ \t]*/, ' ') - user.send message - end - end - end - - # Send a message to the Character as raw text. - # Unlike #tell, this method will not wrap the message in HTML paragraphs. - # - # @param message [String] - def stream(message) - user.send message.strip unless user.nil? - end # Proceed to the next Action in the current stack. # This method is typically used in Action blocks to cascade through # multiple implementations of the same verb. # @@ -114,52 +133,100 @@ # else # actor.proceed # Execute the previous implementation # end # end # - def proceed - Director::Delegate.proceed_for self + def proceed quietly: false + #Director::Delegate.proceed_for self + return if performance_stack.empty? + a = performance_stack.last.shift + unless a.nil? + if quietly + if @buffer_stack == 0 + @buffer = "" + end + @buffer_stack += 1 + end + a.execute + if quietly + @buffer_stack -= 1 + @buffer + end + end end - def cue scene + # Immediately start a new scene for the character. + # Use #prepare if you want to declare a scene to be started at the + # beginning of the next turn. + # + def cue new_scene @next_scene = nil - @scene = scene - @scene.start self unless @scene.nil? + if new_scene.nil? + @scene = nil + else + @scene = new_scene.new(self) + @scene.start + end end - def prepare scene - @next_scene = scene + # Prepare a scene to be started for this character at the beginning of the + # next turn. + # + def prepare s + @next_scene = s end + # Return true if the character is expected to be in the specified scene on + # the next turn. + # + # @return [Boolean] + def will_cue? scene + (@scene.class == scene and @next_scene.nil?) or @next_scene == scene + end + + # Cue a conclusion. This method works like #cue, except it will raise a + # NotConclusionError if the scene is not a Scene::Conclusion. + # def conclude scene - raise NotConclusionError if !scene.kind_of?(Scene::Conclusion) + raise NotConclusionError unless scene <= Scene::Conclusion cue scene end + # True if the character is in a conclusion. + # + # @return [Boolean] def concluded? !scene.nil? and scene.kind_of?(Scene::Conclusion) end def performed order - @last_order = order + order.freeze + @last_action = order end - def prompt - scene.nil? ? '>' : scene.prompt_for(self) + # Get the prompt that the user should see for the current scene. + # + # @return [String] + #def prompt + # scene.nil? ? '>' : scene.prompt + #end + + def accessible? + false end + def inspect + to_s + end + private def delegate_stack @delegate_stack ||= [] end - def last_order=(order) - return if order.nil? - @last_order = order - if !order.action.meta? and !order.arguments[0].nil? and !order.arguments[0][0].nil? and order.arguments[0][0].kind_of?(Entity) - @last_object = order.arguments[0][0] - end + def performance_stack + @performance_stack ||= [] end end end