gamefic 3.4.0 → 3.6.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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +22 -0
  3. data/README.md +1 -1
  4. data/lib/gamefic/action.rb +1 -0
  5. data/lib/gamefic/active/epic.rb +1 -0
  6. data/lib/gamefic/active.rb +4 -0
  7. data/lib/gamefic/chapter.rb +25 -42
  8. data/lib/gamefic/command.rb +49 -1
  9. data/lib/gamefic/dispatcher.rb +8 -31
  10. data/lib/gamefic/entity.rb +26 -0
  11. data/lib/gamefic/expression.rb +3 -0
  12. data/lib/gamefic/narrative.rb +9 -5
  13. data/lib/gamefic/node.rb +3 -5
  14. data/lib/gamefic/plot.rb +5 -0
  15. data/lib/gamefic/proxy/base.rb +27 -0
  16. data/lib/gamefic/proxy/config.rb +16 -0
  17. data/lib/gamefic/proxy/pick.rb +11 -0
  18. data/lib/gamefic/proxy/plot_pick.rb +11 -0
  19. data/lib/gamefic/proxy.rb +79 -0
  20. data/lib/gamefic/query/abstract.rb +12 -0
  21. data/lib/gamefic/query/base.rb +62 -16
  22. data/lib/gamefic/query/general.rb +6 -15
  23. data/lib/gamefic/query/result.rb +4 -1
  24. data/lib/gamefic/query/scoped.rb +3 -21
  25. data/lib/gamefic/query/text.rb +17 -15
  26. data/lib/gamefic/query.rb +1 -0
  27. data/lib/gamefic/response.rb +75 -34
  28. data/lib/gamefic/scanner/base.rb +44 -0
  29. data/lib/gamefic/scanner/fuzzy.rb +17 -0
  30. data/lib/gamefic/scanner/fuzzy_nesting.rb +14 -0
  31. data/lib/gamefic/scanner/nesting.rb +39 -0
  32. data/lib/gamefic/scanner/result.rb +60 -0
  33. data/lib/gamefic/scanner/strict.rb +31 -0
  34. data/lib/gamefic/scanner.rb +33 -111
  35. data/lib/gamefic/scope/descendants.rb +16 -0
  36. data/lib/gamefic/scope/family.rb +31 -8
  37. data/lib/gamefic/scope.rb +1 -0
  38. data/lib/gamefic/scriptable/actions.rb +4 -23
  39. data/lib/gamefic/scriptable/entities.rb +32 -17
  40. data/lib/gamefic/scriptable/plot_proxies.rb +29 -0
  41. data/lib/gamefic/scriptable/proxies.rb +31 -0
  42. data/lib/gamefic/scriptable/queries.rb +27 -8
  43. data/lib/gamefic/scriptable/scenes.rb +7 -1
  44. data/lib/gamefic/scriptable.rb +78 -16
  45. data/lib/gamefic/stage.rb +2 -2
  46. data/lib/gamefic/subplot.rb +33 -1
  47. data/lib/gamefic/version.rb +1 -1
  48. data/lib/gamefic.rb +1 -1
  49. metadata +17 -4
  50. data/lib/gamefic/composer.rb +0 -70
  51. data/lib/gamefic/scriptable/proxy.rb +0 -69
@@ -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
- NEST_REGEXP = / in | on | of | from | inside | from inside /
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>, String, Regexp]
18
+ # @param selection [Array<Entity>]
45
19
  # @param token [String]
46
20
  # @return [Result]
47
21
  def self.scan selection, token
48
- strict_result = strict(selection, token)
49
- strict_result.matched.empty? ? fuzzy(selection, token) : strict_result
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
- # @param selection [Array<Entity>, String, Regexp]
53
- # @param token [String]
54
- # @return [Result]
55
- def self.strict selection, token
56
- return Result.new(selection, token, '', token) unless selection.is_a?(Array)
57
-
58
- scan_strict_or_fuzzy(selection, token, :select_strict)
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
- # @param selection [Array<Entity>, String, Regexp]
62
- # @param token [String]
63
- # @return [Result]
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
- class << self
71
- private
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
@@ -2,18 +2,41 @@
2
2
 
3
3
  module Gamefic
4
4
  module Scope
