gamefic 3.5.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 79309729d2296ceec16e10d3380fba331512f15ca2d303788c1fbcc83c077457
4
- data.tar.gz: f4ce7d947730da4e833d895ae3085c4027d80cf89306b3aeff03d1a2a6ecd2aa
3
+ metadata.gz: d5c61efc096ecbacdd43431dd3f0b755120650258a4a3e5d724692957ee45f53
4
+ data.tar.gz: 499d740594a35e767c86fae7610a51dafa9ba647658af7b3ab46c61de4335501
5
5
  SHA512:
6
- metadata.gz: 01c7c68bdc217447d90bda4c2bdd362e318c644dc3486999f6125d6c124193e0b33782a250272a2455ea98e2f531551f3819611c4139b191d3dccaa61d9e03d8
7
- data.tar.gz: '06101965d9a47188f2b5b22e87f4f7b4bbeefd5f102d2405e1457c8a389a732e782cd4eedccec2176f2b237ad3783b1ab0058ce2721e451caa29e4e8a6d52cdc'
6
+ metadata.gz: dd430f5dfa970cacfe12915ff09284ef286b1116aa701ce5484472fdf880b68a5de91953182189c647e90c527653bde92db26016045ac5ea5304e660082979fe
7
+ data.tar.gz: 1726fc542b40c55443bc9b452df85d94fe0831fa19bc73e40076cd6768e92bc71e504fd2da7998ef65b913c21f8b3ccb7bd4f04329186b88e71eb9dc675b0b69
data/CHANGELOG.md CHANGED
@@ -1,4 +1,12 @@
1
- ## 3.5.0
1
+ ## 3.6.0 - October 6, 2024
2
+ - Normalized arguments accept strings
3
+ - Smarter picks and proxies
4
+ - Commands prefer strictness over precision
5
+ - Queries scan for ambiguity before filtering through arguments
6
+ - Abstract queries
7
+ - Command logging
8
+
9
+ ## 3.5.0 - October 5, 2024
2
10
  - Configurable scanners
3
11
  - Refactored scanners and queries
4
12
  - Allow assignment to nil instance variables in stage
@@ -51,6 +51,7 @@ module Gamefic
51
51
  def execute
52
52
  return self if cancelled? || executed?
53
53
 
54
+ Gamefic.logger.info "Executing #{([verb] + [arguments]).flatten.map(&:inspect).join(', ')}"
54
55
  @executed = true
55
56
  response.execute actor, *arguments
56
57
  self
@@ -30,6 +30,14 @@ module Gamefic
30
30
  @precision = precision
31
31
  end
32
32
 
33
+ def substantiality
34
+ @substantiality ||= arguments.that_are(Entity).length + (verb ? 1 : 0)
35
+ end
36
+
37
+ def inspect
38
+ "#<#{self.class} #{([verb] + arguments).map(&:inspect).join(', ')}>"
39
+ end
40
+
33
41
  class << self
34
42
  # Compose a command from input.
35
43
  #
@@ -48,11 +56,12 @@ module Gamefic
48
56
  # @param expression [Expression]
49
57
  # @return [Array<Command>]
50
58
  def expression_to_commands actor, expression
59
+ Gamefic.logger.info "Evaluating #{expression.inspect}"
51
60
  actor.epic
52
61
  .responses_for(expression.verb)
53
62
  .map { |response| response.to_command(actor, expression) }
54
63
  .compact
55
- .sort_by.with_index { |result, idx| [-result.precision, -result.strictness, idx] }
64
+ .sort_by.with_index { |result, idx| [-result.substantiality, -result.strictness, -result.precision, idx] }
56
65
  end
57
66
  end
58
67
  end
@@ -4,12 +4,15 @@ module Gamefic
4
4
  # The action executor for character commands.
5
5
  #
6
6
  class Dispatcher
7
+ include Logging
8
+
7
9
  # @param actor [Actor]
8
10
  # @param command [Command]
9
11
  def initialize actor, command
10
12
  @actor = actor
11
13
  @command = command
12
14
  @executed = false
15
+ Gamefic.logger.info "Dispatching #{command.inspect}"
13
16
  end
14
17
 
15
18
  # Run the dispatcher.
@@ -17,6 +17,9 @@ module Gamefic
17
17
  @tokens = tokens
