story-teller 1.1.3

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 (46) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +623 -0
  3. data/README.md +188 -0
  4. data/Rakefile +58 -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 +90 -0
  9. data/game/example.rb +105 -0
  10. data/game/forms/example_form.rb +2 -0
  11. data/game/grammar/admin.inf.rb +185 -0
  12. data/game/grammar/builder.inf.rb +310 -0
  13. data/game/grammar/game_grammar.inf.rb +6 -0
  14. data/game/grammar/meta.inf.rb +41 -0
  15. data/game/languages/english.rb +571 -0
  16. data/game/models/example_model.rb +2 -0
  17. data/game/modules/example_module.rb +9 -0
  18. data/game/modules/parser_extensions.rb +264 -0
  19. data/game/rules/example_state.rb +2 -0
  20. data/game/scripts/example_script.rb +2 -0
  21. data/game/topics/example_topic.rb +2 -0
  22. data/game/verbs/game_verbs.rb +35 -0
  23. data/game/verbs/metaverbs.rb +2066 -0
  24. data/lib/story_teller/application.rb +82 -0
  25. data/lib/story_teller/cli.rb +35 -0
  26. data/lib/story_teller/color.rb +144 -0
  27. data/lib/story_teller/config.rb +61 -0
  28. data/lib/story_teller/curses_adapter.rb +30 -0
  29. data/lib/story_teller/database.rb +527 -0
  30. data/lib/story_teller/game/loader.rb +276 -0
  31. data/lib/story_teller/game.rb +22 -0
  32. data/lib/story_teller/inform/models.rb +42 -0
  33. data/lib/story_teller/inform/relational/link.rb +239 -0
  34. data/lib/story_teller/inform/relational/module.rb +203 -0
  35. data/lib/story_teller/inform/relational/object.rb +546 -0
  36. data/lib/story_teller/inform/relational/tag.rb +152 -0
  37. data/lib/story_teller/options.rb +151 -0
  38. data/lib/story_teller/persistence.rb +340 -0
  39. data/lib/story_teller/player_character.rb +99 -0
  40. data/lib/story_teller/privileges.rb +55 -0
  41. data/lib/story_teller/runtime.rb +381 -0
  42. data/lib/story_teller/snapshots.rb +412 -0
  43. data/lib/story_teller/terminal.rb +58 -0
  44. data/lib/story_teller/version.rb +24 -0
  45. data/lib/story_teller_cli.rb +34 -0
  46. metadata +158 -0
