gamefic 3.6.0 → 4.0.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 (117) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +0 -3
  3. data/CHANGELOG.md +19 -0
  4. data/Rakefile +1 -0
  5. data/gamefic.gemspec +1 -1
  6. data/lib/gamefic/action.rb +68 -54
  7. data/lib/gamefic/active/cue.rb +84 -6
  8. data/lib/gamefic/active/messaging.rb +8 -0
  9. data/lib/gamefic/active/narratives.rb +101 -0
  10. data/lib/gamefic/active.rb +80 -92
  11. data/lib/gamefic/binding.rb +44 -0
  12. data/lib/gamefic/chapter.rb +30 -46
  13. data/lib/gamefic/command.rb +22 -40
  14. data/lib/gamefic/core_ext/array.rb +7 -7
  15. data/lib/gamefic/core_ext/string.rb +2 -2
  16. data/lib/gamefic/describable.rb +13 -0
  17. data/lib/gamefic/dispatcher.rb +35 -55
  18. data/lib/gamefic/entity.rb +6 -5
  19. data/lib/gamefic/expression.rb +1 -11
  20. data/lib/gamefic/logging.rb +3 -10
  21. data/lib/gamefic/match.rb +23 -0
  22. data/lib/gamefic/messenger.rb +1 -1
  23. data/lib/gamefic/narrative.rb +38 -74
  24. data/lib/gamefic/narrator.rb +77 -0
  25. data/lib/gamefic/node.rb +40 -8
  26. data/lib/gamefic/order.rb +53 -0
  27. data/lib/gamefic/plot.rb +41 -59
  28. data/lib/gamefic/props/default.rb +5 -17
  29. data/lib/gamefic/props/multiple_choice.rb +5 -2
  30. data/lib/gamefic/props/multiple_partial.rb +16 -0
  31. data/lib/gamefic/props/output.rb +7 -5
  32. data/lib/gamefic/props/yes_or_no.rb +2 -2
  33. data/lib/gamefic/props.rb +1 -0
  34. data/lib/gamefic/proxy/attr.rb +11 -0
  35. data/lib/gamefic/proxy/base.rb +3 -15
  36. data/lib/gamefic/proxy/config.rb +2 -2
  37. data/lib/gamefic/proxy/pick.rb +3 -3
  38. data/lib/gamefic/proxy/pick_ex.rb +11 -0
  39. data/lib/gamefic/proxy.rb +3 -71
  40. data/lib/gamefic/query/ascendants.rb +16 -0
  41. data/lib/gamefic/query/base.rb +47 -73
  42. data/lib/gamefic/query/children.rb +15 -0
  43. data/lib/gamefic/query/descendants.rb +17 -0
  44. data/lib/gamefic/query/extended.rb +20 -0
  45. data/lib/gamefic/query/family.rb +27 -0
  46. data/lib/gamefic/query/global.rb +22 -0
  47. data/lib/gamefic/query/integer.rb +32 -0
  48. data/lib/gamefic/query/myself.rb +13 -0
  49. data/lib/gamefic/query/parent.rb +13 -0
  50. data/lib/gamefic/query/result.rb +1 -1
  51. data/lib/gamefic/query/siblings.rb +12 -0
  52. data/lib/gamefic/query/subqueries.rb +17 -0
  53. data/lib/gamefic/query/text.rb +8 -9
  54. data/lib/gamefic/query.rb +11 -3
  55. data/lib/gamefic/request.rb +60 -0
  56. data/lib/gamefic/response.rb +46 -72
  57. data/lib/gamefic/scanner/nesting.rb +6 -6
  58. data/lib/gamefic/scanner/result.rb +3 -0
  59. data/lib/gamefic/scanner/strict.rb +14 -4
  60. data/lib/gamefic/scanner.rb +11 -6
  61. data/lib/gamefic/scene/active_choice.rb +75 -0
  62. data/lib/gamefic/scene/activity.rb +7 -3
  63. data/lib/gamefic/scene/base.rb +123 -0
  64. data/lib/gamefic/scene/conclusion.rb +4 -1
  65. data/lib/gamefic/scene/multiple_choice.rb +14 -11
  66. data/lib/gamefic/scene/pause.rb +5 -1
  67. data/lib/gamefic/scene/yes_or_no.rb +9 -0
  68. data/lib/gamefic/scene.rb +2 -1
  69. data/lib/gamefic/scriptable/hooks.rb +161 -0
  70. data/lib/gamefic/scriptable/queries.rb +38 -29
  71. data/lib/gamefic/scriptable/responses.rb +70 -0
  72. data/lib/gamefic/scriptable/scenes.rb +88 -115
  73. data/lib/gamefic/scriptable/seeds.rb +69 -0
  74. data/lib/gamefic/scriptable/syntaxes.rb +29 -0
  75. data/lib/gamefic/scriptable.rb +14 -199
  76. data/lib/gamefic/{scriptable → scripting}/entities.rb +22 -22
  77. data/lib/gamefic/scripting/hooks.rb +45 -0
  78. data/lib/gamefic/{scriptable → scripting}/proxies.rb +5 -3
  79. data/lib/gamefic/scripting/responses.rb +21 -0
  80. data/lib/gamefic/scripting/scenes.rb +57 -0
  81. data/lib/gamefic/scripting/seeds.rb +10 -0
  82. data/lib/gamefic/scripting/syntaxes.rb +13 -0
  83. data/lib/gamefic/scripting.rb +43 -0
  84. data/lib/gamefic/subplot.rb +11 -22
  85. data/lib/gamefic/syntax.rb +39 -24
  86. data/lib/gamefic/version.rb +1 -1
  87. data/lib/gamefic.rb +6 -7
  88. metadata +38 -41
  89. data/lib/gamefic/active/epic.rb +0 -74
  90. data/lib/gamefic/active/take.rb +0 -67
  91. data/lib/gamefic/block.rb +0 -28
  92. data/lib/gamefic/callback.rb +0 -16
  93. data/lib/gamefic/proxy/plot_pick.rb +0 -11
  94. data/lib/gamefic/query/abstract.rb +0 -12
  95. data/lib/gamefic/query/general.rb +0 -41
  96. data/lib/gamefic/query/scoped.rb +0 -27
  97. data/lib/gamefic/rulebook/calls.rb +0 -86
  98. data/lib/gamefic/rulebook/events.rb +0 -65
  99. data/lib/gamefic/rulebook/hooks.rb +0 -57
  100. data/lib/gamefic/rulebook/scenes.rb +0 -68
  101. data/lib/gamefic/rulebook.rb +0 -125
  102. data/lib/gamefic/scene/default.rb +0 -88
  103. data/lib/gamefic/scope/base.rb +0 -44
  104. data/lib/gamefic/scope/children.rb +0 -16
  105. data/lib/gamefic/scope/descendants.rb +0 -16
  106. data/lib/gamefic/scope/family.rb +0 -43
  107. data/lib/gamefic/scope/myself.rb +0 -13
  108. data/lib/gamefic/scope/parent.rb +0 -13
  109. data/lib/gamefic/scope/siblings.rb +0 -14
  110. data/lib/gamefic/scope.rb +0 -9
  111. data/lib/gamefic/scriptable/actions.rb +0 -137
  112. data/lib/gamefic/scriptable/events.rb +0 -71
  113. data/lib/gamefic/scriptable/plot_proxies.rb +0 -29
  114. data/lib/gamefic/snapshot.rb +0 -44
  115. data/lib/gamefic/stage.rb +0 -51
  116. data/lib/gamefic/syntax/template.rb +0 -67
  117. data/lib/gamefic/vault.rb +0 -52