18
18
  end
19
19
 
20
+ def inspect
21
+ "#<#{self.class} #{([verb] + tokens).map(&:inspect).join(', ')}>"
22
+ end
20
23
  # Compare two syntaxes for the purpose of ordering them by relevance while
21
24
  # dispatching.
22
25
  #
data/lib/gamefic/node.rb CHANGED
@@ -88,11 +88,9 @@ module Gamefic
88
88
  end
89
89
 
90
90
  def validate_parent(node)
91
- raise NodeError, 'Parent must be a Node' unless node.is_a?(Node) || node.nil?
92
-
93
- raise NodeError, "Node cannot be its own parent" if node == self
94
-
95
- raise NodeError, 'Node cannot be a child of a descendant' if flatten.include?(node)
91
+ raise NodeError, "Parent of #{inspect} must be a Node, received #{node.inspect}" unless node.is_a?(Node) || node.nil?
92
+ raise NodeError, "#{inspect} cannot be its own parent" if node == self
93
+ raise NodeError, "#{inspect} cannot be a child of descendant #{node.inspect}" if flatten.include?(node)
96
94
  end
97
95
  end
98
96
  end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gamefic
4
+ class Proxy
5
+ class Base
6
+ attr_reader :args
7
+
8
+ def initialize *args, raise: false
9
+ @args = args
10
+ @raise = raise
11
+ end
12
+
13
+ def raise?
14
+ @raise
15
+ end
16
+
17
+ def fetch narrative
18
+ result = select(narrative)
19
+ return result if result
20
+ raise "#{self.class} failed for #{args.inspect}" if raise?
21
+ end
22
+
23
+ def select narrative
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gamefic
4
+ class Proxy
5
+ class Config < Base
6
+ def select narrative
7
+ args.inject(narrative.config) { |hash, key| hash[key] }
8
+ end
9
+
10
+ def [](key)
11
+ args.push key
12
+ self
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gamefic
4
+ class Proxy
5
+ class Pick < Base
6
+ def select narrative
7
+ raise? ? narrative.pick!(*args) : narrative.pick(*args)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gamefic
4
+ class Proxy
5
+ class PlotPick < Base
6
+ def select narrative
7
+ raise? ? narrative.plot.pick!(*args) : narrative.plot.pick(*args)
8
+ end
9
+ end
10
+ end
11
+ end
data/lib/gamefic/proxy.rb CHANGED
@@ -1,7 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Gamefic
4
+ # @todo Turn this into a module after the old proxies are completely deprecated
5
+ #
4
6
  class Proxy
7
+ require 'gamefic/proxy/base'
8
+ require 'gamefic/proxy/config'
9
+ require 'gamefic/proxy/pick'
10
+ require 'gamefic/proxy/plot_pick'
11
+
12
+ TYPES = %i[attr ivar pick pick! plot_pick plot_pick! config].freeze
13
+
5
14
  # @return [Symbol]
6
15
  attr_reader :type
7
16
 
@@ -9,11 +18,12 @@ module Gamefic
9
18
  attr_reader :key
10
19
 
11
20
  # @param type [Symbol]
12
- # @param key [Symbol, String]
21
+ # @param key [Symbol, String, Array]
13
22
  def initialize type, key
23
+ Gamefic.logger.debug "Using deprecated #{type} proxy"
14
24
  @type = type
15
- @key = key
16
25
  validate_type
26
+ @key = type == :config ? [key].compact : key
17
27
  end
18
28
 
19
29
  def fetch narrative
@@ -21,6 +31,13 @@ module Gamefic
21
31
  raise(ArgumentError, "Unable to fetch entity from proxy agent symbol `#{key}`")
22
32
  end
23
33
 
34
+ def [](key)
35
+ raise ArgumentError, 'Invalid []' unless type == :config
36
+
37
+ @key.push key
38
+ self
39
+ end
40
+
24
41
  private
25
42
 
26
43
  def attr narrative
@@ -34,13 +51,29 @@ module Gamefic
34
51
  end
35
52
 
36
53
  def pick narrative
