Module: Inform::StdLib

Defined in:
lib/story_teller/stdlib.rb,
lib/story_teller/stdlib.rb,
lib/story_teller/stdlib.rb

Overview

The StdLib module to re-define the InScope method rubocop: disable Metrics/AbcSize rubocop: disable Lint/DuplicateMethods

Constant Summary collapse

DotPattern =
%r{\.}.freeze
UnderscoreString =
'_'.freeze
DefaultXMLAssociations =
%i[tagged modularized].freeze
ParseRoutineString =

rubocop: enable Metrics/AbcSize rubocop: enable Metrics/CyclomaticComplexity rubocop: enable Metrics/PerceivedComplexity

'_parse_routine'.freeze
SubString =
'Sub'.freeze
DefaultVerbRoutineOptions =
{ fail_on_error: true }.freeze
PrimaryHandlers =

rubocop: enable Metrics/AbcSize rubocop: enable Metrics/MethodLength rubocop: enable Metrics/CyclomaticComplexity rubocop: enable Metrics/PerceivedComplexity

%i[before after life].freeze
SynchronousHandlers =
PrimaryHandlers + %i[react_before].freeze
ReactionHandlers =
SynchronousHandlers + %i[react_after].freeze

Instance Method Summary collapse

Instance Method Details

#_invoke(action, *args) ⇒ Object Also known as: silently_invoke

Under invoke execute a routine but tries to prevent any output at all and does not provide other objects the opportunity to react to the action, like invoke does, above.

This should be used to cause an action outcome during the execution of another Verb action subroutine. It should appear as if the invoked action is part of the original action. !rubocop: disable Metrics/AbcSize



224
225
226
227
228
229
230
231
232
233
234
# File 'lib/story_teller/stdlib.rb', line 224

def _invoke(action, *args)
  raise "The action parameter may not be nil" if action.nil?

  # log.debug 'Invocation: ' + name(@player) + ' <<' + action + (args.empty? ? '' : ' ') + args.to_s + '>>'
  ctx = invocation_context(action, *args)
  elementally(ctx) do
    silently do
      send(VerbRoutine(action))
    end
  end
end

#ADirectionObject

Stubs

Designers may override these.



122
123
124
# File 'lib/story_teller/stdlib.rb', line 122

def ADirection(*)
  compass.include?(noun)
end

#AfterLifeObject



454
455
456
# File 'lib/story_teller/stdlib.rb', line 454

def AfterLife(*)
  false
end

#AfterPromptObject



458
459
460
# File 'lib/story_teller/stdlib.rb', line 458

def AfterPrompt(*)
  false
end

#AmusingObject



462
463
464
# File 'lib/story_teller/stdlib.rb', line 462

def Amusing(*)
  false
end

#areaObject



114
115
116
# File 'lib/story_teller/stdlib.rb', line 114

def area
  location&.area
end

#BeforeParsingObject



466
467
468
# File 'lib/story_teller/stdlib.rb', line 466

def BeforeParsing(*)
  false
end

#BeginActivityObject



144
145
146
# File 'lib/story_teller/stdlib.rb', line 144

def BeginActivity(*)
  false
end

#ChooseObjects(_object, _code) ⇒ Object



470
471
472
# File 'lib/story_teller/stdlib.rb', line 470

def ChooseObjects(_object, _code)
  2
end

#consume_remaining_textObject



89
90
91
92
93
94
# File 'lib/story_teller/stdlib.rb', line 89

def consume_remaining_text
  s = preceding_text
  @wn = num_words
  remaining_text = @input[s.length..]
  remaining_text ? remaining_text.strip : ''
end

#DarkToDarkObject



474
475
476
# File 'lib/story_teller/stdlib.rb', line 474

def DarkToDark(*)
  false
end

#DeathMessageObject



478
479
480
# File 'lib/story_teller/stdlib.rb', line 478

def DeathMessage(*)
  false
end

#debug(n = nil) ⇒ Object



26
27
28
29
30
31
32
33
# File 'lib/story_teller/stdlib.rb', line 26

def debug(n = nil)
  if n.nil?
    toggle_constant :DEBUG
  else
    @parser_trace = n
    reset_constant :DEBUG, true
  end
end

#DisplayStatusObject