@@ -1,47 +1,52 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'set'
4
+
3
5
  module Gamefic
4
6
  module Scriptable
5
- # Scriptable methods related to creating scenes.
6
- #
7
7
  module Scenes
8
- # Block a new scene.
9
- #
10
- # @example Prompt the player for a name
11
- # block :name_of_scene do |scene|
12
- # # The scene's start occurs before the user gets prompted for input
13
- # scene.on_start do |actor, props|
14
- # props.prompt = 'What's your name?'
15
- # end
16
- #
17
- # # The scene's finish is where you can process the user's input
18
- # scene.on_finish do |actor, props|
19
- # if props.input.empty?
20
- # # You can use recue to start the scene again
21
- # actor.recue
22
- # else
23
- # actor.tell "Hello, #{props.input}!"
24
- # end
25
- # end
26
- # end
27
- #
28
- # @param name [Symbol]
29
- # @param klass [Class<Scene::Default>]
30
- # @param on_start [Proc, nil]
31
- # @param on_finish [Proc, nil]
32
- # @param block [Proc]
33
- # @yieldparam [Scene]
34
- # @return [Symbol]
35
- def block name, klass = Scene::Default, on_start: nil, on_finish: nil, &blk
36
- rulebook.scenes.add klass.new(name, self, on_start: on_start, on_finish: on_finish, &blk)
37
- name
8
+ # @return [Scene::Base]
9
+ attr_reader :default_scene
10
+
11
+ # @return [Scene::Conclusion]
12
+ attr_reader :default_conclusion
13
+
14
+ def select_default_scene(klass)
15
+ scene_classes.add klass
16
+ @default_scene = klass
38
17
  end
