gamefic 3.2.1 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ea395014528ad74c93bfc7e33809a6b6ea24c0c8e2d37a2f6e0d7067eef564d1
4
- data.tar.gz: ec2fcf7c80c501cf93c036bf2416f3f583755460d38aca464a5844eb2705151f
3
+ metadata.gz: 79b61813b56cbbabb76ecbef9abb370b2fe77b58ea3e155377f8c4dfd2677684
4
+ data.tar.gz: 611c108a0c080796c8308bee5f3e361c60fc4ddd316234bf9468c09742b1a52d
5
5
  SHA512:
6
- metadata.gz: 7875f99a1d9d327f0251fdbeae15ddf31820091fd0cbdc43a1e5cbe7cabd8697057fe6333b84400d5e545fc31e3720c82ecc366dc2cc0066b71f3e2794b6e196
7
- data.tar.gz: 91353478d4a994758c6fcc0a843335819cdb9086be9a29fdd429b333abdf58c4e2179e147b685a2f4720fb9e160ecd83ef5f4e5b4ab41a0ad582f583845f2843
6
+ metadata.gz: 85732c76dae467bd5d032462ca23d34b03b7be59c6327202fd9bef307ef3d06bc40af085f32010cfae9dfd93a85b20524785313388964772534632cc95159625
7
+ data.tar.gz: 5c3db85ff44b268bd7604d33e28b92ca454c82c2a812f023af39a3da816a4252dfea01869d313f1ab6d24a1f062782ac002f5bf8fc40fd435efaa0c1ac14d337
@@ -22,7 +22,7 @@ jobs:
22
22
  runs-on: ubuntu-latest
23
23
  strategy:
24
24
  matrix:
25
- ruby-version: ['2.7', '3.0', '3.1']
25
+ ruby-version: ['2.7', '3.0', '3.1', '3.3']
26
26
 
27
27
  steps:
28
28
  - uses: actions/checkout@v3
data/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ ## 3.4.0 - September 10, 2024
2
+ - Chapters
3
+ - Subplots and chapters do not repeat plot scripts
4
+ - Scriptable.no_scripts is deprecated
5
+ - Refactoring/removing unused methods
6
+
7
+ ## 3.3.0 - September 1, 2024
8
+ - Node#take
9
+ - Node#include?
10
+ - Reject non-string tokens in Text queries
11
+
12
+ ## 3.2.2 - July 21, 2024
13
+ - Describable#described?
14
+
1
15
  ## 3.2.1 - July 1, 2024
2
16
  - MultipleChoice accepts shortened text
3
17
  - Return Proxy::Agent from attr_seed and make_seed
data/Rakefile CHANGED
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "bundler/gem_tasks"
4
- require "rspec/core/rake_task"
3
+ require 'bundler/gem_tasks'
5
4
  require 'rspec/core/rake_task'
6
5
  require 'opal/rspec/rake_task'
7
6
 
@@ -14,7 +13,8 @@ end
14
13
  task :default => :spec
15
14
 
16
15
  Opal::RSpec::RakeTask.new(:opal) do |_, config|
17
- Opal.append_path File.expand_path('../lib', __FILE__)
16
+ Opal.append_path File.join(__dir__, 'lib')
17
+ config.default_path = 'spec'
18
18
  config.pattern = 'spec/**/*_spec.rb'
19
- config.requires = ['spec_helper']
19
+ config.requires = ['opal_helper']
20
20
  end
@@ -16,12 +16,12 @@ module Gamefic
16
16
  # @param [Array<Symbol>]
17
17
  attr_reader :verbs
18
18
 
19
- # @param [Proc]
20
- attr_reader :block
19
+ # @param [Callback]
20
+ attr_reader :callback
21
21
 
22
- def initialize *verbs, &block
22
+ def initialize verbs, callback
23
23
  @verbs = verbs
24
- @block = block
24
+ @callback = callback
25
25
  end
26
26
 
27
27
  def match?(input)
@@ -6,6 +6,7 @@ module Gamefic
6
6
  # a few shortcuts.
7
7
  #
8
8
  module Messaging
9
+ # @return [Messenger]
9
10
  def messenger
