gamefic 2.2.3 → 2.3.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: a57ab533bcfbcbd650cf3ee60c5a639b3ddd40ad3ad96d0d4e42e6658547f3d9
4
- data.tar.gz: 904c5211a6bde65f870ae8bab1218423afd6618c6ddd79c44b01174bb069256b
3
+ metadata.gz: aa8d74d06ad87105050c1ca487acd976d293f4ca52fa6cc760061ccdbb3a14e7
4
+ data.tar.gz: f791519291a168cf31045d6bc2f136d4829f5ddce2fa7c370d6c4887f83f560e
5
5
  SHA512:
6
- metadata.gz: afc3e6a06d6ac451827cea70db94b8c9e4b10c795dac9812df0c24f8c1e0ce654748dfae53c4c0565bbfff8abb5d2f853f866c1aac8b398c5cf8331ee3ea1795
7
- data.tar.gz: dd790a5574ae81e91b61d01c7f18adba0abcd7e43650e72934329dfd84c9515adb54b6e1e0938814c9d6c6582d23801c66a470a28ae99700659fa0c462830660
6
+ metadata.gz: e8abcf0f9ca24db9b8bc3f61be5d0d28dcd86c24f047e420eb117bd09411b70d5f3ae7e9c2c5d2277062c4c72a970d2c9144eb0990a46872eb80b7a7bd75369e
7
+ data.tar.gz: 2bdec46fe7d946a05c3d9bf258314db25c61f32459993f03f1c2096dd31b0056917332fcd5c05ee011e3c352e373a1dcaa74f670d069398e481334ff64c07f1d
data/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
+ ## 2.3.0 - January 25, 2023
2
+ - Remove unused Active#actions method
3
+ - Add before_action and after_action hooks
4
+
1
5
  ## 2.2.3 - July 9, 2022
2
6
  - Fix Ruby version incompatibilities
7
+ - Fix private attr_accessor call
3
8
 
4
9
  ## 2.2.2 - September 6, 2021
5
10
  - Darkroom indexes non-static elements
@@ -1,5 +1,8 @@
1
1
  module Gamefic
2
2
  class Action
3
+ # @return [Gamefic::Actor]
4
+ attr_reader :actor
5
+
3
6
  # An array of objects on which the action will operate, e.g., an entity
4
7
  # that is a direct object of a command.
5
8
  #
@@ -7,17 +10,31 @@ module Gamefic
7
10
  attr_reader :arguments
8
11
  alias parameters arguments
9
12
 
10
- def initialize actor, arguments
13
+ # @param actor [Gamefic::Actor]
14
+ # @param arguments [Array<Object>]
15
+ def initialize actor, arguments, with_callbacks = false
11
16
  @actor = actor
12
17
  @arguments = arguments
13
18
  @executed = false
19
+ @with_callbacks = with_callbacks
14
20
  end
15
21
 
16
22
  # Perform the action.
17
23
  #
18
24
  def execute
19
- @executed = true
25
+ return if @cancelled
26
+ run_before_actions
27
+ return if @cancelled
20
28
  self.class.executor.call(@actor, *arguments) unless self.class.executor.nil?
29
+ @executed = true
30
+ run_after_actions
31
+ end
32
+
33
+ # Cancel an action. This method can be called in a before_action hook to
34
+ # prevent subsequent hooks and the action itself from being executed.
35
+ #
36
+ def cancel
37
+ @cancelled = true
21
38
  end
22
39
 
23
40
  # True if the #execute method has been called for this action.
@@ -68,6 +85,25 @@ module Gamefic
68
85
  act
69
86
  end
70
87
 
88
+ private
89
+
90
+ def run_before_actions
91
+ return unless @with_callbacks
92
+ @actor.playbooks
93
+ .flat_map(&:before_actions)
94
+ .each do |block|
95
+ block.call(self)
96
+ break if @cancelled
97
+ end
98
+ end
99
+
100
+ def run_after_actions
101
+ return unless @with_callbacks
102
+ @actor.playbooks
103
+ .flat_map(&:after_actions)
104
+ .each { |block| block.call(self) }
105
+ end
106
+
71
107
  class << self
72
108
  attr_reader :verb
73
109
 
@@ -135,7 +171,7 @@ module Gamefic
135
171
  # @param action [Gamefic::Entity]
136
172
  # @param command [Command]
137
173
  # @return [self, nil]
138
- def attempt actor, command
174
+ def attempt actor, command, with_callbacks = false
139
175
  return nil if command.verb != verb