39
18
 
40
- def preface name, klass = Scene::Activity, &start
41
- rulebook.scenes.add klass.new(name, self, on_start: start)
42
- name
19
+ def select_default_conclusion(klass)
20
+ scene_classes.add klass
21
+ @default_conclusion = klass
22
+ end
23
+
24
+ def named_scenes
25
+ @named_scenes ||= {}
26
+ end
27
+
28
+ def scene_classes
29
+ @scene_classes ||= Set.new
30
+ end
31
+
32
+ def scene_classes_map
33
+ scene_classes.each_with_object(named_scenes.clone) { |klass, hash| hash[klass] = klass }
34
+ end
35
+
36
+ def scenes
37
+ scene_classes_map.values.uniq
38
+ end
39
+
40
+ def block(scene, name = nil)
41
+ named_scenes[name] = scene if name
42
+ scene_classes.add scene
43
+ scene
44
+ end
45
+ alias scene block
46
+
47
+ def introductions
48
+ @introductions ||= []
43
49
  end
44
- alias precursor preface
45
50
 
46
51
  # Add a block to be executed when a player is added to the game.
47
52
  # Each Plot should only have one introduction.
@@ -57,10 +62,7 @@ module Gamefic
57
62
  # @yieldparam [Props::Default]
58
63
  # @return [Symbol]
59
64
  def introduction(&start)
60
- rulebook.scenes
61
- .introduction Scene::Default.new nil,
62
- self,
63
- on_start: proc { |actor, _props| Stage.run(self, actor, &start) }
65
+ introductions.push start
64
66
  end
65
67
 
66
68
  # Create a multiple-choice scene.
@@ -68,28 +70,23 @@ module Gamefic
68
70
  # will restart if the user input is not a valid choice.
69
71
  #
70
72
  # @example
71
- # multiple_choice :go_somewhere, ['Go to work', 'Go to school'] do |actor, props|
72
- # # Assuming the user selected the first choice:
73
- # props.selection #=> 'Go to work'
74
- # props.index #=> 0
75
- # props.number #=> 1
73
+ # multiple_choice :go_somewhere, do
74
+ # on_start do |actor, props|
75
+ # props.options.push 'Go to work', 'Go to school'
76
+ # end
77
+ #
78
+ # on_finish do |actor, props|
79
+ # # Assuming the user selected the first choice:
80
+ # props.selection #=> 'Go to work'
81
+ # props.index #=> 0
82
+ # props.number #=> 1
83
+ # end
76
84
  # end