10
11
  @messenger ||= Messenger.new
11
12
  end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gamefic
4
+ class Callback
5
+ # @param narrative [Narrative]
6
+ # @param code [Proc]
7
+ def initialize narrative, code
8
+ @narrative = narrative
9
+ @code = code
10
+ end
11
+
12
+ def run *args
13
+ Stage.run @narrative, *args, &@code
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gamefic
4
+ # Chapters are plot extensions that manage their own namespaces. Authors can
5
+ # use them to encapsulate related content in a separate object instead of
6
+ # adding the required instance variables, methods, and attributes to the
7
+ # plot.
8
+ #
9
+ # Chapters are similar to subplots with a few important exceptions:
10
+ # * Chapters persist for the duration of the plot.
11
+ # * Players do not need to be introduced to a chapter.
12
+ # * Scripts in chapters apply to the parent plot's rulebook.
13
+ # * Using `make` to create an entity in a chapter adds it to the parent
14
+ # plot's entity list.
15
+ #
16
+ # @example
17
+ # class MyChapter < Gamefic::Chapter
18
+ # seed do
19
+ # @thing = make Gamefic::Entity, name: 'chapter thing'
20
+ # end
21
+ # end
22
+ #
23
+ # class MyPlot < Gamefic::Plot
24
+ # append MyChapter
25
+ # end
26
+ #
27
+ # plot = MyPlot.new
28
+ # plot.entities #=> [<#Gamefic::Entity a chapter thing>]
29
+ # plot.instance_exec { @thing } #=> nil
30
+ #
31
+ class Chapter
32
+ extend Scriptable
33
+
34
+ include Scriptable::Actions
35
+ include Scriptable::Events
36
+ include Scriptable::Proxy
37
+ include Scriptable::Queries
38
+ include Scriptable::Scenes
39
+
40
+ # @return [Plot]
41
+ attr_reader :plot
42
+
43
+ # @param plot [Plot]
44
+ def initialize plot
45
+ @plot = plot
46
+ end
47
+
48
+ def included_blocks
49
+ self.class.included_blocks - plot.included_blocks
50
+ end
51
+
52
+ def seed
53
+ included_blocks.select(&:seed?).each { |blk| Stage.run self, &blk.code }
54
+ end
55
+
56
+ def script
57
+ included_blocks.select(&:script?).each { |blk| Stage.run self, &blk.code }
58
+ end
59
+
60
+ def rulebook
61
+ plot.rulebook
62
+ end
63
+
64
+ def make klass, **opts
65
+ plot.make klass, **opts
66
+ end
67
+
68
+ def entities
69
+ plot.entities
70
+ end
71
+
72
+ def players
73
+ plot.players
74
+ end
75
+
76
+ def destroy entity
77
+ plot.destroy entity
78
+ end
79
+
80
+ def pick description
81
+ plot.pick description
82
+ end
83
+
84
+ def pick! description
85
+ plot.pick! description
86
+ end
87
+ end
88
+ end
@@ -43,7 +43,7 @@ module Gamefic
43
43
  arguments = []
44
44
  response.queries.each_with_index do |query, idx|
45
45
  result = Scanner.send(method, query.select(actor), "#{remainder} #{expression.tokens[idx]}".strip)
46
- break unless validate_result_from_query(result, query)
46
+ break unless valid_result_from_query?(result, query)
47
47
 
48
48
  if query.ambiguous?
49
49
  arguments.push result.matched
@@ -60,7 +60,7 @@ module Gamefic
60
60
 
61
61
  # @param result [Scanner::Result]
62
62
  # @param query [Query::Base]
63
- def validate_result_from_query result, query
63
+ def valid_result_from_query? result, query
64
64
  return false if result.matched.empty?
65
65
 
66
66
  result.matched.length == 1 || query.ambiguous?
@@ -112,10 +112,11 @@ module Gamefic
112
112
  # Does the object have a description?
113
113
  #
114
114
  # @return [Boolean]
115
- def description?
115
+ def described?
116
116
  @description.to_s != ''
117
117
  end
118
- alias has_description? description?
118
+ alias description? described?
119
+ alias has_description? described?
119
120
 