37
- narrative.pick! key
54
+ narrative.pick *key
55
+ end
56
+
57
+ def pick! narrative
58
+ narrative.pick! *key
59
+ end
60
+
61
+ def plot_pick narrative
62
+ narrative.plot.pick *key
63
+ end
64
+
65
+ def plot_pick! narrative
66
+ narrative.plot.pick! *key
67
+ end
68
+
69
+ def config narrative
70
+ key.inject(narrative.config) { |hash, key| hash[key] }
38
71
  end
39
72
 
40
73
  def validate_type
41
- return if [:attr, :ivar, :pick].include?(type)
74
+ return if TYPES.include?(type)
42
75
 
43
- raise ArgumentError, "Invalid proxy type `#{type}` (must be :attr, :ivar, or :pick)"
76
+ raise ArgumentError, "Invalid proxy type `#{type}` (must be #{TYPES.join_or})"
44
77
  end
45
78
  end
46
79
  end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gamefic
4
+ module Query
5
+ class Abstract < General
6
+ def span subject
7
+ available_entities(subject).that_are(Describable)
8
+ .that_are_not(Entity)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -19,11 +19,13 @@ module Gamefic
19
19
  #
20
20
  # @param arguments [Array<Object>]
21
21
  # @param ambiguous [Boolean]
22
- def initialize *arguments, ambiguous: false
22
+ # @param name [String]
23
+ def initialize *arguments, ambiguous: false, name: self.class.to_s
23
24
  raise ArgumentError, "nil argument in query" if arguments.any?(&:nil?)
24
25
 
25
26
  @arguments = arguments
26
27
  @ambiguous = ambiguous
28
+ @name = name
27
29
  end
28
30
 
29
31
  # Get a query result for a given subject and token.
@@ -38,19 +40,35 @@ module Gamefic
38
40
  # @param token [String]
39
41
  # @return [Result]
40
42
  def query(subject, token)
41
- scan = Scanner.scan(select(subject), token)
42
- ambiguous? ? ambiguous_result(scan) : unambiguous_result(scan)
43
+ first_pass = Scanner.scan(span(subject), token)
44
+ if ambiguous?
45
+ ambiguous_result(first_pass.filter(*normalized_arguments))
46
+ elsif first_pass.match.one?
47
+ unambiguous_result(first_pass.filter(*normalized_arguments))
48
+ else
49
+ unambiguous_result(first_pass)
50
+ end
43
51
  end
44
52
  alias filter query
45
53
 
46
- # Get an array of entities that match the query from the context of the
47
- # subject.
54
+ # Get an array of entities that match the arguments from the context of
55
+ # the subject.
56
+ #
57
+ # @param subject [Entity]
58
+ # @return [Array<Entity>]
59
+ def select subject
60
+ span(subject).that_are(*normalized_arguments)
61
+ end
62
+
63
+ # Get an array of entities that are candidates for selection from the
64
+ # context of the subject. These are the entities that #select will
65
+ # filter through query's arguments.
48
66
  #
49
67
  # Subclasses should override this method.
50
68
  #
51
69
  # @param subject [Entity]
52
70
  # @return [Array<Entity>]
53
- def select _subject
71
+ def span _subject
54
72
  []
55
73
  end
56
74
 
@@ -77,12 +95,20 @@ module Gamefic
77
95
  @ambiguous
78
96
  end
79
97
 
98
+ def name
99
+ @name || self.class.to_s
100
+ end
101
+
102
+ def inspect
103
+ "##{ambiguous? ? '*' : ''}#{name}(#{normalized_arguments.map(&:inspect).join(', ')})"
104
+ end
105
+
80
106
  private
81
107
 
82
108
  def calculate_precision
83
- unproxied_arguments.sum(@ambiguous ? -1000 : 0) do |arg|
109
+ normalized_arguments.sum(@ambiguous ? -1000 : 0) do |arg|
84
110
  case arg
85
- when Entity, Proxy
111
+ when Entity, Proxy, Proxy::Base
86
112
  1000
87
113
  when Class, Module
88
114
  class_depth(arg) * 100
@@ -115,11 +141,15 @@ module Gamefic
115
141
  Result.new(scan.matched.first, scan.remainder, scan.strictness)
116
142
  end
117
143
 
118
- def unproxied_arguments
119
- @unproxied_arguments ||= arguments.map do |arg|
144
+ def normalized_arguments
145
+ @normalized_arguments ||= arguments.map do |arg|
120
146
  case arg
121
- when Proxy
147
+ when Proxy, Proxy::Base
122
148
  arg.fetch(narrative)
149
+ when String
150
+ proc do |entity|
151
+ arg.keywords.all? { |word| entity.keywords.include?(word) }
152
+ end
123
153
  else
124
154
  arg
125
155
  end
@@ -14,13 +14,13 @@ module Gamefic
14
14
  # @param entities [Array, Proc]
15
15
  # @param arguments [Array<Object>]
16
16
  # @param ambiguous [Boolean]
17
- def initialize entities, *arguments, ambiguous: false
18
- super(*arguments, ambiguous: ambiguous)
17
+ def initialize entities, *arguments, ambiguous: false, name: nil
18
+ super(*arguments, ambiguous: ambiguous, name: name)
19
19
  @entities = entities
20
20
  end
21
21
 
22
- def select subject
23
- available_entities(subject).that_are(*unproxied_arguments)
22
+ def span subject
23
+ available_entities(subject)
24
24
  end
25
25
 
26
26
  private
@@ -10,14 +10,13 @@ module Gamefic
10
10
  attr_reader :scope
11
11
 
12
12
  # @param scope [Class<Gamefic::Scope::Base>]
13
- def initialize scope, *arguments, ambiguous: false
14
- super(*arguments, ambiguous: ambiguous)
13
+ def initialize scope, *arguments, ambiguous: false, name: nil
14
+ super(*arguments, ambiguous: ambiguous, name: name)
15
15
  @scope = scope
16
16
  end
17
17
 
18
- def select(subject)
18
+ def span(subject)
19
19
  @scope.matches(subject)
20
- .that_are(*unproxied_arguments)
21
20
  end
22
21
 
23
22
  def precision
@@ -6,9 +6,10 @@ module Gamefic
6
6
  #
7
7
  class Text < Base
8
8
  # @param argument [String, Regexp]
9
- def initialize argument = /.*/
10
- super
11
- validate
9
+ # @param name [String, nil]
10
+ def initialize argument = /.*/, name: self.class.name
11
+ super(argument, name: name)
12
+ validate_argument
12
13
  end
13
14
 
14
15
  def argument
@@ -50,7 +51,7 @@ module Gamefic
50
51
  end
51
52
  end
52
53
 
53
- def validate
54
+ def validate_argument
54
55
  return if argument.is_a?(String) || argument.is_a?(Regexp)
55
56
 
56
57
  raise ArgumentError, 'Invalid text query argument'
data/lib/gamefic/query.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'gamefic/query/base'
4
4
  require 'gamefic/query/general'
5
+ require 'gamefic/query/abstract'
5
6
  require 'gamefic/query/scoped'
6
7
  require 'gamefic/query/text'
7
8
  require 'gamefic/query/result'
@@ -77,11 +77,12 @@ module Gamefic
77
77
  # @param expression [Expression]
78
78
  # @return [Command, nil]
79
79
  def to_command actor, expression
80
- return nil unless expression.verb == verb && expression.tokens.length <= queries.length
80
+ return log_and_discard unless expression.verb == verb && expression.tokens.length <= queries.length
81
81
 
82
82
  results = filter(actor, expression)
83
- return nil unless results
83
+ return log_and_discard unless results
84
84
 
85
+ Gamefic.logger.info "Accepted #{inspect}"
85
86
  Command.new(
86
87
  verb,
87
88
  results.map(&:match),
@@ -90,8 +91,17 @@ module Gamefic
90
91
  )
91
92
  end
92
93
 
94
+ def inspect
95
+ "#<#{self.class} #{([verb] + queries).map(&:inspect).join(', ')}>"
96
+ end
97
+
93
98
  private
94
99
 
100
+ def log_and_discard
101
+ Gamefic.logger.info "Discarded #{inspect}"
102
+ nil
103
+ end
104
+
95
105
  def filter actor, expression
96
106
  remainder = ''
97
107
  result = queries.zip(expression.tokens)
@@ -126,7 +136,7 @@ module Gamefic
126
136
 
127
137
  def select_query arg, narrative
128
138
  case arg
129
- when Entity, Class, Module, Proc, Proxy
139
+ when Entity, Class, Module, Proc, Proxy, Proxy::Base
130
140
  narrative.available(arg)
