lib/gamefic/plot.rb in gamefic-1.4.1 vs lib/gamefic/plot.rb in gamefic-1.5.0
- old
+ new
@@ -3,202 +3,88 @@
require 'gamefic/stage'
require 'gamefic/tester'
require 'gamefic/source'
require 'gamefic/script'
require 'gamefic/query'
-require 'gamefic/subplot'
module Gamefic
class Plot
- autoload :SceneMount, 'gamefic/plot/scene_mount'
+ autoload :SceneMount, 'gamefic/plot/scene_mount'
autoload :CommandMount, 'gamefic/plot/command_mount'
- autoload :EntityMount, 'gamefic/plot/entity_mount'
- autoload :QueryMount, 'gamefic/plot/query_mount'
+ autoload :Entities, 'gamefic/plot/entities'
autoload :ArticleMount, 'gamefic/plot/article_mount'
- autoload :YouMount, 'gamefic/plot/you_mount'
- autoload :Snapshot, 'gamefic/plot/snapshot'
-
- attr_reader :commands, :imported_scripts, :rules, :asserts, :source
- # TODO Metadata could use better protection
- attr_accessor :default_scene, :metadata
+ autoload :YouMount, 'gamefic/plot/you_mount'
+ autoload :Snapshot, 'gamefic/plot/snapshot'
+ autoload :Host, 'gamefic/plot/host'
+ autoload :Players, 'gamefic/plot/players'
+ autoload :Playbook, 'gamefic/plot/playbook'
+ autoload :Callbacks, 'gamefic/plot/callbacks'
+
+ attr_reader :commands, :imported_scripts, :source
+ # TODO: Metadata could use better protection
+ attr_accessor :metadata
include Stage
- # TODO This include is only here to make the module's methods visible in the IDE.
- # Gamefic Studio has a PlotStageMetaMapper that handles it, but it doesn't run if
- # the plugin isn't activated.
- #include Gamefic, Tester, SceneMount, CommandMount, EntityMount, QueryMount, ArticleMount, YouMount, Snapshot
- mount Gamefic, Tester, SceneMount, CommandMount, EntityMount, QueryMount,
- ArticleMount, YouMount, Snapshot, Subplot::Host
- expose :script, :introduction, :assert_action,
- :on_update, :on_player_update, :entities, :on_ready, :on_player_ready,
- :players, :scenes, :metadata
-
+ mount Gamefic, Tester, Players, SceneMount, CommandMount, Entities,
+ ArticleMount, YouMount, Snapshot, Host, Callbacks
+ expose :script, :metadata
+
# @param [Source::Base]
def initialize(source = nil)
@source = source || Source::Text.new({})
- @commands = {}
- @syntaxes = []
- @ready_procs = []
- @update_procs = []
- @player_ready = []
- @player_procs = []
@working_scripts = []
@imported_scripts = []
- @entities = []
- @players = []
- @asserts = {}
- @default_scene = :active
- @subplots = []
+ @running = false
+ @playbook = Playbook.new
post_initialize
end
- def scenes
- if @scenes.nil?
- @scenes = {}
- @scenes[:active] = Scene::Active.new
- @scenes[:concluded] = Scene::Conclusion.new
- end
- @scenes
+ def playbook
+ @playbook ||= Playbook.new
end
-
- def concluded?(actor)
- scenes[actor.scene].kind_of?(Scene::Conclusion)
+
+ def running?
+ @running
end
-
- # Get an Array of all Actions defined in the Plot.
- #
- # @return [Array<Action>]
- def actions
- @commands.values.flatten
- end
-
- # Get an Array of all Actions associated with the specified verb.
- #
- # @param verb [Symbol] The Symbol for the verb (e.g., :go or :look)
- # @return [Array<Action>] The verb's associated Actions
- def actions_with_verb(verb)
- @commands[verb].clone || []
- end
-
+
# Get an Array of all scripts that have been imported into the Plot.
#
# @return [Array<Script>] The imported scripts
def imported_scripts
@imported_scripts ||= []
end
- # Add a Block to be executed for the given verb.
- # If the block returns false, the Action is cancelled.
- #
- # @example Require the player to have a property enabled before performing the Action.
- # assert_action :authorize do |actor, verb, arguments|
- # if actor[:can_authorize] == true
- # true
- # else
- # actor.tell "You don't have permission to use the authorize command."
- # false
- # end
- # end
- #
- # @yieldparam [Character] The character performing the Action.
- # @yieldparam [Symbol] The verb associated with the Action.
- # @yieldparam [Array] The arguments that will be passed to the Action's #execute method.
- def assert_action name, &block
- @asserts[name] = Assert.new(name, &block)
- end
-
def post_initialize
# TODO: Should this method be required by extended classes?
end
- # Get an Array of the Plot's current Entities.
- #
- # @return [Array<Entity>]
- def entities
- @entities.clone
- end
-
# Get an Array of the Plot's current Syntaxes.
#
# @return [Array<Syntax>]
def syntaxes
- @syntaxes.clone
+ playbook.syntaxes
end
- # Get an Array of current players.
- #
- # @return [Array<Character>] The players.
- def players
- @players.clone
- end
-
- # Add a block to be executed on preparation of every turn.
- # Each on_ready block is executed once per turn, as opposed to
- # on_player_ready blocks, which are executed once for each player.
- #
- # @example Increment a turn counter
- # turn = 0
- # on_ready do
- # turn += 1
- # end
- #
- def on_ready(&block)
- @ready_procs.push block
- end
-
- # Add a block to be executed after the Plot is finished updating a turn.
- # Each on_update block is executed once per turn, as opposed to
- # on_player_update blocks, which are executed once for each player.
- def on_update(&block)
- @update_procs.push block
- end
-
- # Add a block to be executed when a player is added to the game.
- # Each Plot can only have one introduction. Subsequent calls will
- # overwrite the existing one.
- #
- # @example Welcome the player to the game
- # introduction do |actor|
- # actor.tell "Welcome to the game!"
- # end
- #
- # @yieldparam [Character]
- def introduction (&proc)
- @introduction = proc
- end
-
- # Introduce a player to the game.
- # This method is typically called by the Engine that manages game execution.
- def introduce(player)
- player.extend Subplot::Feature
- player.cue :active
- @players.push player
- @introduction.call(player) unless @introduction.nil?
- end
-
# Prepare the Plot for the next turn of gameplay.
# This method is typically called by the Engine that manages game execution.
def ready
- @ready_procs.each { |p| p.call }
- # Prepare player scenes for the update.
- @players.each { |player|
- this_scene = player.next_scene || player.scene
- player.prepare nil
- player.cue this_scene unless player.scene == this_scene
- @player_ready.each { |block|
- block.call player
- }
- }
+ playbook.freeze
+ @running = true
+ call_ready
+ call_player_ready
+ p_subplots.each { |s| s.ready }
end
# Update the Plot's current turn of gameplay.
# This method is typically called by the Engine that manages game execution.
def update
- @players.each { |p| process_input p }
- @entities.each { |e| e.update }
- @players.each { |player| update_player player }
- @update_procs.each { |p| p.call }
+ p_players.each { |p| process_input p }
+ p_entities.each { |e| e.update }
+ call_player_update
+ call_update
+ p_subplots.each { |s| s.update unless s.concluded? }
+ p_subplots.delete_if { |s| s.concluded? }
end
def tell entities, message, refresh = false
entities.each { |entity|
entity.tell message, refresh
@@ -212,11 +98,11 @@
# @param path [String] The path to the script being evaluated
# @return [Boolean] true if the script was loaded by this call or false if it was already loaded.
def script path
imported_script = source.export(path)
if imported_script.nil?
- raise "Script not found: #{path}"
+ raise LoadError.new("cannot load script -- #{path}")
end
if !@working_scripts.include?(imported_script) and !imported_scripts.include?(imported_script)
@working_scripts.push imported_script
stage imported_script.read, imported_script.absolute_path
@working_scripts.pop
@@ -225,107 +111,17 @@
else
false
end
end
- # Add a block to be executed for each player when the Plot prepares them
- # for the next turn in the game.
- #
- # @yieldparam [Character]
- def on_player_ready &block
- @player_ready.push block
- end
-
- # Add a block to be executed for each player after they have completed a
- # turn in the game.
- #
- # @yieldparam [Character]
- def on_player_update &block
- @player_procs.push block
- end
-
private
def process_input player
line = player.queue.shift
if !line.nil?
- scenes[player.scene].finish player, line
+ player.scene.finish player, line
end
end
- def update_player player
- @player_procs.each { |proc|
- proc.call player
- }
- end
-
- def rem_entity(entity)
- @entities.delete(entity)
- @players.delete(entity)
- end
-
- def add_syntax syntax
- if @commands[syntax.verb] == nil
- raise "Action \"#{syntax.verb}\" does not exist."
- end
- # Delete duplicate syntaxes
- @syntaxes = @syntaxes.delete_if { |existing|
- existing == syntax
- }
- @syntaxes.unshift syntax
- @syntaxes.sort! { |a, b|
- if a.token_count == b.token_count
- # For syntaxes of the same length, length of action takes precedence
- b.first_word <=> a.first_word
- else
- b.token_count <=> a.token_count
- end
- }
- end
-
- def add_action(action)
- @commands[action.verb] ||= []
- @commands[action.verb].unshift action
- @commands[action.verb].sort! { |a, b|
- if a.specificity == b.specificity
- # Newer action takes precedence
- b.order_key <=> a.order_key
- else
- # Higher specificity takes precedence
- b.specificity <=> a.specificity
- end
- }
- generate_default_syntax action
- end
-
- def generate_default_syntax action
- user_friendly = action.verb.to_s.gsub(/_/, ' ')
- args = []
- used_names = []
- action.queries.each { |c|
- num = 1
- new_name = ":var"
- while used_names.include? new_name
- num = num + 1
- new_name = ":var#{num}"
- end
- used_names.push new_name
- user_friendly += " #{new_name}"
- args.push new_name
- }
- add_syntax Syntax.new(user_friendly.strip, "#{action.verb} #{args.join(' ')}")
- end
-
- def rem_action(action)
- @commands[action.verb].delete(action)
- end
-
- def rem_syntax(syntax)
- @syntaxes.delete syntax
- end
-
- def add_entity(entity)
- @entities.push entity
- end
end
end