5
- # The Family scope returns an entity's parent, siblings, and descendants.
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
- result = context.parent ? [context.parent] : []
10
- result.concat subquery_accessible(context.parent)
11
- result.delete context
12
- context.children.each do |c|
13
- result.push c
14
- result.concat subquery_accessible(c)
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
- result.uniq
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
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'gamefic/scope/base'
4
4
  require 'gamefic/scope/children'
5
+ require 'gamefic/scope/descendants'
5
6
  require 'gamefic/scope/family'
6
7
  require 'gamefic/scope/parent'
7
8
  require 'gamefic/scope/siblings'
@@ -26,11 +26,10 @@ module Gamefic
26
26
  # end
27
27
  #
28
28
  # @param verb [Symbol] An imperative verb for the command
29
- # @param queries [Array<Query::Base, Query::Text>] Filters for the command's tokens
29
+ # @param args [Array<Object>] Filters for the command's tokens
30
30
  # @yieldparam [Gamefic::Actor]
31
31
  # @return [Symbol]
32
- def respond(verb, *queries, &proc)
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 queries [Array<Query::Base, Query::Text>] Filters for the command's tokens
49
+ # @param args [Array<Object>] Filters for the command's tokens
51
50
  # @yieldparam [Gamefic::Actor]
52
51
  # @return [Symbol]
53
- def meta(verb, *queries, &proc)
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 Proxy
12
+ include Proxies
13
13
 
14
14
  def entity_vault
15
15
  @entity_vault ||= Vault.new
@@ -49,30 +49,45 @@ module Gamefic
49
49
  entity_vault.delete entity
50
50
  end
51
51
 
52
- # Pick an entity based on a unique name or description. Return nil if an
53
- # entity could not be found or there is more than one possible match.
52
+ def find *args
53
+ args.inject(entities) do |entities, arg|
54
+ case arg
55
+ when String
56
+ result = Scanner.scan(entities, arg)
57
+ result.remainder.empty? ? result.match : []
58
+ else
59
+ entities.that_are(arg)
60
+ end
61
+ end
62
+ end
63
+
64
+ # Pick a unique entity based on the given arguments. String arguments are
65
+ # used to scan the entities for matching names and synonyms. Return nil
66
+ # if an entity could not be found or there is more than one possible
67
+ # match.
54
68
  #
55
- # @param description [String]
69
+ # @param description [Array]
56
70
  # @return [Gamefic::Entity, nil]
57
- def pick description
58
- result = Scanner.scan(entities, description)
59
- return nil unless result.matched.one?
71
+ def pick *args
72
+ matches = find(*args)
73
+ return nil unless matches.one?
60
74
 
61
- result.matched.first
75
+ matches.first
62
76
  end
63
77
 
64
78
  # Same as #pick, but raise an error if a unique match could not be found.
65
79
  #
66
- # @param description [String]
67
- # @return [Gamefic::Entity, nil]
68
- def pick! description
69
- result = Scanner.scan(entities, description)
70
-
71
- raise "no entity matching '#{description}'" if result.matched.empty?
72
-
73
- raise "multiple entities matching '#{description}': #{result.matched.join_and}" unless result.matched.one?
80
+ #
81
+ # @raise [RuntimeError] if a unique match was not found.
82
+ #
83
+ # @param args [Array]
84
+ # @return [Gamefic::Entity]
85
+ def pick! *args
86
+ matches = find(*args)
87
+ raise "no entity matching '#{args.inspect}'" if matches.empty?
88
+ raise "multiple entities matching '#{args.inspect}': #{matches.join_and}" unless matches.one?
74
89
 
75
- result.matched.first
90
+ matches.first
76
91
  end
77
92
  end
78
93
  end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gamefic
4
+ module Scriptable
5
+ module PlotProxies
6
+ def lazy_plot key
7
+ Logging.logger.warn "#{caller.first ? "#{caller.first}: " : ''}`lazy_plot` is deprecated. Use `plot_pick`, `plot_pick!, or pass the entity from the plot in a `config` option instead."
8
+ Proxy.new(:attr, [:plot, key])
9
+ end
10
+ alias _plot lazy_plot
11
+
12
+ def attr_plot attr
13
+ define_method(attr) { plot.send(attr) }
14
+ end
15
+
16
+ def plot_pick *args
17
+ Proxy::PlotPick.new(*args)
18
+ end
19
+ alias lazy_plot_pick plot_pick
20
+ alias _plot_pick plot_pick
21
+
22
+ def plot_pick! *args
23
+ Proxy::PlotPick.new(*args)
24
+ end
25
+ alias lazy_plot_pick! plot_pick!
26
+ alias _plot_pick! plot_pick!
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gamefic
4
+ module Scriptable
5
+ # Methods for referencing entities from proxies.
6
+ #
7
+ module Proxies
8
+ # Convert a proxy into its referenced entity.
9
+ #
10
+ # This method can receive any kind of object. If it's a proxy, its entity
11
+ # will be returned. If it's an array, each of its elements will be
12
+ # unproxied. If it's a hash, each of its values will be unproxied. Any
13
+ # other object will be returned unchanged.
14
+ #
15
+ # @param object [Object]
16
+ # @return [Object]
17
+ def unproxy object
18
+ case object
19
+ when Proxy, Proxy::Base
20
+ object.fetch self
21
+ when Array
22
+ object.map { |obj| unproxy obj }
23
+ when Hash
24
+ object.transform_values { |val| unproxy val }
25
+ else
26
+ object
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -5,14 +5,25 @@ module Gamefic
5
5
  # Scriptable methods related to creating action queries.
6
6
  #
7
7
  module Queries
8
- include Proxy
8
+ include Proxies
9
9
 
10
10
  # Define a query that searches the entire plot's entities.
11
11
  #
12
12
  # @param args [Array<Object>] Query arguments
13
13
  # @return [Query::General]
14
14
  def anywhere *args, ambiguous: false
15
- Query::General.new -> { entities }, *unproxy(args), ambiguous: ambiguous
15
+ Query::General.new -> { entities }, *args, ambiguous: ambiguous, name: 'anywhere'
16
+ end
17
+
18
+ # Define a query that searches for abstract entities.
19
+ #
20
+ # An abstract entity is a pseudo-entity that is describable but does
21
+ # not have a parent or children.
22
+ #
23
+ # @param args [Array<Object>] Query arguments
24
+ # @return [Query::Abstract]
25
+ def abstract *args, ambiguous: false
26
+ Query::Abstract.new -> { entities }, *args, ambiguous: ambiguous
16
27
  end
17
28
 
18
29
  # Define a query that searches an actor's family of entities. The
@@ -22,7 +33,7 @@ module Gamefic
22
33
  # @param args [Array<Object>] Query arguments
23
34
  # @return [Query::Scoped]
24
35
  def available *args, ambiguous: false
25
- Query::Scoped.new Scope::Family, *unproxy(args), ambiguous: ambiguous
36
+ Query::Scoped.new Scope::Family, *args, ambiguous: ambiguous, name: 'available'
26
37
  end
27
38
  alias family available
28
39
 
@@ -31,7 +42,7 @@ module Gamefic
31
42
  # @param args [Array<Object>] Query arguments
32
43
  # @return [Query::Scoped]
33
44
  def parent *args, ambiguous: false
34
- Query::Scoped.new Scope::Parent, *unproxy(args), ambiguous: ambiguous
45
+ Query::Scoped.new Scope::Parent, *args, ambiguous: ambiguous, name: 'parent'
35
46
  end
36
47
 
37
48
  # Define a query that searches an actor's children.
@@ -39,7 +50,15 @@ module Gamefic
39
50
  # @param args [Array<Object>] Query arguments
40
51
  # @return [Query::Scoped]
41
52
  def children *args, ambiguous: false
42
- Query::Scoped.new Scope::Children, *unproxy(args), ambiguous: ambiguous
53
+ Query::Scoped.new Scope::Children, *args, ambiguous: ambiguous, name: 'children'
54
+ end
55
+
56
+ # Define a query that searches an actor's descendants.
57
+ #
58
+ # @param args [Array<Object>] Query arguments
59
+ # @return [Query::Scoped]
60
+ def descendants *args, ambiguous: false
61
+ Query::Scoped.new Scope::Descendants, *args, ambiguous: ambiguous
43
62
  end
44
63
 
45
64
  # Define a query that searches an actor's siblings.
@@ -47,7 +66,7 @@ module Gamefic
47
66
  # @param args [Array<Object>] Query arguments
48
67
  # @return [Query::Scoped]
49
68
  def siblings *args, ambiguous: false
50
- Query::Scoped.new Scope::Siblings, *unproxy(args), ambiguous: ambiguous
69
+ Query::Scoped.new Scope::Siblings, *args, ambiguous: ambiguous, name: 'siblings'
51
70
  end
52
71
 
53
72
  # Define a query that returns the actor itself.
@@ -55,7 +74,7 @@ module Gamefic
55
74
  # @param args [Array<Object>] Query arguments
56
75
  # @return [Query::Scoped]
57
76
  def myself *args, ambiguous: false
58
- Query::Scoped.new Scope::Myself, *unproxy(args), ambiguous: ambiguous
77
+ Query::Scoped.new Scope::Myself, *args, ambiguous: ambiguous, name: 'myself'
59
78
  end
60
79
 
61
80
  # Define a query that performs a plaintext search. It can take a String
@@ -66,7 +85,7 @@ module Gamefic
66
85
  # @param arg [String, Regexp] The string or regular expression to match
67
86
  # @return [Query::Text]
68
87
  def plaintext arg = /.*/
69
- Query::Text.new arg
88
+ Query::Text.new arg, name: 'plaintext'
70
89
  end
71
90
  end
72
91
  end
@@ -36,7 +36,6 @@ module Gamefic
36
36
  rulebook.scenes.add klass.new(name, self, on_start: on_start, on_finish: on_finish, &blk)
37
37
  name
38
38
  end
39
- alias scene block
40
39
 
41
40
  def preface name, klass = Scene::Activity, &start
42
41
  rulebook.scenes.add klass.new(name, self, on_start: start)
@@ -160,9 +159,16 @@ module Gamefic
160
159
  on_start: start
161
160
  end
162
161
 
162
+ # @return [Array<Symbol>]
163
163
  def scenes
164
164
  rulebook.scenes.names
165
165
  end
166
+
167
+ # @param name [Symbol]
168
+ # @return [Scene::Default, nil]
169
+ def scene(name)
170
+ rulebook.scenes[name]
171
+ end
166
172
  end
167
173
  end
168
174
  end
@@ -23,14 +23,14 @@ module Gamefic
23
23
  # end
24
24
  #
25
25
  module Scriptable
26
- autoload :Actions, 'gamefic/scriptable/actions'
27
- autoload :Entities, 'gamefic/scriptable/entities'
28
- autoload :Events, 'gamefic/scriptable/events'
29
- autoload :Queries, 'gamefic/scriptable/queries'
30
- autoload :Proxy, 'gamefic/scriptable/proxy'
31
- autoload :Scenes, 'gamefic/scriptable/scenes'
32
-
33
- include Proxy
26
+ autoload :Actions, 'gamefic/scriptable/actions'
27
+ autoload :Entities, 'gamefic/scriptable/entities'
28
+ autoload :Events, 'gamefic/scriptable/events'
29
+ autoload :Queries, 'gamefic/scriptable/queries'
30
+ autoload :Proxies, 'gamefic/scriptable/proxies'
31
+ autoload :Scenes, 'gamefic/scriptable/scenes'
32
+ autoload :PlotProxies, 'gamefic/scriptable/plot_proxies'
33
+
34
34
  include Queries
35
35
  # @!parse
36
36
  # include Scriptable::Actions
@@ -101,11 +101,12 @@ module Gamefic
101
101
  # make_seed Gamefic::Entity, name: 'thing'
102
102
  #
103
103
  # @param klass [Class<Gamefic::Entity>]
104
+ # @return [Proxy]
104
105
  def make_seed klass, **opts
105
- @count ||= 0
106
106
  seed { make(klass, **opts) }
107
- Proxy::Agent.new(@count.tap { @count += 1 })
107
+ Proxy::Pick.new(klass, opts[:name], raise: true)
108
108
  end
109
+ alias make make_seed
109
110
 
110
111
  # Seed an entity with an attribute method.
111
112
  #
@@ -119,15 +120,76 @@ module Gamefic
119
120
  #
120
121
  # @param name [Symbol] The attribute name
121
122
  # @param klass [Class<Gamefic::Entity>]
123
+ # @return [Proxy]
122
124
  def attr_seed name, klass, **opts
123
- @count ||= 0
124
- seed do
125
- instance_variable_set("@#{name}", make(klass, **opts))
126
- self.class.define_method(name) { instance_variable_get("@#{name}") }
125
+ ivname = "@#{name}"
126
+ define_method(name) do
127
+ return instance_variable_get(ivname) if instance_variable_defined?(ivname)
128
+
129
+ instance_variable_set(ivname, make(klass, **opts))
130
+ end
131
+ seed { send name }
132
+ Proxy.new(:attr, name)
133
+ end
134
+
135
+ # @param symbol [Symbol]
136
+ # @return [Proxy]
137
+ def proxy symbol
138
+ Logging.logger.warn "#{caller.first ? "#{caller.first}: " : ''}`proxy` is deprecated. Use `pick` or `pick!` instead."
139
+ if symbol.to_s.start_with?('@')
140
+ Proxy.new(:ivar, symbol)
141
+ else
142
+ Proxy.new(:attr, symbol)
127
143
  end
128
- Proxy::Agent.new(@count.tap { @count += 1 })
129
144
  end
130
145
 
146
+ # Lazy reference an entity by its instance variable.
147
+ #
148
+ # @example
149
+ # lazy_ivar(:@variable)
150
+ #
151
+ # @param key [Symbol]
152
+ # @return [Proxy]
153
+ def lazy_ivar key
154
+ Gamefic.logger.warn "#{caller.first ? "#{caller.first}: " : ''}`lazy_ivar` is deprecated. Use `pick` or `pick!` instead."
155
+ Proxy.new(:ivar, key)
156
+ end
157
+ alias _ivar lazy_ivar
158
+
159
+ # Lazy reference an entity by its attribute or method.
160
+ #
161
+ # @example
162
+ # lazy_attr(:method)
163
+ #
164
+ # @param key [Symbol]
165
+ # @return [Proxy]
166
+ def lazy_attr key
167
+ Gamefic.logger.warn "#{caller.first ? "#{caller.first}: " : ''}`lazy_attr` is deprecated. Use `pick` or `pick!` instead."
168
+ Proxy.new(:attr, key)
169
+ end
170
+ alias _attr lazy_attr
171
+
172
+ # Lazy pick an entity.
173
+ #
174
+ # @example
175
+ # pick('the red box')
176
+ #
177
+ # @param args [Array]
178
+ # @return [Proxy]
179
+ def pick *args
180
+ Proxy::Pick.new(*args)
181
+ end
182
+ alias lazy_pick pick
183
+ alias _pick pick
184
+
185
+ # Lazy pick an entity or raise
186
+ #
187
+ def pick! *args
188
+ Proxy::Pick.new(*args)
189
+ end
190
+ alias lazy_pick! pick
191
+ alias _pick! pick
192
+
131
193
  if RUBY_ENGINE == 'opal'
132
194
  # :nocov:
133
195
  def method_missing method, *args, &block
@@ -160,7 +222,7 @@ module Gamefic
160
222
  #
161
223
  # @return [Module<self>]
162
224
  def no_scripts
163
- Logging.logger.warn 'Calling `no_scripts` on Scriptable modules is no longer necessary.'
225
+ Logging.logger.warn "#{caller.first ? "#{caller.first}: " : ''}Calling `no_scripts` on Scriptable modules is no longer necessary."
164
226
  self
165
227
  end
166
228
  end
data/lib/gamefic/stage.rb CHANGED
@@ -14,7 +14,7 @@ module Gamefic
14
14
 
15
15
  OVERWRITEABLE_CLASSES = [String, Numeric, Symbol].freeze
16
16
 
17
- SWAPPABLE_VALUES = [true, false, nil].freeze
17
+ SWAPPABLE_VALUES = [true, false].freeze
18
18
 
19
19
  class << self
20
20
  private
@@ -37,7 +37,7 @@ module Gamefic
37
37
  end
38
38
 
39
39
  def overwriteable? cval, nval
40
- return true if swappable?(cval, nval)
40
+ return true if cval.nil? || swappable?(cval, nval)
41
41
 
42
42
  allowed = OVERWRITEABLE_CLASSES.find { |klass| cval.is_a?(klass) }
43
43
  allowed && cval.is_a?(allowed)