gamefic 3.5.0 → 3.6.0

Sign up to get free protection for your applications and to get access to all the features.
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