inform-runtime 1.0.4

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