140
176
  tokens = command.arguments
141
177
  result = []
@@ -153,7 +189,7 @@ module Gamefic
153
189
  result.push accepted.first
154
190
  end
155
191
  end
156
- new(actor, result)
192
+ new(actor, result, with_callbacks)
157
193
  end
158
194
 
159
195
  protected
@@ -1,3 +1,5 @@
1
+ require 'set'
2
+
1
3
  module Gamefic
2
4
  class NotConclusionError < RuntimeError; end
3
5
 
@@ -37,7 +39,7 @@ module Gamefic
37
39
  end
38
40
 
39
41
  def syntaxes
40
- playbooks.map(&:syntaxes).flatten
42
+ playbooks.flat_map(&:syntaxes)
41
43
  end
42
44
 
43
45
  # An array of actions waiting to be performed.
@@ -84,28 +86,18 @@ module Gamefic
84
86
  end
85
87
 
86
88
  # Perform a command.
87
- # The command can be specified as a String or a verb with a list of
88
- # parameters. Either form should yield the same result, but the
89
- # verb/parameter form can yield better performance since it bypasses the
90
- # parser.
91
89
  #
92
- # The command will be executed immediately regardless of the entity's
93
- # state.
90
+ # The command's action will be executed immediately regardless of the
91
+ # entity's state.
94
92
  #
95
93
  # @example Send a command as a string
96
94
  # character.perform "take the key"
97
95
  #
98
- # @example Send a command as a verb with parameters
99
- # character.perform :take, @key
100
- #
101
- # @todo Modify this method so it only accepts a single command.
102
- # Verbs with parameters should use Active#execute instead.
103
- # It might be necessary to support command splats with a deprecation
104
- # warning until version 3.
105
- #
96
+ # @param command [String, Symbol]
106
97
  # @return [Gamefic::Action]
107
98
  def perform(*command)
108
99
  if command.length > 1
100
+ STDERR.puts "[WARN] #{caller[0]}: Passing a verb and arguments to #perform is deprecated. Use #execute instead."
109
101
  execute command.first, *command[1..-1]
110
102
  else
111
103
  dispatchers.push Dispatcher.dispatch(self, command.first.to_s)
@@ -118,16 +110,18 @@ module Gamefic
118
110
  # This method executes the command exactly as #perform does, except it
119
111
  # buffers the resulting output instead of sending it to the user.
120
112
  #
121
- # @todo Modify this method so it only accepts a single command.
122
- # See Active#perform for more information.
123
- #
113
+ # @param command [String, Symbol]
124
114
  # @return [String] The output that resulted from performing the command.
125
115
  def quietly(*command)
126
- clear_buffer if buffer_stack == 0
127
- set_buffer_stack buffer_stack + 1
128
- self.perform *command
129
- set_buffer_stack buffer_stack - 1
130
- buffer
116
+ if command.length > 1
117
+ STDERR.puts "#{caller[0]}: Passing a verb and arguments to #quietly is deprecated. Use #execute instead"
118
+ execute command.first, *command[1..-1], quietly: true
119
+ else
120
+ dispatchers.push Dispatcher.dispatch(self, command.first.to_s)
121
+ result = proceed quietly: true
122
+ dispatchers.pop
123
+ result
124
+ end
131
125
  end
132
126
 
133
127
  # Perform an action.
@@ -141,6 +135,9 @@ module Gamefic
141
135
  # @example
142
136
  # character.execute :take, @key
143
137
  #
138
+ # @param verb [Symbol]
139
+ # @param params [Array]
140
+ # @params quietly [Boolean]
144
141
  # @return [Gamefic::Action]
145
142
  def execute(verb, *params, quietly: false)
146
143
  dispatchers.push Dispatcher.dispatch_from_params(self, verb, params)
@@ -171,21 +168,15 @@ module Gamefic
171
168
  # end
172
169
  # end
173
170
  #
171
+ # @param quietly [Boolean] If true, return the action's output instead of appending it to #messages
172
+ # @return [String, nil]
174
173
  def proceed quietly: false
175
174
  return if dispatchers.empty?
176
175
  a = dispatchers.last.next
177
176
  return if a.nil?
178
- if quietly
179
- if buffer_stack == 0
180
- @buffer = ""
181
- end
182
- set_buffer_stack(buffer_stack + 1)
183
- end
177
+ prepare_buffer quietly
184
178
  a.execute
