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



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

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.



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

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

#AfterLifeObject



456
457
458
# File 'lib/story_teller/stdlib.rb', line 456

def AfterLife(*)
  false
end

#AfterPromptObject



460
461
462
# File 'lib/story_teller/stdlib.rb', line 460

def AfterPrompt(*)
  false
end

#AmusingObject



464
465
466
# File 'lib/story_teller/stdlib.rb', line 464

def Amusing(*)
  false
end

#areaObject



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

def area
  location&.area
end

#BeforeParsingObject



468
469
470
# File 'lib/story_teller/stdlib.rb', line 468

def BeforeParsing(*)
  false
end

#BeginActivityObject



146
147
148
# File 'lib/story_teller/stdlib.rb', line 146

def BeginActivity(*)
  false
end

#ChooseObjects(_object, _code) ⇒ Object



472
473
474
# File 'lib/story_teller/stdlib.rb', line 472

def ChooseObjects(_object, _code)
  2
end

#consume_remaining_textObject



91
92
93
94
95
96
# File 'lib/story_teller/stdlib.rb', line 91

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

#DarkToDarkObject



476
477
478
# File 'lib/story_teller/stdlib.rb', line 476

def DarkToDark(*)
  false
end

#DeathMessageObject



480
481
482
# File 'lib/story_teller/stdlib.rb', line 480

def DeathMessage(*)
  false
end

#debug(n = nil) ⇒ Object



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

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

#DisplayStatusObject



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

def DisplayStatus
  false
end

#EndActivityObject



150
151
152
# File 'lib/story_teller/stdlib.rb', line 150

def EndActivity(*)
  false
end

#EpilogueObject



484
485
486
# File 'lib/story_teller/stdlib.rb', line 484

def Epilogue(*)
  false
end

#executableObject



37
38
39
# File 'lib/story_teller/stdlib.rb', line 37

def executable
  StoryTeller::Engine.executable
end

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



57
58
59
# File 'lib/story_teller/stdlib.rb', line 57

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

#flush_automaticallyObject



53
54
55
# File 'lib/story_teller/stdlib.rb', line 53

def flush_automatically
  toggle_constant :FLUSH_IO_INSTANTLY
end

#ForActivityObject



154
155
156
# File 'lib/story_teller/stdlib.rb', line 154

def ForActivity(*)
  false
end

#FullScoreSubObject



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

def FullScoreSub
  false
end

#GamePostRoutineObject



488
489
490
# File 'lib/story_teller/stdlib.rb', line 488

def GamePostRoutine(*)
  false
end

#GamePreRoutineObject



492
493
494
# File 'lib/story_teller/stdlib.rb', line 492

def GamePreRoutine(*)
  false
end

#import_object(file) ⇒ Object



64
65
66
67
68
# File 'lib/story_teller/stdlib.rb', line 64

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



85
86
87
88
89
# File 'lib/story_teller/stdlib.rb', line 85

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



72
73
74
75
76
# File 'lib/story_teller/stdlib.rb', line 72

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



78
79
80
81
82
# File 'lib/story_teller/stdlib.rb', line 78

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



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

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



587
588
589
# File 'lib/story_teller/stdlib.rb', line 587

def InScope(*)
  true
end

#inversionObject



41
42
43
# File 'lib/story_teller/stdlib.rb', line 41

def inversion
  print Inform::VERSION
end

#invocation_context(action, *args) ⇒ Object

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



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

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



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

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



106
107
108
# File 'lib/story_teller/stdlib.rb', line 106

def location
  @player.location
end

#location=(o) ⇒ Object



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

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

#LookRoutineObject



500
501
502
# File 'lib/story_teller/stdlib.rb', line 500

def LookRoutine(*)
  false
end

#Message(*args) ⇒ Object



142
143
144
# File 'lib/story_teller/stdlib.rb', line 142

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

#NewRoomObject



504
505
506
# File 'lib/story_teller/stdlib.rb', line 504

def NewRoom(*)
  false
end

#ObjectDoesNotFitObject



508
509
510
# File 'lib/story_teller/stdlib.rb', line 508

def ObjectDoesNotFit(*)
  2
end

#ParseNoun(obj) ⇒ Object

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



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

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

#ParseNumberObject



512
513
514
# File 'lib/story_teller/stdlib.rb', line 512

def ParseNumber(*)
  2
end

#ParserErrorObject



516
517
518
# File 'lib/story_teller/stdlib.rb', line 516

def ParserError(*)
  true
end

#pcountObject



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

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

#preceding_textObject



98
99
100
# File 'lib/story_teller/stdlib.rb', line 98

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

#PrintTaskNameObject



520
521
522
# File 'lib/story_teller/stdlib.rb', line 520

def PrintTaskName(*)
  true
end

#PrintVerbObject



524
525
526
# File 'lib/story_teller/stdlib.rb', line 524

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)


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
438
439
# File 'lib/story_teller/stdlib.rb', line 309

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



130
131
132
# File 'lib/story_teller/stdlib.rb', line 130

def ScoreSub
  false
end

#silentlyObject



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

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

#standard_interpreterObject



45
46
47
# File 'lib/story_teller/stdlib.rb', line 45

def standard_interpreter
  0
end

#StatusLineHeightObject



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

def StatusLineHeight; end

#TimePassesObject



528
529
530
# File 'lib/story_teller/stdlib.rb', line 528

def TimePasses(*)
  false
end

#UnknownVerb(verb) ⇒ Object



532
533
534
# File 'lib/story_teller/stdlib.rb', line 532

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



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

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



49
50
51
# File 'lib/story_teller/stdlib.rb', line 49

def version_number
  0
end