inform-runtime 1.0.4
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 +7 -0
- data/LICENSE +623 -0
- data/README.md +185 -0
- data/Rakefile +65 -0
- data/config/database.yml +37 -0
- data/exe/inform.rb +6 -0
- data/game/config.yml +5 -0
- data/game/example.inf +76 -0
- data/game/example.rb +90 -0
- data/game/forms/example_form.rb +2 -0
- data/game/grammar/game_grammar.inf.rb +11 -0
- data/game/languages/english.rb +2 -0
- data/game/models/example_model.rb +2 -0
- data/game/modules/example_module.rb +9 -0
- data/game/rules/example_state.rb +2 -0
- data/game/scripts/example_script.rb +2 -0
- data/game/topics/example_topic.rb +2 -0
- data/game/verbs/game_verbs.rb +15 -0
- data/game/verbs/metaverbs.rb +2028 -0
- data/lib/runtime/articles.rb +138 -0
- data/lib/runtime/builtins.rb +359 -0
- data/lib/runtime/color.rb +145 -0
- data/lib/runtime/command.rb +470 -0
- data/lib/runtime/config.rb +48 -0
- data/lib/runtime/context.rb +78 -0
- data/lib/runtime/daemon.rb +266 -0
- data/lib/runtime/database.rb +500 -0
- data/lib/runtime/events.rb +771 -0
- data/lib/runtime/experimental/handler_dsl.rb +175 -0
- data/lib/runtime/game.rb +74 -0
- data/lib/runtime/game_loader.rb +132 -0
- data/lib/runtime/grammar_parser.rb +553 -0
- data/lib/runtime/helpers.rb +177 -0
- data/lib/runtime/history.rb +45 -0
- data/lib/runtime/inflector.rb +195 -0
- data/lib/runtime/io.rb +174 -0
- data/lib/runtime/kernel.rb +450 -0
- data/lib/runtime/library.rb +59 -0
- data/lib/runtime/library_loader.rb +135 -0
- data/lib/runtime/link.rb +158 -0
- data/lib/runtime/logging.rb +197 -0
- data/lib/runtime/mixins.rb +570 -0
- data/lib/runtime/module.rb +202 -0
- data/lib/runtime/object.rb +761 -0
- data/lib/runtime/options.rb +104 -0
- data/lib/runtime/persistence.rb +292 -0
- data/lib/runtime/plurals.rb +60 -0
- data/lib/runtime/prototype.rb +307 -0
- data/lib/runtime/publication.rb +92 -0
- data/lib/runtime/runtime.rb +321 -0
- data/lib/runtime/session.rb +202 -0
- data/lib/runtime/stdlib.rb +604 -0
- data/lib/runtime/subscription.rb +47 -0
- data/lib/runtime/tag.rb +287 -0
- data/lib/runtime/tree.rb +204 -0
- data/lib/runtime/version.rb +24 -0
- data/lib/runtime/world_tree.rb +69 -0
- data/lib/runtime.rb +35 -0
- metadata +199 -0
|
@@ -0,0 +1,604 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
# frozen_string_literal: false
|
|
3
|
+
|
|
4
|
+
# Copyright Nels Nelson 2008-2023 but freely usable (see license)
|
|
5
|
+
#
|
|
6
|
+
# This file is part of the Inform Runtime.
|
|
7
|
+
#
|
|
8
|
+
# The Inform Runtime is free software: you can redistribute it and/or
|
|
9
|
+
# modify it under the terms of the GNU General Public License as published
|
|
10
|
+
# by the Free Software Foundation, either version 3 of the License, or
|
|
11
|
+
# (at your option) any later version.
|
|
12
|
+
#
|
|
13
|
+
# The Inform Runtime is distributed in the hope that it will be useful,
|
|
14
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
15
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
16
|
+
# GNU General Public License for more details.
|
|
17
|
+
#
|
|
18
|
+
# You should have received a copy of the GNU General Public License
|
|
19
|
+
# along with the Inform Runtime. If not, see <http://www.gnu.org/licenses/>.
|
|
20
|
+
|
|
21
|
+
# The Inform module
|
|
22
|
+
module Inform
|
|
23
|
+
# These are built-ins primarily found used in the InformLibrary.
|
|
24
|
+
module StdLib
|
|
25
|
+
def debug(n = nil)
|
|
26
|
+
if n.nil?
|
|
27
|
+
toggle_constant :DEBUG
|
|
28
|
+
else
|
|
29
|
+
@parser_trace = n
|
|
30
|
+
reset_constant :DEBUG, true
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def executable
|
|
35
|
+
Runtime.main_gem_spec_executable
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def inversion
|
|
39
|
+
print Inform::VERSION
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def standard_interpreter
|
|
43
|
+
0
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def version_number
|
|
47
|
+
0
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def flush_automatically
|
|
51
|
+
toggle_constant :FLUSH_IO_INSTANTLY
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def export(o, type = :xml)
|
|
55
|
+
o.send('export_' + type) if !o.nil? && o.respond_to?(('export_' + type).to_sym)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
DotPattern = %r{\.}.freeze
|
|
59
|
+
UnderscoreString = '_'.freeze
|
|
60
|
+
|
|
61
|
+
def import_object(file)
|
|
62
|
+
type = File.extname(file)
|
|
63
|
+
return if type.nil?
|
|
64
|
+
send('import_object_' + type.gsub(DotPattern, UnderscoreString), file)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
DefaultXMLAssociations = %i[tagged modularized].freeze
|
|
68
|
+
|
|
69
|
+
def import_object_xml(file, associations = DefaultXMLAssociations)
|
|
70
|
+
return unless defined? Nokogiri
|
|
71
|
+
return if file.nil?
|
|
72
|
+
Inform::Object.from_xml(File.open(file), associations: associations)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def import_object_yaml(file)
|
|
76
|
+
return unless defined? YAML
|
|
77
|
+
return if file.nil?
|
|
78
|
+
YAML.safe_load(File.read(file))
|
|
79
|
+
end
|
|
80
|
+
alias import_object_yml import_object_yaml
|
|
81
|
+
|
|
82
|
+
def import_object_json(file)
|
|
83
|
+
return unless defined? JSON
|
|
84
|
+
return if file.nil?
|
|
85
|
+
JSON.parse(File.read(file))
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def consume_remaining_text
|
|
89
|
+
s = preceding_text
|
|
90
|
+
@wn = num_words
|
|
91
|
+
remaining_text = @input[s.length..]
|
|
92
|
+
remaining_text ? remaining_text.strip : ''
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def preceding_text
|
|
96
|
+
@words[0..(@wn - 1)].join(' ')
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def pcount
|
|
100
|
+
@pattern ? @pattern.length - 1 : 0
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def location
|
|
104
|
+
@player.location
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def location=(o)
|
|
108
|
+
log.warn "Trying to set the location directly"
|
|
109
|
+
log.warn caller[1] and abort
|
|
110
|
+
@player.location = o
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def area
|
|
114
|
+
location&.area
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Stubs
|
|
118
|
+
#
|
|
119
|
+
# Designers may override these.
|
|
120
|
+
|
|
121
|
+
def ADirection(*)
|
|
122
|
+
compass.include?(noun)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def StatusLineHeight; end
|
|
126
|
+
|
|
127
|
+
def ScoreSub
|
|
128
|
+
false
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def FullScoreSub
|
|
132
|
+
false
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def DisplayStatus
|
|
136
|
+
false
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def Message(*args)
|
|
140
|
+
println(args[:fatalerror] || args.values.first)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def BeginActivity(*)
|
|
144
|
+
false
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def EndActivity(*)
|
|
148
|
+
false
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def ForActivity(*)
|
|
152
|
+
false
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def silently(*)
|
|
156
|
+
k = @keep_silent; @keep_silent = true
|
|
157
|
+
yield
|
|
158
|
+
ensure
|
|
159
|
+
@keep_silent = k
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# rubocop: disable Metrics/AbcSize
|
|
163
|
+
# rubocop: disable Metrics/MethodLength
|
|
164
|
+
def invocation_context(action, *args)
|
|
165
|
+
ctx = Inform.context
|
|
166
|
+
ctx.results = [
|
|
167
|
+
ctx.action = ctx.action_to_be = action,
|
|
168
|
+
ctx.parameters = args.length,
|
|
169
|
+
ctx.noun = ctx.inp1 = args.shift,
|
|
170
|
+
ctx.second = ctx.inp2 = args.shift
|
|
171
|
+
]
|
|
172
|
+
ctx.pattern = [
|
|
173
|
+
ctx.results[0].to_s.downcase,
|
|
174
|
+
ctx.results[2],
|
|
175
|
+
ctx.results[3]
|
|
176
|
+
]
|
|
177
|
+
ctx.actor = player
|
|
178
|
+
ctx.special_number1 = ctx.special_number2 = nil
|
|
179
|
+
ctx.special_number1 = ctx.results[2] if ctx.results[2].number?
|
|
180
|
+
ctx.special_number2 = ctx.results[3] if ctx.results[3].number?
|
|
181
|
+
ctx.special_word = args.shift
|
|
182
|
+
ctx.consult_words = args.shift
|
|
183
|
+
ctx.consult_words = ctx.special_word = ctx.results[2] if ctx.results[2].is_a?(String)
|
|
184
|
+
ctx.consult_words = ctx.special_word = ctx.results[3] if ctx.results[3].is_a?(String)
|
|
185
|
+
ctx.match_from = ctx.wn = ctx.verb_wordnum = 0
|
|
186
|
+
ctx.words = []
|
|
187
|
+
ctx
|
|
188
|
+
end
|
|
189
|
+
# rubocop: enable Metrics/AbcSize
|
|
190
|
+
# rubocop: enable Metrics/MethodLength
|
|
191
|
+
|
|
192
|
+
# The invoke method executes without prompting afterward
|
|
193
|
+
# but appends the output to the contextual event's buffer
|
|
194
|
+
# which will be displayed upon completion of the contextual
|
|
195
|
+
# event.
|
|
196
|
+
#
|
|
197
|
+
# TODO: Test: Does this mean that invoke must always be executed
|
|
198
|
+
# within an event context to have its output displayed?
|
|
199
|
+
# !rubocop: disable Metrics/AbcSize
|
|
200
|
+
def invoke(action, *args)
|
|
201
|
+
raise "The action parameter may not be nil" if action.nil?
|
|
202
|
+
|
|
203
|
+
# log.debug 'Invocation: ' + name(@player) + ' <' + action + (args.empty? ? '' : ' ') + args.to_s + '>'
|
|
204
|
+
ctx = invocation_context(action, *args)
|
|
205
|
+
immediately(ctx) do
|
|
206
|
+
if BeforeRoutines(action) == false
|
|
207
|
+
send(VerbRoutine(action))
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
# !rubocop: enable Metrics/AbcSize
|
|
212
|
+
|
|
213
|
+
# Under invoke execute a routine but tries to prevent
|
|
214
|
+
# any output at all and does not provide other objects
|
|
215
|
+
# the opportunity to react to the action, like invoke
|
|
216
|
+
# does, above.
|
|
217
|
+
#
|
|
218
|
+
# This should be used to delegate an action outcome to
|
|
219
|
+
# another Verb action subroutine. It should appear as
|
|
220
|
+
# if the invoked action is part of the original action.
|
|
221
|
+
# !rubocop: disable Metrics/AbcSize
|
|
222
|
+
def _invoke(action, *args)
|
|
223
|
+
raise "The action parameter may not be nil" if action.nil?
|
|
224
|
+
|
|
225
|
+
# log.debug 'Invocation: ' + name(@player) + ' <<' + action + (args.empty? ? '' : ' ') + args.to_s + '>>'
|
|
226
|
+
ctx = invocation_context(action, *args)
|
|
227
|
+
elementally(ctx) do
|
|
228
|
+
silently do
|
|
229
|
+
send(VerbRoutine(action))
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
# !rubocop: enable Metrics/AbcSize
|
|
234
|
+
alias silently_invoke _invoke
|
|
235
|
+
|
|
236
|
+
# rubocop: disable Metrics/AbcSize
|
|
237
|
+
# rubocop: disable Metrics/CyclomaticComplexity
|
|
238
|
+
# rubocop: disable Metrics/PerceivedComplexity
|
|
239
|
+
def indirect(action, *args)
|
|
240
|
+
return false if action.nil?
|
|
241
|
+
# raise "The action parameter may not be nil" if action.nil?
|
|
242
|
+
action = action.to_sym
|
|
243
|
+
subroutine = VerbRoutine(action, fail_on_error: false)
|
|
244
|
+
contextualize # TODO: Test removal
|
|
245
|
+
# Direct objects might need library built-ins
|
|
246
|
+
# noun.inflib = actor.inflib if (!noun.nil?) && noun.object? && noun.inflib.nil?
|
|
247
|
+
# second.inflib = actor.inflib if (!second.nil?) && second.object? && second.inflib.nil?
|
|
248
|
+
@noun.inflib = self if !@noun.nil? && @noun.object? && @noun.inflib.nil?
|
|
249
|
+
@second.inflib = self if !@second.nil? && @second.object? && @second.inflib.nil?
|
|
250
|
+
return send(subroutine, *args) if respond_to? subroutine
|
|
251
|
+
return send(action, *args) if respond_to? action
|
|
252
|
+
false
|
|
253
|
+
end
|
|
254
|
+
# rubocop: enable Metrics/AbcSize
|
|
255
|
+
# rubocop: enable Metrics/CyclomaticComplexity
|
|
256
|
+
# rubocop: enable Metrics/PerceivedComplexity
|
|
257
|
+
|
|
258
|
+
ParseRoutineString = '_parse_routine'.freeze
|
|
259
|
+
SubString = 'Sub'.freeze
|
|
260
|
+
DefaultVerbRoutineOptions = { fail_on_error: true }.freeze
|
|
261
|
+
|
|
262
|
+
# rubocop: disable Metrics/AbcSize
|
|
263
|
+
# rubocop: disable Metrics/MethodLength
|
|
264
|
+
# rubocop: disable Metrics/CyclomaticComplexity
|
|
265
|
+
# rubocop: disable Metrics/PerceivedComplexity
|
|
266
|
+
def VerbRoutine(action, opts = {})
|
|
267
|
+
raise 'The action parameter may not be nil' if action.nil?
|
|
268
|
+
opts = DefaultVerbRoutineOptions.merge(opts)
|
|
269
|
+
if action.respond_to?(:underscore) && respond_to?(
|
|
270
|
+
sub_routine = (action.underscore(downcase: false) + ParseRoutineString))
|
|
271
|
+
sub_routine.to_sym
|
|
272
|
+
elsif respond_to?(sub_routine = (action + SubString))
|
|
273
|
+
sub_routine.to_sym
|
|
274
|
+
elsif respond_to?(sub_routine = (action.camelize + SubString))
|
|
275
|
+
sub_routine.to_sym
|
|
276
|
+
elsif action.respond_to?(:snake_case) && respond_to?(sub_routine = action.snake_case)
|
|
277
|
+
sub_routine.to_sym
|
|
278
|
+
elsif respond_to?(sub_routine = action)
|
|
279
|
+
sub_routine.to_sym
|
|
280
|
+
elsif opts[:fail_on_error]
|
|
281
|
+
raise NoVerbRoutine, 'Please define a verb subroutine for the action ' + action
|
|
282
|
+
else
|
|
283
|
+
log.warn "Please define a verb subroutine for the action #{action}"
|
|
284
|
+
false
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
# rubocop: enable Metrics/AbcSize
|
|
288
|
+
# rubocop: enable Metrics/MethodLength
|
|
289
|
+
# rubocop: enable Metrics/CyclomaticComplexity
|
|
290
|
+
# rubocop: enable Metrics/PerceivedComplexity
|
|
291
|
+
|
|
292
|
+
PrimaryHandlers = %i[before after life].freeze
|
|
293
|
+
SynchronousHandlers = PrimaryHandlers + %i[react_before].freeze
|
|
294
|
+
ReactionHandlers = SynchronousHandlers + %i[react_after].freeze
|
|
295
|
+
|
|
296
|
+
# Hack for Ruby so we can do action-specific reaction handlers.
|
|
297
|
+
# With Inform, one can just implement before [; Whatever: "hi" ]
|
|
298
|
+
# on an Object and include some action-specifying cases as if the
|
|
299
|
+
# before method were a functional switch or case statement.
|
|
300
|
+
# rubocop: disable Metrics/AbcSize
|
|
301
|
+
# rubocop: disable Metrics/MethodLength
|
|
302
|
+
# rubocop: disable Metrics/CyclomaticComplexity
|
|
303
|
+
# rubocop: disable Metrics/PerceivedComplexity
|
|
304
|
+
# rubocop: disable Style/ReturnNilInPredicateMethodDefinition
|
|
305
|
+
def reacts_to?(obj, prop, *args)
|
|
306
|
+
raise "Nothing is not an object" if obj.nil?
|
|
307
|
+
return false unless ReactionHandlers.include?(prop)
|
|
308
|
+
# TODO: Only apply Behavior modules if object is not being controlled
|
|
309
|
+
# by a player.
|
|
310
|
+
obj.apply_modules if obj.respond_to?(:apply_modules)
|
|
311
|
+
si = obj.inflib
|
|
312
|
+
obj.inflib = self
|
|
313
|
+
|
|
314
|
+
# TODO: I have prototyped a possible Inform-esque
|
|
315
|
+
# switch-style dsl for reaction methods based on
|
|
316
|
+
# action name. Here, the prop would be sent the
|
|
317
|
+
# object normally, but the object reaction method
|
|
318
|
+
# would contain a series of method invocations with
|
|
319
|
+
# a block parameter. If any of the method missing
|
|
320
|
+
# invocations method name parameter resembled the
|
|
321
|
+
# action name, then the given block would get
|
|
322
|
+
# executed on the subject, thereby simulating the
|
|
323
|
+
# Inform-style functional switch idiom. Like this:
|
|
324
|
+
#
|
|
325
|
+
# def method_missing(method, *args, &block)
|
|
326
|
+
# return yield if method == action
|
|
327
|
+
# super
|
|
328
|
+
# end
|
|
329
|
+
#
|
|
330
|
+
# def before
|
|
331
|
+
# Touch {
|
|
332
|
+
# publish The(self) + " shudders."
|
|
333
|
+
# println "Your skin crawls."
|
|
334
|
+
# true
|
|
335
|
+
# }
|
|
336
|
+
# Show {
|
|
337
|
+
# publish The(self) + " is uninterested in " + the(noun) + "."
|
|
338
|
+
# "Nothing special."
|
|
339
|
+
# }
|
|
340
|
+
# end
|
|
341
|
+
#
|
|
342
|
+
|
|
343
|
+
# TODO: Zarf has recommended implementing a check for code persisted as
|
|
344
|
+
# a property of an observant object. For instance:
|
|
345
|
+
# object:
|
|
346
|
+
# properties:
|
|
347
|
+
# ---
|
|
348
|
+
# before:
|
|
349
|
+
# ---
|
|
350
|
+
# Touch: publish The(self) + "shudders."; println "Your skin crawls."; true
|
|
351
|
+
|
|
352
|
+
# To preserve support for a game designer to implement catch-all
|
|
353
|
+
# reactions by observant objects, just have the receiver object
|
|
354
|
+
# read the action variable in order to determine course. For example:
|
|
355
|
+
#
|
|
356
|
+
# def before
|
|
357
|
+
# case action
|
|
358
|
+
# when :Poke then println "Keep your hands to yourself!"
|
|
359
|
+
#
|
|
360
|
+
if PrimaryHandlers.include?(prop) && obj.respond_to?(prop)
|
|
361
|
+
# contextualize
|
|
362
|
+
# obj.contextualize
|
|
363
|
+
# TODO: Test
|
|
364
|
+
context_action = Thread.current[:event]&.action
|
|
365
|
+
log.debug "Inform#reacts_to?(obj=#{obj}, prop=#{prop}) action: #{context_action}"
|
|
366
|
+
if (result = obj.send(prop, *args))
|
|
367
|
+
return result
|
|
368
|
+
end
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
# TODO: TESTME Confirm that using the parser's @action variable
|
|
372
|
+
# is sufficiently reliable for composing reaction handler methods.
|
|
373
|
+
# As of 2021-07-21, there appears to be some evidence that using
|
|
374
|
+
# the #invoke method to generate reaction callbacks here is not
|
|
375
|
+
# usable with the @action approach. Apparently, @action is the least
|
|
376
|
+
# reliable because it can include a leftover value from a previous
|
|
377
|
+
# interaction, since the #invoke method does not set the value on
|
|
378
|
+
# the library (and nor should it, since #invoke is for ad-hoc events).
|
|
379
|
+
# The longer term and more correct fix here is likely to consolidate
|
|
380
|
+
# all references to @action as simply action which would pull from an
|
|
381
|
+
# event context. For now I think that I'll simply include @action at
|
|
382
|
+
# the end of the chain, in order to ensure that some action is
|
|
383
|
+
# available for reaction handler method reference construction.
|
|
384
|
+
# act = obj.action || action || @action
|
|
385
|
+
# act = @action || obj.inflib.action || action
|
|
386
|
+
# act = obj.inflib.action || action
|
|
387
|
+
# act = obj.inflib.action || action || @action
|
|
388
|
+
act = Thread.current[:event]&.action || action || @action
|
|
389
|
+
# TODO: Remove debug logging here.
|
|
390
|
+
# log.debug "Inform#reacts_to? #{prop}"
|
|
391
|
+
# log.debug " #{self}.@action: #{@action}"
|
|
392
|
+
# log.debug " #{obj.inflib}.action: #{obj.inflib.action}"
|
|
393
|
+
# log.debug " #{Thread.current[:event]}.current.thread.event.action: #{action}"
|
|
394
|
+
# log.debug " act: #{act}"
|
|
395
|
+
if act.nil?
|
|
396
|
+
log.warn "Failed to reference action invoking #reacts_to?"
|
|
397
|
+
return nil
|
|
398
|
+
end
|
|
399
|
+
reaction = "#{prop}_#{act}"
|
|
400
|
+
# log.debug "obj: #{obj.identity}, reaction: #{reaction}"
|
|
401
|
+
# log.debug "obj.respond_to?(reaction.downcase!):"
|
|
402
|
+
# log.debug " #{obj.respond_to?(reaction.downcase!)} (#{obj.respond_to?(reaction)})"
|
|
403
|
+
if obj.respond_to?(reaction)
|
|
404
|
+
handler = obj.method(reaction)
|
|
405
|
+
elsif obj.respond_to?(reaction.downcase!)
|
|
406
|
+
handler = obj.method(reaction)
|
|
407
|
+
else
|
|
408
|
+
return nil # false
|
|
409
|
+
end
|
|
410
|
+
log.debug "Found reaction handler for #{obj}.#{reaction}: #{handler}"
|
|
411
|
+
if SynchronousHandlers.include?(prop)
|
|
412
|
+
# Go ahead and print any string results from designer-implemented
|
|
413
|
+
# handlers, and return true if there are any.
|
|
414
|
+
# contextualize
|
|
415
|
+
result = obj.instance_exec(*args, &handler)
|
|
416
|
+
return result unless result.is_a?(String)
|
|
417
|
+
println result
|
|
418
|
+
return true
|
|
419
|
+
end
|
|
420
|
+
# ctx = invocation_context(@action, @noun, @second, @special_word, @consult_words)
|
|
421
|
+
ctx = invocation_context(act, noun, second, special_word, consult_words)
|
|
422
|
+
obj.register_callback(ctx, *args, &handler)
|
|
423
|
+
# Because of the event-oriented nature of actions in a multiplayer
|
|
424
|
+
# system, other objects may not interfere with original actions
|
|
425
|
+
# once they have taken place. So, false must always be returned.
|
|
426
|
+
# However, game designers must still have the ability to direct an
|
|
427
|
+
# object, particular an animate one, to respond to an action if
|
|
428
|
+
# appropriate.
|
|
429
|
+
nil # false
|
|
430
|
+
rescue StandardError => e
|
|
431
|
+
log.error "Error getting reaction from #{obj}", e
|
|
432
|
+
e.backtrace.each { |t| log.error t }
|
|
433
|
+
ensure
|
|
434
|
+
obj.inflib = si
|
|
435
|
+
end
|
|
436
|
+
# rubocop: enable Metrics/AbcSize
|
|
437
|
+
# rubocop: enable Metrics/MethodLength
|
|
438
|
+
# rubocop: enable Metrics/CyclomaticComplexity
|
|
439
|
+
# rubocop: enable Metrics/PerceivedComplexity
|
|
440
|
+
# rubocop: enable Style/ReturnNilInPredicateMethodDefinition
|
|
441
|
+
end
|
|
442
|
+
# module StdLib
|
|
443
|
+
|
|
444
|
+
# ------------------------------------------------------------------------------
|
|
445
|
+
# Final task: provide trivial routines if the user hasn't already:
|
|
446
|
+
# ------------------------------------------------------------------------------
|
|
447
|
+
|
|
448
|
+
# The StdLib module
|
|
449
|
+
# TODO: Decide if these should go somewhere else. Inform 6 has them
|
|
450
|
+
# in the standard library grammar.inf.
|
|
451
|
+
module StdLib
|
|
452
|
+
def AfterLife(*)
|
|
453
|
+
false
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
def AfterPrompt(*)
|
|
457
|
+
false
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
def Amusing(*)
|
|
461
|
+
false
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
def BeforeParsing(*)
|
|
465
|
+
false
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
def ChooseObjects(_object, _code)
|
|
469
|
+
2
|
|
470
|
+
end
|
|
471
|
+
|
|
472
|
+
def DarkToDark(*)
|
|
473
|
+
false
|
|
474
|
+
end
|
|
475
|
+
|
|
476
|
+
def DeathMessage(*)
|
|
477
|
+
false
|
|
478
|
+
end
|
|
479
|
+
|
|
480
|
+
def Epilogue(*)
|
|
481
|
+
false
|
|
482
|
+
end
|
|
483
|
+
|
|
484
|
+
def GamePostRoutine(*)
|
|
485
|
+
false
|
|
486
|
+
end
|
|
487
|
+
|
|
488
|
+
def GamePreRoutine(*)
|
|
489
|
+
false
|
|
490
|
+
end
|
|
491
|
+
|
|
492
|
+
def InScope(*)
|
|
493
|
+
true
|
|
494
|
+
end
|
|
495
|
+
|
|
496
|
+
def LookRoutine(*)
|
|
497
|
+
false
|
|
498
|
+
end
|
|
499
|
+
|
|
500
|
+
def NewRoom(*)
|
|
501
|
+
false
|
|
502
|
+
end
|
|
503
|
+
|
|
504
|
+
def ObjectDoesNotFit(*)
|
|
505
|
+
2
|
|
506
|
+
end
|
|
507
|
+
|
|
508
|
+
def ParseNumber(*)
|
|
509
|
+
2
|
|
510
|
+
end
|
|
511
|
+
|
|
512
|
+
def ParserError(*)
|
|
513
|
+
true
|
|
514
|
+
end
|
|
515
|
+
|
|
516
|
+
def PrintTaskName(*)
|
|
517
|
+
true
|
|
518
|
+
end
|
|
519
|
+
|
|
520
|
+
def PrintVerb(*)
|
|
521
|
+
true
|
|
522
|
+
end
|
|
523
|
+
|
|
524
|
+
def TimePasses(*)
|
|
525
|
+
false
|
|
526
|
+
end
|
|
527
|
+
|
|
528
|
+
def UnknownVerb(verb, *)
|
|
529
|
+
verb
|
|
530
|
+
end
|
|
531
|
+
|
|
532
|
+
unless defined? ParseNoun
|
|
533
|
+
# ----------------------------------------------------------------------------
|
|
534
|
+
# To do the job of parsing the name property (if parse_name hasn’t done it
|
|
535
|
+
# already). This takes one argument, the object in question, and returns a
|
|
536
|
+
# value as if it were a parse_name routine.
|
|
537
|
+
#
|
|
538
|
+
# ParseNoun returns the number of words matched, or 0 if there is no match,
|
|
539
|
+
# or -1 to decline to make a decision and give the job back to the parser.
|
|
540
|
+
# Note that if -1 is returned, the word number variable wn must be left set
|
|
541
|
+
# to the first word the parser should look at -- probably the same value it
|
|
542
|
+
# had when ParseNoun was called, but not necessarily.
|
|
543
|
+
# ----------------------------------------------------------------------------
|
|
544
|
+
|
|
545
|
+
# rubocop: disable Lint/SelfAssignment
|
|
546
|
+
# rubocop: disable Lint/UselessAssignment
|
|
547
|
+
def ParseNoun(obj); obj = obj; return -1; end
|
|
548
|
+
# rubocop: enable Lint/SelfAssignment
|
|
549
|
+
# rubocop: enable Lint/UselessAssignment
|
|
550
|
+
end
|
|
551
|
+
# ParseNoun
|
|
552
|
+
|
|
553
|
+
# ==============================================================================
|
|
554
|
+
end
|
|
555
|
+
# module StdLib
|
|
556
|
+
|
|
557
|
+
# The StdLib module to re-define the InScope method
|
|
558
|
+
# rubocop: disable Metrics/AbcSize
|
|
559
|
+
# rubocop: disable Lint/DuplicateMethods
|
|
560
|
+
module StdLib
|
|
561
|
+
# ----------------------------------------------------------------------------
|
|
562
|
+
# (From the Inform Designer's Manual)
|
|
563
|
+
# §32 Scope and what you can see
|
|
564
|
+
# ----------------------------------------------------------------------------
|
|
565
|
+
# "In scope" roughly means "the compass directions, what you're carrying
|
|
566
|
+
# and what you can see". It exactly means this:
|
|
567
|
+
#
|
|
568
|
+
# 1. the compass directions;
|
|
569
|
+
# 2. the player's immediate possessions;
|
|
570
|
+
# 3. if there is light, then the contents of the player's visibility
|
|
571
|
+
# ceiling (see §21 for definition, but roughly speaking the
|
|
572
|
+
# outermost object containing the player which remains visible,
|
|
573
|
+
# which is usually the player's location);
|
|
574
|
+
# 4. if there is darkness, then the contents of the library's object
|
|
575
|
+
# thedark (by default there are no such contents, but some
|
|
576
|
+
# designers have been known to move objects into thedark: see
|
|
577
|
+
# 'Ruins');
|
|
578
|
+
# 5. if the player is inside a container, then that container;
|
|
579
|
+
# 6. if O is in scope and is see-through (see §21), then the contents of O;
|
|
580
|
+
# 7. if O is in scope, then any object which it "adds to scope".
|
|
581
|
+
# ----------------------------------------------------------------------------
|
|
582
|
+
|
|
583
|
+
def InScope(obj, scope = [])
|
|
584
|
+
# This routine may be overridden at the designer's discretion.
|
|
585
|
+
# Returning false here, and leaving the rest of the code in place
|
|
586
|
+
# as instructions for how to go about assembling a scope.
|
|
587
|
+
# Note to designers: remove this line in your override.
|
|
588
|
+
return false if true # rubocop: disable Lint/LiteralAsCondition
|
|
589
|
+
return false if obj.nil?
|
|
590
|
+
scope |= InformLibrary::Compass.descendants.flatten # 1
|
|
591
|
+
scope |= @player.descendants.flatten # 2
|
|
592
|
+
scope |= @visibility_ceiling.descendants.flatten # 3
|
|
593
|
+
scope |= thedark.descendants.flatten # 4
|
|
594
|
+
scope |= @player.parent if @player.parent.hasany?(:container, :supporter) # 5
|
|
595
|
+
scope |= scope.collect { |o| o.descendants if o.has?(:transparent) }.flatten # 6
|
|
596
|
+
scope |= scope.select { |o| o.&(:add_to_scope) }.flatten # 7
|
|
597
|
+
return scope.include?(obj)
|
|
598
|
+
end
|
|
599
|
+
end
|
|
600
|
+
# rubocop: enable Metrics/AbcSize
|
|
601
|
+
# rubocop: enable Lint/DuplicateMethods
|
|
602
|
+
# module StdLib
|
|
603
|
+
end
|
|
604
|
+
# module Inform
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
# frozen_string_literal: false
|
|
3
|
+
|
|
4
|
+
# Copyright Nels Nelson 2008-2023 but freely usable (see license)
|
|
5
|
+
#
|
|
6
|
+
# This file is part of the Inform Runtime.
|
|
7
|
+
#
|
|
8
|
+
# The Inform Runtime is free software: you can redistribute it and/or
|
|
9
|
+
# modify it under the terms of the GNU General Public License as published
|
|
10
|
+
# by the Free Software Foundation, either version 3 of the License, or
|
|
11
|
+
# (at your option) any later version.
|
|
12
|
+
#
|
|
13
|
+
# The Inform Runtime is distributed in the hope that it will be useful,
|
|
14
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
15
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
16
|
+
# GNU General Public License for more details.
|
|
17
|
+
#
|
|
18
|
+
# You should have received a copy of the GNU General Public License
|
|
19
|
+
# along with the Inform Runtime. If not, see <http://www.gnu.org/licenses/>.
|
|
20
|
+
|
|
21
|
+
# The Inform module
|
|
22
|
+
module Inform
|
|
23
|
+
# The Subscribers module
|
|
24
|
+
module Subscribers
|
|
25
|
+
REGISTRY = Struct.new(:memo).new(defined?(Java) ? java.util.concurrent.ConcurrentHashMap.new : {})
|
|
26
|
+
|
|
27
|
+
def subscribe(obj)
|
|
28
|
+
explicit_subscribers << obj
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def unsubscribe(obj)
|
|
32
|
+
explicit_subscribers.delete(obj)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def unsubscribe_all
|
|
36
|
+
explicit_subscribers.clear
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def subscribers
|
|
40
|
+
explicit_subscribers.dup
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def explicit_subscribers
|
|
44
|
+
Inform::Subscribers::REGISTRY.memo[identity] ||= []
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|