77
85
  #
78
- # @param name [Symbol]
79
- # @param choices [Array<String>]
80
- # @param prompt [String, nil]
81
- # @param proc [Proc]
82
- # @yieldparam [Actor]
83
- # @yieldparam [Props::MultipleChoice]
84
- # @return [Symbol]
85
- def multiple_choice name, choices = [], prompt = 'What is your choice?', &blk
86
- block name,
87
- Scene::MultipleChoice,
88
- on_start: proc { |_actor, props|
89
- props.prompt = prompt
90
- props.options.concat choices
91
- },
92
- on_finish: blk
86
+ # @param name [Symbol, nil]
87
+ # @return [Class<Scene::MultipleChoice>]
88
+ def multiple_choice(name = nil, &block)
89
+ block Class.new(Scene::MultipleChoice, &block), name
93
90
  end
94
91
 
95
92
  # Create a yes-or-no scene.
@@ -97,48 +94,36 @@ module Gamefic
97
94
  # will restart if the user input is not a valid choice.
98
95
  #
99
96
  # @example
100
- # yes_or_no :answer_scene, 'What is your answer?' do |actor, props|
101
- # if props.yes?
102
- # actor.tell "You said yes."
103
- # else
104
- # actor.tell "You said no."
97
+ # yes_or_no :answer_scene do
98
+ # on_start do |actor, props|
99
+ # actor.tell 'Yes or no?'
100
+ # end
101
+ #
102
+ # on_finish do |actor, props|
103
+ # if props.yes?
104
+ # actor.tell 'You said yes.'
105
+ # else
106
+ # actor.tell 'You said no.'
107
+ # end
105
108
  # end
106
109
  # end
107
110
  #
108
- # @param name [Symbol]
109
- # @param prompt [String, nil]
110
- # @yieldparam [Actor]
111
- # @yieldparam [Props::YesOrNo]
112
- # @return [Symbol]
113
- def yes_or_no name, prompt = 'Answer:', &blk
114
- block name,
115
- Scene::YesOrNo,
116
- on_start: proc { |_actor, props|
117
- props.prompt = prompt
118
- },
119
- on_finish: blk
111
+ # @param name [Symbol, nil]
112
+ # @return [Class<Scene::YesOrNo>]
113
+ def yes_or_no(name = nil, &block)
114
+ block Class.new(Scene::YesOrNo, &block), name
120
115
  end
121
116
 
122
- # Create a scene that pauses the game.
123
- # This scene will execute the specified block and wait for input from the
124
- # the user (e.g., pressing Enter) to continue.
125
- #
126
- # @example
127
- # pause :wait do |actor|
128
- # actor.tell "After you continue, you will be prompted for a command."
129
- # end
130
- #
131
- # @param name [Symbol]
132
- # @param prompt [String, nil] The text to display when prompting the user to continue
133
- # @yieldparam [Actor]
134
- # @return [Symbol]
135
- def pause name, prompt: 'Press enter to continue...', &start
136
- block name,
137
- Scene::Pause,
138
- on_start: proc { |actor, props|
139
- props.prompt = prompt if prompt
140
- instance_exec(actor, props, &start)
141
- }
117
+ # @param name [Symbol, nil]
118
+ # @return [Class<Scene::ActiveChoice>]
119
+ def active_choice(name = nil, &block)
120
+ block Class.new(Scene::ActiveChoice, &block), name
121
+ end
122
+
123
+ # @param name [Symbol, nil]
124
+ # @return [Class<Scene::Pause>]
125
+ def pause(name = nil, &block)
126
+ block Class.new(Scene::Pause, &block), name
142
127
  end
143
128
 
144
129
  # Create a conclusion.
@@ -150,24 +135,12 @@ module Gamefic
150
135
  # actor.tell 'GAME OVER'
151
136
  # end
152
137
  #