185
- if quietly
186
- set_buffer_stack(buffer_stack - 1)
187
- @buffer
188
- end
179
+ flush_buffer quietly
189
180
  end
190
181
 
191
182
  # Immediately start a new scene for the character.
@@ -251,8 +242,8 @@ module Gamefic
251
242
  # Track the entity's performance of a scene.
252
243
  #
253
244
  def entered scene
254
- klass = (scene.kind_of?(Gamefic::Scene::Base) ? scene.class : scene)
255
- entered_scenes.push klass unless entered_scenes.include?(klass)
245
+ klass = (scene.is_a?(Gamefic::Scene::Base) ? scene.class : scene)
246
+ entered_scenes.add klass
256
247
  end
257
248
 
258
249
  # Determine whether the entity has performed the specified scene.
@@ -265,9 +256,25 @@ module Gamefic
265
256
 
266
257
  private
267
258
 
268
- # @return [Array<Gamefic::Scene::Base>]
259
+ def prepare_buffer quietly
260
+ if quietly
261
+ if buffer_stack == 0
262
+ @buffer = ""
263
+ end
264
+ set_buffer_stack(buffer_stack + 1)
265
+ end
266
+ end
267
+
268
+ def flush_buffer quietly
269
+ if quietly
270
+ set_buffer_stack(buffer_stack - 1)
271
+ @buffer
272
+ end
273
+ end
274
+
275
+ # @return [Set<Gamefic::Scene::Base>]
269
276
  def entered_scenes
270
- @entered_scenes ||= []
277
+ @entered_scenes ||= Set.new
271
278
  end
272
279
 
273
280
  def buffer_stack
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Array
2
4
  # Get a subset of the array that matches the arguments.
3
5
  # If the argument is a Class or Module, the elements must be of the type.
@@ -12,7 +14,7 @@ class Array
12
14
  #
13
15
  # @return [Array]
14
16
  def that_are(*cls)
15
- result = self.clone
17
+ result = clone
16
18
  cls.each do |c|
17
19
  _keep result, c, true
18
20
  end
@@ -24,7 +26,7 @@ class Array
24
26
  #
25
27
  # @return [Array]
26
28
  def that_are_not(*cls)
27
- result = self.clone
29
+ result = clone
28
30
  cls.each do |c|
29
31
  _keep result, c, false
30
32
  end
@@ -34,7 +36,7 @@ class Array
34
36
  # Pop a random element from the array.
35
37
  #
36
38
  def pop_sample
37
- delete_at(rand(self.length))
39
+ delete_at(rand(length))
38
40
  end
39
41
 
40
42
  # Get a string representation of the array that separates elements with
@@ -48,31 +50,32 @@ class Array
48
50
  # @param andSep [String] The separator for the last element
49
51
  # @param serial [Boolean] Use serial separators (e.g., serial commas)
50
52
  # @return [String]
51
- def join_and(sep = ', ', andSep = ' and ', serial = true)
52
- if self.length < 3
53
- self.join(andSep)
53
+ def join_and(sep = ', ', and_sep = ' and ', serial = true)
54
+ if length < 3
55
+ join(and_sep)
54
56
  else
55
57
  start = self[0..-2]
56
- start.join(sep) + "#{serial ? sep.strip : ''}#{andSep}#{self.last}"
58
+ start.join(sep) + "#{serial ? sep.strip : ''}#{and_sep}#{last}"
57
59
  end
58
60
  end
59
61
 
60
62
  # @see Array#join_and
61
63
  #
62
64
  # @return [String]
63
- def join_or(sep = ', ', orSep = ' or ', serial = true)
64
- join_and(sep, orSep, serial)
65
+ def join_or(sep = ', ', or_sep = ' or ', serial = true)
66
+ join_and(sep, or_sep, serial)
65
67
  end
66
68
 
67
69
  private
68
70
 
69
71
  def _keep(arr, cls, bool)
70
- if (cls.kind_of?(Class) or cls.kind_of?(Module))
72
+ case cls
73
+ when Class, Module
71
74
  arr.keep_if { |i| i.is_a?(cls) == bool }
72
- elsif cls.kind_of?(Symbol)
75
+ when Symbol
73
76
  arr.keep_if { |i| i.send(cls) == bool }
74
77
  else
75
- arr.keep_if {|i| (i == cls) == bool}
78
+ arr.keep_if { |i| (i == cls) == bool }
76
79
  end
77
80
  end
78
81
  end
