gamefic 2.2.3 → 2.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 +4 -4
- data/.github/workflows/rspec.yml +40 -0
- data/.rubocop.yml +4 -1
- data/CHANGELOG.md +11 -0
- data/Gemfile +0 -4
- data/gamefic.gemspec +1 -1
- data/lib/gamefic/action.rb +51 -4
- data/lib/gamefic/active.rb +46 -37
- data/lib/gamefic/core_ext/array.rb +15 -12
- data/lib/gamefic/dispatcher.rb +10 -12
- data/lib/gamefic/plot.rb +4 -0
- data/lib/gamefic/query/base.rb +10 -6
- data/lib/gamefic/query/external.rb +26 -5
- data/lib/gamefic/scriptable.rb +8 -3
- data/lib/gamefic/subplot.rb +4 -1
- data/lib/gamefic/version.rb +3 -1
- data/lib/gamefic/world/commands.rb +23 -7
- data/lib/gamefic/world/playbook.rb +32 -8
- data/lib/gamefic/world/scenes.rb +15 -13
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2288262e86ac4a5cf259b3a37e80ba56a3a4ed334b8842770109e1930799585d
|
4
|
+
data.tar.gz: 542aaff9c3f4d0aae1a4471fba5d52ff35b569971c861af006ddc78f0d1cc568
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6a89c7fb5978047517827e3cc2de5f172da5e6905a7e7efe0725763b4ce01d56725df46a443b903906effd962cebdbd238d81719b315eb26f3b8bf289d337089
|
7
|
+
data.tar.gz: 9976d0c58957309ba605e645a8c7e214d798c12e114b71e5d5fa2bb4b6d39878537612e670da6e42168dfd31ee5fce7304eee9983d1177d878262af630e6e582
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# This workflow uses actions that are not certified by GitHub.
|
2
|
+
# They are provided by a third-party and are governed by
|
3
|
+
# separate terms of service, privacy policy, and support
|
4
|
+
# documentation.
|
5
|
+
# This workflow will download a prebuilt Ruby version, install dependencies and run tests with rspec.
|
6
|
+
# For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
|
7
|
+
|
8
|
+
name: RSpec
|
9
|
+
|
10
|
+
on:
|
11
|
+
push:
|
12
|
+
branches: [ master ]
|
13
|
+
pull_request:
|
14
|
+
branches: [ master ]
|
15
|
+
|
16
|
+
permissions:
|
17
|
+
contents: read
|
18
|
+
|
19
|
+
jobs:
|
20
|
+
test:
|
21
|
+
|
22
|
+
runs-on: ubuntu-latest
|
23
|
+
strategy:
|
24
|
+
matrix:
|
25
|
+
ruby-version: ['2.5', '2.6', '2.7', '3.0', '3.1']
|
26
|
+
|
27
|
+
steps:
|
28
|
+
- uses: actions/checkout@v3
|
29
|
+
- name: Set up Ruby
|
30
|
+
# To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
|
31
|
+
# change this to (see https://github.com/ruby/setup-ruby#versioning):
|
32
|
+
uses: ruby/setup-ruby@v1
|
33
|
+
# uses: ruby/setup-ruby@2b019609e2b0f1ea1a2bc8ca11cb82ab46ada124
|
34
|
+
with:
|
35
|
+
ruby-version: ${{ matrix.ruby-version }}
|
36
|
+
bundler-cache: false
|
37
|
+
- name: Install dependencies
|
38
|
+
run: bundle install
|
39
|
+
- name: Run tests
|
40
|
+
run: bundle exec rspec
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,16 @@
|
|
1
|
+
## 2.4.0 - February 11, 2023
|
2
|
+
- Fix arity of delegated methods in scripts
|
3
|
+
- Action hooks accept verb filters
|
4
|
+
- Opal exception for delegated methods
|
5
|
+
- Support separation of kwargs in Ruby >= 2.7
|
6
|
+
|
7
|
+
## 2.3.0 - January 25, 2023
|
8
|
+
- Remove unused Active#actions method
|
9
|
+
- Add before_action and after_action hooks
|
10
|
+
|
1
11
|
## 2.2.3 - July 9, 2022
|
2
12
|
- Fix Ruby version incompatibilities
|
13
|
+
- Fix private attr_accessor call
|
3
14
|
|
4
15
|
## 2.2.2 - September 6, 2021
|
5
16
|
- Darkroom indexes non-static elements
|
data/Gemfile
CHANGED
data/gamefic.gemspec
CHANGED
@@ -19,7 +19,7 @@ Gem::Specification.new do |s|
|
|
19
19
|
end
|
20
20
|
s.require_paths = ['lib']
|
21
21
|
|
22
|
-
s.required_ruby_version = '>= 2.
|
22
|
+
s.required_ruby_version = '>= 2.3.0'
|
23
23
|
|
24
24
|
s.add_development_dependency 'rake', '~> 12.3', '>= 12.3'
|
25
25
|
s.add_development_dependency 'rspec', '~> 3.5', '>= 3.5.0'
|
data/lib/gamefic/action.rb
CHANGED
@@ -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,34 @@ module Gamefic
|
|
7
10
|
attr_reader :arguments
|
8
11
|
alias parameters arguments
|
9
12
|
|
10
|
-
|
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
|
-
|
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
|
+
# Cancelling an action in an after_action hook has no effect.
|
36
|
+
#
|
37
|
+
def cancel
|
38
|
+
# @todo Emit a warning for attempts to cancel an action after it's been
|
39
|
+
# executed
|
40
|
+
@cancelled = true
|
21
41
|
end
|
22
42
|
|
23
43
|
# True if the #execute method has been called for this action.
|
@@ -27,6 +47,10 @@ module Gamefic
|
|
27
47
|
@executed
|
28
48
|
end
|
29
49
|
|
50
|
+
def cancelled?
|
51
|
+
!@executed && @cancelled
|
52
|
+
end
|
53
|
+
|
30
54
|
# The verb associated with this action.
|
31
55
|
#
|
32
56
|
# @return [Symbol] The symbol representing the verb
|
@@ -68,6 +92,29 @@ module Gamefic
|
|
68
92
|
act
|
69
93
|
end
|
70
94
|
|
95
|
+
private
|
96
|
+
|
97
|
+
def run_before_actions
|
98
|
+
return unless @with_callbacks
|
99
|
+
@actor.playbooks
|
100
|
+
.flat_map(&:before_actions)
|
101
|
+
.each do |hook|
|
102
|
+
next unless hook.verb.nil? || hook.verb == verb
|
103
|
+
hook.block.call(self)
|
104
|
+
break if @cancelled
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def run_after_actions
|
109
|
+
return unless @with_callbacks
|
110
|
+
@actor.playbooks
|
111
|
+
.flat_map(&:after_actions)
|
112
|
+
.each do |hook|
|
113
|
+
next unless hook.verb.nil? || hook.verb == verb
|
114
|
+
hook.block.call(self)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
71
118
|
class << self
|
72
119
|
attr_reader :verb
|
73
120
|
|
@@ -135,7 +182,7 @@ module Gamefic
|
|
135
182
|
# @param action [Gamefic::Entity]
|
136
183
|
# @param command [Command]
|
137
184
|
# @return [self, nil]
|
138
|
-
def attempt actor, command
|
185
|
+
def attempt actor, command, with_callbacks = false
|
139
186
|
return nil if command.verb != verb
|
140
187
|
tokens = command.arguments
|
141
188
|
result = []
|
@@ -153,7 +200,7 @@ module Gamefic
|
|
153
200
|
result.push accepted.first
|
154
201
|
end
|
155
202
|
end
|
156
|
-
new(actor, result)
|
203
|
+
new(actor, result, with_callbacks)
|
157
204
|
end
|
158
205
|
|
159
206
|
protected
|
data/lib/gamefic/active.rb
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'set'
|
4
|
+
|
1
5
|
module Gamefic
|
2
6
|
class NotConclusionError < RuntimeError; end
|
3
7
|
|
@@ -37,7 +41,7 @@ module Gamefic
|
|
37
41
|
end
|
38
42
|
|
39
43
|
def syntaxes
|
40
|
-
playbooks.
|
44
|
+
playbooks.flat_map(&:syntaxes)
|
41
45
|
end
|
42
46
|
|
43
47
|
# An array of actions waiting to be performed.
|
@@ -84,28 +88,18 @@ module Gamefic
|
|
84
88
|
end
|
85
89
|
|
86
90
|
# 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
91
|
#
|
92
|
-
# The command will be executed immediately regardless of the
|
93
|
-
# state.
|
92
|
+
# The command's action will be executed immediately regardless of the
|
93
|
+
# entity's state.
|
94
94
|
#
|
95
95
|
# @example Send a command as a string
|
96
96
|
# character.perform "take the key"
|
97
97
|
#
|
98
|
-
# @
|
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
|
-
#
|
98
|
+
# @param command [String, Symbol]
|
106
99
|
# @return [Gamefic::Action]
|
107
100
|
def perform(*command)
|
108
101
|
if command.length > 1
|
102
|
+
STDERR.puts "[WARN] #{caller[0]}: Passing a verb and arguments to #perform is deprecated. Use #execute instead."
|
109
103
|
execute command.first, *command[1..-1]
|
110
104
|
else
|
111
105
|
dispatchers.push Dispatcher.dispatch(self, command.first.to_s)
|
@@ -118,16 +112,18 @@ module Gamefic
|
|
118
112
|
# This method executes the command exactly as #perform does, except it
|
119
113
|
# buffers the resulting output instead of sending it to the user.
|
120
114
|
#
|
121
|
-
# @
|
122
|
-
# See Active#perform for more information.
|
123
|
-
#
|
115
|
+
# @param command [String, Symbol]
|
124
116
|
# @return [String] The output that resulted from performing the command.
|
125
117
|
def quietly(*command)
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
118
|
+
if command.length > 1
|
119
|
+
STDERR.puts "#{caller[0]}: Passing a verb and arguments to #quietly is deprecated. Use #execute instead"
|
120
|
+
execute command.first, *command[1..-1], quietly: true
|
121
|
+
else
|
122
|
+
dispatchers.push Dispatcher.dispatch(self, command.first.to_s)
|
123
|
+
result = proceed quietly: true
|
124
|
+
dispatchers.pop
|
125
|
+
result
|
126
|
+
end
|
131
127
|
end
|
132
128
|
|
133
129
|
# Perform an action.
|
@@ -141,6 +137,9 @@ module Gamefic
|
|
141
137
|
# @example
|
142
138
|
# character.execute :take, @key
|
143
139
|
#
|
140
|
+
# @param verb [Symbol]
|
141
|
+
# @param params [Array]
|
142
|
+
# @params quietly [Boolean]
|
144
143
|
# @return [Gamefic::Action]
|
145
144
|
def execute(verb, *params, quietly: false)
|
146
145
|
dispatchers.push Dispatcher.dispatch_from_params(self, verb, params)
|
@@ -171,21 +170,15 @@ module Gamefic
|
|
171
170
|
# end
|
172
171
|
# end
|
173
172
|
#
|
173
|
+
# @param quietly [Boolean] If true, return the action's output instead of appending it to #messages
|
174
|
+
# @return [String, nil]
|
174
175
|
def proceed quietly: false
|
175
176
|
return if dispatchers.empty?
|
176
177
|
a = dispatchers.last.next
|
177
178
|
return if a.nil?
|
178
|
-
|
179
|
-
if buffer_stack == 0
|
180
|
-
@buffer = ""
|
181
|
-
end
|
182
|
-
set_buffer_stack(buffer_stack + 1)
|
183
|
-
end
|
179
|
+
prepare_buffer quietly
|
184
180
|
a.execute
|
185
|
-
|
186
|
-
set_buffer_stack(buffer_stack - 1)
|
187
|
-
@buffer
|
188
|
-
end
|
181
|
+
flush_buffer quietly
|
189
182
|
end
|
190
183
|
|
191
184
|
# Immediately start a new scene for the character.
|
@@ -251,8 +244,8 @@ module Gamefic
|
|
251
244
|
# Track the entity's performance of a scene.
|
252
245
|
#
|
253
246
|
def entered scene
|
254
|
-
klass = (scene.
|
255
|
-
entered_scenes.
|
247
|
+
klass = (scene.is_a?(Gamefic::Scene::Base) ? scene.class : scene)
|
248
|
+
entered_scenes.add klass
|
256
249
|
end
|
257
250
|
|
258
251
|
# Determine whether the entity has performed the specified scene.
|
@@ -265,9 +258,25 @@ module Gamefic
|
|
265
258
|
|
266
259
|
private
|
267
260
|
|
268
|
-
|
261
|
+
def prepare_buffer quietly
|
262
|
+
if quietly
|
263
|
+
if buffer_stack == 0
|
264
|
+
@buffer = ""
|
265
|
+
end
|
266
|
+
set_buffer_stack(buffer_stack + 1)
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
def flush_buffer quietly
|
271
|
+
if quietly
|
272
|
+
set_buffer_stack(buffer_stack - 1)
|
273
|
+
@buffer
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
# @return [Set<Gamefic::Scene::Base>]
|
269
278
|
def entered_scenes
|
270
|
-
@entered_scenes ||=
|
279
|
+
@entered_scenes ||= Set.new
|
271
280
|
end
|
272
281
|
|
273
282
|
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 =
|
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 =
|
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(
|
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 = ', ',
|
52
|
-
if
|
53
|
-
|
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 : ''}#{
|
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 = ', ',
|
64
|
-
join_and(sep,
|
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
|
-
|
72
|
+
case cls
|
73
|
+
when Class, Module
|
71
74
|
arr.keep_if { |i| i.is_a?(cls) == bool }
|
72
|
-
|
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
|
data/lib/gamefic/dispatcher.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Gamefic
|
2
4
|
# The action selector for character commands.
|
3
5
|
#
|
@@ -9,25 +11,27 @@ module Gamefic
|
|
9
11
|
@actor = actor
|
10
12
|
@commands = commands
|
11
13
|
@actions = actions
|
14
|
+
@started = false
|
12
15
|
end
|
13
16
|
|
17
|
+
# @param dispatcher [Dispatcher]
|
18
|
+
# @return [void]
|
14
19
|
def merge dispatcher
|
15
20
|
commands.concat dispatcher.commands
|
16
21
|
actions.concat dispatcher.actions
|
17
22
|
end
|
18
23
|
|
24
|
+
# Get the next executable action.
|
25
|
+
#
|
26
|
+
# @return [Action]
|
19
27
|
def next
|
20
28
|
instance = nil
|
21
29
|
while instance.nil? && !@actions.empty?
|
22
30
|
action = actions.shift
|
23
31
|
commands.each do |cmd|
|
24
|
-
instance = action.attempt(actor, cmd)
|
32
|
+
instance = action.attempt(actor, cmd, !@started)
|
25
33
|
if instance
|
26
|
-
|
27
|
-
actor.playbooks.reverse.each do |playbook|
|
28
|
-
return nil unless validate_playbook(playbook, instance)
|
29
|
-
end
|
30
|
-
end
|
34
|
+
@started = true
|
31
35
|
break
|
32
36
|
end
|
33
37
|
end
|
@@ -66,11 +70,5 @@ module Gamefic
|
|
66
70
|
|
67
71
|
# @return [Array<Action>]
|
68
72
|
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
73
|
end
|
76
74
|
end
|
data/lib/gamefic/plot.rb
CHANGED
data/lib/gamefic/query/base.rb
CHANGED
@@ -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
|
-
|
4
|
+
# @param container [Plot, Subplot, Array]
|
5
|
+
def initialize container, *args
|
5
6
|
super(*args)
|
6
|
-
@
|
7
|
+
@container = container
|
7
8
|
end
|
8
9
|
|
9
10
|
def context_from subject
|
10
|
-
|
11
|
+
Set.new
|
12
|
+
.merge(container_entities)
|
13
|
+
.merge(container_subplots_for(@container, subject))
|
14
|
+
.to_a
|
11
15
|
end
|
12
16
|
|
13
|
-
|
14
|
-
|
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
|
data/lib/gamefic/scriptable.rb
CHANGED
@@ -76,9 +76,14 @@ Gamefic::Scriptable.module_exec do
|
|
76
76
|
instance = self
|
77
77
|
theater ||= Object.new
|
78
78
|
theater.instance_exec do
|
79
|
-
|
80
|
-
|
81
|
-
|
79
|
+
if RUBY_ENGINE == 'opal' || RUBY_VERSION =~ /^2\.[456]\./
|
80
|
+
define_singleton_method :method_missing do |symbol, *args, &block|
|
81
|
+
instance.public_send :public_send, symbol, *args, &block
|
82
|
+
end
|
83
|
+
else
|
84
|
+
define_singleton_method :method_missing do |symbol, *args, **splat, &block|
|
85
|
+
instance.public_send :public_send, symbol, *args, **splat, &block
|
86
|
+
end
|
82
87
|
end
|
83
88
|
end
|
84
89
|
theater.extend Gamefic::Serialize
|
data/lib/gamefic/subplot.rb
CHANGED
@@ -1,7 +1,10 @@
|
|
1
1
|
require 'gamefic/plot'
|
2
2
|
|
3
3
|
module Gamefic
|
4
|
-
|
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
|
data/lib/gamefic/version.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'gamefic/action'
|
2
4
|
|
3
5
|
module Gamefic
|
@@ -91,15 +93,29 @@ module Gamefic
|
|
91
93
|
# @param command [Symbol] An imperative verb for the command
|
92
94
|
# @param queries [Array<Query::Base>] Filters for the command's tokens
|
93
95
|
# @yieldparam [Gamefic::Actor]
|
94
|
-
def meta(command, *queries, &
|
95
|
-
playbook.meta command, *queries, &
|
96
|
+
def meta(command, *queries, &block)
|
97
|
+
playbook.meta command, *queries, &block
|
98
|
+
end
|
99
|
+
|
100
|
+
# Add a proc to be evaluated before a character executes an action.
|
101
|
+
# When a verb is specified, the proc will only be evaluated if the
|
102
|
+
# action's verb matches it.
|
103
|
+
#
|
104
|
+
# @param verb [Symbol, nil]
|
105
|
+
# @yieldparam [Gamefic::Action]
|
106
|
+
def before_action verb = nil, &block
|
107
|
+
playbook.before_action verb, &block
|
96
108
|
end
|
109
|
+
alias validate before_action
|
97
110
|
|
98
|
-
#
|
111
|
+
# Add a proc to be evaluated after a character executes an action.
|
112
|
+
# When a verb is specified, the proc will only be evaluated if the
|
113
|
+
# action's verb matches it.
|
99
114
|
#
|
100
|
-
# @
|
101
|
-
|
102
|
-
|
115
|
+
# @param [Symbol, nil]
|
116
|
+
# @yieldparam [Gamefic::Action]
|
117
|
+
def after_action verb = nil, &block
|
118
|
+
playbook.after_action verb, &block
|
103
119
|
end
|
104
120
|
|
105
121
|
# Create an alternate Syntax for an Action.
|
@@ -156,7 +172,7 @@ module Gamefic
|
|
156
172
|
elsif q.is_a?(Gamefic::Element) || (q.is_a?(Class) && q <= Gamefic::Element)
|
157
173
|
get_default_query.new(q)
|
158
174
|
else
|
159
|
-
raise ArgumentError
|
175
|
+
raise ArgumentError, "Invalid argument for response: #{q.inspect}"
|
160
176
|
end
|
161
177
|
end
|
162
178
|
end
|
@@ -5,24 +5,33 @@ module Gamefic
|
|
5
5
|
# A collection of rules for performing commands.
|
6
6
|
#
|
7
7
|
class Playbook
|
8
|
+
ActionHook = Struct.new(:verb, :block)
|
9
|
+
|
8
10
|
# An array of available syntaxes.
|
9
11
|
#
|
10
12
|
# @return [Array<Gamefic::Syntax>]
|
11
13
|
attr_reader :syntaxes
|
12
14
|
|
13
|
-
# An array of
|
15
|
+
# An array of blocks to execute before actions.
|
16
|
+
#
|
17
|
+
# @return [Array<Proc>]
|
18
|
+
attr_reader :before_actions
|
19
|
+
|
20
|
+
# An array of blocks to execute after actions.
|
14
21
|
#
|
15
22
|
# @return [Array<Proc>]
|
16
|
-
attr_reader :
|
23
|
+
attr_reader :after_actions
|
17
24
|
|
18
25
|
# @param commands [Hash]
|
19
26
|
# @param syntaxes [Array<Syntax>, Set<Syntax>]
|
20
|
-
# @param
|
21
|
-
|
27
|
+
# @param before_actions [Array]
|
28
|
+
# @param after_actions [Array]
|
29
|
+
def initialize commands: {}, syntaxes: [], before_actions: [], after_actions: []
|
22
30
|
@commands = commands
|
23
31
|
@syntax_set = syntaxes.to_set
|
24
32
|
sort_syntaxes
|
25
|
-
@
|
33
|
+
@before_actions = before_actions
|
34
|
+
@after_actions = after_actions
|
26
35
|
end
|
27
36
|
|
28
37
|
# An array of available actions.
|
@@ -39,10 +48,25 @@ module Gamefic
|
|
39
48
|
@commands.keys
|
40
49
|
end
|
41
50
|
|
42
|
-
# Add a
|
51
|
+
# Add a proc to be evaluated before a character executes an action.
|
52
|
+
# When a verb is specified, the proc will only be evaluated if the
|
53
|
+
# action's verb matches it.
|
54
|
+
#
|
55
|
+
# @param verb [Symbol, nil]
|
56
|
+
# @yieldparam [Gamefic::Action]
|
57
|
+
def before_action verb = nil, &block
|
58
|
+
@before_actions.push ActionHook.new(verb, block)
|
59
|
+
end
|
60
|
+
alias validate before_action
|
61
|
+
|
62
|
+
# Add a proc to be evaluated after a character executes an action.
|
63
|
+
# When a verb is specified, the proc will only be evaluated if the
|
64
|
+
# action's verb matches it.
|
43
65
|
#
|
44
|
-
|
45
|
-
|
66
|
+
# @param verb [Symbol, nil]
|
67
|
+
# @yieldparam [Gamefic::Action]
|
68
|
+
def after_action verb = nil, &block
|
69
|
+
@after_actions.push ActionHook.new(verb, block)
|
46
70
|
end
|
47
71
|
|
48
72
|
# Get an Array of all Actions associated with the specified verb.
|
data/lib/gamefic/world/scenes.rb
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
module Gamefic
|
2
2
|
module World
|
3
3
|
module Scenes
|
4
|
+
include Commands
|
5
|
+
include Players
|
6
|
+
|
4
7
|
# @return [Class<Gamefic::Scene::Activity>]
|
5
8
|
def default_scene
|
6
9
|
@default_scene ||= Scene::Activity
|
@@ -29,13 +32,12 @@ module Gamefic
|
|
29
32
|
# This method is typically called by the Engine that manages game execution.
|
30
33
|
#
|
31
34
|
# @param [Gamefic::Actor]
|
35
|
+
# @return [void]
|
32
36
|
def introduce(player)
|
33
37
|
player.playbooks.push playbook unless player.playbooks.include?(playbook)
|
34
38
|
player.cue default_scene
|
35
39
|
players.push player
|
36
|
-
@introduction
|
37
|
-
# @todo Find a better way to persist player characters
|
38
|
-
# Gamefic::Index.stick
|
40
|
+
@introduction&.call(player)
|
39
41
|
end
|
40
42
|
|
41
43
|
# Create a multiple-choice scene.
|
@@ -53,7 +55,7 @@ module Gamefic
|
|
53
55
|
# @yieldparam [Gamefic::Scene::MultipleChoice]
|
54
56
|
# @return [Class<Gamefic::Scene::MultipleChoice>]
|
55
57
|
def multiple_choice *choices, &block
|
56
|
-
s = Scene::MultipleChoice.subclass do |
|
58
|
+
s = Scene::MultipleChoice.subclass do |_actor, scene|
|
57
59
|
scene.options.concat choices
|
58
60
|
scene.on_finish &block
|
59
61
|
end
|
@@ -73,12 +75,12 @@ module Gamefic
|
|
73
75
|
# end
|
74
76
|
# end
|
75
77
|
#
|
76
|
-
# @param prompt [String]
|
78
|
+
# @param prompt [String, nil]
|
77
79
|
# @yieldparam [Gamefic::Actor]
|
78
80
|
# @yieldparam [Gamefic::Scene::YesOrNo]
|
79
81
|
# @return [Class<Gamefic::Scene::YesOrNo>]
|
80
82
|
def yes_or_no prompt = nil, &block
|
81
|
-
s = Scene::YesOrNo.subclass do |
|
83
|
+
s = Scene::YesOrNo.subclass do |_actor, scene|
|
82
84
|
scene.prompt = prompt
|
83
85
|
scene.on_finish &block
|
84
86
|
end
|
@@ -98,7 +100,7 @@ module Gamefic
|
|
98
100
|
# @yieldparam [Gamefic::Scene::Base]
|
99
101
|
# @return [Class<Gamefic::Scene::Base>]
|
100
102
|
def question prompt = 'What is your answer?', &block
|
101
|
-
s = Scene::Base.subclass do |
|
103
|
+
s = Scene::Base.subclass do |_actor, scene|
|
102
104
|
scene.prompt = prompt
|
103
105
|
scene.on_finish &block
|
104
106
|
end
|
@@ -116,13 +118,13 @@ module Gamefic
|
|
116
118
|
# actor.prepare default_scene
|
117
119
|
# end
|
118
120
|
#
|
119
|
-
# @param prompt [String] The text to display when prompting the user to continue
|
121
|
+
# @param prompt [String, nil] The text to display when prompting the user to continue
|
120
122
|
# @yieldparam [Gamefic::Actor]
|
121
123
|
# @return [Class<Gamefic::Scene::Pause>]
|
122
124
|
def pause prompt = nil, &block
|
123
125
|
s = Scene::Pause.subclass do |actor, scene|
|
124
126
|
scene.prompt = prompt unless prompt.nil?
|
125
|
-
block
|
127
|
+
block&.call(actor, scene)
|
126
128
|
end
|
127
129
|
scene_classes.push s
|
128
130
|
s
|
@@ -163,7 +165,7 @@ module Gamefic
|
|
163
165
|
# end
|
164
166
|
# end
|
165
167
|
#
|
166
|
-
# @param cls [Class] The class of scene to be instantiated.
|
168
|
+
# @param cls [Class<Scene::Base>] The class of scene to be instantiated.
|
167
169
|
# @yieldparam [Gamefic::Actor]
|
168
170
|
# @return [Class<Gamefic::Scene::Base>]
|
169
171
|
def custom cls = Scene::Base, &block
|
@@ -208,10 +210,10 @@ module Gamefic
|
|
208
210
|
# @return [Class<Gamefic::Scene::MultipleScene>]
|
209
211
|
def multiple_scene map = {}, &block
|
210
212
|
s = Scene::MultipleScene.subclass do |actor, scene|
|
211
|
-
map.each_pair
|
213
|
+
map.each_pair do |k, v|
|
212
214
|
scene.map k, v
|
213
|
-
|
214
|
-
block
|
215
|
+
end
|
216
|
+
block&.call actor, scene
|
215
217
|
end
|
216
218
|
scene_classes.push s
|
217
219
|
s
|
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.
|
4
|
+
version: 2.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:
|
11
|
+
date: 2023-02-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -70,6 +70,7 @@ executables: []
|
|
70
70
|
extensions: []
|
71
71
|
extra_rdoc_files: []
|
72
72
|
files:
|
73
|
+
- ".github/workflows/rspec.yml"
|
73
74
|
- ".gitignore"
|
74
75
|
- ".rspec"
|
75
76
|
- ".rubocop.yml"
|
@@ -142,7 +143,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
142
143
|
requirements:
|
143
144
|
- - ">="
|
144
145
|
- !ruby/object:Gem::Version
|
145
|
-
version: 2.
|
146
|
+
version: 2.3.0
|
146
147
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
147
148
|
requirements:
|
148
149
|
- - ">="
|