153
- # @param name [Symbol]
154
- # @yieldparam [Actor]
155
- # @return [Symbol]
156
- def conclusion name, &start
157
- block name,
158
- Scene::Conclusion,
159
- on_start: start
160
- end
161
-
162
- # @return [Array<Symbol>]
163
- def scenes
164
- rulebook.scenes.names
165
- end
166
-
167
- # @param name [Symbol]
168
- # @return [Scene::Default, nil]
169
- def scene(name)
170
- rulebook.scenes[name]
138
+ # @param name [Symbol, nil]
139
+ # @return [Class<Scene::Conclusion>]
140
+ def conclusion(name = nil, &block)
141
+ block(Class.new(Scene::Conclusion) do
142
+ on_start(&block)
143
+ end, name)
171
144
  end
172
145
  end
173
146
  end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gamefic
4
+ module Scriptable
5
+ module Seeds
6
+ def seeds
7
+ @seeds ||= []
8
+ end
9
+
10
+ def seed *methods, &block
11
+ seeds.push(proc { methods.flatten.each { |method| send(method) } }) unless methods.empty?
12
+ seeds.push block if block
13
+ end
14
+
15
+ # Construct an entity.
16
+ #
17
+ # This method adds an instance method for the entity and a class method to
18
+ # reference it with a proxy.
19
+ #
20
+ # @param name [Symbol, String] The method name for the entity
21
+ # @param klass [Class<Gamefic::Entity>]
22
+ # @return [void]
23
+ def construct name, klass, **opts
24
+ ivname = "@#{name}"
25
+ define_method(name) do
26
+ return instance_variable_get(ivname) if instance_variable_defined?(ivname)
27
+
28
+ instance_variable_set(ivname, make(klass, **unproxy(opts)))
29
+ end
30
+ seed { send(name) }
31
+ define_singleton_method(name) { Proxy::Attr.new(name) }
32
+ end
33
+ alias attr_make construct
34
+ alias attr_seed construct
35
+
36
+ # Add an entity to be seeded when the narrative gets instantiated.
37
+ #
38
+ # @param klass [Class<Gamefic::Entity>]
39
+ # @return [void]
40
+ def make klass, **opts
41
+ seed { make(klass, **unproxy(opts)) }
42
+ end
43
+ alias make_seed make
44
+ alias seed_make make
45
+ end
46
+
47
+ # Lazy pick an entity.
48
+ #
49
+ # @example
50
+ # pick('the red box')
51
+ #
52
+ # @param args [Array]
53
+ # @return [Proxy]
54
+ def pick *args
55
+ Proxy::Pick.new(*args)
56
+ end
57
+ alias lazy_pick pick
58
+
59
+ # Lazy pick an entity or raise an error.
60
+ #
61
+ # @note The class method version of `pick!` returns a proxy, so the error
62
+ # won't get raised until it gets unproxied in an instance.
63
+ #
64
+ def pick! *args
65
+ Proxy::PickEx.new(*args)
66
+ end
67
+ alias lazy_pick! pick!
68
+ end
69
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gamefic
4
+ module Scriptable
5
+ module Syntaxes
6
+ # Create an alternate Syntax for a response.
7
+ # The command and its translation can be parameterized.
8
+ #
9
+ # @example Create a synonym for an `inventory` response.
10
+ # interpret "catalogue", "inventory"
11
+ # # The command "catalogue" will be translated to "inventory"
12
+ #
13
+ # @example Create a parameterized synonym for a `look` response.
14
+ # interpret "scrutinize :entity", "look :entity"
15
+ # # The command "scrutinize chair" will be translated to "look chair"
16
+ #
17
+ # @param command [String] The format of the original command
18
+ # @param translation [String] The format of the translated command
19
+ # @return [Syntax] the Syntax object
20
+ def interpret command, translation
21
+ syntaxes.push Syntax.new(command, translation)
22
+ end
23
+
24
+ def syntaxes
25
+ @syntaxes ||= []
26
+ end
27
+ end
28
+ end
29
+ end
@@ -23,207 +23,22 @@ 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 :Proxies, 'gamefic/scriptable/proxies'
31
- autoload :Scenes, 'gamefic/scriptable/scenes'
32
- autoload :PlotProxies, 'gamefic/scriptable/plot_proxies'
33
-
26
+ require 'gamefic/scriptable/hooks'
27
+ require 'gamefic/scriptable/queries'
28
+ require 'gamefic/scriptable/syntaxes'
29
+ require 'gamefic/scriptable/responses'
30
+ require 'gamefic/scriptable/scenes'
31
+ require 'gamefic/scriptable/seeds'
32
+
33
+ include Hooks
34
34
  include Queries