120
121
  # Get the object's description.
121
122
  #
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'set'
4
+
3
5
  module Gamefic
4
6
  # A base class for building and managing the resources that compose a story.
5
7
  # The Plot and Subplot classes inherit from Narrative and provide additional
@@ -19,10 +21,27 @@ module Gamefic
19
21
  attr_reader :rulebook
20
22
 
21
23
  def initialize
22
- self.class.included_blocks.select(&:seed?).each { |blk| Stage.run self, &blk.code }
24
+ seed
25
+ script
26
+ post_initialize
27
+ end
28
+
29
+ def seed
30
+ included_blocks.select(&:seed?).each { |blk| Stage.run self, &blk.code }
31
+ end
32
+
33
+ def script
34
+ @rulebook = Rulebook.new
35
+ included_blocks.select(&:script?).each { |blk| Stage.run self, &blk.code }
36
+ end
37
+
38
+ def included_blocks
39
+ self.class.included_blocks
40
+ end
41
+
42
+ def post_initialize
23
43
  entity_vault.lock
24
- @rulebook = nil
25
- hydrate
44
+ rulebook.freeze
26
45
  end
27
46
 
28
47
  def scenes
@@ -91,9 +110,8 @@ module Gamefic
91
110
  end
92
111
 
93
112
  def hydrate
94
- @rulebook = Rulebook.new(self)
95
- @rulebook.script_with_defaults
96
- @rulebook.freeze
113
+ script
114
+ post_initialize
97
115
  end
98
116
 
99
117
  def self.inherited klass
data/lib/gamefic/node.rb CHANGED
@@ -42,6 +42,15 @@ module Gamefic
42
42
  parent&.add_child self
43
43
  end
44
44
 
45
+ # Add children to the node. Return all the node's children.
46
+ #
47
+ # @param children [Array<Node, Array<Node>>]
48
+ # @return [Array<Node>]
49
+ def take *children
50
+ children.flatten.each { |child| child.parent = self }
51
+ children
52
+ end
53
+
45
54
  # Determine if external objects can interact with this object's children.
46
55
  # For example, a game can designate that the contents of a bowl are
47
56
  # accessible, while the contents of a locked safe are not.
@@ -54,10 +63,14 @@ module Gamefic
54
63
  # True if this node is the other's parent.
55
64
  #
56
65
  # @param other [Node]
57
- def has?(other)
66
+ def include?(other)
58
67
  other.parent == self
59
68
  end
60
69
 
70
+ def adjacent?(other)
71
+ other.parent == parent
72
+ end
73
+
61
74
  protected
62
75
 
63
76
  def add_child(node)
data/lib/gamefic/plot.rb CHANGED
@@ -5,10 +5,26 @@ module Gamefic
5
5
  # methods for creating entities, actions, scenes, and hooks.
6
6
  #
7
7
  class Plot < Narrative
8
+ def seed
9
+ super
10
+ chapters.each(&:seed)
11
+ end
12
+
13
+ def script
14
+ super
15
+ chapters.each(&:script)
16
+ rulebook.scenes.with_defaults self
17
+ end
18
+
19
+ def chapters
20
+ @chapters ||= self.class.appended_chapters.map { |klass| klass.new(self) }
21
+ end
22
+
8
23
  def ready
9
24
  super
10
25
  subplots.each(&:ready)
11
26
  players.each(&:start_take)
27
+ subplots.each(&:conclude) if concluding?
12
28
  players.select(&:concluding?).each { |plyr| rulebook.run_player_conclude_blocks plyr }
13
29
  subplots.delete_if(&:concluding?)
14
30
  end
@@ -74,6 +90,14 @@ module Gamefic
74
90
  subplots.each(&:hydrate)
75
91
  end
76
92
 
93
+ def self.append chapter
94
+ appended_chapters.add chapter
95
+ end
96
+
97
+ def self.appended_chapters
98
+ @appended_chapters ||= Set.new
99
+ end
100
+
77
101
  def self.restore data
78
102
  Snapshot.restore data
79
103
  end
@@ -24,16 +24,24 @@ module Gamefic
24
24
  @ambiguous = ambiguous
25
25
  end
26
26
 
27
- # @deprecated Queries should only be used to select entities that are
28
- # eligible to be response arguments. After a text command is tokenized
29
- # into an array of expressions, the composer builds the command that
30
- # the dispatcher uses to execute actions. The #accept? method verifies
31
- # that the command's arguments match the response's queries.
27
+ # Get a query result for a given subject and token.
28
+ #
29
+ # @note This method is retained as a convenience for authors. Narratives
30
+ # should use Composer to build commands, as it provides more precise
31
+ # matching of tokens to valid response arguments. Authors can use
32
+ # #query to find entities that match a token regardless of whether the
33
+ # result matches an available response.
34
+ #
35
+ # @example
36
+ # respond :reds do |actor|
37
+ # reds = available(ambiguous: true).query(actor, 'red').match
38
+ # actor.tell "The red things you can see here are #{reds.join_and}."
39
+ # end
32
40
  #
33
41
  # @param subject [Gamefic::Entity]
34
42
  # @param token [String]
35
43
  # @return [Result]
36
- def query(subject, token)
44
+ def query(_subject, _token)
37
45
  raise "#query not implemented for #{self.class}"
38
46
  end
39
47
 
@@ -42,7 +50,7 @@ module Gamefic
42
50
  #
43
51
  # @param subject [Entity]
44
52
  # @return [Array<Entity>]
45
- def select subject
53
+ def select _subject
46
54
  raise "#select not implemented for #{self.class}"
47
55
  end
48
56
 
@@ -88,12 +96,14 @@ module Gamefic
88
96
  depth
89
97
  end
90
98
 
99
+ # @param scan [Scanner::Result]
91
100
  def ambiguous_result scan
92
101
  return Result.new(nil, scan.token) if scan.matched.empty?
93
102
 
94
103
  Result.new(scan.matched, scan.remainder)
95
104
  end
96
105
 
106
+ # @param scan [Scanner::Result]
97
107
  def unambiguous_result scan
98
108
  return Result.new(nil, scan.token) unless scan.matched.one?
99
109
 
@@ -6,7 +6,6 @@ module Gamefic
6
6
  # relationship to the entity performing the query. For example,
7
7
  # Scope::Children would filter from an array of the entity's descendants.
8
8
  #
9
- # @return [Class<Gamefic::Scope::Base>]
10
9
  class Scoped < Base
11
10
  attr_reader :scope
12
11
 
@@ -39,6 +39,8 @@ module Gamefic
39
39
  private
40
40
 
41
41
  def match? token
42
+ return false unless token.is_a?(String)
43
+
42
44
  case @argument
43
45
  when Regexp
44
46
  token =~ @argument
@@ -11,9 +11,6 @@ module Gamefic
11
11
  # @return [Array<Query::Base>]
12
12
  attr_reader :queries
13
13
 
14
- # @return [Narrative]
15
- attr_reader :narrative
16
-
17
14
  # @param verb [Symbol]
18
15
  # @param narrative [Narrative]
19
16
  # @param queries [Array<Query::Base>]
@@ -21,10 +18,10 @@ module Gamefic
21
18
  # @param block [Proc]
22
19
  def initialize verb, narrative, *queries, meta: false, &block
23
20
  @verb = verb
24
- @narrative = narrative
25
21
  @queries = map_queryable_objects(queries)
26
22
  @meta = meta
27
23
  @block = block
24
+ @callback = Callback.new(narrative, block)
28
25
  end
29
26
 
30
27
  # The `meta?` flag is just a way for authors to identify responses that
@@ -70,7 +67,7 @@ module Gamefic
70
67
  end
71
68
 
72
69
  def execute *args
73
- Stage.run(narrative, *args, &@block)
70
+ @callback.run *args
74
71
  end
75
72
 
76
73
  def precision
@@ -67,10 +67,6 @@ module Gamefic
67
67
  verb_response_map.empty? && synonym_syntax_map.empty?
68
68
  end
69
69
 
70
- def self.new_array_map
71
- Hash.new { |hash, key| hash[key] = [] }
72
- end
73
-
74
70
  private