136
137
138
# File 'lib/story_teller/stdlib.rb', line 136

def DisplayStatus
  false
end

#EndActivityObject



148
149
150
# File 'lib/story_teller/stdlib.rb', line 148

def EndActivity(*)
  false
end

#EpilogueObject



482
483
484
# File 'lib/story_teller/stdlib.rb', line 482

def Epilogue(*)
  false
end

#executableObject



35
36
37
# File 'lib/story_teller/stdlib.rb', line 35

def executable
  StoryTeller::Engine.executable
end

#export(o, type = :xml) ⇒ Object



55
56
57
# File 'lib/story_teller/stdlib.rb', line 55

def export(o, type = :xml)
  o.send('export_' + type) if !o.nil? && o.respond_to?(('export_' + type).to_sym)
end

#flush_automaticallyObject



51
52
53
# File 'lib/story_teller/stdlib.rb', line 51

def flush_automatically
  toggle_constant :FLUSH_IO_INSTANTLY
end

#ForActivityObject



152
153
154
# File 'lib/story_teller/stdlib.rb', line 152

def ForActivity(*)
  false
end

#FullScoreSubObject



132
133
134
# File 'lib/story_teller/stdlib.rb', line 132

def FullScoreSub
  false
end

#GamePostRoutineObject



486
487
488
# File 'lib/story_teller/stdlib.rb', line 486

def GamePostRoutine(*)
  false
end

#GamePreRoutineObject



490
491
492
# File 'lib/story_teller/stdlib.rb', line 490

def GamePreRoutine(*)
  false
end

#import_object(file) ⇒ Object



62
63
64
65
66
# File 'lib/story_teller/stdlib.rb', line 62

def import_object(file)
  type = File.extname(file)
  return if type.nil?
  send('import_object_' + type.gsub(DotPattern, UnderscoreString), file)
end

#import_object_json(file) ⇒ Object



83
84
85
86
87
# File 'lib/story_teller/stdlib.rb', line 83

def import_object_json(file)
  return unless defined? JSON
  return if file.nil?
  JSON.parse(File.read(file))
end

#import_object_xml(file, associations = DefaultXMLAssociations) ⇒ Object



70
71
72
73
74
# File 'lib/story_teller/stdlib.rb', line 70

def import_object_xml(file, associations = DefaultXMLAssociations)
  return unless defined? Nokogiri
  return if file.nil?
  Inform::Object.from_xml(File.open(file), associations: associations)
end

#import_object_yaml(file) ⇒ Object Also known as: import_object_yml



76
77
78
79
80
# File 'lib/story_teller/stdlib.rb', line 76

def import_object_yaml(file)
  return unless defined? YAML
  return if file.nil?
  YAML.safe_load_file(file)
end

#indirect(action, *args) ⇒ Object

rubocop: disable Metrics/AbcSize rubocop: disable Metrics/CyclomaticComplexity rubocop: disable Metrics/PerceivedComplexity



241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
# File 'lib/story_teller/stdlib.rb', line 241

def indirect(action, *args)
  return false if action.nil?
  # raise "The action parameter may not be nil" if action.nil?
  action = action.to_sym
  subroutine = VerbRoutine(action, fail_on_error: false)
  contextualize # TODO: Test removal
  # Direct objects might need library built-ins
  # noun.inflib = actor.inflib if (!noun.nil?) && noun.object? && noun.inflib.nil?
  # second.inflib = actor.inflib if (!second.nil?) && second.object? && second.inflib.nil?
  @noun.inflib = self if !@noun.nil? && @noun.object? && @noun.inflib.nil?
  @second.inflib = self if !@second.nil? && @second.object? && @second.inflib.nil?
  return send(subroutine, *args) if respond_to? subroutine
  return send(action, *args) if respond_to? action
  false
end

#InScope(obj, scope = []) ⇒ Object


(From the Inform Designer's Manual)
§32 Scope and what you can see

"In scope" roughly means "the compass directions, what you're carrying
and what you can see". It exactly means this:

1. the compass directions;
2. the player's immediate possessions;
3. if there is light, then the contents of the player's visibility
   ceiling (see §21 for definition, but roughly speaking the
   outermost object containing the player which remains visible,
   which is usually the player's location);
4. if there is darkness, then the contents of the library's object
   thedark (by default there are no such contents, but some
   designers have been known to move objects into thedark: see
   'Ruins');
5. if the player is inside a container, then that container;
6. if O is in scope and is see-through (see §21), then the contents of O;
7. if O is in scope, then any object which it "adds to scope".



585
586
587
# File 'lib/story_teller/stdlib.rb', line 585

def InScope(*)
  true
end

#inversionObject



39
40
41
# File 'lib/story_teller/stdlib.rb', line 39

def inversion
  print Inform::VERSION
end

#invocation_context(action, *args) ⇒ Object

rubocop: disable Metrics/AbcSize rubocop: disable Metrics/MethodLength



165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# File 'lib/story_teller/stdlib.rb', line 165

def invocation_context(action, *args)
  ctx = StoryTeller.context
  ctx.results = [
    ctx.action = ctx.action_to_be = action,
    ctx.parameters = args.length,
    ctx.noun = ctx.inp1 = args.shift,
    ctx.second = ctx.inp2 = args.shift
  ]
  ctx.pattern = [
    ctx.results[0].to_s.downcase,
    ctx.results[2],
    ctx.results[3]
  ]
  ctx.actor = player
  ctx.special_number1 = ctx.special_number2 = nil
  ctx.special_number1 = ctx.results[2] if ctx.results[2].number?
  ctx.special_number2 = ctx.results[3] if ctx.results[3].number?
  ctx.special_word = args.shift
  ctx.consult_words = args.shift
  ctx.consult_words = ctx.special_word = ctx.results[2] if ctx.results[2].is_a?(String)
  ctx.consult_words = ctx.special_word = ctx.results[3] if ctx.results[3].is_a?(String)
  ctx.match_from = ctx.wn = ctx.verb_wordnum = 0
  ctx.words = []
  ctx
end

#invoke(action, *args) ⇒ Object

The invoke method executes without prompting afterward but appends the output to the contextual event’s buffer which will be displayed upon completion of the contextual event.

TODO: Test: Does this mean that invoke must always be executed within an event context to have its output displayed? !rubocop: disable Metrics/AbcSize



201
202
203
204
205
206
207
208
209
210
211
# File 'lib/story_teller/stdlib.rb', line 201

def invoke(action, *args)
  raise "The action parameter may not be nil" if action.nil?

  # log.debug 'Invocation: ' + name(@player) + ' <' + action + (args.empty? ? '' : ' ') + args.to_s + '>'
  ctx = invocation_context(action, *args)
  immediately(ctx) do
    if BeforeRoutines(action) == false
      send(VerbRoutine(action))
    end
  end
end

#locationObject



104
105
106
# File 'lib/story_teller/stdlib.rb', line 104

def location
  @player.location
end

#location=(o) ⇒ Object



108
109
110
111
112
# File 'lib/story_teller/stdlib.rb', line 108

def location=(o)
  log.warn "Trying to set the location directly"
  log.warn caller[1] and abort
  @player.location = o
end

#LookRoutineObject



498
499
500
# File 'lib/story_teller/stdlib.rb', line 498

def LookRoutine(*)
  false
end

#Message(*args) ⇒ Object



140
141
142
# File 'lib/story_teller/stdlib.rb', line 140

def Message(*args)
  println(args[:fatalerror] || args.values.first)
end

#NewRoomObject



502
503
504
# File 'lib/story_teller/stdlib.rb', line 502

def NewRoom(*)
  false
end

#ObjectDoesNotFitObject



506
507
508
# File 'lib/story_teller/stdlib.rb', line 506

def ObjectDoesNotFit(*)
  2
end

#ParseNoun(obj) ⇒ Object

rubocop: disable Lint/SelfAssignment rubocop: disable Lint/UselessAssignment



549
# File 'lib/story_teller/stdlib.rb', line 549

def ParseNoun(obj); obj = obj; return -1; end

#ParseNumberObject



510
511
512
# File 'lib/story_teller/stdlib.rb', line 510

def ParseNumber(*)
  2
end

#ParserErrorObject



514
515
516
# File 'lib/story_teller/stdlib.rb', line 514

def ParserError(*)
  true
end

#pcountObject



100
101
102
# File 'lib/story_teller/stdlib.rb', line 100

def pcount
  @pattern ? @pattern.length - 1 : 0
end

#preceding_textObject



96
97
98
# File 'lib/story_teller/stdlib.rb', line 96

def preceding_text
  @words[0..(@wn - 1)].join(' ')
end

#PrintTaskNameObject



518
519
520
# File 'lib/story_teller/stdlib.rb', line 518

def PrintTaskName(*)
  true
end

#PrintVerbObject



522
523
524
# File 'lib/story_teller/stdlib.rb', line 522

def PrintVerb(*)
  true
end

#reacts_to?(obj, prop, *args) ⇒ Boolean

Hack for Ruby so we can do action-specific reaction handlers. With Inform, one can just implement before [; Whatever: “hi” ] on an Object and include some action-specifying cases as if the before method were a functional switch or case statement. rubocop: disable Metrics/AbcSize rubocop: disable Metrics/MethodLength rubocop: disable Metrics/CyclomaticComplexity rubocop: disable Metrics/PerceivedComplexity rubocop: disable Style/ReturnNilInPredicateMethodDefinition

Returns:

  • (Boolean)


307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
# File 'lib/story_teller/stdlib.rb', line 307

def reacts_to?(obj, prop, *args)
  raise "Nothing is not an object" if obj.nil?
  return false unless ReactionHandlers.include?(prop)
  # TODO: Only apply Behavior modules if object is not being controlled
  # by a player.
  obj.apply_modules if obj.respond_to?(:apply_modules)
  si = obj.inflib
  obj.inflib = self

  # TODO: I have prototyped a possible Inform-esque
  # switch-style dsl for reaction methods based on
  # action name.  Here, the prop would be sent the
  # object normally, but the object reaction method
  # would contain a series of method invocations with
  # a block parameter.  If any of the method missing
  # invocations method name parameter resembled the
  # action name, then the given block would get
  # executed on the subject, thereby simulating the
  # Inform-style functional switch idiom. Like this:
  #
  #   def method_missing(method, *args, &block)
  #     return yield if method == action
  #     super
  #   end
  #
  #   def before
  #     Touch {
  #       publish The(self) + " shudders."
  #       println "Your skin crawls."
  #       true
  #     }
  #     Show {
  #       publish The(self) + " is uninterested in " + the(noun) + "."
  #       "Nothing special."
  #     }
  #   end
  #

  # TODO: Zarf has recommended implementing a check for code persisted as
  # a property of an observant object.  For instance:
  # object:
  #   properties:
  #     ---
  #     before:
  #       ---
  #       Touch: publish The(self) + "shudders."; println "Your skin crawls."; true

  # To preserve support for a game designer to implement catch-all
  # reactions by observant objects, just have the receiver object
  # read the action variable in order to determine course.  For example:
  #
  #   def before
  #     case action
  #     when :Poke then println "Keep your hands to yourself!"
  #
  if PrimaryHandlers.include?(prop) && obj.respond_to?(prop)
    # contextualize
    # obj.contextualize
    # TODO: Test
    context_action = Thread.current[:event]&.action
    log.debug "Inform#reacts_to?(obj=#{obj}, prop=#{prop}) action: #{context_action}"
    if (result = obj.send(prop, *args))
      return result
    end
  end

  # TODO: TESTME Confirm that using the parser's @action variable
  # is sufficiently reliable for composing reaction handler methods.
  # As of 2021-07-21, there appears to be some evidence that using
  # the #invoke method to generate reaction callbacks here is not
  # usable with the @action approach. Apparently, @action is the least
  # reliable because it can include a leftover value from a previous
  # interaction, since the #invoke method does not set the value on
  # the library (and nor should it, since #invoke is for ad-hoc events).
  # The longer term and more correct fix here is likely to consolidate
  # all references to @action as simply action which would pull from an
  # event context. For now I think that I'll simply include @action at
  # the end of the chain, in order to ensure that some action is
  # available for reaction handler method reference construction.
  # act = obj.action || action || @action
  # act = @action || obj.inflib.action || action
  # act = obj.inflib.action || action
  # act = obj.inflib.action || action || @action
  act = Thread.current[:event]&.action || action || @action
  # TODO: Remove debug logging here.
  # log.debug "Inform#reacts_to? #{prop}"
  # log.debug "  #{self}.@action: #{@action}"
  # log.debug "  #{obj.inflib}.action: #{obj.inflib.action}"
  # log.debug "  #{Thread.current[:event]}.current.thread.event.action: #{action}"
  # log.debug "  act: #{act}"
  if act.nil?
    log.warn "Failed to reference action invoking #reacts_to?"
    return nil
  end
  reaction = "#{prop}_#{act}"
  # log.debug "obj: #{obj.identity}, reaction: #{reaction}"
  # log.debug "obj.respond_to?(reaction.downcase!):"
  # log.debug "  #{obj.respond_to?(reaction.downcase!)} (#{obj.respond_to?(reaction)})"
  if obj.respond_to?(reaction)
    handler = obj.method(reaction)
  elsif obj.respond_to?(reaction.downcase!)
    handler = obj.method(reaction)
  else
    return nil # false
  end
  log.debug "Found reaction handler for #{obj}.#{reaction}: #{handler}"
  if SynchronousHandlers.include?(prop)
    # Go ahead and print any string results from designer-implemented
    # handlers, and return true if there are any.
    # contextualize
    result = obj.instance_exec(*args, &handler)
    return result unless result.is_a?(String)
    println result
    return true
  end
  # ctx = invocation_context(@action, @noun, @second, @special_word, @consult_words)
  ctx = invocation_context(act, noun, second, special_word, consult_words)
  obj.register_callback(ctx, *args, &handler)
  # Because of the event-oriented nature of actions in a multiplayer
  # system, other objects may not interfere with original actions
  # once they have taken place. So, false must always be returned.
  # However, game designers must still have the ability to direct an
  # object, particular an animate one, to respond to an action if
  # appropriate.
  nil # false
rescue StandardError => e
  log.error "Error getting reaction from #{obj}", e
  e.backtrace.each { |t| log.error t }
ensure
  obj.inflib = si
end

#ScoreSubObject



128
129
130
# File 'lib/story_teller/stdlib.rb', line 128

def ScoreSub
  false
end

#silentlyObject



156
157
158
159
160
161
# File 'lib/story_teller/stdlib.rb', line 156

def silently(*)
  k = @keep_silent; @keep_silent = true
  yield
ensure
  @keep_silent = k
end

#standard_interpreterObject



43
44
45
# File 'lib/story_teller/stdlib.rb', line 43

def standard_interpreter
  0
end

#StatusLineHeightObject



126
# File 'lib/story_teller/stdlib.rb', line 126

def StatusLineHeight; end

#TimePassesObject



526
527
528
# File 'lib/story_teller/stdlib.rb', line 526

def TimePasses(*)
  false
end

#UnknownVerb(verb) ⇒ Object



530
531
532
# File 'lib/story_teller/stdlib.rb', line 530

def UnknownVerb(verb, *)
  verb
end

#VerbRoutine(action, opts = {}) ⇒ Object

rubocop: disable Metrics/AbcSize rubocop: disable Metrics/MethodLength rubocop: disable Metrics/CyclomaticComplexity rubocop: disable Metrics/PerceivedComplexity



268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
# File 'lib/story_teller/stdlib.rb', line 268

def VerbRoutine(action, opts = {})
  raise 'The action parameter may not be nil' if action.nil?
  opts = DefaultVerbRoutineOptions.merge(opts)
  if action.respond_to?(:underscore) && respond_to?(
    sub_routine = (action.underscore(downcase: false) + ParseRoutineString))
    sub_routine.to_sym
  elsif respond_to?(sub_routine = (action + SubString))
    sub_routine.to_sym
  elsif respond_to?(sub_routine = (action.camelize + SubString))
    sub_routine.to_sym
  elsif action.respond_to?(:snake_case) && respond_to?(sub_routine = action.snake_case)
    sub_routine.to_sym
  elsif respond_to?(sub_routine = action)
    sub_routine.to_sym
  elsif opts[:fail_on_error]
    raise NoVerbRoutine, 'Please define a verb subroutine for the action ' + action
  else
    log.warn "Please define a verb subroutine for the action #{action}"
    false
  end
end

#version_numberObject



47
48
49
# File 'lib/story_teller/stdlib.rb', line 47

def version_number
  0
end