@@ -0,0 +1,2066 @@
1
+ # game/verbs/metaverbs.rb
2
+ # encoding: utf-8
3
+ # frozen_string_literal: false
4
+
5
+ # Copyright Nels Nelson 2008-2025 but freely usable (see license)
6
+ #
7
+ # This file is part of the StoryTeller.
8
+ #
9
+ # The StoryTeller is free software: you can redistribute it and/or
10
+ # modify it under the terms of the GNU General Public License as published
11
+ # by the Free Software Foundation, either version 3 of the License, or
12
+ # (at your option) any later version.
13
+ #
14
+ # The StoryTeller is distributed in the hope that it will be useful,
15
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
16
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
+ # GNU General Public License for more details.
18
+ #
19
+ # You should have received a copy of the GNU General Public License
20
+ # along with the StoryTeller. If not, see <http://www.gnu.org/licenses/>.
21
+
22
+ require 'fileutils'
23
+
24
+ # The Metaverbs module
25
+ module Metaverbs
26
+ Parser = Inform::Parser
27
+ Newline = "\n".freeze
28
+
29
+ def TestSub
30
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
31
+ case special
32
+
33
+ when /loop/
34
+ add_event do
35
+ iteration = 0
36
+ loop do
37
+ publish 'Iteration: ' + (iteration += 1)
38
+ sleep 1
39
+ end
40
+ end
41
+ when /events/
42
+ if special_number1.nil?
43
+ add_event do
44
+ delay(0.5) do
45
+ @player.inform "Something happened."
46
+ add_event do
47
+ "Something descendant happened."
48
+ end
49
+ end.delay(0.5) do
50
+ "Something else will happen..."
51
+ end.delay(1) do
52
+ "In 3..."
53
+ end.delay(1) do
54
+ "2..."
55
+ end.delay(1) do
56
+ "1..."
57
+ end
58
+ end.delay(5) do
59
+ "Something else happened."
60
+ end
61
+ "Testing events..."
62
+ else
63
+ n = special_number1.to_i
64
+ add_event do
65
+ @player.inform "Something happened."
66
+ end.delay(0.5) do
67
+ "Something else will happen..."
68
+ end.delay(0.5) do
69
+ # TODO: Why is there a maximum of 6927??? FIXME
70
+ @player.inform "#{n} events will happen..."
71
+ e = add_event do
72
+ "Something happened. [0]"
73
+ end
74
+ (n - 1).times do |i|
75
+ e = e.add_event do
76
+ format("Something happened. [%<iteration>s]", iteration: i + 1)
77
+ end
78
+ end
79
+ false
80
+ end
81
+ "Testing #{special_number1} events..."
82
+ end
83
+ when /error/
84
+ println "Testing errors..."
85
+ raise "Testing errors..."
86
+ else
87
+ "You passed the test."
88
+ end
89
+ end
90
+
91
+ def IsInSub
92
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
93
+ if noun.in? second
94
+ The(noun) + " is in " + the(second) + "."
95
+ elsif second == player
96
+ L__M(:Drop, 2, noun)
97
+ else
98
+ The(noun) + " is not in " + the(second) + "."
99
+ end
100
+ end
101
+ alias IsNotInSub IsInSub
102
+
103
+ def admins
104
+ StoryTeller::Runtime.instance
105
+ .privileged_identities(:admin).map(&:privilege_label).sort
106
+ end
107
+
108
+ def AdminsSub
109
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
110
+
111
+ names = admins
112
+ return 'There are no current admins.' if names.empty?
113
+
114
+ "Current admins are: #{names.join(', ')}"
115
+ end
116
+
117
+ def builders
118
+ StoryTeller::Runtime.instance
119
+ .privileged_identities(:builder).map(&:privilege_label).sort
120
+ end
121
+
122
+ def BuildersSub
123
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
124
+
125
+ names = builders
126
+ return 'There are no current builders.' if names.empty?
127
+
128
+ "Current builders are: #{names.join(', ')}"
129
+ end
130
+
131
+ def ActionSub
132
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
133
+ target = noun
134
+ command = text
135
+ target.add_event do
136
+ target.parse(command)
137
+ end
138
+ "You force " + the(target) + " to " + command + "."
139
+ end
140
+
141
+ def AgainSub
142
+ history.pop
143
+ parse history.last
144
+ end
145
+
146
+ ConverterTemplate = 'to_%<data_format>'.freeze
147
+
148
+ def AncestorsSub
149
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
150
+ o = @player
151
+ o = noun if noun.object?
152
+ a = o.ancestors
153
+ unless special.nil?
154
+ converter = format(ConverterTemplate, data_format: special).to_sym
155
+ return a.send(converter, { include: %i[tagged modularized] }) if a.respond_to? converter
156
+ end
157
+ a.identities
158
+ end
159
+
160
+ def AssignSub
161
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
162
+ return "That value is invalid." if text.nil? || text.empty?
163
+ property = special.to_sym
164
+ if property == :name
165
+ noun.values[:name] = text
166
+ noun.save
167
+ return "The #{property} of " + the(noun) + " is " + ensure_punctuation(noun.name_words)
168
+ elsif Object.methods.include?(property)
169
+ return "That property may not be modified."
170
+ else
171
+ value = text.number? ? text.to_n : text
172
+ # The following can accidentally invoke a method of the noun object:
173
+ # noun.&property, value
174
+ # Don't do that here, since property is user input. Do this instead:
175
+ noun.properties[property] = value
176
+ noun.save
177
+ end
178
+ "The #{property} of " + the(noun) + " is " + ensure_punctuation(noun.&property)
179
+ end
180
+
181
+ def UnassignSub
182
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
183
+ property = special.to_sym
184
+ noun.properties.delete special
185
+ noun.properties.delete property
186
+ noun.save
187
+ The(noun) + " no longer has #{property}."
188
+ end
189
+
190
+ def AssignRoomSub
191
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
192
+ invoke :Assign, location, text
193
+ end
194
+
195
+ def UnassignRoomSub
196
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
197
+ invoke :Unassign, location, text
198
+ end
199
+
200
+ def AttributesSub
201
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
202
+ if !special.nil? && Inform::Tag.respond_to?(special)
203
+ result = Inform::Tag.send(special)
204
+ return result || "None."
205
+ end
206
+ Inform::Tag.map(&:name).sort
207
+ end
208
+
209
+ def AreasScope
210
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
211
+ case @scope_stage
212
+ when 1 then false # Return single objects only
213
+ when 2
214
+ areas = Inform::Object.where(object_type: 'Area').all
215
+ ScopeWithin(areas); true
216
+ when 3 then L__M(:Take, 8) # TODO: This is broken somehow FIXME
217
+ end
218
+ end
219
+
220
+ def AreaSub
221
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
222
+ return "For some reason your location cannot be determined." if location.nil?
223
+ location.link :area, noun if noun.nil? && noun.is_a?(Area)
224
+ area = location.linkto :area
225
+ while parent(area)
226
+ println "You are in a sub-area called " + A(area) + " [#{area.identity}]."
227
+ area = parent(area)
228
+ end
229
+ return "This room does not belong to any area." unless area
230
+ "The main area here is called " + A(area) + " [#{area.identity}]."
231
+ end
232
+
233
+ def AreasSub
234
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
235
+ areas = Inform::Object.where(object_type: 'Area').order(:name).all
236
+ return "There are no areas." if areas.empty?
237
+ println "The following areas are available:"
238
+ areas.identities
239
+ end
240
+
241
+ def BackupSub
242
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
243
+ unless Inform::Game.config.include?(:admin_email)
244
+ return "Please define admin_email in the config.yml file."
245
+ end
246
+ unless Inform::Game.config.include?(:game_name)
247
+ return "Please define game_name in the config.yml file."
248
+ end
249
+ unless special == 'wetrun'
250
+ println `scripts/backup.rb`.strip
251
+ return "Try again with 'backup wetrun' if you really want to do this."
252
+ end
253
+ database = Inform::Game.config[:game_name]
254
+ event do
255
+ file = `scripts/backup.rb --wet-run`.strip
256
+ return "Failed to backup #{database} database." unless File.exist? file
257
+ to_address = StoryTeller::IO::Session.of(@player).login.account.email
258
+ from_address = Inform::Game.config[:admin_email]
259
+ subject = 'Database backup'
260
+ message = "This is a copy of the #{database} database in SQL dump format."
261
+ send_email to_address, from_address, subject, message, file
262
+ new_line
263
+ println "A file was created at #{file} containing a backup of the #{database} database."
264
+ "A copy of that file has been e-mailed to #{to_address}."
265
+ end
266
+ "Backing up the #{database} database..."
267
+ end
268
+
269
+ # TODO: When the game shuts down, or the player quits, all of
270
+ # this session re-wiring must be undone.
271
+ def BecomeSub
272
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
273
+ begin
274
+ take @player, :admin
275
+ give noun, :admin
276
+ session = StoryTeller::IO::Session.of(@player)
277
+ if session
278
+ @player.inflib.unsubscribe session
279
+ # @player.unsubscribe session
280
+ session.outbound.unsubscribe_all
281
+
282
+ unsubscribe @player
283
+ @player = actor = selfobj = noun
284
+
285
+ # session.inbound.subscribe @player.inflib
286
+ # @player.inflib.subscribe session
287
+ @player.inflib.subscribe session.outbound
288
+ @player.subscribe session.outbound
289
+ session.control(@player)
290
+ else
291
+ unsubscribe @player
292
+ @player = actor = selfobj = noun
293
+ if session
294
+ session.inbound.subscribe @player.inflib
295
+ @player.inflib.subscribe session
296
+ @player.inflib.subscribe session.outbound
297
+ @player.subscribe session.outbound
298
+ end
299
+ end
300
+ _invoke :Look
301
+ rescue StandardError => e
302
+ log.error "Error becoming other #{noun}", e
303
+ end
304
+ true
305
+ end
306
+
307
+ def BellSub
308
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
309
+ "\aBong!"
310
+ end
311
+
312
+ def BootSub
313
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
314
+ return L__M(:Boot, 1) if noun.nil?
315
+ StoryTeller::IO::Session.of(noun).disconnect
316
+ "Dropped connection for #{noun}."
317
+ end
318
+
319
+ def BranchSub
320
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
321
+ o = noun.object? ? noun : @player
322
+ b = o.branch
323
+ unless special.nil?
324
+ converter = format(ConverterTemplate, data_format: special).to_sym
325
+ return b.send(converter, { include: %i[tagged modularized] }) if branch.respond_to?(converter)
326
+ end
327
+ b
328
+ end
329
+
330
+ def BroadcastSub
331
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
332
+ StoryTeller::IO::Session.players.inform text
333
+ "[ Informed every player of your communication: #{text} ]"
334
+ end
335
+
336
+ def ClassesSub
337
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
338
+ return "ObjectSpace is not enabled" unless ObjectSpace.enabled?
339
+ ObjectSpace.each_object(Class) do |klass|
340
+ loop do
341
+ print klass
342
+ klass = klass.superclass
343
+ break if klass.nil?
344
+ print " < "
345
+ end
346
+ println
347
+ end
348
+ ""
349
+ end
350
+
351
+ def ClassifySub
352
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
353
+ unless special.nil?
354
+ klass = find_class special
355
+ return "No such class #{special}." if klass.nil?
356
+ o = noun.classify_as klass
357
+ PronounNotice(o)
358
+ end
359
+ "The class of " + the(o) + " is " + o.object_type + "."
360
+ end
361
+
362
+ def TaxonomySub
363
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
364
+ inheritance = noun.class.ancestors
365
+ taxonomy = inheritance[0..inheritance.index(Inform::Object)]
366
+ "The taxonomy of " + the(noun) + " is " + taxonomy.join(' < ') + "."
367
+ end
368
+
369
+ def ClearSub
370
+ "Not yet implemented."
371
+ end
372
+
373
+ def clear_history
374
+ n = history.length
375
+ history.clear
376
+ "Forgot #{n} commands."
377
+ end
378
+
379
+ def ClockSub
380
+ require 'lib/clock'
381
+ "You turn the clock #{Clock.toggle}."
382
+ end
383
+
384
+ def StopwatchSub
385
+ require 'lib/stopwatch'
386
+ println "You turn the stopwatch #{Stopwatch.toggle}."
387
+ end
388
+
389
+ Colors = %i[red green yellow blue purple cyan white black].freeze
390
+ Styles = %i[bold italic underline blink flash inverse hidden strikethrough].freeze
391
+ SeventySpacesString = ' ' * 70
392
+
393
+ def ColorSub
394
+ style :bold
395
+ print "bold"
396
+ unstyle :bold
397
+ new_line
398
+
399
+ color :black_on_white
400
+ bgcolor :white
401
+ print "black on white"
402
+ unbgcolor :white
403
+ uncolor :black_on_white
404
+ new_line
405
+
406
+ Colors.each do |color_name|
407
+ color(color_name); print color_name.to_s; uncolor(color_name); new_line
408
+ end
409
+
410
+ Colors.each do |color_name|
411
+ bgcolor(color_name); print SeventySpacesString; unbgcolor(color_name); new_line
412
+ end
413
+
414
+ Styles.each do |style_name|
415
+ style(style_name); print style_name.to_s; unstyle(style_name); new_line
416
+ end
417
+
418
+ false
419
+ end
420
+
421
+ def CommandsSub
422
+ more(many_per_line(Inform::Grammar::Verbs.all(@player)))
423
+ end
424
+
425
+ def CommandsByModeSub
426
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
427
+ return CommandsSub() if special.nil? || (special.to_sym == :admin && !privileged?(:admin))
428
+ more(many_per_line(Inform::Grammar::Verbs.by_mode(special)))
429
+ end
430
+
431
+ def ConnectionsSub
432
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
433
+ if noun
434
+ println "#{player} #=> #{player.connections}"
435
+ return true
436
+ end
437
+ StoryTeller::IO::Session.players.each do |player|
438
+ println "#{player} #=> #{player.connections}"
439
+ end
440
+ true
441
+ end
442
+
443
+ def CopySub
444
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
445
+ if noun
446
+ o = noun.clone
447
+ @player << o
448
+ PronounNotice(o)
449
+ "You create a copy of " + the(noun) + "."
450
+ else
451
+ "Sorry, that could not be copied."
452
+ end
453
+ end
454
+
455
+ def CreateSub
456
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
457
+ words = text.split
458
+ words.shift while Inform::English::Articles.include?(words.first)
459
+ name = words.join(' ').strip
460
+ return "Creation failed." if name.empty?
461
+ o = Inform::Object.new(name: name)
462
+ @player << o
463
+ PronounNotice(o)
464
+ "You create " + a(o) + "!"
465
+ end
466
+
467
+ def CreateBeforeSub
468
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
469
+ "You are trying to " + text + " when you " + special + " " + the(second) + "."
470
+ end
471
+
472
+ def CreateBeforeRoomSub
473
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
474
+ "When you " + special + " " + the(location) + ", you will be informed that \"" + text + "\""
475
+ end
476
+
477
+ def CreateDoorSub
478
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
479
+ return unless noun.respond_to? :door_dir
480
+ here = location
481
+ room = here.linkto(noun.door_dir)
482
+ unless room
483
+ room = Room.create
484
+ room.description = "There is nothing here yet."
485
+ room.save
486
+ room.name = "Room #{room.id}"
487
+ room.save
488
+ room.link :author, @player
489
+ end
490
+
491
+ door = Door.new(name: text || 'door')
492
+ here << door
493
+ room << door.portal
494
+ door.to room
495
+
496
+ opposite_direc = Inform::English::CompassDirectionOpposites[noun]
497
+ door.door_dir = noun.door_dir
498
+ door.portal.door_dir = opposite_direc.door_dir
499
+ here.link noun.door_dir, door
500
+ room.link opposite_direc.door_dir, door.portal
501
+ room.refresh
502
+ here.refresh
503
+
504
+ PronounNotice(door.portal)
505
+ _invoke :Goto, room
506
+ end
507
+
508
+ def id_parse_routine
509
+ l = TryNumber(@wn); @wn += 1
510
+ return false unless l.number?
511
+ getobject(l.to_i)
512
+ end
513
+
514
+ class Room < Inform::Object; end
515
+ class Region < Inform::Object; end
516
+ class Area < Region; end
517
+
518
+ def CreateRoomSub
519
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
520
+ room = Room.create
521
+ room.description = "There is nothing here yet."
522
+ room.save
523
+ room.name = text ? text.titleize : "Room #{room.id}"
524
+ room.save
525
+ room.link :author, @player
526
+
527
+ # Links
528
+ if !noun.nil? && !second.nil? && [noun, second].all? { |a| a.respond_to? :door_dir }
529
+ parent(@player).link noun.door_dir, room
530
+ room.link second.door_dir, parent(@player)
531
+ elsif !noun.nil?
532
+ parent(@player).link noun.door_dir, room
533
+ room.link Inform::English::CompassDirectionOpposites[noun].door_dir, parent(@player)
534
+ end
535
+
536
+ _invoke :Goto, room
537
+ end
538
+
539
+ def CreatePairSub
540
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
541
+ words = text.split
542
+ words.shift while Inform::English::Articles.include?(words.first)
543
+ name = words.join(' ').strip
544
+ return "Creation failed." if name.empty?
545
+ o = Pairable::Pair.new(name: name)
546
+ @player << o
547
+ PronounNotice(o)
548
+ "You create " + a(o) + "!"
549
+ end
550
+
551
+ def DebugSub
552
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
553
+ if special_number1
554
+ session&.settings&.[]=(:parser_trace, special_number1)
555
+ debug special_number1
556
+ println "Set parser trace to level #{@parser_trace}."
557
+ else
558
+ debug
559
+ end
560
+
561
+ if defined? DEBUG
562
+ "Debugging is on."
563
+ else
564
+ "Debugging is off."
565
+ end
566
+ end
567
+
568
+ def DeleteSub
569
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
570
+ # Never remove stuff like Compass directions or the dark
571
+ return "Sorry, " + the(noun) + " is a library object." if noun.ephemeral?
572
+
573
+ noun.remove
574
+ The(noun) + " has been removed."
575
+ end
576
+
577
+ def DescendantsSub(include_ephemeral_objects = nil)
578
+ include_ephemeral_objects = false if include_ephemeral_objects.nil?
579
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
580
+ o = noun.object? ? noun : @player
581
+ d = o.descendants
582
+ if include_ephemeral_objects
583
+ ephemeral_descendants = o.ephemeral_children +
584
+ o.ephemeral_children.map(&:descendants).flatten
585
+ d |= ephemeral_descendants
586
+ end
587
+ unless special.nil?
588
+ converter = format(ConverterTemplate, data_format: special).to_sym
589
+ return d.send(converter, { include: %i[tagged modularized] }) if d.respond_to?(converter)
590
+ end
591
+ if d.empty?
592
+ "None"
593
+ else
594
+ d.identities
595
+ end
596
+ end
597
+
598
+ def AllDescendantsSub
599
+ DescendantsSub(true)
600
+ end
601
+
602
+ def ClearDescriptionSub
603
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
604
+ noun.description = noun == location ? "There is nothing here yet." : nil
605
+ noun.save
606
+ println The(noun) + " is now described as the following:"
607
+ return PlayerTo(noun, 2) if noun == location
608
+ _invoke :Examine, noun
609
+ end
610
+
611
+ def DescribeSub(obj = noun)
612
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
613
+ if !obj.nil? && !text.nil?
614
+ reset_prompt
615
+ obj.description = capitalize_sentences(ensure_punctuation(text))
616
+ obj.save
617
+ new_line
618
+ println The(obj) + " is now described as the following:"
619
+ return PlayerTo(obj, 2) if obj == location
620
+ _invoke :Examine, obj
621
+ elsif !solicited?
622
+ solicit "By all means, describe " + the(obj) + ": "
623
+ end
624
+ end
625
+
626
+ def DescribeRoomSub
627
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
628
+ DescribeSub(location)
629
+ end
630
+
631
+ def ClearRoomDescriptionSub
632
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
633
+ _invoke :ClearDescription, location
634
+ end
635
+
636
+ DefaultPurgeSubParams = { force: false }.freeze
637
+
638
+ def PurgeSub(params = {})
639
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
640
+ params = DefaultPurgeSubParams.merge(params)
641
+ force = params[:force]
642
+ # Stuff like Compass directions or the dark may not be destroyed
643
+ return "Sorry, " + the(noun) + " (#{noun.identity}) is a library object." if noun.ephemeral?
644
+
645
+ # The player must explicitly remove the :prized attribute from an
646
+ # object before destroying it
647
+ return The(noun) + " (#{noun.identity}) is a prized object." if noun.has? :prized
648
+
649
+ # Require confirmation when destroying character objects
650
+ if defined?(Character) && noun.is_a?(Character) && !force && !asked?
651
+ return yesorno "Are you sure you want to destroy the database record for the " +
652
+ "character #{noun} (#{noun.identity})? "
653
+ end
654
+
655
+ # TODO: Also check descendants of rooms in a hub area or region being destroyed
656
+ if defined?(Character) && noun.descendants.any?(Character) && !force
657
+ return "One or more Characters occupying " + the(noun) + " (#{noun.identity}) must " +
658
+ "be destroyed individually."
659
+ end
660
+
661
+ PlayerTo(@player.spawn_point) if noun == parent(@player)
662
+ message = "You have destroyed the database record for " + the(noun) + " (#{noun.identity})."
663
+ destroy noun
664
+ message
665
+ end
666
+
667
+ def PurgeDangerouslySub
668
+ PurgeSub(force: true)
669
+ end
670
+
671
+ def PurgeAllOrphansSub
672
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
673
+ return ask "Are you sure you want to purge all orphaned objects?" unless asked?
674
+ for obj in Inform::Object.all
675
+ next if obj.is_a?(Character) || obj.is_a?(Room)
676
+ unless obj.parent
677
+ destroy obj
678
+ println "You have destroyed the database record for " + a(obj) + "."
679
+ end
680
+ end
681
+ "Done purging orphaned objects from the database."
682
+ end
683
+
684
+ def ExportSub
685
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
686
+ # Never export stuff like Compass directions or the dark
687
+ return "Sorry, " + the(noun) + " is a library object." if noun.ephemeral?
688
+
689
+ type = special ? special.to_sym : :xml
690
+
691
+ if export noun, type
692
+ "Exported " + a(noun) + " [#{noun.identity}] as #{type}."
693
+ else
694
+ "Failed to export " + a(noun) + " [#{noun.identity}] as #{type}."
695
+ end
696
+ end
697
+
698
+ def ImportSub
699
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
700
+ file = text
701
+ return "Specify a file for importing." if file.nil?
702
+ return "That's not a file." unless File.exist? file
703
+
704
+ obj = import_object file
705
+ return "Import failed." if obj.nil?
706
+ println obj.inspect
707
+
708
+ move obj, @player
709
+ "Imported " + a(obj) + " [" + obj.identity + "]."
710
+ end
711
+
712
+ def FetchSub
713
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
714
+ return "Your search - #{text} - did not match any objects." unless noun
715
+
716
+ # Never fetch stuff like Compass directions or the dark
717
+ return "Sorry, " + the(noun) + " is a library object." if noun.ephemeral?
718
+
719
+ move noun, @player
720
+ PronounNotice(noun)
721
+ "Fetched " + a(noun) + " [" + noun.identity + "]."
722
+ end
723
+
724
+ def FindSub
725
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
726
+ return "Your search - #{text} - did not match any objects." if @search_results.empty?
727
+ n = @search_results.count
728
+ println "#{n} " + pluralize("instance", n) + " found."
729
+ @search_results.each do |obj|
730
+ print "Found " + a(obj) + " [" + obj.identity + "]"
731
+ if (parent_obj = parent(obj))
732
+ if parent_obj.has?(:supporter) then print " on "
733
+ else print " in "
734
+ end
735
+ print a(parent_obj) + " [" + parent_obj.identity + "]"
736
+ end
737
+ println "."
738
+ end
739
+ false
740
+ end
741
+
742
+ def FindByClassSub
743
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
744
+ results = Inform::Object.where(Sequel.like(:object_type, "%#{special}%")).order(:name, :id)
745
+ return "Your search - class #{special} - did not match any objects." if results.empty?
746
+ n = results.count
747
+ println "#{n} " + pluralize("instance", n) + " found."
748
+ results.each do |obj|
749
+ print "Found " + a(obj) + " [" + obj.identity + "]"
750
+ print " in " + a(parent(obj)) + " [" + parent(obj).identity + "]" if parent(obj)
751
+ println "."
752
+ end
753
+ false
754
+ end
755
+
756
+ def FindByPropertySub
757
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
758
+ query = text == 'anything' ? '' : text
759
+ results = Inform::Object.where(Sequel.like(:properties, "%:#{special}: #{query}%")).order(:name, :id)
760
+ return "Your search - for object #{special} set to #{text} - did not match any objects." if results.empty?
761
+ n = results.count
762
+ println "#{n} " + pluralize("instance", n) + " found."
763
+ results.each do |obj|
764
+ print "Found " + a(obj) + " [" + obj.identity + "]"
765
+ print " in " + a(parent(obj)) + " [" + parent(obj).identity + "]" if parent(obj)
766
+ println "."
767
+ end
768
+ false
769
+ end
770
+
771
+ def FindByDescriptionSub
772
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
773
+ query = text == 'anything' ? '' : text
774
+ results = Inform::Object.where(Sequel.like(:description, "%#{query}%")).order(:name, :id)
775
+ return "Your search - for object.description set to #{text} - did not match any objects." if results.empty?
776
+ n = results.count
777
+ println "#{n} " + pluralize("instance", n) + " found."
778
+ results.each do |obj|
779
+ print "Found " + a(obj) + " [" + obj.identity + "]"
780
+ print " in " + a(parent(obj)) + " [" + parent(obj).identity + "]" if parent(obj)
781
+ println "."
782
+ end
783
+ false
784
+ end
785
+
786
+ # TODO: Remove (2026-05-03)
787
+ # def find_by_tag_name(tag_name)
788
+ # Inform::Object.select_all(:object)
789
+ # .join(:tagged, object_id: :id)
790
+ # .join(:tag, id: :tag_id)
791
+ # .where([[Sequel[:tag][:name], tag_name]].to_h).order(:name, :id)
792
+ # end
793
+ def find_by_tag_name(tag_name)
794
+ tag = tag_name.to_s
795
+
796
+ Inform::Object.all
797
+ .select { |obj| Array(obj.tags).map(&:to_s).include?(tag) }
798
+ .sort_by { |obj| [Array(obj.name).join(', '), (obj.id || 0).to_i] }
799
+ end
800
+
801
+ def FindByAttributeSub
802
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
803
+ results = find_by_tag_name(special.to_s)
804
+ return "Your search - for objects having #{special} - did not match any objects." if results.empty?
805
+ n = results.count
806
+ println "#{n} " + pluralize("instance", n) + " found."
807
+ results.each do |obj|
808
+ print "Found " + a(obj) + " [" + obj.identity + "]"
809
+ print " in " + a(parent(obj)) + " [" + parent(obj).identity + "]" if parent(obj)
810
+ println "."
811
+ end
812
+ false
813
+ end
814
+
815
+ # TODO: Remove (2026-05-03)
816
+ # def find_by_module_name(module_name)
817
+ # Inform::Object.select_all(:object)
818
+ # .join(:modularized, object_id: :id)
819
+ # .join(:module, id: :module_id)
820
+ # .where([[Sequel[:module][:name], module_name]].to_h).order(:name, :id)
821
+ # end
822
+ def find_by_module_name(module_name)
823
+ mod = module_name.to_s
824
+
825
+ Inform::Object.all
826
+ .select { |obj| Array(obj.modules).map(&:to_s).include?(mod) }
827
+ .sort_by { |obj| [Array(obj.name).join(', '), (obj.id || 0).to_i] }
828
+ end
829
+
830
+ def FindByModuleSub
831
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
832
+ results = find_by_module_name(special.to_s)
833
+ return "Your search - for objects with the #{special} module - did not match any objects." if results.empty?
834
+ n = results.count
835
+ println "#{n} " + pluralize("instance", n) + " found."
836
+ results.each do |obj|
837
+ print "Found " + a(obj) + " [" + obj.identity + "]"
838
+ print " in " + a(parent(obj)) + " [" + parent(obj).identity + "]" if parent(obj)
839
+ println "."
840
+ end
841
+ false
842
+ end
843
+
844
+ def find_by_link_name_or_linked(link_name, linked_obj)
845
+ return Inform::Link.where([[Sequel[:link][:name], link_name]].to_h).order(:name, :id).all if linked_obj.nil?
846
+ Inform::Link.where(
847
+ Sequel.expr([[Sequel[:link][:name], link_name]].to_h) & (
848
+ Sequel.expr([[Sequel[:link][:from_id], linked_obj.id]].to_h) |
849
+ Sequel.expr([[Sequel[:link][:to_id], linked_obj.id]].to_h)
850
+ )
851
+ ).order(:name, :id)
852
+ end
853
+
854
+ def FindByLinkSub
855
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
856
+ results = find_by_link_name_or_linked(special.to_s, second)
857
+ return "Your search - for objects linked through #{special} - did not match any objects." if results.empty?
858
+ n = results.count
859
+ println "#{n} " + pluralize("instance", n) + " found."
860
+ results.each do |link|
861
+ obj = link.from
862
+ print "Found " + a(obj) + " [" + obj.identity + "]"
863
+ print " in " + a(parent(obj)) + " [" + parent(obj).identity + "]" if parent(obj)
864
+ print " linked to " + a(link.to) + " through " + link.name
865
+ println "."
866
+ end
867
+ false
868
+ end
869
+
870
+ def FlushSub
871
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
872
+ flush_automatically
873
+
874
+ if defined? FLUSH_IO_INSTANTLY
875
+ "IO will always flush."
876
+ else
877
+ "IO will buffer."
878
+ end
879
+ end
880
+
881
+ def ReadmeSub
882
+ file_path = 'README.md'
883
+ println markdown_metadata(file_path)
884
+ `cat #{file_path}`
885
+ end
886
+
887
+ def GenerateSub
888
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
889
+ "Not yet implemented."
890
+ end
891
+
892
+ def RegenerateSub
893
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
894
+ "Not yet implemented."
895
+ end
896
+
897
+ def GrammarsSub
898
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
899
+ Inform.load_grammars
900
+ "Reloaded grammar specifications."
901
+ end
902
+
903
+ def ReturnSub
904
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
905
+ destination = @player.linkto :previous_location
906
+ return "[Not a safe place.]" unless destination.is_a?(Inform::Object)
907
+ return "Cannot go to there." if destination == @player
908
+ PlayerTo(destination)
909
+ end
910
+
911
+ def HelpSub
912
+ return "That's not how life works." unless topic
913
+ verb = Inform::Grammar::Verbs.lookup topic.to_s, @player
914
+ if verb
915
+ "\e[51m#{verb}\e[56m"
916
+ else
917
+ "No help on that topic."
918
+ end
919
+ end
920
+
921
+ def htop
922
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
923
+ "Not yet implemented."
924
+ # htop_path = `which htop`.strip
925
+ # if htop_path.empty?
926
+ # if macos?
927
+ # return "Please install htop from: https://github.com/htop-dev/htop.git"
928
+ # else
929
+ # return "Please install htop : sudo apt install htop"
930
+ # end
931
+ # end
932
+ # println "#{htop_path}"
933
+ # output = IO.popen(htop_path, "r+") do |pipe|
934
+ # println pipe.readlines.join("\n")
935
+ # pipe.write "q"
936
+ # pipe.flush
937
+ # pipe.close_write
938
+ # begin
939
+ # pipe.read
940
+ # rescue StandardError => e
941
+ # log.warn e.message
942
+ # end
943
+ # end
944
+ # output = IO.popen(htop_path, "r+") do |pipe|
945
+ # out pipe.readlines.join("\n")
946
+ # pipe.write "q"
947
+ # pipe.flush
948
+ # pipe.close_write
949
+ # begin
950
+ # pipe.read
951
+ # rescue StandardError => e
952
+ # log.warn e.message
953
+ # end
954
+ # end
955
+ # true
956
+ end
957
+
958
+ def HistorySub
959
+ history = @player.inflib&.history
960
+ return "Your history is empty." if history.respond_to?(:empty?) && history.empty?
961
+ return history.join(Newline) if history.respond_to?(:join) && special_number1.nil?
962
+ @history_limit = special_number1
963
+ "You will now remember #{@history_limit} commands in your history."
964
+ end
965
+
966
+ def IdentifySub
967
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
968
+ noun.identity
969
+ end
970
+
971
+ def IdentitiesSub
972
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
973
+ return GlobalSequelIdentityMap.inspect if defined? GlobalSequelIdentityMap
974
+ "Identity mapping is not enabled."
975
+ end
976
+
977
+ InspectTemplate = %(%<short_name>s:
978
+ name: %<name>s
979
+ id: %<id>s
980
+ class: %<classification>s
981
+ description: %<description>s
982
+ parent: %<parent>s
983
+ children: %<children>s
984
+ created: %<created_at>s
985
+ modifed: %<modified_at>s
986
+ properties:
987
+ %<properties>s
988
+
989
+ attributes:
990
+ %<attributes>s
991
+
992
+ links:
993
+ %<links>s
994
+
995
+ modules:
996
+ %<modules>s).freeze
997
+
998
+ ObjectReprTemplate = '%<obj>s [%<identity>s]'.freeze
999
+ ObjectInspectFields = %i[
1000
+ id classification description short_name name parent children created_at modified_at
1001
+ properties attributes links modules
1002
+ ].freeze
1003
+ DefaultObjectValues = ObjectInspectFields.each_with_object({}) { |key, memo| memo[key] = 'none' }
1004
+ InspectDelimiter = %(\n ).freeze
1005
+ PropertyReprTemplate = '%<key>s: %<value>s'.freeze
1006
+
1007
+ def InspectSub
1008
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
1009
+ raise Parser::CantSeeError if noun.nil?
1010
+ noun.refresh if noun.respond_to? :refresh
1011
+
1012
+ object_field_values = {}
1013
+ object_field_values[:id] = noun.id
1014
+ object_field_values[:classification] = noun.classification
1015
+ object_field_values[:description] = noun.values[:description] unless noun.values[:description].empty?
1016
+ object_field_values[:short_name] = noun.short_name if !noun.short_name.nil? && !noun.short_name.empty?
1017
+ object_field_values[:name] = noun.name_words
1018
+ object_field_values[:created_at] = noun.created_at
1019
+ object_field_values[:modified_at] = noun.modified_at
1020
+ unless (pobj = parent(noun)).nil?
1021
+ object_field_values[:parent] = format(ObjectReprTemplate, obj: pobj, identity: pobj.identity)
1022
+ end
1023
+ unless noun.children.empty?
1024
+ object_field_values[:children] = noun.children.sort.map do |obj|
1025
+ format(ObjectReprTemplate, obj: obj, identity: obj.identity)
1026
+ end
1027
+ end
1028
+ unless noun.properties.empty?
1029
+ properties = noun.properties.sort.map do |key, value|
1030
+ format(PropertyReprTemplate, key: key, value: value)
1031
+ end
1032
+ object_field_values[:properties] = properties.join(InspectDelimiter)
1033
+ end
1034
+ unless noun.attributes.empty?
1035
+ object_field_values[:attributes] = noun.attributes.sort.join(InspectDelimiter)
1036
+ end
1037
+ object_field_values[:links] = noun.links.sort.join(InspectDelimiter) unless noun.links.empty?
1038
+ object_field_values[:modules] = noun.modules.sort.map(&:name).join(InspectDelimiter) unless noun.modules.empty?
1039
+
1040
+ format(InspectTemplate, **DefaultObjectValues, **object_field_values)
1041
+ end
1042
+
1043
+ def InspectLocationSub
1044
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
1045
+ invoke :Inspect, location
1046
+ end
1047
+
1048
+ def InspectParentSub
1049
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
1050
+ invoke :Inspect, parent(@player)
1051
+ end
1052
+
1053
+ def InspectModuleSub
1054
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
1055
+ mod = find_module(special.capitalize)
1056
+ return "No such module." if mod.nil?
1057
+ mod.ancestors.reverse.collect { |m| m.instance_methods(false) }.flatten
1058
+ end
1059
+
1060
+ def RequestAssistanceSub
1061
+ a = admins
1062
+ failure_message = "[There are currently no admins available. Please try again later.]"
1063
+ unless Inform::Game.config.include?(:admin_email)
1064
+ log.error "Please define AdminEmail in the Configuration module."
1065
+ return failure_message
1066
+ end
1067
+ if a.empty?
1068
+ return "#{failure_message}\n" +
1069
+ "[You may also try sending an e-mail to: #{Inform::Game.config[:admin_email]}]"
1070
+ end
1071
+ a.inform "Assistance request from #{@player.name} (#{@player.id}) " +
1072
+ "in " + the(location) + "(#{location.id}) at #{Time.now}"
1073
+ "[The admins have been informed of your request for assistance.]"
1074
+ end
1075
+
1076
+ def InterruptSub
1077
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
1078
+ if !noun.nil?
1079
+ noun.interrupt
1080
+ "You interrupt " + the(noun) + "."
1081
+ else
1082
+ @player.interrupt
1083
+ "You interrupt yourself."
1084
+ end
1085
+ end
1086
+
1087
+ def LibrariesSub
1088
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
1089
+ InformLibrary.libraries.each do |k, v|
1090
+ println "#{k} => #{v.identity} #{v}"
1091
+ end
1092
+ true
1093
+ end
1094
+
1095
+ ProtectedLinks = %i[author].freeze
1096
+
1097
+ def LinkSub
1098
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
1099
+ if special == noun
1100
+ to = second
1101
+ from = parent(@player)
1102
+ link = special.nil? || second.nil? ? noun.&(:door_dir) : special
1103
+ elsif !special.nil? && !noun.nil? && !second.nil?
1104
+ link = special
1105
+ from = noun
1106
+ to = second
1107
+ elsif !special.nil? && !noun.nil?
1108
+ link = special
1109
+ from = parent(@player)
1110
+ to = noun
1111
+ else
1112
+ link = noun.&:door_dir
1113
+ from = parent(@player)
1114
+ to = second
1115
+ end
1116
+
1117
+ if link.nil? || from.nil? || to.nil?
1118
+ "Sorry, I'm having trouble figuring out what you want to " \
1119
+ "link. (This is probably a bug.)"
1120
+ else
1121
+ from.link(link, to)
1122
+ "You link " + the(from) + " to " + the(to) + " through #{link}."
1123
+ end
1124
+ end
1125
+
1126
+ def ListTogetherSub
1127
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
1128
+ return "Only two objects may be listed together." if noun.nil? || second.nil?
1129
+ noun.link :list_together, second
1130
+ second.link :list_together, noun
1131
+ The(from) + " and " + the(to) + " will now be listed together."
1132
+ end
1133
+
1134
+ def UnlinkSub
1135
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
1136
+ if !noun.nil? && noun != special
1137
+ if !special.nil?
1138
+ link = special
1139
+ from = noun
1140
+ else
1141
+ link = noun.&:door_dir
1142
+ from = parent(@player)
1143
+ end
1144
+ else
1145
+ link = special
1146
+ from = parent(@player) || @player
1147
+ end
1148
+ if link.nil? || from.nil?
1149
+ return "Sorry, I'm having trouble figuring out what you want to unlink. " \
1150
+ "(This is probably a bug.)"
1151
+ end
1152
+ return "Sorry, that's a protected link." if ProtectedLinks.include?(link.to_sym) && !privileged?(:admin)
1153
+
1154
+ to = from.unlink link
1155
+ "The #{link} link between " + the(from) + " and " + the(to) + " has been severed."
1156
+ end
1157
+
1158
+ def LocateSub
1159
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
1160
+ parent_obj = parent(noun)
1161
+ A(noun) + " is in " + a(parent_obj) + " [" + parent_obj.identity + "]."
1162
+ end
1163
+
1164
+ def LocationSub
1165
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
1166
+ obj = location
1167
+ unless special.nil?
1168
+ converter = format(ConverterTemplate, data_format: special).to_sym
1169
+ return obj.send(converter, { include: %i[tagged modularized] }) if obj.respond_to? converter
1170
+ end
1171
+ "Your location is " + the(obj) + "."
1172
+ end
1173
+
1174
+ def LoginSub
1175
+ AfterRoutines()
1176
+ true
1177
+ end
1178
+
1179
+ def MaterializeSub
1180
+ AfterRoutines()
1181
+ true
1182
+ end
1183
+
1184
+ def LogsSub
1185
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
1186
+ `tail -n#{special_number1 || 20} #{ServerLogFile}`
1187
+ end
1188
+
1189
+ def all_other_players
1190
+ StoryTeller::IO::Session.players - [@player]
1191
+ end
1192
+
1193
+ def MassBootSub
1194
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
1195
+ players = all_other_players
1196
+ return "No other connections to drop." if players.empty?
1197
+ players.each { |player| StoryTeller::IO::Session.of(player).disconnect }
1198
+ "Dropped other connections."
1199
+ end
1200
+
1201
+ def CacheSub
1202
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
1203
+ return "Caching is not enabled." unless defined? GlobalCache
1204
+ GlobalCache.values.map(&:id)
1205
+ end
1206
+
1207
+ def ModulesSub
1208
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
1209
+ l = noun.modules.map(&:name)
1210
+ return The(noun) + " has no special modules." if l.empty?
1211
+ The(noun) + " is imbued with the #{l} module" + (l.length > 1 ? 's' : '') + "."
1212
+ end
1213
+
1214
+ def ApplyBehaviorSub
1215
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
1216
+ if !special.nil? && !noun.nil?
1217
+ mod = find_module(special)
1218
+ mod ||= find_module(special.capitalize)
1219
+ return "No such module." if mod.nil? || !mod.is_a?(Module)
1220
+ return The(noun) + " is already imbued with the #{mod} module." if noun.is_a?(mod)
1221
+ noun.mod mod
1222
+ if defined?(DEBUG)
1223
+ mod.instance_methods.each do |method|
1224
+ println "#{noun} now responds to #{method}" if noun.respond_to?(method)
1225
+ end
1226
+ println "Is " + the(noun) + " now a #{mod}? #{noun.is_a? mod}."
1227
+ end
1228
+ "You have added the #{mod} module to " + the(noun) + "."
1229
+ elsif !noun.nil?
1230
+ ModulesSub()
1231
+ else
1232
+ "Module application failed."
1233
+ end
1234
+ end
1235
+
1236
+ def RemoveBehaviorSub
1237
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
1238
+ if !special.nil? && !noun.nil?
1239
+ mod = find_module special
1240
+ mod ||= find_module special.capitalize
1241
+ println "No such module." unless !mod.nil? && mod.is_a?(Module)
1242
+ if !mod.nil? && mod.is_a?(Module)
1243
+ return The(noun) + " is not imbued with the #{mod} module." unless noun.is_a? mod
1244
+ else
1245
+ mod = 'unknown'
1246
+ end
1247
+ noun.unmod special
1248
+ if defined? DEBUG
1249
+ mod.instance_methods.each do |method|
1250
+ println "#{noun} now responds to #{method}" if noun.respond_to? method
1251
+ end
1252
+ println "Is " + the(noun) + " now a #{mod}? #{noun.is_a? mod}."
1253
+ end
1254
+ "You have removed the #{mod} module from " + the(noun) + "."
1255
+ elsif noun
1256
+ ModulesSub()
1257
+ else
1258
+ "Module removal failed."
1259
+ end
1260
+ end
1261
+
1262
+ def motd
1263
+ motd_file = File.new(MudLib::MOTD_FILE_PATH)
1264
+ require motd_file
1265
+
1266
+ %(#{MOTD}
1267
+ The current time is now #{Time.new}.)
1268
+ end
1269
+
1270
+ def NameSub
1271
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
1272
+ return @player.name if noun.nil?
1273
+ return noun.name if text.nil?
1274
+
1275
+ the_old_name = the(noun)
1276
+ log.warn "#{self}.NameSub text: #{text}"
1277
+ noun.short_name = text
1278
+ noun.save
1279
+ PlayerTo(noun, 1) if noun == location
1280
+ "You rename " + the_old_name + " to " + a(noun) + "."
1281
+ end
1282
+
1283
+ def NameRoomSub
1284
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
1285
+ log.warn "#{self}.NameRoomSub text: #{text}"
1286
+ _invoke :Name, location, text[0].capitalize + text[1..]
1287
+ end
1288
+
1289
+ def Places1Sub
1290
+ unless special.nil?
1291
+ dataset = Inform::Room.naked.all
1292
+ converter = format(ConverterTemplate, data_format: special).to_sym
1293
+ return dataset.send(converter, { include: %i[tagged modularized] }) if dataset.respond_to? converter
1294
+ end
1295
+ Inform::Room.order(:name).all.identities
1296
+ end
1297
+
1298
+ def Objects1Sub
1299
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
1300
+ # TODO: Support pagination
1301
+ if ObjectSpace.enabled?
1302
+ s = ''
1303
+ ObjectSpace.each_object(Inform::Object) { |x| s << x + ", " }
1304
+ return s[0..-3]
1305
+ end
1306
+ unless special.nil?
1307
+ dataset = Inform::Object.naked.all
1308
+ converter = format(ConverterTemplate, data_format: special).to_sym
1309
+ return dataset.send(converter, { include: %i[tagged modularized] }) if dataset.respond_to? converter
1310
+ end
1311
+ Inform::Object.order(:name).all.identities
1312
+ end
1313
+
1314
+ def OnSub
1315
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
1316
+ "You are trying to make " + the(noun) + " " + text + " when the player does a " + special + " to it."
1317
+ end
1318
+
1319
+ def OptionsSub
1320
+ log.debug "OptionsSub special: #{special}"
1321
+ opts = session&.settings
1322
+ return "You can't set preferences." if opts.nil?
1323
+ if !special.nil? && !text.nil?
1324
+ text = nil if text == 'nothing'
1325
+ opts[special] = text
1326
+ end
1327
+ opts.to_yaml
1328
+ end
1329
+
1330
+ def os
1331
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
1332
+ "The operating system of this server is " + java.lang.System.getProperty('os.name') + "."
1333
+ end
1334
+
1335
+ def PredictableSub
1336
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
1337
+ setrandom 100
1338
+ "[Random number generator now predictable.]"
1339
+ end
1340
+
1341
+ def SpecificallyPredictableSub
1342
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
1343
+ setrandom special_number1.to_i
1344
+ "[Random number generator now predictable.]"
1345
+ end
1346
+
1347
+ def PrepositionsSub
1348
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
1349
+ Inform::English::Prepositions
1350
+ end
1351
+
1352
+ def ProceedSub
1353
+ return "Not implemented." unless @player.respond_to? :proceed
1354
+ @player.proceed
1355
+ end
1356
+
1357
+ def ProcsSub
1358
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
1359
+ available_processors
1360
+ end
1361
+
1362
+ def CpuSub
1363
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
1364
+ `ps -p #{pid} -o %cpu | tail -n1 | tr -s ' ' | cut -d ' ' -f 2`
1365
+ end
1366
+
1367
+ def ProfileSub
1368
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
1369
+ bytes = memory_usage + ' B'
1370
+ if !special.nil? && defined?(Unit)
1371
+ unit_name = special.length <= 2 ? special.upcase : special
1372
+ bytes = Unit.new(bytes).to(unit_name).to_s
1373
+ end
1374
+ Story + " is using #{bytes.to_human} of memory."
1375
+ rescue ArgumentError
1376
+ "The unit given is invalid."
1377
+ end
1378
+
1379
+ def ReferenceSub
1380
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
1381
+ the_old_name = the(noun)
1382
+ noun.values[:name] = text
1383
+ noun.save
1384
+ print "You may now refer to " + the_old_name + " as "
1385
+ synonyms = text.split(/[,\s]+/)
1386
+ if synonyms.length > 1
1387
+ synonyms[0..-2].each do |s|
1388
+ print "'" + s + "', "
1389
+ end
1390
+ print "or "
1391
+ end
1392
+ "'#{synonyms.last}'."
1393
+ end
1394
+
1395
+ def RefreshSub
1396
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
1397
+ noun.refresh
1398
+ noun.tags
1399
+ "Refreshed " + the(noun) + "."
1400
+ end
1401
+
1402
+ def reload_feature_safely(feature)
1403
+ reload_feature(feature)
1404
+ println "Reloaded #{feature}"
1405
+ rescue StandardError => e
1406
+ println "Failed to reload #{feature}: #{e.message}"
1407
+ end
1408
+
1409
+ def reload
1410
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
1411
+ reload_feature_safely(EntryPoint)
1412
+
1413
+ features = ['inform.rb', 'english.rb', 'metaverbs.rb', 'emotes.rb', 'verbs.rb']
1414
+ features.each do |feature|
1415
+ reload_feature_safely(feature)
1416
+ end
1417
+
1418
+ println "Reloading grammars..."
1419
+ Inform.load_grammar 'grammar'
1420
+ Inform.load_grammar 'emotes'
1421
+
1422
+ Inform.reload_game
1423
+
1424
+ "Done reloading."
1425
+ end
1426
+
1427
+ def GarbageCollectionSub
1428
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
1429
+ add_event(delay: 1) do
1430
+ collect_garbage
1431
+ println "Garbage collection complete."
1432
+ end
1433
+ "Having the Java Virtual Machine collect its garbage..."
1434
+ end
1435
+
1436
+ def LibraryMethodsSub
1437
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
1438
+ StoryTeller::Library.methods_index
1439
+ end
1440
+
1441
+ def MethodSub
1442
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
1443
+ m = special.to_sym
1444
+ print "The method ##{m}(...) is "
1445
+ if noun
1446
+ print "not " unless noun.respond_to?(m)
1447
+ "available on " + the(noun) + "."
1448
+ else
1449
+ print "not " unless StoryTeller::Library.methods_index.include?(m)
1450
+ "available."
1451
+ end
1452
+ end
1453
+
1454
+ def ResetSub
1455
+ noun.properties.clear
1456
+ noun.save
1457
+ "You have reset the properties for " + the(noun) + "."
1458
+ end
1459
+
1460
+ def RestartSub
1461
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
1462
+ if time
1463
+ println "Time: #{time}"
1464
+ elsif special_number1
1465
+ println "Number: #{special_number1}"
1466
+ end
1467
+ add_event(delay: 2.0) do
1468
+ StoryTeller::IO::Session.players.inform "[ Broadcast: Scheduled server restart. ]"
1469
+ StoryTeller::IO::Session.players.each do |player|
1470
+ StoryTeller::IO::Session.of(player).disconnect "Server is restarting."
1471
+ end
1472
+ sleep 1
1473
+ builder = java.lang.ProcessBuilder.new('./zmud')
1474
+ builder.start()
1475
+ java.lang.System.exit(0)
1476
+ end
1477
+ "Scheduled server restart."
1478
+ end
1479
+
1480
+ def RestoreSub(save_dir_path = StoryTeller::Runtime.game_saves_dir_path)
1481
+ FileUtils.mkdir_p(save_dir_path)
1482
+ default_restore_file = `ls -t #{save_dir_path}`.strip.split.first
1483
+
1484
+ return solicit("Enter a file path.\nDefault is \"#{default_restore_file}\": ") unless solicited?
1485
+
1486
+ save_file_name = text.empty? ? default_restore_file : text
1487
+ save_file_path = File.join(save_dir_path, save_file_name)
1488
+ return println L__M(:Restore, 1) unless File.exist?(save_file_path)
1489
+ restoration_successful = restore(save_file_path)
1490
+ return println L__M(:Restore, 1) unless restoration_successful
1491
+ println "Restored."
1492
+ prompt
1493
+ session.state = :playing
1494
+ end
1495
+
1496
+ def restore(file_path)
1497
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
1498
+ restore_cmd = []
1499
+ restore_cmd << File.join('scripts', 'restore.rb')
1500
+ restore_cmd << file_path
1501
+ # restore_cmd << "--wet-run"
1502
+ restore_cmd = restore_cmd.join(' ')
1503
+ result = `#{restore_cmd}`.strip
1504
+ log.debug "Restored: #{result}"
1505
+ result == :specific
1506
+ end
1507
+
1508
+ # TODO: Implement unit test for this TESTME
1509
+ def RegionsScope
1510
+ case @scope_stage
1511
+ when 1 then false # Return single objects only
1512
+ when 2
1513
+ regions = Inform::Object.where(object_type: 'Region').all
1514
+ ScopeWithin(regions); true
1515
+ when 3 then L__M(:Take, 8) # TODO: This is broken somehow FIXME
1516
+ end
1517
+ end
1518
+
1519
+ def RegionSub
1520
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
1521
+ return "For some reason your location cannot be determined." if location.nil?
1522
+ location.link :region, noun if !noun.nil? && noun.is_a?(Region)
1523
+ region = location.linkto :region
1524
+ loop do
1525
+ break if parent(region).nil?
1526
+ println "You are in a sub-region called " + A(region) + " [#{region.identity}]."
1527
+ region = parent(region)
1528
+ end
1529
+ return "This room does not belong to any region." if region.nil?
1530
+ "The main region here is called " + A(region) + " [#{region.identity}]."
1531
+ end
1532
+
1533
+ def RegionsSub
1534
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
1535
+ regions = Inform::Object.where(object_type: 'Region').order(:name).all
1536
+ return "There are no regions." if regions.empty?
1537
+ println "The following regions are available:"
1538
+ regions.identities
1539
+ end
1540
+
1541
+ def RoomsSub
1542
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
1543
+ unless special.nil?
1544
+ dataset = Room.naked.all
1545
+ converter = format(ConverterTemplate, data_format: special).to_sym
1546
+ return dataset.send(converter, { include: %i[tagged modularized] }) if dataset.respond_to? converter
1547
+ end
1548
+ Room.all.identities
1549
+ end
1550
+
1551
+ def RootSub
1552
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
1553
+ obj = noun || @player
1554
+ root = obj.root
1555
+ "The root of " + the(obj) + " is " + a(root) + " [" + root.identity + "]."
1556
+ end
1557
+
1558
+ def SaveSub
1559
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
1560
+ return "It would take a very long time to save everything at once." if noun.nil?
1561
+ noun.save
1562
+ The(noun) + " has been saved."
1563
+ end
1564
+
1565
+ def send_email(to, from, subject, message = '', attachment = nil)
1566
+ email_cmd = []
1567
+ email_cmd << 'scripts/email.rb'
1568
+ email_cmd << %(--to="#{to}")
1569
+ email_cmd << %(--from="#{from}")
1570
+ email_cmd << %(--subject="#{subject}")
1571
+ email_cmd << %(--message="#{message}")
1572
+ email_cmd << %(--attachment="#{attachment}")
1573
+ email_cmd = email_cmd.join(' ')
1574
+ `#{email_cmd}`.strip
1575
+ end
1576
+
1577
+ def SessionsSub
1578
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
1579
+ StoryTeller::IO::Session.players.each do |player|
1580
+ session = StoryTeller::IO::Session.of(player)
1581
+ println "#{player} #=> #{session} [#{session.status}:#{session.state}]"
1582
+ end
1583
+ true
1584
+ end
1585
+
1586
+ def SiblingsSub
1587
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
1588
+ o = noun.object? ? noun : @player
1589
+ s = o.siblings
1590
+ unless special.nil?
1591
+ converter = format(ConverterTemplate, data_format: special).to_sym
1592
+ return s.send(converter, { include: %i[tagged modularized] }) if s.respond_to? converter
1593
+ end
1594
+ s.sort.identities
1595
+ end
1596
+
1597
+ # override :InformShowVerbSub, :ShowVerbSub
1598
+ def ShowVerbSub
1599
+ log.warn "#{self}.Metaverbs#ShowVerbSub"
1600
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
1601
+ InformShowVerbSub()
1602
+ end
1603
+
1604
+ # override :InformShowobjSub, :ShowobjSub
1605
+ def ShowobjSub
1606
+ log.warn "#{self}.Metaverbs#ShowobjSub"
1607
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
1608
+ InformShowobjSub()
1609
+ end
1610
+
1611
+ def StateSub
1612
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
1613
+ if !noun.nil? && noun != special
1614
+ return The(noun) + " has no session." unless noun.respond_to?(:session) && !noun.session.nil?
1615
+ noun.session_state = noun.session.state = special.to_sym if special
1616
+ The(noun) + "'s session's state is #{noun.session.state}."
1617
+ else
1618
+ return "Your character has no session." unless @player.respond_to?(:session) && !@player.session.nil?
1619
+ @player.session_state = @player.session.state = special.to_sym unless special.nil?
1620
+ "Your session's state is #{@player.session.state}."
1621
+ end
1622
+ end
1623
+
1624
+ def EphemeralObjectsSub
1625
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
1626
+ Inform::EphemeralObjects.values.map(&:name)
1627
+ end
1628
+
1629
+ def SystemSpecsSub
1630
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
1631
+ class_loader_methods = (class_loader.methods - Object.methods).grep(/get_.*/).sort
1632
+ class_loader_parent = class_loader.get_parent()
1633
+ class_loader_grandparent = class_loader_parent.get_parent()
1634
+ class_loader_great_grandparent = class_loader_grandparent.get_parent()
1635
+ println "JRuby:"
1636
+ println " Class loader:"
1637
+ println " #{class_loader} (#{class_loader.inspect})"
1638
+ println " Classloader methods:"
1639
+ println class_loader_methods.inspect
1640
+ println " Classpath:"
1641
+ println " #{$CLASSPATH}"
1642
+ println " Packages:"
1643
+ class_loader.packages.each { |x| println " #{x}" }
1644
+ println " URLs:"
1645
+ class_loader.getURLs().each { |x| println " #{x}" }
1646
+ println " Parent:"
1647
+ println " #{class_loader_parent} (#{class_loader_parent.inspect})"
1648
+ println " Grandparent:"
1649
+ println " #{class_loader_grandparent} (#{class_loader_grandparent.inspect})"
1650
+ println " Great-grandparent:"
1651
+ println " #{class_loader_great_grandparent} (#{class_loader_great_grandparent.inspect})"
1652
+
1653
+ println
1654
+ println " Runtime:"
1655
+ println " #{JRuby.runtime} (#{JRuby.runtime.inspect})"
1656
+ println " Runtime methods:"
1657
+ println " #{(JRuby.runtime.methods - Object.methods).grep(/get_.*/).sort.inspect}"
1658
+ println
1659
+ println " Object space:"
1660
+ println " #{object_space} (#{object_space.inspect})"
1661
+ println " #{(object_space.methods - Object.methods).grep(/get_.*/).sort.inspect}"
1662
+ println
1663
+ println " Load service:"
1664
+ println " #{load_service} (#{load_service.inspect})"
1665
+ println " Load service features:"
1666
+ load_service.loaded_features.each { |x| println " #{x}" }
1667
+ println " Load service methods:"
1668
+ println " #{(load_service.methods - Object.methods).grep(/get_.*/).sort.inspect}"
1669
+ true
1670
+ rescue NotImplementedError
1671
+ "Not implemented."
1672
+ end
1673
+
1674
+ def ShutdownSub
1675
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
1676
+ if !time.nil?
1677
+ println "Time: #{time}"
1678
+ elsif !special_number1.nil?
1679
+ println "Number: #{special_number1}"
1680
+ end
1681
+ add_event(delay: 2.0) do
1682
+ StoryTeller::IO::Session.players.inform "[ Broadcast: Shutting down. ]"
1683
+ StoryTeller::IO::Session.players.each do |player|
1684
+ StoryTeller::IO::Session.of(player).disconnect "Server is shutting down."
1685
+ end
1686
+ sleep 1
1687
+ java.lang.System.exit(0)
1688
+ end
1689
+ "Scheduled server shutdown."
1690
+ end
1691
+ alias shutdown ShutdownSub
1692
+ alias shut_down shutdown
1693
+
1694
+ def StackTraceSub
1695
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
1696
+ caller.join("\n")
1697
+ end
1698
+
1699
+ def SubscribersSub
1700
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
1701
+ if noun.nil?
1702
+ Inform::Subscribers::ALL.each do |key, value|
1703
+ println "#{key} #=> #{value}"
1704
+ end
1705
+ else
1706
+ noun.subscribers.each do |subscriber|
1707
+ println subscriber.to_s
1708
+ end
1709
+ end
1710
+ true
1711
+ end
1712
+
1713
+ def TagsSub
1714
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
1715
+ if noun.attributes.empty?
1716
+ The(noun) + " has no attributes."
1717
+ else
1718
+ The(noun) + " has " + noun.attributes.join(' ') + "."
1719
+ end
1720
+ end
1721
+
1722
+ ProtectedTags = %w[admin builder].freeze
1723
+
1724
+ def TagSub
1725
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
1726
+ attributes = text ? text.split : []
1727
+ attributes -= language_descriptors.keys
1728
+ attributes -= ProtectedTags unless privileged?(:admin)
1729
+ unless attributes.empty?
1730
+ if attributes.any? { |a| /^!/.match?(a) }
1731
+ attributes.each do |attribute|
1732
+ if attribute.gsub!(/(not|!)/, '')
1733
+ take noun, attribute.to_sym
1734
+ else
1735
+ give noun, attribute.to_sym
1736
+ end
1737
+ end
1738
+ else
1739
+ give noun, attributes
1740
+ end
1741
+ end
1742
+ TagsSub()
1743
+ end
1744
+
1745
+ def UntagSub
1746
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
1747
+ attributes = text ? text.split : []
1748
+ attributes -= language_descriptors.keys
1749
+ attributes -= ProtectedTags unless privileged?(:admin)
1750
+ unless attributes.empty?
1751
+ take noun, attributes unless attributes.empty?
1752
+ println The(noun) + " no longer has " + attributes.join(' ') + "."
1753
+ end
1754
+ TagsSub()
1755
+ end
1756
+
1757
+ def TagRoomSub
1758
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
1759
+ invoke :Tag, location, text
1760
+ end
1761
+
1762
+ def UntagRoomSub
1763
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
1764
+ invoke :Untag, location, text
1765
+ end
1766
+
1767
+ def UsersSub
1768
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
1769
+ Account.select(:username).all.map(&:username)
1770
+ end
1771
+
1772
+ def CreateUserSub
1773
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
1774
+ account = Account.find(username: text)
1775
+ return "There is already an account with that username." unless account.nil?
1776
+ account = Account.create(username: text)
1777
+ return "Failed to create account." if account.nil?
1778
+ "Created account for #{account.username}."
1779
+ end
1780
+
1781
+ def DeleteUserSub
1782
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
1783
+ account = Account.find(username: text)
1784
+ return "There is no account with that username." if account.nil?
1785
+ return ask("Are you sure you want to delete the account for #{text}?") unless asked?
1786
+ if account.destroy
1787
+ "Deleted account for #{account.username}."
1788
+ else
1789
+ "Failed to delete account."
1790
+ end
1791
+ end
1792
+
1793
+ def PasswordSub
1794
+ if session[:account].nil? && session[:password].nil?
1795
+ account = Account.find_by_character @player
1796
+ return "There is no account associated with your user!" if account.nil?
1797
+ session[:account] = account
1798
+ solicit "Enter new password: "
1799
+ elsif session[:password].nil?
1800
+ return solicit "Enter new password: " if text.empty?
1801
+ session[:password] = text
1802
+ solicit "Confirm new password: "
1803
+ elsif !session[:account].nil? && !session[:password].nil?
1804
+ password = session.delete(:password)
1805
+ account = session.delete(:account)
1806
+ if text == password
1807
+ account.password = text
1808
+ if account.save
1809
+ println "Set password for #{account.username}."
1810
+ else
1811
+ println "Failed to set password."
1812
+ end
1813
+ else
1814
+ println "Passwords do not match."
1815
+ end
1816
+ session.state = :playing
1817
+ end
1818
+ end
1819
+
1820
+ def UserPasswordSub
1821
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
1822
+ if session[:account].nil? && session[:password].nil?
1823
+ account = @search_results.first
1824
+ return "There is no account with that username." if account.nil?
1825
+ session[:account] = account
1826
+ solicit "Enter new password: "
1827
+ elsif session[:password].nil?
1828
+ session[:password] = text
1829
+ solicit "Confirm new password: "
1830
+ elsif !session[:account].nil? && !session[:password].nil?
1831
+ password = session.delete(:password)
1832
+ account = session.delete(:account)
1833
+ if text == password
1834
+ account.password = text
1835
+ if account.save
1836
+ println "Set password for #{account.username}."
1837
+ else
1838
+ println "Failed to set password."
1839
+ end
1840
+ else
1841
+ println "Passwords do not match."
1842
+ end
1843
+ session.state = :playing
1844
+ end
1845
+ end
1846
+
1847
+ def CharactersSub
1848
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
1849
+ return Inform::Object.where(Sequel.like(:object_type, "%Character%")).order(:name).all if text.nil?
1850
+ account = Account.find(username: text)
1851
+ return "There is no account with that username." if account.nil?
1852
+ "Characters for account #{account} are #{account.characters}."
1853
+ end
1854
+
1855
+ def TeleportSub
1856
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
1857
+ target = noun
1858
+ l = second || @player.location
1859
+ target.event do
1860
+ target.println "Inexplicably, you feel momentarily serene."
1861
+ target.new_line
1862
+ target.inflib.PlayerTo(l)
1863
+ end
1864
+ "You teleport " + the(target) + " to " + the(l) + "."
1865
+ end
1866
+
1867
+ def ThreadSub
1868
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
1869
+ println "Main thread is #{Thread.main}"
1870
+ println "Current thread is #{Thread.current}"
1871
+ true
1872
+ end
1873
+
1874
+ def TimeSub
1875
+ Time.now
1876
+ end
1877
+
1878
+ def ActiveSub
1879
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
1880
+ active = Inform::Events::ActiveObjects.to_a - Inform::Daemons::Schedules.keys.to_a
1881
+ return "No active objects." if active.empty?
1882
+ active.sort
1883
+ end
1884
+
1885
+ def DaemonsSub
1886
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
1887
+ println "[Daemons listing " + (Inform::Daemons.on? ? "on" : "off") + ".]"
1888
+ println "Scanning daemons..."
1889
+ add_event do
1890
+ Inform::Daemons.rescan
1891
+ daemons = Inform::Daemons::Spawn.map(&:name).sort
1892
+ return "There are no known daemons." if daemons.empty?
1893
+ n = daemons.length
1894
+ println "There " + IsorAre(n) + " active " + pluralize("daemon", n) + ":"
1895
+ daemons
1896
+ end
1897
+ true
1898
+ end
1899
+
1900
+ def daemons_listing_status
1901
+ return "on" if Inform::Daemons.on?
1902
+ "off"
1903
+ end
1904
+
1905
+ def DaemonsOnSub
1906
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
1907
+ Inform::Daemons.start
1908
+ "[Daemons listing #{daemons_listing_status}.]"
1909
+ end
1910
+
1911
+ def DaemonsOffSub
1912
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
1913
+ Inform::Daemons.stop
1914
+ "[Daemons listing #{daemons_listing_status}.]"
1915
+ end
1916
+
1917
+ def FrequencySub
1918
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
1919
+ Inform::Game.config[:daemons_period] = special_number1 unless special_number1.nil?
1920
+ "Daemon frequency is #{Inform::Game.config[:daemons_period]} milliseconds."
1921
+ end
1922
+
1923
+ def TimersOnSub
1924
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
1925
+ @debug_flag = @debug_flag | 4; "[Timers listing on.]"
1926
+ end
1927
+
1928
+ def TimersOffSub
1929
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
1930
+ @debug_flag = @debug_flag & 11; "[Timers listing off.]"
1931
+ end
1932
+
1933
+ def TraceOnSub
1934
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
1935
+ DebugSub()
1936
+ end
1937
+
1938
+ def TraceLevelSub
1939
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
1940
+ DebugSub()
1941
+ end
1942
+
1943
+ def VocabularySub
1944
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
1945
+ Inform::Dictionary.sort.to_s
1946
+ end
1947
+
1948
+ def WeatherSub
1949
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
1950
+ weather = noun
1951
+ return "For some reason your location cannot be determined." if location.nil?
1952
+ area = location.linkto :area
1953
+ loop do
1954
+ area_parent = parent(area)
1955
+ break if area_parent.nil?
1956
+ area = area_parent
1957
+ end
1958
+ if weather.nil?
1959
+ return "No weather here." if area.nil?
1960
+ current_weather = area.children.find { |o| o.is_a? Weather }
1961
+ return "No weather here." if current_weather.nil?
1962
+ new_line
1963
+ style :bold
1964
+ color :yellow
1965
+ println current_weather.name
1966
+ uncolor :yellow
1967
+ unstyle :bold
1968
+ return current_weather.description
1969
+ end
1970
+ return "Cannot set weather here." if area.nil?
1971
+ area.children.each { |o| o.remove if o.is_a? Weather }
1972
+ move weather, area
1973
+ "The weather for " + the(area) + " is now " + the(weather) + "."
1974
+ end
1975
+
1976
+ def WhoamiSub
1977
+ "You are " + a(@player) + "."
1978
+ end
1979
+
1980
+ def WhoamiVerboseSub
1981
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
1982
+ "#{@player.identity} (" + a(@player) + ") => " +
1983
+ StoryTeller::IO::Session.of(@player).channel.to_s
1984
+ end
1985
+
1986
+ def WrapSub
1987
+ session = StoryTeller::IO::Session.of(@player)
1988
+ return "Cannot set preferences." if session.preferences.nil?
1989
+ word_wrap = special_number1
1990
+ # prefs.word_wrap = word_wrap unless word_wrap.nil?
1991
+ session.preferences[:word_wrap] = word_wrap unless word_wrap.nil?
1992
+ log.debug "#{@player} preferences are: #{session.preferences.inspect}"
1993
+ "Lines will now wrap at #{session.preferences[:word_wrap]} columns."
1994
+ end
1995
+
1996
+ def VisitedSub
1997
+ @player.visited.join(', ')
1998
+ end
1999
+
2000
+ def markdown_metadata(file_path, metadata = {})
2001
+ metadata['path'] = file_path
2002
+ "\n#{metadata.to_yaml}---"
2003
+ end
2004
+
2005
+ def ruby_metadata(file_path)
2006
+ "# file: #{file_path}"
2007
+ end
2008
+
2009
+ def ConcatenateSub
2010
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
2011
+ file_path = text
2012
+ println ruby_metadata(file_path) if /.*\.rb$/.match?(file_path)
2013
+ println markdown_metadata(file_path) if /.*\.md$/.match?(file_path)
2014
+ `cat #{file_path}`
2015
+ end
2016
+
2017
+ def ListStatsSub
2018
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
2019
+ file_path = text
2020
+ return `/bin/ls -l #{file_path}`.strip unless file_path.empty?
2021
+ `/bin/ls -l`.strip
2022
+ end
2023
+
2024
+ def HostSub
2025
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
2026
+ `hostname`.strip
2027
+ end
2028
+
2029
+ def pid
2030
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
2031
+ java.lang.management.ManagementFactory.getRuntimeMXBean().name.split('@').first
2032
+ rescue StandardError
2033
+ println "I can't seem to determine my process ID: #{e.message}"
2034
+ false
2035
+ end
2036
+ end
2037
+
2038
+ # module PrivilegedInformVerbs
2039
+ module PrivilegedInformVerbs
2040
+ def ScopeSub
2041
+ raise Parser::VerbUnrecognized unless privileged?(:admin)
2042
+
2043
+ super
2044
+ end
2045
+
2046
+ def XTreeSub
2047
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
2048
+
2049
+ super
2050
+ end
2051
+
2052
+ def GotoSub
2053
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
2054
+
2055
+ super
2056
+ end
2057
+
2058
+ def GonearSub
2059
+ raise Parser::VerbUnrecognized unless privileged?(:builder)
2060
+
2061
+ super
2062
+ end
2063
+ end
2064
+
2065
+ Inform::Verbs.prepend PrivilegedInformVerbs
2066
+ Inform::Verbs.include Metaverbs