75
71
 
76
72
  attr_reader :verb_response_map
@@ -35,44 +35,30 @@ module Gamefic
35
35
  end
36
36
 
37
37
  # @return [void]
38
- def on_ready &block
39
- @ready_blocks.push block
38
+ def on_ready callback
39
+ @ready_blocks.push callback
40
40
  end
41
41
 
42
- # @yieldparam [Actor]
43
- # @return [void]
44
- def on_player_ready &block
45
- @ready_blocks.push(proc do
46
- players.each { |plyr| instance_exec plyr, &block }
47
- end)
48
- end
49
-
50
- def on_update &block
51
- @update_blocks.push block
52
- end
53
-
54
- def on_player_update &block
55
- @update_blocks.push(proc do
56
- players.each { |plyr| instance_exec plyr, &block }
57
- end)
42
+ def on_update callback
43
+ @update_blocks.push callback
58
44
  end
59
45
 
60
46
  # @return [void]
61
- def on_conclude &block
62
- @conclude_blocks.push block
47
+ def on_conclude callback
48
+ @conclude_blocks.push callback
63
49
  end
64
50
 
65
51
  # @yieldparam [Actor]
66
52
  # @return [void]
67
- def on_player_conclude &block
68
- @player_conclude_blocks.push block
53
+ def on_player_conclude callback
54
+ @player_conclude_blocks.push callback
69
55
  end
70
56
 
71
57
  # @yieldparam [Actor]
72
58
  # @yieldparam [Hash]
73
59
  # @return [void]
74
- def on_player_output &block
75
- @player_output_blocks.push block
60
+ def on_player_output callback
61
+ @player_output_blocks.push callback
76
62
  end
77
63
  end
78
64
  end
@@ -21,35 +21,35 @@ module Gamefic
21
21
  self
22
22
  end
23
23
 
24
- def before_action *verbs, &block
25
- before_actions.push Action::Hook.new(*verbs, &block)
24
+ def before_action narrative, *verbs, &block
25
+ before_actions.push Action::Hook.new(verbs, Callback.new(narrative, block))
26
26
  end
27
27
 
28
- def after_action *verbs, &block
29
- after_actions.push Action::Hook.new(*verbs, &block)
28
+ def after_action narrative, *verbs, &block
29
+ after_actions.push Action::Hook.new(verbs, Callback.new(narrative, block))
30
30
  end
31
31
 
32
32
  def empty?
33
33
  before_actions.empty? && after_actions.empty?
34
34
  end
35
35
 
36
- def run_before action, narrative
37
- run_action_hooks action, narrative, before_actions
36
+ def run_before action
37
+ run_action_hooks action, before_actions
38
38
  end
39
39
 
40
- def run_after action, narrative
41
- run_action_hooks action, narrative, after_actions
40
+ def run_after action
41
+ run_action_hooks action, after_actions
42
42
  end
43
43
 
44
44
  private
45
45
 
46
- def run_action_hooks action, narrative, hooks
46
+ def run_action_hooks action, hooks
47
47
  hooks.each do |hook|
48
48
  break if action.cancelled?
49
49
 
50
50
  next unless hook.match?(action.verb)
51
51
 
52
- Stage.run(narrative) { instance_exec(action, &hook.block) }
52
+ hook.callback.run action
53
53
  end
54
54
  end
55
55
  end
@@ -25,12 +25,7 @@ module Gamefic
25
25
  # @return [Scenes]
26
26
  attr_reader :scenes
27
27
 
28
- # @return [Narrative]
29
- attr_reader :narrative
30
-
31
- # @param narrative [Narrative]
32
- def initialize(narrative)
33
- @narrative = narrative
28
+ def initialize
34
29
  @calls = Calls.new
35
30
  @events = Events.new
36
31
  @hooks = Hooks.new
@@ -96,44 +91,35 @@ module Gamefic
96
91
  end
97
92
 
98
93
  def run_ready_blocks
99
- events.ready_blocks.each { |blk| Stage.run narrative, &blk }
94
+ events.ready_blocks.each(&:run)
100
95
  end
101
96
 