@@ -9,6 +9,7 @@ module Gamefic
9
9
  @actor = actor
10
10
  @commands = commands
11
11
  @actions = actions
12
+ @started = false
12
13
  end
13
14
 
14
15
  def merge dispatcher
@@ -21,13 +22,9 @@ module Gamefic
21
22
  while instance.nil? && !@actions.empty?
22
23
  action = actions.shift
23
24
  commands.each do |cmd|
24
- instance = action.attempt(actor, cmd)
25
+ instance = action.attempt(actor, cmd, !@started)
25
26
  if instance
26
- unless instance.meta?
27
- actor.playbooks.reverse.each do |playbook|
28
- return nil unless validate_playbook(playbook, instance)
29
- end
30
- end
27
+ @started = true
31
28
  break
32
29
  end
33
30
  end
@@ -66,11 +63,5 @@ module Gamefic
66
63
 
67
64
  # @return [Array<Action>]
68
65
  attr_reader :actions
69
-
70
- private
71
-
72
- def validate_playbook playbook, action
73
- playbook.validators.all? { |v| v.call(actor, action.verb, action.parameters) != false }
74
- end
75
66
  end
76
67
  end
data/lib/gamefic/plot.rb CHANGED
@@ -34,6 +34,10 @@ module Gamefic
34
34
  @static = [self] + scene_classes + entities
35
35
  end
36
36
 
37
+ def plot
38
+ self
39
+ end
40
+
37
41
  # Get an Array of the Plot's current Syntaxes.
38
42
  #
39
43
  # @return [Array<Syntax>]
@@ -1,7 +1,7 @@
1
1
  module Gamefic
2
2
  module Query
3
3
  class Base
4
- NEST_REGEXP = / in | on | of | from | inside /
4
+ NEST_REGEXP = / in | on | of | from | inside | from inside /
5
5
 
6
6
  attr_reader :arguments
7
7
 
@@ -31,6 +31,9 @@ module Gamefic
31
31
  # Get a collection of objects that exist in the subject's context and
32
32
  # match the provided token. The result is provided as a Matches object.
33
33
  #
34
+ # @param subject [Entity]
35
+ # @param token [String]
36
+ # @param continued [Boolean]
34
37
  # @return [Gamefic::Query::Matches]
35
38
  def resolve(subject, token, continued: false)
36
39
  available = context_from(subject)
@@ -38,14 +41,13 @@ module Gamefic
38
41
  if continued
39
42
  return Matches.execute(available, token, continued: continued)
40
43
  elsif nested?(token)
41
- drill = denest(available, token)
42
- drill.keep_if{ |e| accept?(e) }
44
+ drill = denest(available, token).select { |e| accept?(e) }
43
45
  return Matches.new(drill, token, '') unless drill.length != 1
44
46
  return Matches.new([], '', token)
45
47
  end
46
- result = available.select{ |e| e.specified?(token) }
47
- result = available.select{ |e| e.specified?(token, fuzzy: true) } if result.empty?
48
- result.keep_if{ |e| accept? e }
48
+ result = available.select { |e| e.specified?(token) }
49
+ result = available.select { |e| e.specified?(token, fuzzy: true) } if result.empty?
50
+ result.keep_if { |e| accept? e }
49
51
  Matches.new(result, (result.empty? ? '' : token), (result.empty? ? token : ''))
50
52
  end
51
53
 
@@ -82,6 +84,7 @@ module Gamefic
82
84
 
83
85
  # Determine whether the specified entity passes the query's arguments.
84
86
  #
87
+ # @param [Entity]
85
88
  # @return [Boolean]
86
89
  def accept?(entity)
87
90
  result = true
@@ -106,6 +109,7 @@ module Gamefic
106
109
  # recursively append its children.
107
110
  # The result will NOT include the original entity itself.
108
111
  #
112
+ # @param [Entity]
109
113
  # @return [Array<Object>]
110
114
  def subquery_accessible entity
111
115
  return [] if entity.nil?
@@ -1,17 +1,38 @@
1
1
  module Gamefic
2
2
  module Query
3
3
  class External < Base
4
- def initialize objects, *args
4
+ # @param container [Plot, Subplot, Array]
5
+ def initialize container, *args
5
6
  super(*args)
6
- @objects = objects
7
+ @container = container
7
8
  end
8
9
 
9
10
  def context_from subject
10
- @objects
11
+ Set.new
12
+ .merge(container_entities)
13
+ .merge(container_subplots_for(@container, subject))
14
+ .to_a
11
15
  end
