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.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +623 -0
  3. data/README.md +185 -0
  4. data/Rakefile +65 -0
  5. data/config/database.yml +37 -0
  6. data/exe/inform.rb +6 -0
  7. data/game/config.yml +5 -0
  8. data/game/example.inf +76 -0
  9. data/game/example.rb +90 -0
  10. data/game/forms/example_form.rb +2 -0
  11. data/game/grammar/game_grammar.inf.rb +11 -0
  12. data/game/languages/english.rb +2 -0
  13. data/game/models/example_model.rb +2 -0
  14. data/game/modules/example_module.rb +9 -0
  15. data/game/rules/example_state.rb +2 -0
  16. data/game/scripts/example_script.rb +2 -0
  17. data/game/topics/example_topic.rb +2 -0
  18. data/game/verbs/game_verbs.rb +15 -0
  19. data/game/verbs/metaverbs.rb +2028 -0
  20. data/lib/runtime/articles.rb +138 -0
  21. data/lib/runtime/builtins.rb +359 -0
  22. data/lib/runtime/color.rb +145 -0
  23. data/lib/runtime/command.rb +470 -0
  24. data/lib/runtime/config.rb +48 -0
  25. data/lib/runtime/context.rb +78 -0
  26. data/lib/runtime/daemon.rb +266 -0
  27. data/lib/runtime/database.rb +500 -0
  28. data/lib/runtime/events.rb +771 -0
  29. data/lib/runtime/experimental/handler_dsl.rb +175 -0
  30. data/lib/runtime/game.rb +74 -0
  31. data/lib/runtime/game_loader.rb +132 -0
  32. data/lib/runtime/grammar_parser.rb +553 -0
  33. data/lib/runtime/helpers.rb +177 -0
  34. data/lib/runtime/history.rb +45 -0
  35. data/lib/runtime/inflector.rb +195 -0
  36. data/lib/runtime/io.rb +174 -0
  37. data/lib/runtime/kernel.rb +450 -0
  38. data/lib/runtime/library.rb +59 -0
  39. data/lib/runtime/library_loader.rb +135 -0
  40. data/lib/runtime/link.rb +158 -0
  41. data/lib/runtime/logging.rb +197 -0
  42. data/lib/runtime/mixins.rb +570 -0
  43. data/lib/runtime/module.rb +202 -0
  44. data/lib/runtime/object.rb +761 -0
  45. data/lib/runtime/options.rb +104 -0
  46. data/lib/runtime/persistence.rb +292 -0
  47. data/lib/runtime/plurals.rb +60 -0
  48. data/lib/runtime/prototype.rb +307 -0
  49. data/lib/runtime/publication.rb +92 -0
  50. data/lib/runtime/runtime.rb +321 -0
  51. data/lib/runtime/session.rb +202 -0
  52. data/lib/runtime/stdlib.rb +604 -0
  53. data/lib/runtime/subscription.rb +47 -0
  54. data/lib/runtime/tag.rb +287 -0
  55. data/lib/runtime/tree.rb +204 -0
  56. data/lib/runtime/version.rb +24 -0
  57. data/lib/runtime/world_tree.rb +69 -0
  58. data/lib/runtime.rb +35 -0
  59. 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