131
141
  when String, Regexp
132
142
  narrative.plaintext(arg)
@@ -12,7 +12,7 @@ module Gamefic
12
12
  end
13
13
 
14
14
  def scan
15
- return Result.unmatched(selection, token, self.class) unless token =~ NEST_REGEXP
15
+ return unmatched_result unless token =~ NEST_REGEXP
16
16
 
17
17
  denest selection, token
18
18
  end
@@ -27,7 +27,7 @@ module Gamefic
27
27
  current = parts.pop
28
28
  last_result = subprocessor.scan(near, current)
29
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
30
+ return unmatched_result if last_result.matched.empty? || last_result.matched.length > 1
31
31
 
32
32
  near = last_result.matched.first.children & objects
33
33
  far = last_result.matched.first.flatten & objects
@@ -42,6 +42,16 @@ module Gamefic
42
42
  @strictness ||= Scanner.strictness(processor)
43
43
  end
44
44
 
45
+ def filter *args
46
+ Scanner::Result.new(
47
+ scanned,
48
+ token,
49
+ match.that_are(*args),
50
+ remainder,
51
+ processor
52
+ )
53
+ end
54
+
45
55
  def self.unmatched scanned, token, processor
46
56
  new(scanned, token, [], token, processor)
47
57
  end
@@ -1,16 +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
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
@@ -49,16 +49,30 @@ 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.
@@ -66,16 +80,14 @@ module Gamefic
66
80
  #
67
81
  # @raise [RuntimeError] if a unique match was not found.
68
82
  #
69
- # @param description [String]
70
- # @return [Gamefic::Entity, nil]
71
- def pick! description
72
- result = Scanner.scan(entities, description)
73
-
74
- raise "no entity matching '#{description}'" if result.matched.empty?
75
-
76
- raise "multiple entities matching '#{description}': #{result.matched.join_and}" unless result.matched.one?
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?
77
89
 
78
- result.matched.first
90
+ matches.first
79
91
  end
80
92
  end
81
93
  end
@@ -4,6 +4,7 @@ module Gamefic
4
4
  module Scriptable
5
5
  module PlotProxies
6
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."
7
8
  Proxy.new(:attr, [:plot, key])
8
9
  end
9
10
  alias _plot lazy_plot
@@ -11,6 +12,18 @@ module Gamefic
11
12
  def attr_plot attr
12
13
  define_method(attr) { plot.send(attr) }
13
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!
14
27
  end
15
28
  end
16
29
  end
@@ -16,7 +16,7 @@ module Gamefic
16
16
  # @return [Object]
17
17
  def unproxy object
18
18
  case object
19
- when Proxy
19
+ when Proxy, Proxy::Base
20
20
  object.fetch self
21
21
  when Array
22
22
  object.map { |obj| unproxy obj }
@@ -12,7 +12,18 @@ module Gamefic
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 }, *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, *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, *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,7 @@ 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, *args, ambiguous: ambiguous
53
+ Query::Scoped.new Scope::Children, *args, ambiguous: ambiguous, name: 'children'
43
54
  end
44
55
 
45
56
  # Define a query that searches an actor's descendants.
@@ -55,7 +66,7 @@ module Gamefic
55
66
  # @param args [Array<Object>] Query arguments
56
67
  # @return [Query::Scoped]
57
68
  def siblings *args, ambiguous: false
58
- Query::Scoped.new Scope::Siblings, *args, ambiguous: ambiguous
69
+ Query::Scoped.new Scope::Siblings, *args, ambiguous: ambiguous, name: 'siblings'
59
70
  end
60
71
 
61
72
  # Define a query that returns the actor itself.
@@ -63,7 +74,7 @@ module Gamefic
63
74
  # @param args [Array<Object>] Query arguments
64
75
  # @return [Query::Scoped]
65
76
  def myself *args, ambiguous: false
66
- Query::Scoped.new Scope::Myself, *args, ambiguous: ambiguous
77
+ Query::Scoped.new Scope::Myself, *args, ambiguous: ambiguous, name: 'myself'
67
78
  end
68
79
 
69
80
  # Define a query that performs a plaintext search. It can take a String
@@ -74,7 +85,7 @@ module Gamefic
74
85
  # @param arg [String, Regexp] The string or regular expression to match
75
86
  # @return [Query::Text]
76
87
  def plaintext arg = /.*/
77
- Query::Text.new arg
88
+ Query::Text.new arg, name: 'plaintext'
78
89
  end
79
90
  end
80
91
  end
@@ -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 [void]
104
+ # @return [Proxy]
105
105
  def make_seed klass, **opts
106
106
  seed { make(klass, **opts) }
107
- nil
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
  #
@@ -134,11 +135,11 @@ module Gamefic
134
135
  # @param symbol [Symbol]
135
136
  # @return [Proxy]
136
137
  def proxy symbol
137
- Logging.logger.warn "#proxy is deprecated. Use lazy_attr, lazy_ivar, or lazy_pick instead"
138
+ Logging.logger.warn "#{caller.first ? "#{caller.first}: " : ''}`proxy` is deprecated. Use `pick` or `pick!` instead."
138
139
  if symbol.to_s.start_with?('@')
139
- lazy_ivar(symbol)
140
+ Proxy.new(:ivar, symbol)
140
141
  else
141
- lazy_attr(symbol)
142
+ Proxy.new(:attr, symbol)
142
143
  end
143
144
  end
144
145
 
@@ -150,6 +151,7 @@ module Gamefic
150
151
  # @param key [Symbol]
151
152
  # @return [Proxy]
152
153
  def lazy_ivar key
154
+ Gamefic.logger.warn "#{caller.first ? "#{caller.first}: " : ''}`lazy_ivar` is deprecated. Use `pick` or `pick!` instead."
153
155
  Proxy.new(:ivar, key)
154
156
  end
155
157
  alias _ivar lazy_ivar
@@ -162,23 +164,31 @@ module Gamefic
162
164
  # @param key [Symbol]
163
165
  # @return [Proxy]
164
166
  def lazy_attr key
167
+ Gamefic.logger.warn "#{caller.first ? "#{caller.first}: " : ''}`lazy_attr` is deprecated. Use `pick` or `pick!` instead."
165
168
  Proxy.new(:attr, key)
166
169
  end
167
170
  alias _attr lazy_attr
168
171
 
169
- # Lazy reference an entity by its description.
172
+ # Lazy pick an entity.
170
173
  #
171
174
  # @example
172
- # lazy_pick('the red box')
173
- #
174
- # @raise [RuntimeError] if a unique match could not be found.
175
+ # pick('the red box')
175
176
  #
176
- # @param description [String]
177
+ # @param args [Array]
177
178
  # @return [Proxy]
178
- def lazy_pick description
179
- Proxy.new(:pick, description)
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)
180
189
  end
181
- alias _pick lazy_pick
190
+ alias lazy_pick! pick
191
+ alias _pick! pick
182
192
 
183
193
  if RUBY_ENGINE == 'opal'
184
194
  # :nocov:
@@ -212,7 +222,7 @@ module Gamefic
212
222
  #
213
223
  # @return [Module<self>]
214
224
  def no_scripts
215
- 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."
216
226
  self
217
227
  end
218
228
  end
@@ -98,5 +98,11 @@ module Gamefic
98
98
  def inspect
99
99
  "#<#{self.class}>"
100
100
  end
101
+
102
+ class << self
103
+ def config
104
+ Proxy::Config.new
105
+ end
106
+ end
101
107
  end
102
108
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Gamefic
4
- VERSION = '3.5.0'
4
+ VERSION = '3.6.0'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gamefic
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.5.0
4
+ version: 3.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Fred Snyder
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-10-05 00:00:00.000000000 Z
11
+ date: 2024-10-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: opal
@@ -156,7 +156,12 @@ files:
156
156
  - lib/gamefic/props/pause.rb
157
157
  - lib/gamefic/props/yes_or_no.rb
158
158
  - lib/gamefic/proxy.rb
159
+ - lib/gamefic/proxy/base.rb
160
+ - lib/gamefic/proxy/config.rb
161
+ - lib/gamefic/proxy/pick.rb
162
+ - lib/gamefic/proxy/plot_pick.rb
159
163
  - lib/gamefic/query.rb
164
+ - lib/gamefic/query/abstract.rb
160
165
  - lib/gamefic/query/base.rb
161
166
  - lib/gamefic/query/general.rb
162
167
  - lib/gamefic/query/result.rb