12
16
 
13
- def accept?(entity)
14
- @objects.include?(entity) && super(entity)
17
+ private
18
+
19
+ # @return [Array<Entity>]
20
+ def container_entities
21
+ if @container.is_a?(World::Entities)
22
+ @container.entities
23
+ elsif @container.is_a?(Enumerable)
24
+ @container
25
+ else
26
+ raise ArgumentError, "Unable to derive entities from #{@container}"
27
+ end
28
+ end
29
+
30
+ # @return [Array<Entity>]
31
+ def container_subplots_for container, subject
32
+ return [] unless container.is_a?(Plot::Host)
33
+ container.subplots_featuring(subject).flat_map do |subplot|
34
+ subplot.entities + container_subplots_for(subplot, subject)
35
+ end
15
36
  end
16
37
  end
17
38
  end
@@ -1,7 +1,10 @@
1
1
  require 'gamefic/plot'
2
2
 
3
3
  module Gamefic
4
- class Subplot #< Container
4
+ # Subplots are disposable plots that run inside a parent plot. They can be
5
+ # started and concluded at any time during the parent plot's execution.
6
+ #
7
+ class Subplot
5
8
  include World
6
9
  include Scriptable
7
10
  include Gamefic::Serialize
@@ -1,3 +1,3 @@
1
1
  module Gamefic
2
- VERSION = '2.2.3'
2
+ VERSION = '2.3.0'
3
3
  end
@@ -95,11 +95,19 @@ module Gamefic
95
95
  playbook.meta command, *queries, &proc
96
96
  end
97
97
 
98
- # Validate an order before a character can execute its command.
98
+ # Add a proc to be evaluated before a character executes an action.
99
99
  #
100
- # @yieldparam [Gamefic::Director::Order]
101
- def validate &block
102
- playbook.validate &block
100
+ # @yieldparam [Gamefic::Action]
101
+ def before_action &block
102
+ playbook.before_actions.push block
103
+ end
104
+ alias validate before_action
105
+
106
+ # Add a proc to be evaluated after a character executes an action.
107
+ #
108
+ # @yieldparam [Gamefic::Action]
109
+ def after_action &block
110
+ playbook.after_actions.push block
103
111
  end
104
112
 
105
113
  # Create an alternate Syntax for an Action.
@@ -10,19 +10,26 @@ module Gamefic
10
10
  # @return [Array<Gamefic::Syntax>]
11
11
  attr_reader :syntaxes
12
12
 
13
- # An array of defined validators.
13
+ # An array of blocks to execute before actions.
14
14
  #
15
15
  # @return [Array<Proc>]
16
- attr_reader :validators
16
+ attr_reader :before_actions
17
+
18
+ # An array of blocks to execute after actions.
19
+ #
20
+ # @return [Array<Proc>]
21
+ attr_reader :after_actions
17
22
 
18
23
  # @param commands [Hash]
19
24
  # @param syntaxes [Array<Syntax>, Set<Syntax>]
20
- # @param validators [Array]
21
- def initialize commands: {}, syntaxes: [], validators: []
25
+ # @param before_actions [Array]
26
+ # @param after_actions [Array]
27
+ def initialize commands: {}, syntaxes: [], before_actions: [], after_actions: []
22
28
  @commands = commands
23
29
  @syntax_set = syntaxes.to_set
24
30
  sort_syntaxes
25
- @validators = validators
31
+ @before_actions = before_actions
32
+ @after_actions = after_actions
26
33
  end
27
34
 
28
35
  # An array of available actions.
@@ -39,10 +46,19 @@ module Gamefic
39
46
  @commands.keys
40
47
  end
41
48
 
42
- # Add a block that determines whether an action can be executed.
49
+ # Add a proc to be evaluated before a character executes an action.
50
+ #
51
+ # @yieldparam [Gamefic::Action]
52
+ def before_action &block
53
+ @before_actions.push block
54
+ end
55
+ alias validate before_action
56
+
57
+ # Add a proc to be evaluated after a character executes an action.
43
58
  #
44
- def validate &block
45
- @validators.push block
59
+ # @yieldparam [Gamefic::Action]
60
+ def after_action &block
61
+ @after_actions.push block
46
62
  end
47
63
 
48
64
  # Get an Array of all Actions associated with the specified verb.
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: 2.2.3
4
+ version: 2.3.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: 2022-07-09 00:00:00.000000000 Z
11
+ date: 2023-01-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake