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



236
237
238
239
240
241
242
243
244
245
246
# File 'lib/story_teller/stdlib.rb', line 236

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.



134
135
136
# File 'lib/story_teller/stdlib.rb', line 134

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

#AfterLifeObject



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

def AfterLife(*)
  false
end

#AfterPromptObject



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

def AfterPrompt(*)
  false
end

#AmusingObject



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

def Amusing(*)
  false
end

#areaObject



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

def area
  location&.area
end

#BeforeParsingObject



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

def BeforeParsing(*)
  false
end

#BeginActivityObject



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

def BeginActivity(*)
  false
end

#ChooseObjects(_object, _code) ⇒ Object



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

def ChooseObjects(_object, _code)
  2
end

#consume_remaining_textObject



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

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

#DarkToDarkObject



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

def DarkToDark(*)
  false
end

#DeathMessageObject



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

def DeathMessage(*)
  false
end

#debug(n = nil) ⇒ Object



38
39
40
41
42
43
44
45
# File 'lib/story_teller/stdlib.rb', line 38

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

#DisplayStatusObject



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

def DisplayStatus
  false
end

#EndActivityObject



160
161
162
# File 'lib/story_teller/stdlib.rb', line 160

def EndActivity(*)
  false
end

#EpilogueObject



494
495
496
# File 'lib/story_teller/stdlib.rb', line 494

def Epilogue(*)
  false
end

#executableObject



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

def executable
  StoryTeller::Engine.executable
end

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



67
68
69
# File 'lib/story_teller/stdlib.rb', line 67

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

#flush_automaticallyObject



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

def flush_automatically
  toggle_constant :FLUSH_IO_INSTANTLY
end

#ForActivityObject



164
165
166
# File 'lib/story_teller/stdlib.rb', line 164

def ForActivity(*)
  false
end

#FullScoreSubObject



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

def FullScoreSub
  false
end

#GamePostRoutineObject



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

def GamePostRoutine(*)
  false
end

#GamePreRoutineObject



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

def GamePreRoutine(*)
  false
end

#import_object(file) ⇒ Object



74
75
76
77
78
# File 'lib/story_teller/stdlib.rb', line 74

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



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

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



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

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



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

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



253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
# File 'lib/story_teller/stdlib.rb', line 253

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".



597
598
599
# File 'lib/story_teller/stdlib.rb', line 597

def InScope(*)
  true
end

#inversionObject



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

def inversion
  print Inform::VERSION
end

#invocation_context(action, *args) ⇒ Object

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



177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/story_teller/stdlib.rb', line 177

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



213
214
215
216
217
218
219
220
221
222
223
# File 'lib/story_teller/stdlib.rb', line 213

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



116
117
118
# File 'lib/story_teller/stdlib.rb', line 116

def location
  @player.location
end

#location=(o) ⇒ Object



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

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

#LookRoutineObject



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

def LookRoutine(*)
  false
end

#Message(*args) ⇒ Object



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

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

#NewRoomObject



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

def NewRoom(*)
  false
end

#ObjectDoesNotFitObject



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

def ObjectDoesNotFit(*)
  2
end

#ParseNoun(obj) ⇒ Object

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



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

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

#ParseNumberObject



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

def ParseNumber(*)
  2
end

#ParserErrorObject



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

def ParserError(*)
  true
end

#pcountObject



112
113
114
# File 'lib/story_teller/stdlib.rb', line 112

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

#preceding_textObject



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

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

#PrintTaskNameObject



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

def PrintTaskName(*)
  true
end

#PrintVerbObject



534
535
536
# File 'lib/story_teller/stdlib.rb', line 534

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)


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
438
439
440
441
442
443
444
445
446
447
448
449
# File 'lib/story_teller/stdlib.rb', line 319

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.identity}", e
  e.backtrace.each { |t| log.error t }
ensure
  obj.inflib = si
end

#ScoreSubObject



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

def ScoreSub
  false
end

#silentlyObject



168
169
170
171
172
173
# File 'lib/story_teller/stdlib.rb', line 168

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

#standard_interpreterObject



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

def standard_interpreter
  0
end

#StatusLineHeightObject



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

def StatusLineHeight; end

#TimePassesObject



538
539
540
# File 'lib/story_teller/stdlib.rb', line 538

def TimePasses(*)
  false
end

#UnknownVerb(verb) ⇒ Object



542
543
544
# File 'lib/story_teller/stdlib.rb', line 542

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



280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
# File 'lib/story_teller/stdlib.rb', line 280

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



59
60
61
# File 'lib/story_teller/stdlib.rb', line 59

def version_number
  0
end