-
Notifications
You must be signed in to change notification settings - Fork 3
/
reverb.lic
512 lines (440 loc) · 18.5 KB
/
reverb.lic
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
=begin
Adds convenience and safety features for certain in-game verbs and other general functionality improvements.
Configuration will be added in a later release. While reverb is running, the following changes are made to commands
input through the front end. (Commands generated by scripts are unaffected):
* GO will usually close your locker first, if it is open.
* PUT <item> is forbidden to avoid accidentally dropping items. (Use DROP instead)
* SMILE <word> is changed to SMILE ::<word> to avoid awkwardness when smiling at a player whose name you mistyped or
that has left the room. This only occurs if there is exactly one word and no punctuation, so you can still
"smile happily." but you cannot "smile happily" (unless, of course, there is a player named Happily.)
* INVENTORY is fully explanded to INVENTORY (which is allowed during RT) rather than I/IN/INV/INVE (which are not).
(I/IN/INV/INVE incur RT because the enginer checks for INQUIRE/INVEST first, per Wyrom)
* INVENTORY LOCATION shows the number (and maximum number) of functional/total slots per displayed location.
* GIVE <player> ALL will give a player all of your silver without you needing to look up how much silver that is
first.
* GIVE <player> will attempt to autocomplete to a player in your current room. 'give ath' will prefer the Athalamar
in your room over the Athalla who happens to show first in FIND. GIVE <item> TO <target> is not affected.
* GIVE <player> 5k will give a player 5000 silver. GIVE <player> 5m will give a player 5,000,000 silver. This
also works for PAY, EXCHANGE, DROP, BANK, WITHDRAW, DEPOSIT, BANK and SHOP.
* GS3-era language verbs ('common', 'elven', 'guildspeak', etc.) are re-enabled and rewritten to SPEAK <language>.
This includes language verbs that never existed as their own verbs, such as Aelotian, Dhenarsi, Faendryl, etc.
* You can now SING ~language to sing in that language without changing your current default language. This also
applies to LORESING and RECITE.
* SING @target is remapped to SING ::target for consistency with SAY. (Likewise with LORESING and RECITE.)
author: LostRanger ([email protected])
game: Gemstone
tags: utility
required: Lich >= 4.6.0.
version: 0.2 (2019-11-20)
changelog:
version 0.2 (2019-11-30)
* Many commands that manipulate silver now accept shortened forms; "give player 5k" will give them 5000 silver.
This includes: GIVE, PAY, EXCHANGE, DROP, BANK, WITHDRAW, DEPOSIT, BANK and SHOP
WITHDRAW 1M SILVER and WITHDRAW 1M NOTE work as expeceted.
version 0.1.7 (2019-07-21)
* Bugfix: GIVE <player> ALL no longer offers the item in your hand if you happen to have no silver when using it.
version 0.1.6 (2019-06-27)
* Actually remember the space between 'inventory' and 'full' (or other words) when sending it.
version 0.1.5 (2019-06-27)
* Expand shortened versions of 'inventory' to 'inventory'. i/in/inv/inve all cannot be used in RT, but inven can.
version 0.1.4 (2019-06-27)
* Fix typo that prevented LORESING verb trap from triggering.
* GIVE <player> now attempts to autocomplete player names to someone in your current room. GIVE <item> TO <target>
is not affected.
version 0.1.3 (2019-06-25)
* SING ~language now sings in that language, similiar to SAY ~language. This also applies to LORESING and RECITE.
* SING @target is likewise now an alias to SING ::target
version 0.1.2 (2019-06-25)
* GIVE <player> ALL is now much more reliable.
version 0.1.1 (2019-06-24)
* Add more inventory locations
* Fix completely broken inventory location display logic.
version 0.1 (2019-06-23)
* Initial release
=end
module Reverb
@verb_lookup = {}
@verb_info = {}
@verb_groups = {}
def self.verb(text, description=nil, group: nil, &block)
base, rest = text.split("|", 2)
name = (rest && (base + rest)) || text
group ||= name
@verb_groups[group] ||= {:name => group, :enabled => true, :verbs => []}
group = @verb_groups[group]
group[:desc] ||= description
entry = {
:name => name,
:text => text,
:group => group,
:proc => block,
}
group[:verbs] << entry
@verb_info[base] = entry
@verb_lookup[base] = entry
rest.chars.each do |ch|
base += ch
@verb_lookup[base] = entry
end if rest
end
# Convert 50k to 50000, 5m to 5000000
def self.numberize(s)
return s.gsub(/\b(\d+)([kKmM])\b/){
next "#{$1}000" if $2 == 'k' or $2 == 'K'
next "#{$1}000000"
}
end
# Safer PUT
verb("put", "Don't treat PUT without a container as DROP") do |line, prefix, rest|
next :asis unless rest
next :asis if rest =~ /\b(?:in|on|under|behind)\b/i
_respond "Use DROP if you want to drop an item. (This friendly item protection brought to you by reverb.)\r\n#{prompt}"
end
# Improved INV LOC
class InvLocRewriter
START_PATTERN = /^You are currently wearing:\s*$/
DATA_PATTERN = /^ .+?(\(functional\))?\s*$|^ ([A-Z].*:)\s*$/
LOCATIONS = {
'As a pin:' => [8, 20], # Pin
'On your back:' => [1, 2], # Back
'Around your waist:' => [1, 3], # Waist
'On your head:' => [1, 2], # Head
'Slung over your shoulder:' => [2, 2], # Shoulder
'Draped over your shoulders:' => [1, 2], # Shoulders
'Pulled over your legs:' => [1, 1], # Legs
'Over your chest:' => [1, 3], # Torso
'Attached to your wrist:' => [2, 4], # Wrist
'On your fingers:' => [2, 6],
'On your feet:' => [1, 1], # Feet
'Hung around your neck:' => [3, 6], # Neck
'Attached to your belt:' => [3, 5], # Belt
'Attached to your arms:' => [1, 2],
'Attached to your legs:' => [1, 2],
'Hung from a single ear:' => [1, 3],
'Hung from both ears:' => [1, 3],
'Attached to your ankle:' => [1, 3],
'Put over your front:' => [1, 2], # Front
'Slipped over your hands:' => [1, 2], # Hands
'Slipped on your feet:' => [1, 3], # Feet
'TODO HAIR:' => [1, 2],
'Skipped into, on your chest:' => [1, 1],
}
def initialize
@hook = Reverb.anon_hook
@started = false
@functional = 0
@max_functional = nil
@total = 0
@max_total = nil
@lines = []
@timeout = false
Thread.new do
sleep 10
@timeout = true
end
DownstreamHook.add(@hook, proc{|xml|
if @started
if @timeout or xml !~ DATA_PATTERN
# Met an unexpected thing; probably the end of our data.
DownstreamHook.remove(@hook)
next end_group(xml) # Flush anything pending
end
next start_group($2, xml) if $2 # New location name
# Still here? It's an item
@total += 1
@functional += 1 if $1
@lines << xml
next nil
end
@started = true if xml =~ START_PATTERN
next xml
})
end
def end_group(xml=nil)
if @lines[0]
@lines[0] = "#{@lines[0].rstrip} (#{@functional}/#{@max_functional} functional, #{@total}/#{@max_total} total)\r\n"
@lines << xml if xml
result = @lines.join
@lines.clear
return result
else
return xml
end
end
def start_group(group, xml)
max_functional, max_total = LOCATIONS[group]
# echo "#{group.inspect} #{LOCATIONS[group].inspect}"
return end_group(xml) unless max_functional
result = end_group
@max_functional = max_functional
@max_total = max_total
@lines << xml
@functional = @total = 0
return result
end
end
verb("i|nventory", "Show functional/total item counts when doing INV LOCATION") do |line, prefix, rest|
InvLocRewriter.new if rest =~ /^(lo\w*)\b/i and 'location'.start_with?($1.downcase)
if rest
next "inventory #{rest}"
else
next "inventory"
end
end
verb("g|o", "Automatically close your locker when leaving it.", group: 'locker') do |line, prefix, rest|
next :asis unless @locker_opened
if rest =~ /\b(o\w*|c\w*)/i
noun = $1.downcase
if 'opening'.start_with?(noun) or 'curtain'.start_with?(noun)
verbose "[Assuming you meant to close your locker first.]"
end
qput "close locker" if 'opening'.start_with?(noun) or 'curtain'.start_with?(noun)
end
next :asis
end
verb("sm|ile", "Avoid mistakes when smiling at players.") do |line, verb, rest|
# "smile bob" but not "smile at bob" or "smile ::bob" or "smile bob."
next assume "#{verb} ::#{rest}" if rest =~ /^[a-z]*$/
next :asis
end
verb("com|m", "Allow old-style language changing verbs.", group: 'language') do |line, prefix, rest|
return :asis if rest # In case someone is trying to use COMMENT, even though it is no longer in use.
next assume "speak common"
end
verb("commo|n", group: 'language') do assume "speak common" end
verb("ae|lotian", group: 'language') do assume "speak aelotian" end
verb("dar|kelf", group: 'language') do assume "speak darkelf" end
verb("dhe|'narsi", group: 'language') do assume "speak dhe'narsi" end
verb("dhen|arsi", group: 'language') do assume "speak dhe'narsi" end
verb("dw|arven", group: 'language') do assume "speak dwarven" end
verb("el|ven", group: 'language') do assume "speak elven" end
verb("er|ithian", group: 'language') do assume "speak erithian" end
verb("fae|ndryl", group: 'language') do assume "speak faendryl" end
verb("gia|nt", group: 'language') do assume "speak giantman" end
verb("gno|mish", group: 'language') do assume "speak gnomish" end
verb("kr|olvin", group: 'language') do assume "speak krolvin" end
verb("syl|vankind", group: 'language') do assume "speak sylvankind" end
verb("teh|ir", group: 'language') do assume "speak tehir" end
verb("hal|fling", group: 'language') do assume "speak halfling" end
verb("gui|ldspeak", group: 'language') do |line, prefix, rest|
next assume "speak #{Char.prof}"
end
sing_proc = proc{|line, verb, rest|
next :asis unless rest =~ /[@~]/
command = [verb]
language = nil
while rest =~ /^((:|::|@|~)([\w']+))(?:\s+(.+))?$/
rest = $4
if $2 == '@'
command << "::#{$3}"
elsif $2 == '~'
language = $3
if language.length < 3
_respond "Please provide at least 3 characters in the name of the language you wish to speak.", prompt
next nil
end
else
command << $1
end
end
command << rest
command = command.join(' ')
if language
language = Char.prof if "guildspeak".start_with?(language.downcase)
verbose "[Changing to #{language}, will change back to #{@last_language} afterwards.]"
next ["speak #{language}", assume(command), "speak #{@last_language}"]
else
next assume command
end
}
verb("sin|g", group: 'sing', &sing_proc)
verb("lor|esing", group: 'sing', &sing_proc)
verb("rec|ite", group: 'sing', &sing_proc)
verb("giv|e", "GIVE prefers players in current room, and GIVE <player> <all> gives all silver on hand.") do |line, verb, rest|
next :asis unless rest =~ /^.+\bto\b|^(\w+)(\s+(all)|\s+.+)?/i
# 1=name 2=rest 3='all'
# If they're using GIVE <item> TO <target> syntax, $1 won't be set. And we don't handle that syntax soo
next :asis unless $1
# Autocomplete the name.
who = $1
rest = ($2 || '')
all = $3
partial = who.capitalize
# Prefer an exact match
if GameObj.pcs.find{|x| x.name == partial}
who = partial
elsif (pc = GameObj.pcs.reverse.find{|x| x.name.start_with?(partial)})
who = pc.noun
end # Else leave unchanged
if all # ALL
lines = quiet_command("info", /<output class="mono"/, /<prompt/, false, 5)
silver = nil
lines.each{|line|
if line =~ /^Mana:.*Silver: (\d+)/
silver = $1
break
end
}
unless silver
respond("[reverb: could not determine the amount of silver you are carrying]")
next nil
end
if silver == '0'
_respond("But you don't have any silver!\r\n#{prompt}")
next nil
else
rest = " #{silver}"
end
else
rest = numberize($2)
end
next assume "#{verb} #{who}#{rest}"
end
numberize_proc = proc{|line, verb, rest|
next :asis unless rest
"#{verb} #{numberize(rest)}"
}
verb("depo|sit", &numberize_proc)
verb("ban|k", &numberize_proc)
verb("wit|hdraw", &numberize_proc)
verb("pa|y", &numberize_proc)
verb("exc|hange", &numberize_proc)
verb("shop", &numberize_proc)
verb("dr|op", &numberize_proc)
# _verb("depo|sit") # Handle note in left hand
def self.verbose(msg)
_respond msg if @verbose # For now.
end
def self.assume(verb)
_respond escape("[Assuming you meant '#{verb}'.]") if @verbose
return verb
end
def self.handle_upstream(xml)
return xml unless xml =~ /^(<c>)?([\w']+)(?:\s+(.+))?$/
verb = $2.downcase
if (lookup = @verb_lookup[verb]) and lookup[:group][:enabled]
prefix = $1
rest = $3
result = lookup[:proc].call(xml, verb, rest)
return xml if result == :asis
if result.is_a?(Array)
# echo result.inspect
# echo result.map{|x| prefix + x}.join("\r\n") if prefix
return result.map{|x| prefix + x}.join("\r\n") if prefix
return result.join("\r\n)")
end
return prefix + result if prefix && result
return result
end
return xml
end
# def self.show_status
# msg = []
# msg << "Usage:"
# msg << " REVERB {option} {ON|OFF}"
# msg << ''
# msg << "Example:"
# msg << ' REVERB SMILE ON'
# msg << ' REVERB SMILE OFF'
# msg << ''
# msg << " #{monsterbold_start}#{'Option'.lpad(10)} Status Description when enabled#{monsterbold_end}"
# msg << ''
# @verb_groups.each do |name, info|
#
# end
#
#
#
#
#
# end
def self.run(script)
@script = script
@locker_opened = false
@last_language = 'Common'
@reget_queue = nil
@verbose = false
script.want_downstream_xml = true
script.want_downstream = false
before_dying do self.cleanup end
UpstreamHook.add("reverb::upstream", proc{|xml| self.handle_upstream(xml)})
while line = get
@reget_queue << line if @reget_queue
if line =~ /(^Your locker is currently holding)|^You close the locker|^You hear the faint creak of a pulley turning behind the wall/
@locker_opened = $1 && true || false
elsif line =~ /^You are (?:currently|now) speaking (\w+)\.$/
@last_language = $1
@last_language = Char.prof if @last_language == "Guildspeak"
end
end
end
def self.cleanup
[DownstreamHook, UpstreamHook].each{|provider|
provider.list.find_all{|name| name.start_with?('reverb::')}.each{|name| provider.remove(name)}
}
end
def self.anon_hook(prefix = '')
"reverb::#{prefix}-#{Time.now.to_f}-#{Random.rand(10000)}"
end
def self.escape(what)
return what unless what
return what if $frontend == 'wizard'
return what.gsub('&', '&').gsub('<', '<').gsub('>', '>')
end
def self.prompt
return escape(XMLData.prompt)
end
def self.show_prompt
_respond self.prompt
end
def self.qput(what)
Game._puts(what)
end
def self.quiet_command(command, start_pattern, end_pattern = /<prompt/, include_end = true, timeout=5)
result = []
name = self.anon_hook
filter = false
begin
@reget_queue = Queue.new
Timeout::timeout(timeout, Interrupt) {
DownstreamHook.add(name, proc {|xml|
if filter
if xml =~ end_pattern
DownstreamHook.remove(name)
filter = false
# result << xml.rstrip if include_end
# thread.raise(Interrupt)
# next(include_end ? nil : xml)
else
# result << xml.rstrip
next(nil)
end
elsif xml =~ start_pattern
filter = true
# result << xml.rstrip
next(nil)
else
xml
end
})
qput command
until (xml = @reget_queue.pop) =~ start_pattern; end
result << xml.rstrip
until (xml = @reget_queue.pop) =~ end_pattern
result << xml.rstrip
end
if include_end
result << xml.rstrip
end
}
rescue Interrupt
nil
ensure
@reget_queue = nil
end
return result
end
end
echo "I don't really have proper documentation yet; for help see #{$lich_char}repo info #{script.name}"
echo "Stay updated: Type #{$lich_char}repo set-updatable #{script.name} "
Reverb.run(script)