35
- # @!parse
36
- # include Scriptable::Actions
37
- # include Scriptable::Events
38
- # include Scriptable::Scenes
39
-
40
- # @return [Array<Block>]
41
- def blocks
42
- @blocks ||= []
43
- end
44
- alias scripts blocks
45
-
46
- # Add a block of code to be executed during initialization.
47
- #
48
- # These blocks are primarily used to define actions, scenes, and hooks in
49
- # the narrative's rulebook. Entities and game data should be initialized
50
- # with `seed`.
51
- #
52
- # @example
53
- # class MyPlot < Gamefic::Plot
54
- # script do
55
- # introduction do |actor|
56
- # actor.tell 'Hello, world!'
57
- # end
58
- #
59
- # respond :wait do |actor|
60
- # actor.tell 'Time passes.'
61
- # end
62
- # end
63
- # end
64
- #
65
- def script &block
66
- blocks.push Block.new(:script, block)
67
- end
68
-
69
- # Add a block of code to generate content after initialization.
70
- #
71
- # Seeds run after the initial scripts have been executed. Their primary
72
- # use is to add entities and other data components, especially randomized
73
- # or procedurally generated content that can vary between instances.
74
- #
75
- # @note Seeds do not get executed when a narrative is restored from a
76
- # snapshot.
77
- #
78
- # @example
79
- # class MyPlot < Gamefic::Plot
80
- # seed do
81
- # @thing = make Gamefic::Entity, name: 'a thing'
82
- # end
83
- # end
84
- #
85
- def seed &block
86
- blocks.push Block.new(:seed, block)
87
- end
88
-
89
- # @return [Array<Block>]
90
- def included_blocks
91
- included_modules.that_are(Scriptable)
92
- .uniq
93
- .reverse
94
- .flat_map(&:blocks)
95
- .concat(blocks)
96
- end
97
-
98
- # Seed an entity.
99
- #
100
- # @example
101
- # make_seed Gamefic::Entity, name: 'thing'
102
- #
103
- # @param klass [Class<Gamefic::Entity>]
104
- # @return [Proxy]
105
- def make_seed klass, **opts
106
- seed { make(klass, **opts) }
107
- Proxy::Pick.new(klass, opts[:name], raise: true)
108
- end
109
- alias make make_seed
110
-
111
- # Seed an entity with an attribute method.
112
- #
113
- # @example
114
- # class Plot < Gamefic::Plot
115
- # attr_seed :thing, Gamefic::Entity, name: 'thing'
116
- # end
117
- #
118
- # plot = Plot.new
119
- # plot.thing #=> #<Gamefic::Entity a thing>
120
- #
121
- # @param name [Symbol] The attribute name
122
- # @param klass [Class<Gamefic::Entity>]
123
- # @return [Proxy]
124
- def attr_seed name, klass, **opts
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)
143
- end
144
- end
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
-
193
- if RUBY_ENGINE == 'opal'
194
- # :nocov:
195
- def method_missing method, *args, &block
196
- return super unless respond_to_missing?(method)
197
-
198
- script { send(method, *args, &block) }
199
- end
200
- # :nocov:
201
- else
202
- def method_missing method, *args, **kwargs, &block
203
- return super unless respond_to_missing?(method)
204
-
205
- script { send(method, *args, **kwargs, &block) }
206
- end
207
- end
208
-
209
- def respond_to_missing?(method, _with_private = false)
210
- [Scriptable::Actions, Scriptable::Events, Scriptable::Scenes].flat_map(&:public_instance_methods)
211
- .include?(method)
212
- end
35
+ include Responses
36
+ include Scenes
37
+ include Seeds
38
+ include Syntaxes
213
39
 