102
97
  def run_update_blocks
103
- events.update_blocks.each { |blk| Stage.run narrative, &blk }
98
+ events.update_blocks.each(&:run)
104
99
  end
105
100
 
106
101
  def run_before_actions action
107
- hooks.run_before action, narrative
102
+ hooks.run_before action
108
103
  end
109
104
 
110
105
  def run_after_actions action
111
- hooks.run_after action, narrative
106
+ hooks.run_after action
112
107
  end
113
108
 
114
109
  def run_conclude_blocks
115
- events.conclude_blocks.each { |blk| Stage.run narrative, &blk }
110
+ events.conclude_blocks.each(&:run)
116
111
  end
117
112
 
118
113
  def run_player_conclude_blocks player
119
- events.player_conclude_blocks.each { |blk| Stage.run(narrative, player, &blk) }
114
+ events.player_conclude_blocks.each { |blk| blk.run(player) }
120
115
  end
121
116
 
122
117
  def run_player_output_blocks player, output
123
- events.player_output_blocks.each { |blk| Stage.run(narrative, player, output, &blk) }
118
+ events.player_output_blocks.each { |blk| blk.run(player, output) }
124
119
  end
125
120
 
126
121
  def empty?
127
122
  calls.empty? && hooks.empty? && scenes.empty? && events.empty?
128
123
  end
129
-
130
- def script
131
- narrative.class.included_blocks.select(&:script?).each { |blk| Stage.run(narrative, &blk.code) }
132
- end
133
-
134
- def script_with_defaults
135
- script
136
- scenes.with_defaults narrative
137
- end
138
124
  end
139
125
  end
@@ -31,7 +31,7 @@ module Gamefic
31
31
  # @return [Symbol]
32
32
  def respond(verb, *queries, &proc)
33
33
  args = map_response_args(queries)
34
- rulebook.calls.add_response Response.new(verb, rulebook.narrative, *args, &proc)
34
+ rulebook.calls.add_response Response.new(verb, self, *args, &proc)
35
35
  verb
36
36
  end
37
37
 
@@ -52,7 +52,7 @@ module Gamefic
52
52
  # @return [Symbol]
53
53
  def meta(verb, *queries, &proc)
54
54
  args = map_response_args(queries)
55
- rulebook.calls.add_response Response.new(verb, rulebook.narrative, *args, meta: true, &proc)
55
+ rulebook.calls.add_response Response.new(verb, self, *args, meta: true, &proc)
56
56
  verb
57
57
  end
58
58
 
@@ -64,7 +64,7 @@ module Gamefic
64
64
  # @yieldparam [Gamefic::Action]
65
65
  # @return [Action::Hook]
66
66
  def before_action *verbs, &block
67
- rulebook.hooks.before_action *verbs, &block
67
+ rulebook.hooks.before_action self, *verbs, &block
68
68
  end
69
69
 
70
70
  # Add a proc to be evaluated after a character executes an action.
@@ -75,7 +75,7 @@ module Gamefic
75
75
  # @yieldparam [Gamefic::Action]
76
76
  # @return [Action::Hook]
77
77
  def after_action *verbs, &block
78
- rulebook.hooks.after_action *verbs, &block
78
+ rulebook.hooks.after_action self, *verbs, &block
79
79
  end
80
80
 
81
81
  # Create an alternate Syntax for a response.
@@ -14,7 +14,7 @@ module Gamefic
14
14
  # end
15
15
  #
16
16
  def on_ready &block
17
- rulebook.events.on_ready(&block)
17
+ rulebook.events.on_ready(Callback.new(self, block))
18
18
  end
19
19
 
20
20
  # Add a block to be executed for each player at the beginning of a turn.
@@ -28,37 +28,43 @@ module Gamefic
28
28
  #
29
29
  # @yieldparam [Gamefic::Actor]
30
30
  def on_player_ready &block
31
- rulebook.events.on_player_ready(&block)
31
+ wrapper = proc do
32
+ players.each { |player| Stage.run(self, player, &block) }
33
+ end
34
+ on_ready &wrapper
32
35
  end
33
36
 
34
37
  # Add a block to be executed after the Plot is finished updating a turn.
35
38
  #
36
39
  def on_update &block
37
- rulebook.events.on_update(&block)
40
+ rulebook.events.on_update(Callback.new(self, block))
38
41
  end
39
42
 
40
43
  # Add a block to be executed for each player at the end of a turn.
41
44
  #
42
45
  # @yieldparam [Gamefic::Actor]
43
46
  def on_player_update &block
44
- rulebook.events.on_player_update(&block)
47
+ wrapper = proc do
48
+ players.each { |player| Stage.run(self, player, &block) }
49
+ end
50
+ on_update &wrapper
45
51
  end
46
52
 
47
53
  def on_conclude &block
48
- rulebook.events.on_conclude(&block)
54
+ rulebook.events.on_conclude(Callback.new(self, block))
49
55
  end
50
56
 
51
57
  # @yieldparam [Actor]
52
58
  # @return [Proc]
53
59
  def on_player_conclude &block
54
- rulebook.events.on_player_conclude(&block)
60
+ rulebook.events.on_player_conclude(Callback.new(self, block))
55
61
  end
56
62
 
57
63
  # @yieldparam [Actor]
58
64
  # @yieldparam [Hash]
59
65
  # @return [Proc]
60
66
  def on_player_output &block
61
- rulebook.events.on_player_output(&block)
67
+ rulebook.events.on_player_output Callback.new(self, block)
62
68
  end
63
69
  end
64
70
  end
@@ -45,6 +45,7 @@ module Gamefic
45
45
  # proxy(:@instance_variable_name)
46
46
  #
47
47
  # @param symbol [Symbol]
48
+ # @return [Agent]
48
49
  def proxy symbol
49
50
  Agent.new(symbol)
50
51
  end
@@ -33,11 +33,17 @@ module Gamefic
33
33
  # @yieldparam [Scene]
34
34
  # @return [Symbol]
35
35
  def block name, klass = Scene::Default, on_start: nil, on_finish: nil, &blk
36
- rulebook.scenes.add klass.new(name, rulebook.narrative, on_start: on_start, on_finish: on_finish, &blk)
36
+ rulebook.scenes.add klass.new(name, self, on_start: on_start, on_finish: on_finish, &blk)
37
37
  name
38
38
  end
39
39
  alias scene block
40
40
 
41
+ def preface name, klass = Scene::Activity, &start
42
+ rulebook.scenes.add klass.new(name, self, on_start: start)
43
+ name
44
+ end
45
+ alias precursor preface
46
+
41
47
  # Add a block to be executed when a player is added to the game.
42
48
  # Each Plot should only have one introduction.
43
49
  #
@@ -54,8 +60,8 @@ module Gamefic
54
60
  def introduction(&start)
55
61
  rulebook.scenes
56
62
  .introduction Scene::Default.new nil,
57
- rulebook.narrative,
58
- on_start: proc { |actor, _props| instance_exec(actor, &start) }
63
+ self,
64
+ on_start: proc { |actor, _props| Stage.run(self, actor, &start) }
59
65
  end
60
66
 
61
67
  # Create a multiple-choice scene.
@@ -117,6 +117,7 @@ module Gamefic
117
117
  # plot = Plot.new
118
118
  # plot.thing #=> #<Gamefic::Entity a thing>
119
119
  #
120
+ # @param name [Symbol] The attribute name
120
121
  # @param klass [Class<Gamefic::Entity>]
121
122
  def attr_seed name, klass, **opts
122
123
  @count ||= 0
@@ -154,35 +155,13 @@ module Gamefic
154
155
  # This can be useful when you need access to the Scriptable's constants and
155
156
  # instance methods, but you don't want to duplicate its rules.
156
157
  #
157
- # @example
158
- # # Plot and Subplot will both include the `info` method, but
159
- # # only Plot will implement the `think` action.
160
- #
161
- # module Shared
162
- # extend Gamefic::Scriptable
163
- #
164
- # def info
165
- # "This method was added by the Shared module."
166
- # end
167
- #
168
- # respond :think do |actor|
169
- # actor.tell 'You ponder your predicament.'
170
- # end
171
- # end
158
+ # @deprecated Removing script blocks is no longer necessary. This method
159
+ # will simply return self until it's removed.
172
160
  #
173
- # class Plot < Gamefic::Plot
174
- # include Shared
175
- # end
176
- #
177
- # class Subplot < Gamefic::Subplot
178
- # include Shared.no_scripts
179
- # end
180
- #
181
- # @return [Module]
161
+ # @return [Module<self>]
182
162
  def no_scripts
183
- Module.new.tap do |mod|
184
- append_features(mod)
185
- end
163
+ Logging.logger.warn 'Calling `no_scripts` on Scriptable modules is no longer necessary.'
164
+ self
186
165
  end
187
166
  end
188
167
  end
@@ -25,6 +25,10 @@ module Gamefic
25
25
  [introduce].flatten.each { |pl| self.introduce pl }
26
26
  end
27
27
 
28
+ def included_blocks
29
+ super - plot.included_blocks
30
+ end
31
+
28
32
  def ready
29
33
  super
30
34
  conclude if concluding?
@@ -68,11 +72,5 @@ module Gamefic
68
72
  def inspect
69
73
  "#<#{self.class}>"
70
74
  end
71
-
72
- def hydrate
73
- @rulebook = Rulebook.new(self)
74
- @rulebook.script
75
- @rulebook.freeze
76
- end
77
75
  end
78
76
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Gamefic
4
- VERSION = '3.2.1'
4
+ VERSION = '3.4.0'
5
5
  end
data/lib/gamefic.rb CHANGED
@@ -8,6 +8,7 @@ require 'gamefic/core_ext/string'
8
8
  require 'gamefic/syntax'
9
9
  require 'gamefic/response'
10
10
  require 'gamefic/rulebook'
11
+ require 'gamefic/callback'
11
12
  require 'gamefic/query'
12
13
  require 'gamefic/scanner'
13
14
  require 'gamefic/scope'
@@ -22,6 +23,7 @@ require 'gamefic/stage'
22
23
  require 'gamefic/vault'
23
24
  require 'gamefic/narrative'
24
25
  require 'gamefic/plot'
26
+ require 'gamefic/chapter'
25
27
  require 'gamefic/subplot'
26
28
  require 'gamefic/snapshot'
27
29
  require 'gamefic/node'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gamefic
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.2.1
4
+ version: 3.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Fred Snyder
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-07-01 00:00:00.000000000 Z
11
+ date: 2024-09-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: opal
@@ -135,6 +135,8 @@ files:
135
135
  - lib/gamefic/active/take.rb
136
136
  - lib/gamefic/actor.rb
137
137
  - lib/gamefic/block.rb
138
+ - lib/gamefic/callback.rb
139
+ - lib/gamefic/chapter.rb
138
140
  - lib/gamefic/command.rb
139
141
  - lib/gamefic/composer.rb
140
142
  - lib/gamefic/core_ext/array.rb
@@ -195,7 +197,6 @@ files:
195
197
  - lib/gamefic/syntax/template.rb
196
198
  - lib/gamefic/vault.rb
197
199
  - lib/gamefic/version.rb
198
- - spec-opal/spec_helper.rb
199
200
  homepage: https://gamefic.com
200
201
  licenses:
201
202
  - MIT
@@ -1,24 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'gamefic'
4
- require 'ostruct'
5
-
6
- RSpec.configure do |config|
7
- # Run specs in random order to surface order dependencies. If you find an
8
- # order dependency and want to debug it, you can fix the order by providing
9
- # the seed, which is printed after each run.
10
- # --seed 1234
11
- # config.order = :random
12
-
13
- # Seed global randomization in this process using the `--seed` CLI option.
14
- # Setting this allows you to use `--seed` to deterministically reproduce
15
- # test failures related to randomization by passing the same `--seed` value
16
- # as the one that triggered the failure.
17
- # Kernel.srand config.seed
18
-
19
- config.after :each do
20
- Gamefic::Narrative.blocks.clear
21
- Gamefic::Plot.blocks.clear
22
- Gamefic::Subplot.blocks.clear
23
- end
24
- end