gamefic 3.2.1 → 3.4.0

Sign up to get free protection for your applications and to get access to all the features.
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