214
- # Create an anonymous module that includes the features of a Scriptable
215
- # module but does not include its scripts.
216
- #
217
- # This can be useful when you need access to the Scriptable's constants and
218
- # instance methods, but you don't want to duplicate its rules.
219
- #
220
- # @deprecated Removing script blocks is no longer necessary. This method
221
- # will simply return self until it's removed.
222
- #
223
- # @return [Module<self>]
224
- def no_scripts
225
- Logging.logger.warn "#{caller.first ? "#{caller.first}: " : ''}Calling `no_scripts` on Scriptable modules is no longer necessary."
226
- self
40
+ def included_scripts
41
+ ancestors.that_are(Scriptable).uniq
227
42
  end
228
43
  end
229
44
  end
@@ -1,32 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'set'
4
+
3
5
  module Gamefic
4
- module Scriptable
5
- # Scriptable methods related to managing entities.
6
- #
7
- # @note The public versions of the entity and player arrays are frozen.
8
- # Authors need access to them but shouldn't modify them directly. Use
9
- # #make and #destroy instead.
6
+ module Scripting
7
+ # Methods related to managing entities.
10
8
  #
11
9
  module Entities
10
+ # extend Scriptable
12
11
  include Proxies
13
12
 
14
- def entity_vault
15
- @entity_vault ||= Vault.new
16
- end
17
-
18
- def player_vault
19
- @player_vault ||= Vault.new
20
- end
21
-
22
13
  # @return [Array<Gamefic::Entity>]
23
14
  def entities
24
- entity_vault.array
15
+ entity_set.to_a
25
16
  end
26
17
 
27
18
  # @return [Array<Gamefic::Actor, Gamefic::Active>]
28
19
  def players
29
- player_vault.array
20
+ player_set.to_a
30
21
  end
31
22
 
32
23
  # Create an entity.
@@ -36,17 +27,17 @@ module Gamefic
36
27
  # seed { make Gamefic::Entity, name: 'thing' }
37
28
  # end
38
29
  #
39
- # @param [Class<Gamefic::Entity>]
40
- # @param args [Hash]
30
+ # @param klass [Class<Gamefic::Entity>]
41
31
  # @return [Gamefic::Entity]
42
32
  def make klass, **opts
43
- entity_vault.add klass.new(**unproxy(opts))
33
+ klass.new(**unproxy(opts)).tap { |entity| entity_set.add entity }
44
34
  end
45
35
 
46
- def destroy entity
36
+ def destroy(entity)
47
37
  entity.children.each { |child| destroy child }
48
38
  entity.parent = nil
49
- entity_vault.delete entity
39
+ entity_set.delete entity
40
+ entity
50
41
  end
51
42
 
52
43
  def find *args
@@ -66,7 +57,6 @@ module Gamefic
66
57
  # if an entity could not be found or there is more than one possible
67
58
  # match.
68
59
  #
69
- # @param description [Array]
70
60
  # @return [Gamefic::Entity, nil]
71
61
  def pick *args
72
62
  matches = find(*args)
@@ -89,6 +79,16 @@ module Gamefic
89
79
 
90
80
  matches.first
91
81
  end
82
+
83
+ private
84
+
85
+ def entity_set
86
+ @entity_set ||= Set.new
87
+ end
88
+
89
+ def player_set
90
+ @player_set ||= Set.new
91
+ end
92
92
  end
93
93
  end
94
94
  end