gamefic 3.4.0 → 3.5.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/CHANGELOG.md +14 -0
- data/README.md +1 -1
- data/lib/gamefic/active/epic.rb +1 -0
- data/lib/gamefic/active.rb +4 -0
- data/lib/gamefic/chapter.rb +25 -42
- data/lib/gamefic/command.rb +40 -1
- data/lib/gamefic/dispatcher.rb +5 -31
- data/lib/gamefic/entity.rb +26 -0
- data/lib/gamefic/narrative.rb +9 -5
- data/lib/gamefic/plot.rb +5 -0
- data/lib/gamefic/proxy.rb +46 -0
- data/lib/gamefic/query/base.rb +28 -12
- data/lib/gamefic/query/general.rb +3 -12
- data/lib/gamefic/query/result.rb +4 -1
- data/lib/gamefic/query/scoped.rb +1 -18
- data/lib/gamefic/query/text.rb +13 -12
- data/lib/gamefic/response.rb +65 -34
- data/lib/gamefic/scanner/base.rb +44 -0
- data/lib/gamefic/scanner/fuzzy.rb +17 -0
- data/lib/gamefic/scanner/fuzzy_nesting.rb +14 -0
- data/lib/gamefic/scanner/nesting.rb +39 -0
- data/lib/gamefic/scanner/result.rb +50 -0
- data/lib/gamefic/scanner/strict.rb +31 -0
- data/lib/gamefic/scanner.rb +33 -111
- data/lib/gamefic/scope/descendants.rb +16 -0
- data/lib/gamefic/scope/family.rb +31 -8
- data/lib/gamefic/scope.rb +1 -0
- data/lib/gamefic/scriptable/actions.rb +4 -23
- data/lib/gamefic/scriptable/entities.rb +4 -1
- data/lib/gamefic/scriptable/plot_proxies.rb +16 -0
- data/lib/gamefic/scriptable/proxies.rb +31 -0
- data/lib/gamefic/scriptable/queries.rb +15 -7
- data/lib/gamefic/scriptable/scenes.rb +7 -1
- data/lib/gamefic/scriptable.rb +67 -15
- data/lib/gamefic/stage.rb +2 -2
- data/lib/gamefic/subplot.rb +27 -1
- data/lib/gamefic/version.rb +1 -1
- data/lib/gamefic.rb +1 -1
- metadata +12 -4
- data/lib/gamefic/composer.rb +0 -70
- data/lib/gamefic/scriptable/proxy.rb +0 -69
data/lib/gamefic/response.rb
CHANGED
@@ -8,19 +8,18 @@ module Gamefic
|
|
8
8
|
# @return [Symbol]
|
9
9
|
attr_reader :verb
|
10
10
|
|
11
|
-
# @return [Array<Query::Base>]
|
11
|
+
# @return [Array<Query::Base, Query::Text>]
|
12
12
|
attr_reader :queries
|
13
13
|
|
14
14
|
# @param verb [Symbol]
|
15
15
|
# @param narrative [Narrative]
|
16
|
-
# @param
|
16
|
+
# @param args [Array<Object>]
|
17
17
|
# @param meta [Boolean]
|
18
18
|
# @param block [Proc]
|
19
|
-
def initialize verb, narrative, *
|
19
|
+
def initialize verb, narrative, *args, meta: false, &block
|
20
20
|
@verb = verb
|
21
|
-
@queries =
|
21
|
+
@queries = map_queries(args, narrative)
|
22
22
|
@meta = meta
|
23
|
-
@block = block
|
24
23
|
@callback = Callback.new(narrative, block)
|
25
24
|
end
|
26
25
|
|
@@ -57,53 +56,85 @@ module Gamefic
|
|
57
56
|
# @param actor [Active]
|
58
57
|
# @param command [Command]
|
59
58
|
def accept? actor, command
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
return false unless query.accept?(actor, command.arguments[idx])
|
64
|
-
end
|
65
|
-
|
66
|
-
true
|
59
|
+
command.verb == verb &&
|
60
|
+
command.arguments.length == queries.length &&
|
61
|
+
queries.zip(command.arguments).all? { |query, argument| query.accept?(actor, argument) }
|
67
62
|
end
|
68
63
|
|
69
64
|
def execute *args
|
70
|
-
@callback.run
|
65
|
+
@callback.run(*args)
|
71
66
|
end
|
72
67
|
|
73
68
|
def precision
|
74
69
|
@precision ||= calculate_precision
|
75
70
|
end
|
76
71
|
|
72
|
+
# Turn an actor and an expression into a command by matching the
|
73
|
+
# expression's tokens to queries. Return nil if the expression
|
74
|
+
# could not be matched.
|
75
|
+
#
|
76
|
+
# @param actor [Actor]
|
77
|
+
# @param expression [Expression]
|
78
|
+
# @return [Command, nil]
|
79
|
+
def to_command actor, expression
|
80
|
+
return nil unless expression.verb == verb && expression.tokens.length <= queries.length
|
81
|
+
|
82
|
+
results = filter(actor, expression)
|
83
|
+
return nil unless results
|
84
|
+
|
85
|
+
Command.new(
|
86
|
+
verb,
|
87
|
+
results.map(&:match),
|
88
|
+
results.sum(&:strictness),
|
89
|
+
precision
|
90
|
+
)
|
91
|
+
end
|
92
|
+
|
77
93
|
private
|
78
94
|
|
95
|
+
def filter actor, expression
|
96
|
+
remainder = ''
|
97
|
+
result = queries.zip(expression.tokens)
|
98
|
+
.map do |query, token|
|
99
|
+
token = "#{remainder} #{token}".strip
|
100
|
+
result = query.filter(actor, token)
|
101
|
+
return nil unless result.match
|
102
|
+
|
103
|
+
remainder = result.remainder
|
104
|
+
result
|
105
|
+
end
|
106
|
+
result if remainder.empty?
|
107
|
+
end
|
108
|
+
|
79
109
|
def generate_default_syntax
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
queries.each do |_c|
|
84
|
-
num = 1
|
85
|
-
new_name = ":var"
|
86
|
-
while used_names.include? new_name
|
87
|
-
num += 1
|
88
|
-
new_name = ":var#{num}"
|
89
|
-
end
|
90
|
-
used_names.push new_name
|
91
|
-
user_friendly += " #{new_name}"
|
92
|
-
args.push new_name
|
93
|
-
end
|
94
|
-
Syntax.new(user_friendly.strip, "#{verb} #{args.join(' ')}".strip)
|
110
|
+
args = queries.length.times.map { |num| num.zero? ? ':var' : ":var#{num + 1}" }
|
111
|
+
tmpl = "#{verb} #{args.join(' ')}".strip
|
112
|
+
Syntax.new(tmpl.gsub('_', ' '), tmpl)
|
95
113
|
end
|
96
114
|
|
97
115
|
def calculate_precision
|
98
|
-
total =
|
99
|
-
|
100
|
-
total -= 1000 if verb.nil?
|
116
|
+
total = queries.sum(&:precision)
|
117
|
+
total -= 1000 unless verb
|
101
118
|
total
|
102
119
|
end
|
103
120
|
|
104
|
-
def
|
105
|
-
|
106
|
-
|
121
|
+
def map_queries args, narrative
|
122
|
+
args.map do |arg|
|
123
|
+
select_query(arg, narrative).tap { |qry| qry.narrative = narrative }
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def select_query arg, narrative
|
128
|
+
case arg
|
129
|
+
when Entity, Class, Module, Proc, Proxy
|
130
|
+
narrative.available(arg)
|
131
|
+
when String, Regexp
|
132
|
+
narrative.plaintext(arg)
|
133
|
+
when Query::Base, Query::Text
|
134
|
+
arg
|
135
|
+
else
|
136
|
+
raise ArgumentError, "invalid argument in response: #{arg.inspect}"
|
137
|
+
end
|
107
138
|
end
|
108
139
|
end
|
109
140
|
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Gamefic
|
4
|
+
module Scanner
|
5
|
+
# A base class for scanners that match tokens to entities.
|
6
|
+
#
|
7
|
+
class Base
|
8
|
+
# @return [Array<Entity>]
|
9
|
+
attr_reader :selection
|
10
|
+
|
11
|
+
# @return [String]
|
12
|
+
attr_reader :token
|
13
|
+
|
14
|
+
# @param selection [Array<Entity>]
|
15
|
+
# @param token [String]
|
16
|
+
def initialize selection, token
|
17
|
+
@selection = selection
|
18
|
+
@token = token
|
19
|
+
end
|
20
|
+
|
21
|
+
# @return [Result]
|
22
|
+
def scan
|
23
|
+
unmatched_result
|
24
|
+
end
|
25
|
+
|
26
|
+
# @param selection [Array<Entity>]
|
27
|
+
# @param token [String]
|
28
|
+
# @return [Result]
|
29
|
+
def self.scan selection, token
|
30
|
+
new(selection, token).scan
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def unmatched_result
|
36
|
+
Result.unmatched(selection, token, self.class)
|
37
|
+
end
|
38
|
+
|
39
|
+
def matched_result matched, remainder
|
40
|
+
Result.new(selection, token, matched, remainder, self.class)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Gamefic
|
4
|
+
module Scanner
|
5
|
+
# Fuzzy token matching.
|
6
|
+
#
|
7
|
+
# An entity will match a word in a fuzzy scan if it matches the beginning
|
8
|
+
# of one of the entity's keywords, e.g., `pen` is a fuzzy token match for
|
9
|
+
# the keyword `pencil`.
|
10
|
+
#
|
11
|
+
class Fuzzy < Strict
|
12
|
+
def match_word available, word
|
13
|
+
available.select { |obj| obj.keywords.any? { |wrd| wrd.start_with?(word) } }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Gamefic
|
4
|
+
module Scanner
|
5
|
+
# Fuzzy scanning for entities inside of other entities, e.g., `soc in draw`
|
6
|
+
# would match `sock in drawer`.
|
7
|
+
#
|
8
|
+
class FuzzyNesting < Nesting
|
9
|
+
def subprocessor
|
10
|
+
Fuzzy
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Gamefic
|
4
|
+
module Scanner
|
5
|
+
# Strict scanning for entities inside of other entities, e.g., `sock inside drawer`.
|
6
|
+
#
|
7
|
+
class Nesting < Base
|
8
|
+
NEST_REGEXP = / in | on | of | from | inside | inside of | from inside | off | out | out of /.freeze
|
9
|
+
|
10
|
+
def subprocessor
|
11
|
+
Strict
|
12
|
+
end
|
13
|
+
|
14
|
+
def scan
|
15
|
+
return Result.unmatched(selection, token, self.class) unless token =~ NEST_REGEXP
|
16
|
+
|
17
|
+
denest selection, token
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def denest objects, token
|
23
|
+
near = objects
|
24
|
+
far = objects
|
25
|
+
parts = token.split(NEST_REGEXP)
|
26
|
+
until parts.empty?
|
27
|
+
current = parts.pop
|
28
|
+
last_result = subprocessor.scan(near, current)
|
29
|
+
last_result = subprocessor.scan(far, current) if last_result.matched.empty? && near != far
|
30
|
+
return Result.unmatched(selection, token, self.class) if last_result.matched.empty? || last_result.matched.length > 1
|
31
|
+
|
32
|
+
near = last_result.matched.first.children & objects
|
33
|
+
far = last_result.matched.first.flatten & objects
|
34
|
+
end
|
35
|
+
last_result
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Gamefic
|
4
|
+
module Scanner
|
5
|
+
# The result of an attempt to scan objects against a token in a Scanner. It
|
6
|
+
# provides an array of matching objects, the text that matched them, and the
|
7
|
+
# text that remains unmatched.
|
8
|
+
#
|
9
|
+
class Result
|
10
|
+
# The scanned objects
|
11
|
+
#
|
12
|
+
# @return [Array<Entity>, String, Regexp]
|
13
|
+
attr_reader :scanned
|
14
|
+
|
15
|
+
# The scanned token
|
16
|
+
#
|
17
|
+
# @return [String]
|
18
|
+
attr_reader :token
|
19
|
+
|
20
|
+
# The matched objects
|
21
|
+
#
|
22
|
+
# @return [Array<Entity>, String]
|
23
|
+
attr_reader :matched
|
24
|
+
alias match matched
|
25
|
+
|
26
|
+
# The remaining (unmatched) portion of the token
|
27
|
+
#
|
28
|
+
# @return [String]
|
29
|
+
attr_reader :remainder
|
30
|
+
|
31
|
+
attr_reader :processor
|
32
|
+
|
33
|
+
def initialize scanned, token, matched, remainder, processor
|
34
|
+
@scanned = scanned
|
35
|
+
@token = token
|
36
|
+
@matched = matched
|
37
|
+
@remainder = remainder
|
38
|
+
@processor = processor
|
39
|
+
end
|
40
|
+
|
41
|
+
def strictness
|
42
|
+
@strictness ||= Scanner.strictness(processor)
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.unmatched scanned, token, processor
|
46
|
+
new(scanned, token, [], token, processor)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Gamefic
|
4
|
+
module Scanner
|
5
|
+
# Strict token matching.
|
6
|
+
#
|
7
|
+
# An entity will only match a word in a strict scan if the entire word
|
8
|
+
# matches one of the entity's keywords.
|
9
|
+
#
|
10
|
+
class Strict < Base
|
11
|
+
# @return [Result]
|
12
|
+
def scan
|
13
|
+
words = token.keywords
|
14
|
+
available = selection.clone
|
15
|
+
filtered = []
|
16
|
+
words.each_with_index do |word, idx|
|
17
|
+
tested = match_word(available, word)
|
18
|
+
return matched_result(filtered, words[idx..].join(' ')) if tested.empty?
|
19
|
+
|
20
|
+
filtered = tested
|
21
|
+
available = filtered
|
22
|
+
end
|
23
|
+
matched_result filtered, ''
|
24
|
+
end
|
25
|
+
|
26
|
+
def match_word available, word
|
27
|
+
available.select { |obj| obj.keywords.include?(word) }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/gamefic/scanner.rb
CHANGED
@@ -1,132 +1,54 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'gamefic/scanner/result'
|
4
|
+
require 'gamefic/scanner/base'
|
5
|
+
require 'gamefic/scanner/strict'
|
6
|
+
require 'gamefic/scanner/fuzzy'
|
7
|
+
require 'gamefic/scanner/nesting'
|
8
|
+
require 'gamefic/scanner/fuzzy_nesting'
|
9
|
+
|
3
10
|
module Gamefic
|
4
11
|
# A module for matching objects to tokens.
|
5
12
|
#
|
6
13
|
module Scanner
|
7
|
-
|
8
|
-
|
9
|
-
# The result of an attempt to scan objects against a token in a Scanner. It
|
10
|
-
# provides an array of matching objects, the text that matched them, and the
|
11
|
-
# text that remains unmatched.
|
12
|
-
#
|
13
|
-
class Result
|
14
|
-
# The scanned objects
|
15
|
-
#
|
16
|
-
# @return [Array<Entity>, String, Regexp]
|
17
|
-
attr_reader :scanned
|
18
|
-
|
19
|
-
# The scanned token
|
20
|
-
#
|
21
|
-
# @return [String]
|
22
|
-
attr_reader :token
|
23
|
-
|
24
|
-
# The matched objects
|
25
|
-
#
|
26
|
-
# @return [Array<Entity>, String]
|
27
|
-
attr_reader :matched
|
28
|
-
|
29
|
-
# The remaining (unmatched) portion of the token
|
30
|
-
#
|
31
|
-
# @return [String]
|
32
|
-
attr_reader :remainder
|
33
|
-
|
34
|
-
def initialize scanned, token, matched, remainder
|
35
|
-
@scanned = scanned
|
36
|
-
@token = token
|
37
|
-
@matched = matched
|
38
|
-
@remainder = remainder
|
39
|
-
end
|
40
|
-
end
|
14
|
+
DEFAULT_PROCESSORS = [Nesting, Strict, FuzzyNesting, Fuzzy].freeze
|
41
15
|
|
42
16
|
# Scan entities against a token.
|
43
17
|
#
|
44
|
-
# @param selection [Array<Entity
|
18
|
+
# @param selection [Array<Entity>]
|
45
19
|
# @param token [String]
|
46
20
|
# @return [Result]
|
47
21
|
def self.scan selection, token
|
48
|
-
|
49
|
-
|
22
|
+
result = nil
|
23
|
+
processors.each do |processor|
|
24
|
+
result = processor.scan(selection, token)
|
25
|
+
break unless result.matched.empty?
|
26
|
+
end
|
27
|
+
result
|
50
28
|
end
|
51
29
|
|
52
|
-
#
|
53
|
-
#
|
54
|
-
#
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
30
|
+
# Select the scanner processors to use in entity queries. Each processor
|
31
|
+
# will be used in order until one of them returns matches. The default
|
32
|
+
# processor list is `DEFAULT_PROCESSORS`.
|
33
|
+
#
|
34
|
+
# Processor classes should be in order from most to least strict rules
|
35
|
+
# for matching tokens to entities.
|
36
|
+
#
|
37
|
+
# @param klasses [Array<Class<Base>>]
|
38
|
+
# @return [Array<Class<Base>>]
|
39
|
+
def self.use *klasses
|
40
|
+
processors.replace klasses.flatten
|
59
41
|
end
|
60
42
|
|
61
|
-
# @
|
62
|
-
|
63
|
-
|
64
|
-
def self.fuzzy selection, token
|
65
|
-
return scan_text(selection, token) unless selection.is_a?(Array)
|
66
|
-
|
67
|
-
scan_strict_or_fuzzy(selection, token, :select_fuzzy)
|
43
|
+
# @return [Array<Class<Base>>]
|
44
|
+
def self.processors
|
45
|
+
@processors ||= []
|
68
46
|
end
|
69
47
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
def scan_strict_or_fuzzy objects, token, method
|
74
|
-
if nested?(token) && objects.all?(&:children)
|
75
|
-
denest(objects, token)
|
76
|
-
else
|
77
|
-
words = token.keywords
|
78
|
-
available = objects.clone
|
79
|
-
filtered = []
|
80
|
-
words.each_with_index do |word, idx|
|
81
|
-
tested = send(method, available, word)
|
82
|
-
return Result.new(objects, token, filtered, words[idx..].join(' ')) if tested.empty?
|
83
|
-
|
84
|
-
filtered = tested
|
85
|
-
available = filtered
|
86
|
-
end
|
87
|
-
Result.new(objects, token, filtered, '')
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
def select_strict available, word
|
92
|
-
available.select { |obj| obj.keywords.include?(word) }
|
93
|
-
end
|
94
|
-
|
95
|
-
def select_fuzzy available, word
|
96
|
-
available.select { |obj| obj.keywords.any? { |wrd| wrd.start_with?(word) } }
|
97
|
-
end
|
98
|
-
|
99
|
-
def nested?(token)
|
100
|
-
token.match(NEST_REGEXP)
|
101
|
-
end
|
102
|
-
|
103
|
-
def scan_text selection, token
|
104
|
-
case selection
|
105
|
-
when Regexp
|
106
|
-
return Result.new(selection, token, token, '') if token =~ selection
|
107
|
-
else
|
108
|
-
return Result.new(selection, token, selection, token[selection.length..]) if token.start_with?(selection)
|
109
|
-
end
|
110
|
-
Result.new(selection, token, '', token)
|
111
|
-
end
|
112
|
-
|
113
|
-
def denest(objects, token)
|
114
|
-
parts = token.split(NEST_REGEXP)
|
115
|
-
current = parts.pop
|
116
|
-
last_result = scan(objects, current)
|
117
|
-
until parts.empty?
|
118
|
-
current = "#{parts.last} #{current}"
|
119
|
-
result = scan(last_result.matched, current)
|
120
|
-
break if result.matched.empty?
|
121
|
-
|
122
|
-
parts.pop
|
123
|
-
last_result = result
|
124
|
-
end
|
125
|
-
return Result.new(objects, token, [], '') if last_result.matched.empty? || last_result.matched.length > 1
|
126
|
-
return last_result if parts.empty?
|
127
|
-
|
128
|
-
denest(last_result.matched.first.children, parts.join(' '))
|
129
|
-
end
|
48
|
+
def self.strictness processor
|
49
|
+
(processors.length - (processors.find_index(processor) || processors.length)) * 100
|
130
50
|
end
|
51
|
+
|
52
|
+
use DEFAULT_PROCESSORS
|
131
53
|
end
|
132
54
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Gamefic
|
4
|
+
module Scope
|
5
|
+
# The Descendants scope returns an entity's children and accessible
|
6
|
+
# descendants.
|
7
|
+
#
|
8
|
+
class Descendants < Base
|
9
|
+
def matches
|
10
|
+
context.children.flat_map do |child|
|
11
|
+
[child] + subquery_accessible(child)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/gamefic/scope/family.rb
CHANGED
@@ -2,18 +2,41 @@
|
|
2
2
|
|
3
3
|
module Gamefic
|
4
4
|
module Scope
|
5
|
-
# The Family scope returns an entity's
|
5
|
+
# The Family scope returns an entity's ascendants, descendants, siblings,
|
6
|
+
# and siblings' descendants.
|
6
7
|
#
|
7
8
|
class Family < Base
|
8
9
|
def matches
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
10
|
+
match_ascendants + match_descendants + match_siblings
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def match_ascendants
|
16
|
+
[].tap do |result|
|
17
|
+
here = context.parent
|
18
|
+
while here
|
19
|
+
result.push here
|
20
|
+
here = here.parent
|
21
|
+
end
|
15
22
|
end
|
16
|
-
|
23
|
+
end
|
24
|
+
|
25
|
+
def match_descendants
|
26
|
+
context.children.flat_map do |child|
|
27
|
+
[child] + subquery_accessible(child)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def match_siblings
|
32
|
+
return [] unless context.parent
|
33
|
+
|
34
|
+
context.parent
|
35
|
+
.children
|
36
|
+
.that_are_not(context)
|
37
|
+
.flat_map do |child|
|
38
|
+
[child] + subquery_accessible(child)
|
39
|
+
end
|
17
40
|
end
|
18
41
|
end
|
19
42
|
end
|
data/lib/gamefic/scope.rb
CHANGED
@@ -26,11 +26,10 @@ module Gamefic
|
|
26
26
|
# end
|
27
27
|
#
|
28
28
|
# @param verb [Symbol] An imperative verb for the command
|
29
|
-
# @param
|
29
|
+
# @param args [Array<Object>] Filters for the command's tokens
|
30
30
|
# @yieldparam [Gamefic::Actor]
|
31
31
|
# @return [Symbol]
|
32
|
-
def respond(verb, *
|
33
|
-
args = map_response_args(queries)
|
32
|
+
def respond(verb, *args, &proc)
|
34
33
|
rulebook.calls.add_response Response.new(verb, self, *args, &proc)
|
35
34
|
verb
|
36
35
|
end
|
@@ -47,11 +46,10 @@ module Gamefic
|
|
47
46
|
# end
|
48
47
|
#
|
49
48
|
# @param verb [Symbol] An imperative verb for the command
|
50
|
-
# @param
|
49
|
+
# @param args [Array<Object>] Filters for the command's tokens
|
51
50
|
# @yieldparam [Gamefic::Actor]
|
52
51
|
# @return [Symbol]
|
53
|
-
def meta(verb, *
|
54
|
-
args = map_response_args(queries)
|
52
|
+
def meta(verb, *args, &proc)
|
55
53
|
rulebook.calls.add_response Response.new(verb, self, *args, meta: true, &proc)
|
56
54
|
verb
|
57
55
|
end
|
@@ -134,23 +132,6 @@ module Gamefic
|
|
134
132
|
def syntaxes
|
135
133
|
rulebook.syntaxes
|
136
134
|
end
|
137
|
-
|
138
|
-
private
|
139
|
-
|
140
|
-
def map_response_args args
|
141
|
-
args.map do |arg|
|
142
|
-
case arg
|
143
|
-
when Entity, Class, Module, Proc, Proxy::Agent
|
144
|
-
available(arg)
|
145
|
-
when String, Regexp
|
146
|
-
plaintext(arg)
|
147
|
-
when Gamefic::Query::Base, Gamefic::Query::Text
|
148
|
-
arg
|
149
|
-
else
|
150
|
-
raise ArgumentError, "invalid argument in response: #{arg.inspect}"
|
151
|
-
end
|
152
|
-
end
|
153
|
-
end
|
154
135
|
end
|
155
136
|
end
|
156
137
|
end
|
@@ -9,7 +9,7 @@ module Gamefic
|
|
9
9
|
# #make and #destroy instead.
|
10
10
|
#
|
11
11
|
module Entities
|
12
|
-
include
|
12
|
+
include Proxies
|
13
13
|
|
14
14
|
def entity_vault
|
15
15
|
@entity_vault ||= Vault.new
|
@@ -63,6 +63,9 @@ module Gamefic
|
|
63
63
|
|
64
64
|
# Same as #pick, but raise an error if a unique match could not be found.
|
65
65
|
#
|
66
|
+
#
|
67
|
+
# @raise [RuntimeError] if a unique match was not found.
|
68
|
+
#
|
66
69
|
# @param description [String]
|
67
70
|
# @return [Gamefic::Entity, nil]
|
68
71
|
def pick! description
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Gamefic
|
4
|
+
module Scriptable
|
5
|
+
module PlotProxies
|
6
|
+
def lazy_plot key
|
7
|
+
Proxy.new(:attr, [:plot, key])
|
8
|
+
end
|
9
|
+
alias _plot lazy_plot
|
10
|
+
|
11
|
+
def attr_plot attr
|
12
|
+
define_method(attr) { plot.send(attr) }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|