· 7 years ago · Jan 04, 2019, 04:18 AM
1#!/usr/bin/env ruby
2# encoding: US-ASCII
3#####
4# Copyright (C) 2005-2006 Murray Miron
5# All rights reserved.
6#
7# Redistribution and use in source and binary forms, with or without
8# modification, are permitted provided that the following conditions
9# are met:
10#
11# Redistributions of source code must retain the above copyright
12# notice, this list of conditions and the following disclaimer.
13#
14# Redistributions in binary form must reproduce the above copyright
15# notice, this list of conditions and the following disclaimer in the
16# documentation and/or other materials provided with the distribution.
17#
18# Neither the name of the organization nor the names of its contributors
19# may be used to endorse or promote products derived from this software
20# without specific prior written permission.
21#
22# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
25# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
26# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
27# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
28# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
29# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
30# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
31# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
32# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33#####
34
35#
36# Lich is maintained by Matt Lowe (tillmen@lichproject.org)
37#
38
39LICH_VERSION = '4.6.49'
40TESTING = false
41
42if RUBY_VERSION !~ /^2/
43 if (RUBY_PLATFORM =~ /mingw|win/) and (RUBY_PLATFORM !~ /darwin/i)
44 if RUBY_VERSION =~ /^1\.9/
45 require 'fiddle'
46 Fiddle::Function.new(DL.dlopen('user32.dll')['MessageBox'], [Fiddle::TYPE_INT, Fiddle::TYPE_VOIDP, Fiddle::TYPE_VOIDP, Fiddle::TYPE_INT], Fiddle::TYPE_INT).call(0, 'Upgrade Ruby to version 2.0', "Lich v#{LICH_VERSION}", 16)
47 else
48 # fixme: This message never shows up on Ruby 1.8 because it errors out on negative lookbehind regex later in the file
49 require 'dl'
50 DL.dlopen('user32.dll')['MessageBox', 'LLPPL'].call(0, 'Upgrade Ruby to version 2.0', "Lich v#{LICH_VERSION}", 16)
51 end
52 else
53 puts "Upgrade Ruby to version 2.0"
54 end
55 exit
56end
57
58require 'time'
59require 'socket'
60require 'rexml/document'
61require 'rexml/streamlistener'
62require 'stringio'
63require 'zlib'
64require 'drb'
65require 'resolv'
66require 'digest/md5'
67
68begin
69 # stupid workaround for Windows
70 # seems to avoid a 10 second lag when starting lnet, without adding a 10 second lag at startup
71 require 'openssl'
72 OpenSSL::PKey::RSA.new(512)
73rescue LoadError
74 nil # not required for basic Lich; however, lnet and repository scripts will fail without openssl
75rescue
76 nil
77end
78if (RUBY_PLATFORM =~ /mingw|win/i) and (RUBY_PLATFORM !~ /darwin/i)
79 #
80 # Windows API made slightly less annoying
81 #
82 require 'fiddle'
83 require 'fiddle/import'
84 module Win32
85 SIZEOF_CHAR = Fiddle::SIZEOF_CHAR
86 SIZEOF_LONG = Fiddle::SIZEOF_LONG
87 SEE_MASK_NOCLOSEPROCESS = 0x00000040
88 MB_OK = 0x00000000
89 MB_OKCANCEL = 0x00000001
90 MB_YESNO = 0x00000004
91 MB_ICONERROR = 0x00000010
92 MB_ICONQUESTION = 0x00000020
93 MB_ICONWARNING = 0x00000030
94 IDIOK = 1
95 IDICANCEL = 2
96 IDIYES = 6
97 IDINO = 7
98 KEY_ALL_ACCESS = 0xF003F
99 KEY_CREATE_SUB_KEY = 0x0004
100 KEY_ENUMERATE_SUB_KEYS = 0x0008
101 KEY_EXECUTE = 0x20019
102 KEY_NOTIFY = 0x0010
103 KEY_QUERY_VALUE = 0x0001
104 KEY_READ = 0x20019
105 KEY_SET_VALUE = 0x0002
106 KEY_WOW64_32KEY = 0x0200
107 KEY_WOW64_64KEY = 0x0100
108 KEY_WRITE = 0x20006
109 TokenElevation = 20
110 TOKEN_QUERY = 8
111 STILL_ACTIVE = 259
112 SW_SHOWNORMAL = 1
113 SW_SHOW = 5
114 PROCESS_QUERY_INFORMATION = 1024
115 PROCESS_VM_READ = 16
116 HKEY_LOCAL_MACHINE = -2147483646
117 REG_NONE = 0
118 REG_SZ = 1
119 REG_EXPAND_SZ = 2
120 REG_BINARY = 3
121 REG_DWORD = 4
122 REG_DWORD_LITTLE_ENDIAN = 4
123 REG_DWORD_BIG_ENDIAN = 5
124 REG_LINK = 6
125 REG_MULTI_SZ = 7
126 REG_QWORD = 11
127 REG_QWORD_LITTLE_ENDIAN = 11
128
129 module Kernel32
130 extend Fiddle::Importer
131 dlload 'kernel32'
132 extern 'int GetCurrentProcess()'
133 extern 'int GetExitCodeProcess(int, int*)'
134 extern 'int GetModuleFileName(int, void*, int)'
135 extern 'int GetVersionEx(void*)'
136# extern 'int OpenProcess(int, int, int)' # fixme
137 extern 'int GetLastError()'
138 extern 'int CreateProcess(void*, void*, void*, void*, int, int, void*, void*, void*, void*)'
139 end
140 def Win32.GetLastError
141 return Kernel32.GetLastError()
142 end
143 def Win32.CreateProcess(args)
144 if args[:lpCommandLine]
145 lpCommandLine = args[:lpCommandLine].dup
146 else
147 lpCommandLine = nil
148 end
149 if args[:bInheritHandles] == false
150 bInheritHandles = 0
151 elsif args[:bInheritHandles] == true
152 bInheritHandles = 1
153 else
154 bInheritHandles = args[:bInheritHandles].to_i
155 end
156 if args[:lpEnvironment].class == Array
157 # fixme
158 end
159 lpStartupInfo = [ 68, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]
160 lpStartupInfo_index = { :lpDesktop => 2, :lpTitle => 3, :dwX => 4, :dwY => 5, :dwXSize => 6, :dwYSize => 7, :dwXCountChars => 8, :dwYCountChars => 9, :dwFillAttribute => 10, :dwFlags => 11, :wShowWindow => 12, :hStdInput => 15, :hStdOutput => 16, :hStdError => 17 }
161 for sym in [ :lpDesktop, :lpTitle ]
162 if args[sym]
163 args[sym] = "#{args[sym]}\0" unless args[sym][-1,1] == "\0"
164 lpStartupInfo[lpStartupInfo_index[sym]] = Fiddle::Pointer.to_ptr(args[sym]).to_i
165 end
166 end
167 for sym in [ :dwX, :dwY, :dwXSize, :dwYSize, :dwXCountChars, :dwYCountChars, :dwFillAttribute, :dwFlags, :wShowWindow, :hStdInput, :hStdOutput, :hStdError ]
168 if args[sym]
169 lpStartupInfo[lpStartupInfo_index[sym]] = args[sym]
170 end
171 end
172 lpStartupInfo = lpStartupInfo.pack('LLLLLLLLLLLLSSLLLL')
173 lpProcessInformation = [ 0, 0, 0, 0, ].pack('LLLL')
174 r = Kernel32.CreateProcess(args[:lpApplicationName], lpCommandLine, args[:lpProcessAttributes], args[:lpThreadAttributes], bInheritHandles, args[:dwCreationFlags].to_i, args[:lpEnvironment], args[:lpCurrentDirectory], lpStartupInfo, lpProcessInformation)
175 lpProcessInformation = lpProcessInformation.unpack('LLLL')
176 return :return => (r > 0 ? true : false), :hProcess => lpProcessInformation[0], :hThread => lpProcessInformation[1], :dwProcessId => lpProcessInformation[2], :dwThreadId => lpProcessInformation[3]
177 end
178# Win32.CreateProcess(:lpApplicationName => 'Launcher.exe', :lpCommandLine => 'lich2323.sal', :lpCurrentDirectory => 'C:\\PROGRA~1\\SIMU')
179# def Win32.OpenProcess(args={})
180# return Kernel32.OpenProcess(args[:dwDesiredAccess].to_i, args[:bInheritHandle].to_i, args[:dwProcessId].to_i)
181# end
182 def Win32.GetCurrentProcess
183 return Kernel32.GetCurrentProcess
184 end
185 def Win32.GetExitCodeProcess(args)
186 lpExitCode = [ 0 ].pack('L')
187 r = Kernel32.GetExitCodeProcess(args[:hProcess].to_i, lpExitCode)
188 return :return => r, :lpExitCode => lpExitCode.unpack('L')[0]
189 end
190 def Win32.GetModuleFileName(args={})
191 args[:nSize] ||= 256
192 buffer = "\0" * args[:nSize].to_i
193 r = Kernel32.GetModuleFileName(args[:hModule].to_i, buffer, args[:nSize].to_i)
194 return :return => r, :lpFilename => buffer.gsub("\0", '')
195 end
196 def Win32.GetVersionEx
197 a = [ 156, 0, 0, 0, 0, ("\0" * 128), 0, 0, 0, 0, 0].pack('LLLLLa128SSSCC')
198 r = Kernel32.GetVersionEx(a)
199 a = a.unpack('LLLLLa128SSSCC')
200 return :return => r, :dwOSVersionInfoSize => a[0], :dwMajorVersion => a[1], :dwMinorVersion => a[2], :dwBuildNumber => a[3], :dwPlatformId => a[4], :szCSDVersion => a[5].strip, :wServicePackMajor => a[6], :wServicePackMinor => a[7], :wSuiteMask => a[8], :wProductType => a[9]
201 end
202
203 module User32
204 extend Fiddle::Importer
205 dlload 'user32'
206 extern 'int MessageBox(int, char*, char*, int)'
207 end
208 def Win32.MessageBox(args)
209 args[:lpCaption] ||= "Lich v#{LICH_VERSION}"
210 return User32.MessageBox(args[:hWnd].to_i, args[:lpText], args[:lpCaption], args[:uType].to_i)
211 end
212
213 module Advapi32
214 extend Fiddle::Importer
215 dlload 'advapi32'
216 extern 'int GetTokenInformation(int, int, void*, int, void*)'
217 extern 'int OpenProcessToken(int, int, void*)'
218 extern 'int RegOpenKeyEx(int, char*, int, int, void*)'
219 extern 'int RegQueryValueEx(int, char*, void*, void*, void*, void*)'
220 extern 'int RegSetValueEx(int, char*, int, int, char*, int)'
221 extern 'int RegDeleteValue(int, char*)'
222 extern 'int RegCloseKey(int)'
223 end
224 def Win32.GetTokenInformation(args)
225 if args[:TokenInformationClass] == TokenElevation
226 token_information_length = SIZEOF_LONG
227 token_information = [ 0 ].pack('L')
228 else
229 return nil
230 end
231 return_length = [ 0 ].pack('L')
232 r = Advapi32.GetTokenInformation(args[:TokenHandle].to_i, args[:TokenInformationClass], token_information, token_information_length, return_length)
233 if args[:TokenInformationClass] == TokenElevation
234 return :return => r, :TokenIsElevated => token_information.unpack('L')[0]
235 end
236 end
237 def Win32.OpenProcessToken(args)
238 token_handle = [ 0 ].pack('L')
239 r = Advapi32.OpenProcessToken(args[:ProcessHandle].to_i, args[:DesiredAccess].to_i, token_handle)
240 return :return => r, :TokenHandle => token_handle.unpack('L')[0]
241 end
242 def Win32.RegOpenKeyEx(args)
243 phkResult = [ 0 ].pack('L')
244 r = Advapi32.RegOpenKeyEx(args[:hKey].to_i, args[:lpSubKey].to_s, 0, args[:samDesired].to_i, phkResult)
245 return :return => r, :phkResult => phkResult.unpack('L')[0]
246 end
247 def Win32.RegQueryValueEx(args)
248 args[:lpValueName] ||= 0
249 lpcbData = [ 0 ].pack('L')
250 r = Advapi32.RegQueryValueEx(args[:hKey].to_i, args[:lpValueName], 0, 0, 0, lpcbData)
251 if r == 0
252 lpcbData = lpcbData.unpack('L')[0]
253 lpData = String.new.rjust(lpcbData, "\x00")
254 lpcbData = [ lpcbData ].pack('L')
255 lpType = [ 0 ].pack('L')
256 r = Advapi32.RegQueryValueEx(args[:hKey].to_i, args[:lpValueName], 0, lpType, lpData, lpcbData)
257 lpType = lpType.unpack('L')[0]
258 lpcbData = lpcbData.unpack('L')[0]
259 if [REG_EXPAND_SZ, REG_SZ, REG_LINK].include?(lpType)
260 lpData.gsub!("\x00", '')
261 elsif lpType == REG_MULTI_SZ
262 lpData = lpData.gsub("\x00\x00", '').split("\x00")
263 elsif lpType == REG_DWORD
264 lpData = lpData.unpack('L')[0]
265 elsif lpType == REG_QWORD
266 lpData = lpData.unpack('Q')[0]
267 elsif lpType == REG_BINARY
268 # fixme
269 elsif lpType == REG_DWORD_BIG_ENDIAN
270 # fixme
271 else
272 # fixme
273 end
274 return :return => r, :lpType => lpType, :lpcbData => lpcbData, :lpData => lpData
275 else
276 return :return => r
277 end
278 end
279 def Win32.RegSetValueEx(args)
280 if [REG_EXPAND_SZ, REG_SZ, REG_LINK].include?(args[:dwType]) and (args[:lpData].class == String)
281 lpData = args[:lpData].dup
282 lpData.concat("\x00")
283 cbData = lpData.length
284 elsif (args[:dwType] == REG_MULTI_SZ) and (args[:lpData].class == Array)
285 lpData = args[:lpData].join("\x00").concat("\x00\x00")
286 cbData = lpData.length
287 elsif (args[:dwType] == REG_DWORD) and (args[:lpData].class == Fixnum)
288 lpData = [args[:lpData]].pack('L')
289 cbData = 4
290 elsif (args[:dwType] == REG_QWORD) and (args[:lpData].class == Fixnum or args[:lpData].class == Bignum)
291 lpData = [args[:lpData]].pack('Q')
292 cbData = 8
293 elsif args[:dwType] == REG_BINARY
294 # fixme
295 return false
296 elsif args[:dwType] == REG_DWORD_BIG_ENDIAN
297 # fixme
298 return false
299 else
300 # fixme
301 return false
302 end
303 args[:lpValueName] ||= 0
304 return Advapi32.RegSetValueEx(args[:hKey].to_i, args[:lpValueName], 0, args[:dwType], lpData, cbData)
305 end
306 def Win32.RegDeleteValue(args)
307 args[:lpValueName] ||= 0
308 return Advapi32.RegDeleteValue(args[:hKey].to_i, args[:lpValueName])
309 end
310 def Win32.RegCloseKey(args)
311 return Advapi32.RegCloseKey(args[:hKey])
312 end
313
314 module Shell32
315 extend Fiddle::Importer
316 dlload 'shell32'
317 extern 'int ShellExecuteEx(void*)'
318 extern 'int ShellExecute(int, char*, char*, char*, char*, int)'
319 end
320 def Win32.ShellExecuteEx(args)
321# struct = [ (SIZEOF_LONG * 15), 0, 0, 0, 0, 0, 0, SW_SHOWNORMAL, 0, 0, 0, 0, 0, 0, 0 ]
322 struct = [ (SIZEOF_LONG * 15), 0, 0, 0, 0, 0, 0, SW_SHOW, 0, 0, 0, 0, 0, 0, 0 ]
323 struct_index = { :cbSize => 0, :fMask => 1, :hwnd => 2, :lpVerb => 3, :lpFile => 4, :lpParameters => 5, :lpDirectory => 6, :nShow => 7, :hInstApp => 8, :lpIDList => 9, :lpClass => 10, :hkeyClass => 11, :dwHotKey => 12, :hIcon => 13, :hMonitor => 13, :hProcess => 14 }
324 for sym in [ :lpVerb, :lpFile, :lpParameters, :lpDirectory, :lpIDList, :lpClass ]
325 if args[sym]
326 args[sym] = "#{args[sym]}\0" unless args[sym][-1,1] == "\0"
327 struct[struct_index[sym]] = Fiddle::Pointer.to_ptr(args[sym]).to_i
328 end
329 end
330 for sym in [ :fMask, :hwnd, :nShow, :hkeyClass, :dwHotKey, :hIcon, :hMonitor, :hProcess ]
331 if args[sym]
332 struct[struct_index[sym]] = args[sym]
333 end
334 end
335 struct = struct.pack('LLLLLLLLLLLLLLL')
336 r = Shell32.ShellExecuteEx(struct)
337 struct = struct.unpack('LLLLLLLLLLLLLLL')
338 return :return => r, :hProcess => struct[struct_index[:hProcess]], :hInstApp => struct[struct_index[:hInstApp]]
339 end
340 def Win32.ShellExecute(args)
341 args[:lpOperation] ||= 0
342 args[:lpParameters] ||= 0
343 args[:lpDirectory] ||= 0
344 args[:nShowCmd] ||= 1
345 return Shell32.ShellExecute(args[:hwnd].to_i, args[:lpOperation], args[:lpFile], args[:lpParameters], args[:lpDirectory], args[:nShowCmd])
346 end
347
348 begin
349 module Kernel32
350 extern 'int EnumProcesses(void*, int, void*)'
351 end
352 def Win32.EnumProcesses(args={})
353 args[:cb] ||= 400
354 pProcessIds = Array.new((args[:cb]/SIZEOF_LONG), 0).pack(''.rjust((args[:cb]/SIZEOF_LONG), 'L'))
355 pBytesReturned = [ 0 ].pack('L')
356 r = Kernel32.EnumProcesses(pProcessIds, args[:cb], pBytesReturned)
357 pBytesReturned = pBytesReturned.unpack('L')[0]
358 return :return => r, :pProcessIds => pProcessIds.unpack(''.rjust((args[:cb]/SIZEOF_LONG), 'L'))[0...(pBytesReturned/SIZEOF_LONG)], :pBytesReturned => pBytesReturned
359 end
360 rescue
361 module Psapi
362 extend Fiddle::Importer
363 dlload 'psapi'
364 extern 'int EnumProcesses(void*, int, void*)'
365 end
366 def Win32.EnumProcesses(args={})
367 args[:cb] ||= 400
368 pProcessIds = Array.new((args[:cb]/SIZEOF_LONG), 0).pack(''.rjust((args[:cb]/SIZEOF_LONG), 'L'))
369 pBytesReturned = [ 0 ].pack('L')
370 r = Psapi.EnumProcesses(pProcessIds, args[:cb], pBytesReturned)
371 pBytesReturned = pBytesReturned.unpack('L')[0]
372 return :return => r, :pProcessIds => pProcessIds.unpack(''.rjust((args[:cb]/SIZEOF_LONG), 'L'))[0...(pBytesReturned/SIZEOF_LONG)], :pBytesReturned => pBytesReturned
373 end
374 end
375
376 def Win32.isXP?
377 return (Win32.GetVersionEx[:dwMajorVersion] < 6)
378 end
379# def Win32.isWin8?
380# r = Win32.GetVersionEx
381# return ((r[:dwMajorVersion] == 6) and (r[:dwMinorVersion] >= 2))
382# end
383 def Win32.admin?
384 if Win32.isXP?
385 return true
386 else
387 r = Win32.OpenProcessToken(:ProcessHandle => Win32.GetCurrentProcess, :DesiredAccess => TOKEN_QUERY)
388 token_handle = r[:TokenHandle]
389 r = Win32.GetTokenInformation(:TokenInformationClass => TokenElevation, :TokenHandle => token_handle)
390 return (r[:TokenIsElevated] != 0)
391 end
392 end
393 def Win32.AdminShellExecute(args)
394 # open ruby/lich as admin and tell it to open something else
395 if not caller.any? { |c| c =~ /eval|run/ }
396 r = Win32.GetModuleFileName
397 if r[:return] > 0
398 if File.exists?(r[:lpFilename])
399 Win32.ShellExecuteEx(:lpVerb => 'runas', :lpFile => r[:lpFilename], :lpParameters => "#{File.expand_path($PROGRAM_NAME)} shellexecute #{[Marshal.dump(args)].pack('m').gsub("\n",'')}")
400 end
401 end
402 end
403 end
404 end
405else
406 if arg = ARGV.find { |a| a =~ /^--wine=.+$/i }
407 $wine_bin = arg.sub(/^--wine=/, '')
408 else
409 begin
410 $wine_bin = `which wine`.strip
411 rescue
412 $wine_bin = nil
413 end
414 end
415 if arg = ARGV.find { |a| a =~ /^--wine-prefix=.+$/i }
416 $wine_prefix = arg.sub(/^--wine-prefix=/, '')
417 elsif ENV['WINEPREFIX']
418 $wine_prefix = ENV['WINEPREFIX']
419 elsif ENV['HOME']
420 $wine_prefix = ENV['HOME'] + '/.wine'
421 else
422 $wine_prefix = nil
423 end
424 if $wine_bin and File.exists?($wine_bin) and File.file?($wine_bin) and $wine_prefix and File.exists?($wine_prefix) and File.directory?($wine_prefix)
425 module Wine
426 BIN = $wine_bin
427 PREFIX = $wine_prefix
428 def Wine.registry_gets(key)
429 hkey, subkey, thingie = /(HKEY_LOCAL_MACHINE|HKEY_CURRENT_USER)\\(.+)\\([^\\]*)/.match(key).captures # fixme: stupid highlights ]/
430 if File.exists?(PREFIX + '/system.reg')
431 if hkey == 'HKEY_LOCAL_MACHINE'
432 subkey = "[#{subkey.gsub('\\', '\\\\\\')}]"
433 if thingie.nil? or thingie.empty?
434 thingie = '@'
435 else
436 thingie = "\"#{thingie}\""
437 end
438 lookin = result = false
439 File.open(PREFIX + '/system.reg') { |f| f.readlines }.each { |line|
440 if line[0...subkey.length] == subkey
441 lookin = true
442 elsif line =~ /^\[/
443 lookin = false
444 elsif lookin and line =~ /^#{thingie}="(.*)"$/i
445 result = $1.split('\\"').join('"').split('\\\\').join('\\').sub(/\\0$/, '')
446 break
447 end
448 }
449 return result
450 else
451 return false
452 end
453 else
454 return false
455 end
456 end
457 def Wine.registry_puts(key, value)
458 hkey, subkey, thingie = /(HKEY_LOCAL_MACHINE|HKEY_CURRENT_USER)\\(.+)\\([^\\]*)/.match(key).captures # fixme ]/
459 if File.exists?(PREFIX)
460 if thingie.nil? or thingie.empty?
461 thingie = '@'
462 else
463 thingie = "\"#{thingie}\""
464 end
465 # gsub sucks for this..
466 value = value.split('\\').join('\\\\')
467 value = value.split('"').join('\"')
468 begin
469 regedit_data = "REGEDIT4\n\n[#{hkey}\\#{subkey}]\n#{thingie}=\"#{value}\"\n\n"
470 filename = "#{TEMP_DIR}/wine-#{Time.now.to_i}.reg"
471 File.open(filename, 'w') { |f| f.write(regedit_data) }
472 system("#{BIN} regedit #{filename}")
473 sleep 0.2
474 File.delete(filename)
475 rescue
476 return false
477 end
478 return true
479 end
480 end
481 end
482 end
483 $wine_bin = nil
484 $wine_prefix = nil
485end
486
487if ARGV[0] == 'shellexecute'
488 args = Marshal.load(ARGV[1].unpack('m')[0])
489 Win32.ShellExecute(:lpOperation => args[:op], :lpFile => args[:file], :lpDirectory => args[:dir], :lpParameters => args[:params])
490 exit
491end
492
493begin
494 require 'sqlite3'
495rescue LoadError
496 if defined?(Win32)
497 r = Win32.MessageBox(:lpText => "Lich needs sqlite3 to save settings and data, but it is not installed.\n\nWould you like to install sqlite3 now?", :lpCaption => "Lich v#{LICH_VERSION}", :uType => (Win32::MB_YESNO | Win32::MB_ICONQUESTION))
498 if r == Win32::IDIYES
499 r = Win32.GetModuleFileName
500 if r[:return] > 0
501 ruby_bin_dir = File.dirname(r[:lpFilename])
502 if File.exists?("#{ruby_bin_dir}\\gem.bat")
503 verb = (Win32.isXP? ? 'open' : 'runas')
504 # fixme: using --source http://rubygems.org to avoid https because it has been failing to validate the certificate on Windows
505 r = Win32.ShellExecuteEx(:fMask => Win32::SEE_MASK_NOCLOSEPROCESS, :lpVerb => verb, :lpFile => "#{ruby_bin_dir}\\gem.bat", :lpParameters => 'install sqlite3 --source http://rubygems.org --no-ri --no-rdoc')
506 if r[:return] > 0
507 pid = r[:hProcess]
508 sleep 1 while Win32.GetExitCodeProcess(:hProcess => pid)[:lpExitCode] == Win32::STILL_ACTIVE
509 r = Win32.MessageBox(:lpText => "Install finished. Lich will restart now.", :lpCaption => "Lich v#{LICH_VERSION}", :uType => Win32::MB_OKCANCEL)
510 else
511 # ShellExecuteEx failed: this seems to happen with an access denied error even while elevated on some random systems
512 r = Win32.ShellExecute(:lpOperation => verb, :lpFile => "#{ruby_bin_dir}\\gem.bat", :lpParameters => 'install sqlite3 --source http://rubygems.org --no-ri --no-rdoc')
513 if r <= 32
514 Win32.MessageBox(:lpText => "error: failed to start the sqlite3 installer\n\nfailed command: Win32.ShellExecute(:lpOperation => #{verb.inspect}, :lpFile => \"#{ruby_bin_dir}\\gem.bat\", :lpParameters => \"install sqlite3 --source http://rubygems.org --no-ri --no-rdoc\")\n\nerror code: #{Win32.GetLastError}", :lpCaption => "Lich v#{LICH_VERSION}", :uType => (Win32::MB_OK | Win32::MB_ICONERROR))
515 exit
516 end
517 r = Win32.MessageBox(:lpText => "When the installer is finished, click OK to restart Lich.", :lpCaption => "Lich v#{LICH_VERSION}", :uType => Win32::MB_OKCANCEL)
518 end
519 if r == Win32::IDIOK
520 if File.exists?("#{ruby_bin_dir}\\rubyw.exe")
521 Win32.ShellExecute(:lpOperation => 'open', :lpFile => "#{ruby_bin_dir}\\rubyw.exe", :lpParameters => "\"#{File.expand_path($PROGRAM_NAME)}\"")
522 else
523 Win32.MessageBox(:lpText => "error: failed to find rubyw.exe; can't restart Lich for you", :lpCaption => "Lich v#{LICH_VERSION}", :uType => (Win32::MB_OK | Win32::MB_ICONERROR))
524 end
525 else
526 # user doesn't want to restart Lich
527 end
528 else
529 Win32.MessageBox(:lpText => "error: Could not find gem.bat in directory #{ruby_bin_dir}", :lpCaption => "Lich v#{LICH_VERSION}", :uType => (Win32::MB_OK | Win32::MB_ICONERROR))
530 end
531 else
532 Win32.MessageBox(:lpText => "error: GetModuleFileName failed", :lpCaption => "Lich v#{LICH_VERSION}", :uType => (Win32::MB_OK | Win32::MB_ICONERROR))
533 end
534 else
535 # user doesn't want to install sqlite3 gem
536 end
537 else
538 # fixme: no sqlite3 on Linux/Mac
539 puts "The sqlite3 gem is not installed (or failed to load), you may need to: sudo gem install sqlite3"
540 end
541 exit
542end
543
544begin
545 require 'gtk2'
546 HAVE_GTK = true
547rescue LoadError
548 if (ENV['RUN_BY_CRON'].nil? or ENV['RUN_BY_CRON'] == 'false') and ARGV.empty? or ARGV.any? { |arg| arg =~ /^--gui$/ } or not $stdout.isatty
549 if defined?(Win32)
550 r = Win32.MessageBox(:lpText => "Lich uses gtk2 to create windows, but it is not installed. You can use Lich from the command line (ruby lich.rbw --help) or you can install gtk2 for a point and click interface.\n\nWould you like to install gtk2 now?", :lpCaption => "Lich v#{LICH_VERSION}", :uType => (Win32::MB_YESNO | Win32::MB_ICONQUESTION))
551 if r == Win32::IDIYES
552 r = Win32.GetModuleFileName
553 if r[:return] > 0
554 ruby_bin_dir = File.dirname(r[:lpFilename])
555 if File.exists?("#{ruby_bin_dir}\\gem.bat")
556 verb = (Win32.isXP? ? 'open' : 'runas')
557 r = Win32.ShellExecuteEx(:fMask => Win32::SEE_MASK_NOCLOSEPROCESS, :lpVerb => verb, :lpFile => "#{ruby_bin_dir}\\gem.bat", :lpParameters => 'install cairo:1.14.3 gtk2:2.2.5 --source http://rubygems.org --no-ri --no-rdoc')
558 if r[:return] > 0
559 pid = r[:hProcess]
560 sleep 1 while Win32.GetExitCodeProcess(:hProcess => pid)[:lpExitCode] == Win32::STILL_ACTIVE
561 r = Win32.MessageBox(:lpText => "Install finished. Lich will restart now.", :lpCaption => "Lich v#{LICH_VERSION}", :uType => Win32::MB_OKCANCEL)
562 else
563 # ShellExecuteEx failed: this seems to happen with an access denied error even while elevated on some random systems
564 r = Win32.ShellExecute(:lpOperation => verb, :lpFile => "#{ruby_bin_dir}\\gem.bat", :lpParameters => 'install cairo:1.14.3 gtk2:2.2.5 --source http://rubygems.org --no-ri --no-rdoc')
565 if r <= 32
566 Win32.MessageBox(:lpText => "error: failed to start the gtk2 installer\n\nfailed command: Win32.ShellExecute(:lpOperation => #{verb.inspect}, :lpFile => \"#{ruby_bin_dir}\\gem.bat\", :lpParameters => \"install cairo:1.14.3 gtk2:2.2.5 --source http://rubygems.org --no-ri --no-rdoc\")\n\nerror code: #{Win32.GetLastError}", :lpCaption => "Lich v#{LICH_VERSION}", :uType => (Win32::MB_OK | Win32::MB_ICONERROR))
567 exit
568 end
569 r = Win32.MessageBox(:lpText => "When the installer is finished, click OK to restart Lich.", :lpCaption => "Lich v#{LICH_VERSION}", :uType => Win32::MB_OKCANCEL)
570 end
571 if r == Win32::IDIOK
572 if File.exists?("#{ruby_bin_dir}\\rubyw.exe")
573 Win32.ShellExecute(:lpOperation => 'open', :lpFile => "#{ruby_bin_dir}\\rubyw.exe", :lpParameters => "\"#{File.expand_path($PROGRAM_NAME)}\"")
574 else
575 Win32.MessageBox(:lpText => "error: failed to find rubyw.exe; can't restart Lich for you", :lpCaption => "Lich v#{LICH_VERSION}", :uType => (Win32::MB_OK | Win32::MB_ICONERROR))
576 end
577 else
578 # user doesn't want to restart Lich
579 end
580 else
581 Win32.MessageBox(:lpText => "error: Could not find gem.bat in directory #{ruby_bin_dir}", :lpCaption => "Lich v#{LICH_VERSION}", :uType => (Win32::MB_OK | Win32::MB_ICONERROR))
582 end
583 else
584 Win32.MessageBox(:lpText => "error: GetModuleFileName failed", :lpCaption => "Lich v#{LICH_VERSION}", :uType => (Win32::MB_OK | Win32::MB_ICONERROR))
585 end
586 else
587 # user doesn't want to install gtk2 gem
588 end
589 else
590 # fixme: no gtk2 on Linux/Mac
591 puts "The gtk2 gem is not installed (or failed to load), you may need to: sudo gem install gtk2"
592 end
593 exit
594 else
595 # gtk is optional if command line arguments are given or started in a terminal
596 HAVE_GTK = false
597 early_gtk_error = "warning: failed to load GTK\n\t#{$!}\n\t#{$!.backtrace.join("\n\t")}"
598 end
599end
600
601if defined?(Gtk)
602 module Gtk
603 # Calling Gtk API in a thread other than the main thread may cause random segfaults
604 def Gtk.queue &block
605 GLib::Timeout.add(1) {
606 begin
607 block.call
608 rescue
609 respond "error in Gtk.queue: #{$!}"
610 Lich.log "error in Gtk.queue: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
611 rescue SyntaxError
612 respond "error in Gtk.queue: #{$!}"
613 Lich.log "error in Gtk.queue: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
614 rescue SystemExit
615 nil
616 rescue SecurityError
617 respond "error in Gtk.queue: #{$!}"
618 Lich.log "error in Gtk.queue: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
619 rescue ThreadError
620 respond "error in Gtk.queue: #{$!}"
621 Lich.log "error in Gtk.queue: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
622 rescue SystemStackError
623 respond "error in Gtk.queue: #{$!}"
624 Lich.log "error in Gtk.queue: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
625 rescue Exception
626 respond "error in Gtk.queue: #{$!}"
627 Lich.log "error in Gtk.queue: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
628 rescue ScriptError
629 respond "error in Gtk.queue: #{$!}"
630 Lich.log "error in Gtk.queue: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
631 rescue LoadError
632 respond "error in Gtk.queue: #{$!}"
633 Lich.log "error in Gtk.queue: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
634 rescue NoMemoryError
635 respond "error in Gtk.queue: #{$!}"
636 Lich.log "error in Gtk.queue: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
637 rescue
638 respond "error in Gtk.queue: #{$!}"
639 Lich.log "error in Gtk.queue: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
640 end
641 false # don't repeat timeout
642 }
643 end
644 end
645end
646
647module Lich
648 @@hosts_file = nil
649 @@lich_db = nil
650 def Lich.db
651 if $SAFE == 0
652 @@lich_db ||= SQLite3::Database.new("#{DATA_DIR}/lich.db3")
653 else
654 nil
655 end
656 end
657 def Lich.init_db
658 begin
659 Lich.db.execute("CREATE TABLE IF NOT EXISTS script_setting (script TEXT NOT NULL, name TEXT NOT NULL, value BLOB, PRIMARY KEY(script, name));")
660 Lich.db.execute("CREATE TABLE IF NOT EXISTS script_auto_settings (script TEXT NOT NULL, scope TEXT, hash BLOB, PRIMARY KEY(script, scope));")
661 Lich.db.execute("CREATE TABLE IF NOT EXISTS lich_settings (name TEXT NOT NULL, value TEXT, PRIMARY KEY(name));")
662 Lich.db.execute("CREATE TABLE IF NOT EXISTS uservars (scope TEXT NOT NULL, hash BLOB, PRIMARY KEY(scope));")
663 if (RUBY_VERSION =~ /^2\.[012]\./)
664 Lich.db.execute("CREATE TABLE IF NOT EXISTS trusted_scripts (name TEXT NOT NULL);")
665 end
666 Lich.db.execute("CREATE TABLE IF NOT EXISTS simu_game_entry (character TEXT NOT NULL, game_code TEXT NOT NULL, data BLOB, PRIMARY KEY(character, game_code));")
667 Lich.db.execute("CREATE TABLE IF NOT EXISTS enable_inventory_boxes (player_id INTEGER NOT NULL, PRIMARY KEY(player_id));")
668 rescue SQLite3::BusyException
669 sleep 0.1
670 retry
671 end
672 end
673 def Lich.class_variable_get(*a); nil; end
674 def Lich.class_eval(*a); nil; end
675 def Lich.module_eval(*a); nil; end
676 def Lich.log(msg)
677 $stderr.puts "#{Time.now.strftime("%Y-%m-%d %H:%M:%S")}: #{msg}"
678 end
679 def Lich.msgbox(args)
680 if defined?(Win32)
681 if args[:buttons] == :ok_cancel
682 buttons = Win32::MB_OKCANCEL
683 elsif args[:buttons] == :yes_no
684 buttons = Win32::MB_YESNO
685 else
686 buttons = Win32::MB_OK
687 end
688 if args[:icon] == :error
689 icon = Win32::MB_ICONERROR
690 elsif args[:icon] == :question
691 icon = Win32::MB_ICONQUESTION
692 elsif args[:icon] == :warning
693 icon = Win32::MB_ICONWARNING
694 else
695 icon = 0
696 end
697 args[:title] ||= "Lich v#{LICH_VERSION}"
698 r = Win32.MessageBox(:lpText => args[:message], :lpCaption => args[:title], :uType => (buttons|icon))
699 if r == Win32::IDIOK
700 return :ok
701 elsif r == Win32::IDICANCEL
702 return :cancel
703 elsif r == Win32::IDIYES
704 return :yes
705 elsif r == Win32::IDINO
706 return :no
707 else
708 return nil
709 end
710 elsif defined?(Gtk)
711 if args[:buttons] == :ok_cancel
712 buttons = Gtk::MessageDialog::BUTTONS_OK_CANCEL
713 elsif args[:buttons] == :yes_no
714 buttons = Gtk::MessageDialog::BUTTONS_YES_NO
715 else
716 buttons = Gtk::MessageDialog::BUTTONS_OK
717 end
718 if args[:icon] == :error
719 type = Gtk::MessageDialog::ERROR
720 elsif args[:icon] == :question
721 type = Gtk::MessageDialog::QUESTION
722 elsif args[:icon] == :warning
723 type = Gtk::MessageDialog::WARNING
724 else
725 type = Gtk::MessageDialog::INFO
726 end
727 dialog = Gtk::MessageDialog.new(nil, Gtk::Dialog::MODAL, type, buttons, args[:message])
728 args[:title] ||= "Lich v#{LICH_VERSION}"
729 dialog.title = args[:title]
730 response = nil
731 dialog.run { |r|
732 response = r
733 dialog.destroy
734 }
735 if response == Gtk::Dialog::RESPONSE_OK
736 return :ok
737 elsif response == Gtk::Dialog::RESPONSE_CANCEL
738 return :cancel
739 elsif response == Gtk::Dialog::RESPONSE_YES
740 return :yes
741 elsif response == Gtk::Dialog::RESPONSE_NO
742 return :no
743 else
744 return nil
745 end
746 elsif $stdout.isatty
747 $stdout.puts(args[:message])
748 return nil
749 end
750 end
751 def Lich.get_simu_launcher
752 if defined?(Win32)
753 begin
754 launcher_key = Win32.RegOpenKeyEx(:hKey => Win32::HKEY_LOCAL_MACHINE, :lpSubKey => 'Software\\Classes\\Simutronics.Autolaunch\\Shell\\Open\\command', :samDesired => (Win32::KEY_ALL_ACCESS|Win32::KEY_WOW64_32KEY))[:phkResult]
755 launcher_cmd = Win32.RegQueryValueEx(:hKey => launcher_key, :lpValueName => 'RealCommand')[:lpData]
756 if launcher_cmd.nil? or launcher_cmd.empty?
757 launcher_cmd = Win32.RegQueryValueEx(:hKey => launcher_key)[:lpData]
758 end
759 return launcher_cmd
760 ensure
761 Win32.RegCloseKey(:hKey => launcher_key) rescue nil
762 end
763 elsif defined?(Wine)
764 launcher_cmd = Wine.registry_gets('HKEY_LOCAL_MACHINE\\Software\\Classes\\Simutronics.Autolaunch\\Shell\\Open\\command\\RealCommand')
765 unless launcher_cmd and not launcher_cmd.empty?
766 launcher_cmd = Wine.registry_gets('HKEY_LOCAL_MACHINE\\Software\\Classes\\Simutronics.Autolaunch\\Shell\\Open\\command\\')
767 end
768 return launcher_cmd
769 else
770 return nil
771 end
772 end
773 def Lich.link_to_sge
774 if defined?(Win32)
775 if Win32.admin?
776 begin
777 launcher_key = Win32.RegOpenKeyEx(:hKey => Win32::HKEY_LOCAL_MACHINE, :lpSubKey => 'Software\\Simutronics\\Launcher', :samDesired => (Win32::KEY_ALL_ACCESS|Win32::KEY_WOW64_32KEY))[:phkResult]
778 r = Win32.RegQueryValueEx(:hKey => launcher_key, :lpValueName => 'RealDirectory')
779 if (r[:return] == 0) and not r[:lpData].empty?
780 # already linked
781 return true
782 end
783 r = Win32.GetModuleFileName
784 unless r[:return] > 0
785 # fixme
786 return false
787 end
788 new_launcher_dir = "\"#{r[:lpFilename]}\" \"#{File.expand_path($PROGRAM_NAME)}\" "
789 r = Win32.RegQueryValueEx(:hKey => launcher_key, :lpValueName => 'Directory')
790 launcher_dir = r[:lpData]
791 r = Win32.RegSetValueEx(:hKey => launcher_key, :lpValueName => 'RealDirectory', :dwType => Win32::REG_SZ, :lpData => launcher_dir)
792 return false unless (r == 0)
793 r = Win32.RegSetValueEx(:hKey => launcher_key, :lpValueName => 'Directory', :dwType => Win32::REG_SZ, :lpData => new_launcher_dir)
794 return (r == 0)
795 ensure
796 Win32.RegCloseKey(:hKey => launcher_key) rescue nil
797 end
798 else
799 begin
800 r = Win32.GetModuleFileName
801 file = ((r[:return] > 0) ? r[:lpFilename] : 'rubyw.exe')
802 params = "#{$PROGRAM_NAME.split(/\/|\\/).last} --link-to-sge"
803 r = Win32.ShellExecuteEx(:lpVerb => 'runas', :lpFile => file, :lpDirectory => LICH_DIR.tr("/", "\\"), :lpParameters => params, :fMask => Win32::SEE_MASK_NOCLOSEPROCESS)
804 if r[:return] > 0
805 process_id = r[:hProcess]
806 sleep 0.2 while Win32.GetExitCodeProcess(:hProcess => process_id)[:lpExitCode] == Win32::STILL_ACTIVE
807 sleep 3
808 else
809 Win32.ShellExecute(:lpOperation => 'runas', :lpFile => file, :lpDirectory => LICH_DIR.tr("/", "\\"), :lpParameters => params)
810 sleep 6
811 end
812 rescue
813 Lich.msgbox(:message => $!)
814 end
815 end
816 elsif defined?(Wine)
817 launch_dir = Wine.registry_gets('HKEY_LOCAL_MACHINE\\Software\\Simutronics\\Launcher\\Directory')
818 return false unless launch_dir
819 lich_launch_dir = "#{File.expand_path($PROGRAM_NAME)} --wine=#{Wine::BIN} --wine-prefix=#{Wine::PREFIX} "
820 result = true
821 if launch_dir
822 if launch_dir =~ /lich/i
823 $stdout.puts "--- warning: Lich appears to already be installed to the registry"
824 Lich.log "warning: Lich appears to already be installed to the registry"
825 Lich.log 'info: launch_dir: ' + launch_dir
826 else
827 result = result && Wine.registry_puts('HKEY_LOCAL_MACHINE\\Software\\Simutronics\\Launcher\\RealDirectory', launch_dir)
828 result = result && Wine.registry_puts('HKEY_LOCAL_MACHINE\\Software\\Simutronics\\Launcher\\Directory', lich_launch_dir)
829 end
830 end
831 return result
832 else
833 return false
834 end
835 end
836 def Lich.unlink_from_sge
837 if defined?(Win32)
838 if Win32.admin?
839 begin
840 launcher_key = Win32.RegOpenKeyEx(:hKey => Win32::HKEY_LOCAL_MACHINE, :lpSubKey => 'Software\\Simutronics\\Launcher', :samDesired => (Win32::KEY_ALL_ACCESS|Win32::KEY_WOW64_32KEY))[:phkResult]
841 real_directory = Win32.RegQueryValueEx(:hKey => launcher_key, :lpValueName => 'RealDirectory')[:lpData]
842 if real_directory.nil? or real_directory.empty?
843 # not linked
844 return true
845 end
846 r = Win32.RegSetValueEx(:hKey => launcher_key, :lpValueName => 'Directory', :dwType => Win32::REG_SZ, :lpData => real_directory)
847 return false unless (r == 0)
848 r = Win32.RegDeleteValue(:hKey => launcher_key, :lpValueName => 'RealDirectory')
849 return (r == 0)
850 ensure
851 Win32.RegCloseKey(:hKey => launcher_key) rescue nil
852 end
853 else
854 begin
855 r = Win32.GetModuleFileName
856 file = ((r[:return] > 0) ? r[:lpFilename] : 'rubyw.exe')
857 params = "#{$PROGRAM_NAME.split(/\/|\\/).last} --unlink-from-sge"
858 r = Win32.ShellExecuteEx(:lpVerb => 'runas', :lpFile => file, :lpDirectory => LICH_DIR.tr("/", "\\"), :lpParameters => params, :fMask => Win32::SEE_MASK_NOCLOSEPROCESS)
859 if r[:return] > 0
860 process_id = r[:hProcess]
861 sleep 0.2 while Win32.GetExitCodeProcess(:hProcess => process_id)[:lpExitCode] == Win32::STILL_ACTIVE
862 sleep 3
863 else
864 Win32.ShellExecute(:lpOperation => 'runas', :lpFile => file, :lpDirectory => LICH_DIR.tr("/", "\\"), :lpParameters => params)
865 sleep 6
866 end
867 rescue
868 Lich.msgbox(:message => $!)
869 end
870 end
871 elsif defined?(Wine)
872 real_launch_dir = Wine.registry_gets('HKEY_LOCAL_MACHINE\\Software\\Simutronics\\Launcher\\RealDirectory')
873 result = true
874 if real_launch_dir and not real_launch_dir.empty?
875 result = result && Wine.registry_puts('HKEY_LOCAL_MACHINE\\Software\\Simutronics\\Launcher\\Directory', real_launch_dir)
876 result = result && Wine.registry_puts('HKEY_LOCAL_MACHINE\\Software\\Simutronics\\Launcher\\RealDirectory', '')
877 end
878 return result
879 else
880 return false
881 end
882 end
883 def Lich.link_to_sal
884 if defined?(Win32)
885 if Win32.admin?
886 begin
887 # fixme: 64 bit browsers?
888 launcher_key = Win32.RegOpenKeyEx(:hKey => Win32::HKEY_LOCAL_MACHINE, :lpSubKey => 'Software\\Classes\\Simutronics.Autolaunch\\Shell\\Open\\command', :samDesired => (Win32::KEY_ALL_ACCESS|Win32::KEY_WOW64_32KEY))[:phkResult]
889 r = Win32.RegQueryValueEx(:hKey => launcher_key, :lpValueName => 'RealCommand')
890 if (r[:return] == 0) and not r[:lpData].empty?
891 # already linked
892 return true
893 end
894 r = Win32.GetModuleFileName
895 unless r[:return] > 0
896 # fixme
897 return false
898 end
899 new_launcher_cmd = "\"#{r[:lpFilename]}\" \"#{File.expand_path($PROGRAM_NAME)}\" %1"
900 r = Win32.RegQueryValueEx(:hKey => launcher_key)
901 launcher_cmd = r[:lpData]
902 r = Win32.RegSetValueEx(:hKey => launcher_key, :lpValueName => 'RealCommand', :dwType => Win32::REG_SZ, :lpData => launcher_cmd)
903 return false unless (r == 0)
904 r = Win32.RegSetValueEx(:hKey => launcher_key, :dwType => Win32::REG_SZ, :lpData => new_launcher_cmd)
905 return (r == 0)
906 ensure
907 Win32.RegCloseKey(:hKey => launcher_key) rescue nil
908 end
909 else
910 begin
911 r = Win32.GetModuleFileName
912 file = ((r[:return] > 0) ? r[:lpFilename] : 'rubyw.exe')
913 params = "#{$PROGRAM_NAME.split(/\/|\\/).last} --link-to-sal"
914 r = Win32.ShellExecuteEx(:lpVerb => 'runas', :lpFile => file, :lpDirectory => LICH_DIR.tr("/", "\\"), :lpParameters => params, :fMask => Win32::SEE_MASK_NOCLOSEPROCESS)
915 if r[:return] > 0
916 process_id = r[:hProcess]
917 sleep 0.2 while Win32.GetExitCodeProcess(:hProcess => process_id)[:lpExitCode] == Win32::STILL_ACTIVE
918 sleep 3
919 else
920 Win32.ShellExecute(:lpOperation => 'runas', :lpFile => file, :lpDirectory => LICH_DIR.tr("/", "\\"), :lpParameters => params)
921 sleep 6
922 end
923 rescue
924 Lich.msgbox(:message => $!)
925 end
926 end
927 elsif defined?(Wine)
928 launch_cmd = Wine.registry_gets('HKEY_LOCAL_MACHINE\\Software\\Classes\\Simutronics.Autolaunch\\Shell\\Open\\command\\')
929 return false unless launch_cmd
930 new_launch_cmd = "#{File.expand_path($PROGRAM_NAME)} --wine=#{Wine::BIN} --wine-prefix=#{Wine::PREFIX} %1"
931 result = true
932 if launch_cmd
933 if launch_cmd =~ /lich/i
934 $stdout.puts "--- warning: Lich appears to already be installed to the registry"
935 Lich.log "warning: Lich appears to already be installed to the registry"
936 Lich.log 'info: launch_cmd: ' + launch_cmd
937 else
938 result = result && Wine.registry_puts('HKEY_LOCAL_MACHINE\\Software\\Classes\\Simutronics.Autolaunch\\Shell\\Open\\command\\RealCommand', launch_cmd)
939 result = result && Wine.registry_puts('HKEY_LOCAL_MACHINE\\Software\\Classes\\Simutronics.Autolaunch\\Shell\\Open\\command\\', new_launch_cmd)
940 end
941 end
942 return result
943 else
944 return false
945 end
946 end
947 def Lich.unlink_from_sal
948 if defined?(Win32)
949 if Win32.admin?
950 begin
951 launcher_key = Win32.RegOpenKeyEx(:hKey => Win32::HKEY_LOCAL_MACHINE, :lpSubKey => 'Software\\Classes\\Simutronics.Autolaunch\\Shell\\Open\\command', :samDesired => (Win32::KEY_ALL_ACCESS|Win32::KEY_WOW64_32KEY))[:phkResult]
952 real_directory = Win32.RegQueryValueEx(:hKey => launcher_key, :lpValueName => 'RealCommand')[:lpData]
953 if real_directory.nil? or real_directory.empty?
954 # not linked
955 return true
956 end
957 r = Win32.RegSetValueEx(:hKey => launcher_key, :dwType => Win32::REG_SZ, :lpData => real_directory)
958 return false unless (r == 0)
959 r = Win32.RegDeleteValue(:hKey => launcher_key, :lpValueName => 'RealCommand')
960 return (r == 0)
961 ensure
962 Win32.RegCloseKey(:hKey => launcher_key) rescue nil
963 end
964 else
965 begin
966 r = Win32.GetModuleFileName
967 file = ((r[:return] > 0) ? r[:lpFilename] : 'rubyw.exe')
968 params = "#{$PROGRAM_NAME.split(/\/|\\/).last} --unlink-from-sal"
969 r = Win32.ShellExecuteEx(:lpVerb => 'runas', :lpFile => file, :lpDirectory => LICH_DIR.tr("/", "\\"), :lpParameters => params, :fMask => Win32::SEE_MASK_NOCLOSEPROCESS)
970 if r[:return] > 0
971 process_id = r[:hProcess]
972 sleep 0.2 while Win32.GetExitCodeProcess(:hProcess => process_id)[:lpExitCode] == Win32::STILL_ACTIVE
973 sleep 3
974 else
975 Win32.ShellExecute(:lpOperation => 'runas', :lpFile => file, :lpDirectory => LICH_DIR.tr("/", "\\"), :lpParameters => params)
976 sleep 6
977 end
978 rescue
979 Lich.msgbox(:message => $!)
980 end
981 end
982 elsif defined?(Wine)
983 real_launch_cmd = Wine.registry_gets('HKEY_LOCAL_MACHINE\\Software\\Classes\\Simutronics.Autolaunch\\Shell\\Open\\command\\RealCommand')
984 result = true
985 if real_launch_cmd and not real_launch_cmd.empty?
986 result = result && Wine.registry_puts('HKEY_LOCAL_MACHINE\\Software\\Classes\\Simutronics.Autolaunch\\Shell\\Open\\command\\', real_launch_cmd)
987 result = result && Wine.registry_puts('HKEY_LOCAL_MACHINE\\Software\\Classes\\Simutronics.Autolaunch\\Shell\\Open\\command\\RealCommand', '')
988 end
989 return result
990 else
991 return false
992 end
993 end
994 def Lich.hosts_file
995 Lich.find_hosts_file if @@hosts_file.nil?
996 return @@hosts_file
997 end
998 def Lich.find_hosts_file
999 if defined?(Win32)
1000 begin
1001 key = Win32.RegOpenKeyEx(:hKey => Win32::HKEY_LOCAL_MACHINE, :lpSubKey => 'System\\CurrentControlSet\\Services\\Tcpip\\Parameters', :samDesired => Win32::KEY_READ)[:phkResult]
1002 hosts_path = Win32.RegQueryValueEx(:hKey => key, :lpValueName => 'DataBasePath')[:lpData]
1003 ensure
1004 Win32.RegCloseKey(:hKey => key) rescue nil
1005 end
1006 if hosts_path
1007 windir = (ENV['windir'] || ENV['SYSTEMROOT'] || 'c:\windows')
1008 hosts_path.gsub('%SystemRoot%', windir)
1009 hosts_file = "#{hosts_path}\\hosts"
1010 if File.exists?(hosts_file)
1011 return (@@hosts_file = hosts_file)
1012 end
1013 end
1014 if (windir = (ENV['windir'] || ENV['SYSTEMROOT'])) and File.exists?("#{windir}\\system32\\drivers\\etc\\hosts")
1015 return (@@hosts_file = "#{windir}\\system32\\drivers\\etc\\hosts")
1016 end
1017 for drive in ['C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z']
1018 for windir in ['winnt','windows']
1019 if File.exists?("#{drive}:\\#{windir}\\system32\\drivers\\etc\\hosts")
1020 return (@@hosts_file = "#{drive}:\\#{windir}\\system32\\drivers\\etc\\hosts")
1021 end
1022 end
1023 end
1024 else # Linux/Mac
1025 if File.exists?('/etc/hosts')
1026 return (@@hosts_file = '/etc/hosts')
1027 elsif File.exists?('/private/etc/hosts')
1028 return (@@hosts_file = '/private/etc/hosts')
1029 end
1030 end
1031 return (@@hosts_file = false)
1032 end
1033 def Lich.modify_hosts(game_host)
1034 if Lich.hosts_file and File.exists?(Lich.hosts_file)
1035 at_exit { Lich.restore_hosts }
1036 Lich.restore_hosts
1037 if File.exists?("#{Lich.hosts_file}.bak")
1038 return false
1039 end
1040 begin
1041 # copy hosts to hosts.bak
1042 File.open("#{Lich.hosts_file}.bak", 'w') { |hb| File.open(Lich.hosts_file) { |h| hb.write(h.read) } }
1043 rescue
1044 File.unlink("#{Lich.hosts_file}.bak") if File.exists?("#{Lich.hosts_file}.bak")
1045 return false
1046 end
1047 File.open(Lich.hosts_file, 'a') { |f| f.write "\r\n127.0.0.1\t\t#{game_host}" }
1048 return true
1049 else
1050 return false
1051 end
1052 end
1053 def Lich.restore_hosts
1054 if Lich.hosts_file and File.exists?(Lich.hosts_file)
1055 begin
1056 # fixme: use rename instead? test rename on windows
1057 if File.exists?("#{Lich.hosts_file}.bak")
1058 File.open("#{Lich.hosts_file}.bak") { |infile|
1059 File.open(Lich.hosts_file, 'w') { |outfile|
1060 outfile.write(infile.read)
1061 }
1062 }
1063 File.unlink "#{Lich.hosts_file}.bak"
1064 end
1065 rescue
1066 $stdout.puts "--- error: restore_hosts: #{$!}"
1067 Lich.log "error: restore_hosts: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
1068 exit(1)
1069 end
1070 end
1071 end
1072 def Lich.inventory_boxes(player_id)
1073 begin
1074 v = Lich.db.get_first_value('SELECT player_id FROM enable_inventory_boxes WHERE player_id=?;', player_id.to_i)
1075 rescue SQLite3::BusyException
1076 sleep 0.1
1077 retry
1078 end
1079 if v
1080 true
1081 else
1082 false
1083 end
1084 end
1085 def Lich.set_inventory_boxes(player_id, enabled)
1086 if enabled
1087 begin
1088 Lich.db.execute('INSERT OR REPLACE INTO enable_inventory_boxes values(?);', player_id.to_i)
1089 rescue SQLite3::BusyException
1090 sleep 0.1
1091 retry
1092 end
1093 else
1094 begin
1095 Lich.db.execute('DELETE FROM enable_inventory_boxes where player_id=?;', player_id.to_i)
1096 rescue SQLite3::BusyException
1097 sleep 0.1
1098 retry
1099 end
1100 end
1101 nil
1102 end
1103 def Lich.win32_launch_method
1104 begin
1105 val = Lich.db.get_first_value("SELECT value FROM lich_settings WHERE name='win32_launch_method';")
1106 rescue SQLite3::BusyException
1107 sleep 0.1
1108 retry
1109 end
1110 val
1111 end
1112 def Lich.win32_launch_method=(val)
1113 begin
1114 Lich.db.execute("INSERT OR REPLACE INTO lich_settings(name,value) values('win32_launch_method',?);", val.to_s.encode('UTF-8'))
1115 rescue SQLite3::BusyException
1116 sleep 0.1
1117 retry
1118 end
1119 nil
1120 end
1121 def Lich.fix_game_host_port(gamehost,gameport)
1122 if (gamehost == 'gs-plat.simutronics.net') and (gameport.to_i == 10121)
1123 gamehost = 'storm.gs4.game.play.net'
1124 gameport = 10124
1125 elsif (gamehost == 'gs3.simutronics.net') and (gameport.to_i == 4900)
1126 gamehost = 'storm.gs4.game.play.net'
1127 gameport = 10024
1128 elsif (gamehost == 'gs4.simutronics.net') and (gameport.to_i == 10321)
1129 game_host = 'storm.gs4.game.play.net'
1130 game_port = 10324
1131 elsif (gamehost == 'prime.dr.game.play.net') and (gameport.to_i == 4901)
1132 gamehost = 'dr.simutronics.net'
1133 gameport = 11024
1134 end
1135 [ gamehost, gameport ]
1136 end
1137 def Lich.break_game_host_port(gamehost,gameport)
1138 if (gamehost == 'storm.gs4.game.play.net') and (gameport.to_i == 10324)
1139 gamehost = 'gs4.simutronics.net'
1140 gameport = 10321
1141 elsif (gamehost == 'storm.gs4.game.play.net') and (gameport.to_i == 10124)
1142 gamehost = 'gs-plat.simutronics.net'
1143 gameport = 10121
1144 elsif (gamehost == 'storm.gs4.game.play.net') and (gameport.to_i == 10024)
1145 gamehost = 'gs3.simutronics.net'
1146 gameport = 4900
1147 elsif (gamehost == 'storm.gs4.game.play.net') and (gameport.to_i == 10324)
1148 game_host = 'gs4.simutronics.net'
1149 game_port = 10321
1150 elsif (gamehost == 'dr.simutronics.net') and (gameport.to_i == 11024)
1151 gamehost = 'prime.dr.game.play.net'
1152 gameport = 4901
1153 end
1154 [ gamehost, gameport ]
1155 end
1156end
1157
1158class NilClass
1159 def dup
1160 nil
1161 end
1162 def method_missing(*args)
1163 nil
1164 end
1165 def split(*val)
1166 Array.new
1167 end
1168 def to_s
1169 ""
1170 end
1171 def strip
1172 ""
1173 end
1174 def +(val)
1175 val
1176 end
1177 def closed?
1178 true
1179 end
1180end
1181
1182class Numeric
1183 def as_time
1184 sprintf("%d:%02d:%02d", (self / 60).truncate, self.truncate % 60, ((self % 1) * 60).truncate)
1185 end
1186 def with_commas
1187 self.to_s.reverse.scan(/(?:\d*\.)?\d{1,3}-?/).join(',').reverse
1188 end
1189end
1190
1191class TrueClass
1192 def method_missing(*usersave)
1193 true
1194 end
1195end
1196
1197class FalseClass
1198 def method_missing(*usersave)
1199 nil
1200 end
1201end
1202
1203class String
1204 @@elevated_untaint = proc { |what| what.orig_untaint }
1205 alias :orig_untaint :untaint
1206 def untaint
1207 @@elevated_untaint.call(self)
1208 end
1209 def to_s
1210 self.dup
1211 end
1212 def stream
1213 @stream
1214 end
1215 def stream=(val)
1216 @stream ||= val
1217 end
1218end
1219
1220class StringProc
1221 def initialize(string)
1222 @string = string
1223 @string.untaint
1224 end
1225 def kind_of?(type)
1226 Proc.new {}.kind_of? type
1227 end
1228 def class
1229 Proc
1230 end
1231 def call(*a)
1232 proc { begin; $SAFE = 3; rescue; nil; end; eval(@string) }.call
1233 end
1234 def _dump(d=nil)
1235 @string
1236 end
1237 def inspect
1238 "StringProc.new(#{@string.inspect})"
1239 end
1240end
1241
1242class SynchronizedSocket
1243 def initialize(o)
1244 @delegate = o
1245 @mutex = Mutex.new
1246 self
1247 end
1248 def puts(*args, &block)
1249 @mutex.synchronize {
1250 @delegate.puts *args, &block
1251 }
1252 end
1253 def write(*args, &block)
1254 @mutex.synchronize {
1255 @delegate.write *args, &block
1256 }
1257 end
1258 def method_missing(method, *args, &block)
1259 @delegate.__send__ method, *args, &block
1260 end
1261end
1262
1263class LimitedArray < Array
1264 attr_accessor :max_size
1265 def initialize(size=0, obj=nil)
1266 @max_size = 200
1267 super
1268 end
1269 def push(line)
1270 self.shift while self.length >= @max_size
1271 super
1272 end
1273 def shove(line)
1274 push(line)
1275 end
1276 def history
1277 Array.new
1278 end
1279end
1280
1281class XMLParser
1282 attr_reader :mana, :max_mana, :health, :max_health, :spirit, :max_spirit, :last_spirit, :stamina, :max_stamina, :stance_text, :stance_value, :mind_text, :mind_value, :prepared_spell, :encumbrance_text, :encumbrance_full_text, :encumbrance_value, :indicator, :injuries, :injury_mode, :room_count, :room_title, :room_description, :room_exits, :room_exits_string, :familiar_room_title, :familiar_room_description, :familiar_room_exits, :bounty_task, :injury_mode, :server_time, :server_time_offset, :roundtime_end, :cast_roundtime_end, :last_pulse, :level, :next_level_value, :next_level_text, :society_task, :stow_container_id, :name, :game, :in_stream, :player_id, :active_spells, :prompt, :current_target_ids, :current_target_id, :room_window_disabled
1283 attr_accessor :send_fake_tags
1284
1285 @@warned_deprecated_spellfront = 0
1286
1287 include REXML::StreamListener
1288
1289 def initialize
1290 @buffer = String.new
1291 @unescape = { 'lt' => '<', 'gt' => '>', 'quot' => '"', 'apos' => "'", 'amp' => '&' }
1292 @bold = false
1293 @active_tags = Array.new
1294 @active_ids = Array.new
1295 @last_tag = String.new
1296 @last_id = String.new
1297 @current_stream = String.new
1298 @current_style = String.new
1299 @stow_container_id = nil
1300 @obj_location = nil
1301 @obj_exist = nil
1302 @obj_noun = nil
1303 @obj_before_name = nil
1304 @obj_name = nil
1305 @obj_after_name = nil
1306 @pc = nil
1307 @last_obj = nil
1308 @in_stream = false
1309 @player_status = nil
1310 @fam_mode = String.new
1311 @room_window_disabled = false
1312 @wound_gsl = String.new
1313 @scar_gsl = String.new
1314 @send_fake_tags = false
1315 @prompt = String.new
1316 @nerve_tracker_num = 0
1317 @nerve_tracker_active = 'no'
1318 @server_time = Time.now.to_i
1319 @server_time_offset = 0
1320 @roundtime_end = 0
1321 @cast_roundtime_end = 0
1322 @last_pulse = Time.now.to_i
1323 @level = 0
1324 @next_level_value = 0
1325 @next_level_text = String.new
1326 @current_target_ids = Array.new
1327
1328 @room_count = 0
1329 @room_title = String.new
1330 @room_description = String.new
1331 @room_exits = Array.new
1332 @room_exits_string = String.new
1333
1334 @familiar_room_title = String.new
1335 @familiar_room_description = String.new
1336 @familiar_room_exits = Array.new
1337
1338 @bounty_task = String.new
1339 @society_task = String.new
1340
1341 @name = String.new
1342 @game = String.new
1343 @player_id = String.new
1344 @mana = 0
1345 @max_mana = 0
1346 @health = 0
1347 @max_health = 0
1348 @spirit = 0
1349 @max_spirit = 0
1350 @last_spirit = nil
1351 @stamina = 0
1352 @max_stamina = 0
1353 @stance_text = String.new
1354 @stance_value = 0
1355 @mind_text = String.new
1356 @mind_value = 0
1357 @prepared_spell = 'None'
1358 @encumbrance_text = String.new
1359 @encumbrance_full_text = String.new
1360 @encumbrance_value = 0
1361 @indicator = Hash.new
1362 @injuries = {'back' => {'scar' => 0, 'wound' => 0}, 'leftHand' => {'scar' => 0, 'wound' => 0}, 'rightHand' => {'scar' => 0, 'wound' => 0}, 'head' => {'scar' => 0, 'wound' => 0}, 'rightArm' => {'scar' => 0, 'wound' => 0}, 'abdomen' => {'scar' => 0, 'wound' => 0}, 'leftEye' => {'scar' => 0, 'wound' => 0}, 'leftArm' => {'scar' => 0, 'wound' => 0}, 'chest' => {'scar' => 0, 'wound' => 0}, 'leftFoot' => {'scar' => 0, 'wound' => 0}, 'rightFoot' => {'scar' => 0, 'wound' => 0}, 'rightLeg' => {'scar' => 0, 'wound' => 0}, 'neck' => {'scar' => 0, 'wound' => 0}, 'leftLeg' => {'scar' => 0, 'wound' => 0}, 'nsys' => {'scar' => 0, 'wound' => 0}, 'rightEye' => {'scar' => 0, 'wound' => 0}}
1363 @injury_mode = 0
1364
1365 @active_spells = Hash.new
1366
1367 end
1368
1369 def reset
1370 @active_tags = Array.new
1371 @active_ids = Array.new
1372 @current_stream = String.new
1373 @current_style = String.new
1374 end
1375
1376 def make_wound_gsl
1377 @wound_gsl = sprintf("0b0%02b%02b%02b%02b%02b%02b%02b%02b%02b%02b%02b%02b%02b%02b",@injuries['nsys']['wound'],@injuries['leftEye']['wound'],@injuries['rightEye']['wound'],@injuries['back']['wound'],@injuries['abdomen']['wound'],@injuries['chest']['wound'],@injuries['leftHand']['wound'],@injuries['rightHand']['wound'],@injuries['leftLeg']['wound'],@injuries['rightLeg']['wound'],@injuries['leftArm']['wound'],@injuries['rightArm']['wound'],@injuries['neck']['wound'],@injuries['head']['wound'])
1378 end
1379
1380 def make_scar_gsl
1381 @scar_gsl = sprintf("0b0%02b%02b%02b%02b%02b%02b%02b%02b%02b%02b%02b%02b%02b%02b",@injuries['nsys']['scar'],@injuries['leftEye']['scar'],@injuries['rightEye']['scar'],@injuries['back']['scar'],@injuries['abdomen']['scar'],@injuries['chest']['scar'],@injuries['leftHand']['scar'],@injuries['rightHand']['scar'],@injuries['leftLeg']['scar'],@injuries['rightLeg']['scar'],@injuries['leftArm']['scar'],@injuries['rightArm']['scar'],@injuries['neck']['scar'],@injuries['head']['scar'])
1382 end
1383
1384 def parse(line)
1385 @buffer.concat(line)
1386 loop {
1387 if str = @buffer.slice!(/^[^<]+/)
1388 text(str.gsub(/&(lt|gt|quot|apos|amp)/) { @unescape[$1] })
1389 elsif str = @buffer.slice!(/^<\/[^<]+>/)
1390 element = /^<\/([^\s>\/]+)/.match(str).captures.first
1391 tag_end(element)
1392 elsif str = @buffer.slice!(/^<[^<]+>/)
1393 element = /^<([^\s>\/]+)/.match(str).captures.first
1394 attributes = Hash.new
1395 str.scan(/([A-z][A-z0-9_\-]*)=(["'])(.*?)\2/).each { |attr| attributes[attr[0]] = attr[2] }
1396 tag_start(element, attributes)
1397 tag_end(element) if str =~ /\/>$/
1398 else
1399 break
1400 end
1401 }
1402 end
1403
1404 def tag_start(name, attributes)
1405 begin
1406 @active_tags.push(name)
1407 @active_ids.push(attributes['id'].to_s)
1408 if name =~ /^(?:a|right|left)$/
1409 @obj_exist = attributes['exist']
1410 @obj_noun = attributes['noun']
1411 elsif name == 'inv'
1412 if attributes['id'] == 'stow'
1413 @obj_location = @stow_container_id
1414 else
1415 @obj_location = attributes['id']
1416 end
1417 @obj_exist = nil
1418 @obj_noun = nil
1419 @obj_name = nil
1420 @obj_before_name = nil
1421 @obj_after_name = nil
1422 elsif name == 'dialogData' and attributes['id'] == 'ActiveSpells' and attributes['clear'] == 't'
1423 @active_spells.clear
1424 elsif name == 'resource' or name == 'nav'
1425 nil
1426 elsif name == 'pushStream'
1427 @in_stream = true
1428 @current_stream = attributes['id'].to_s
1429 GameObj.clear_inv if attributes['id'].to_s == 'inv'
1430 elsif name == 'popStream'
1431 if attributes['id'] == 'room'
1432 unless @room_window_disabled
1433 @room_count += 1
1434 $room_count += 1
1435 end
1436 end
1437 @in_stream = false
1438 if attributes['id'] == 'bounty'
1439 @bounty_task.strip!
1440 end
1441 @current_stream = String.new
1442 elsif name == 'pushBold'
1443 @bold = true
1444 elsif name == 'popBold'
1445 @bold = false
1446 elsif (name == 'streamWindow')
1447 if (attributes['id'] == 'main') and attributes['subtitle']
1448 @room_title = '[' + attributes['subtitle'][3..-1] + ']'
1449 end
1450 elsif name == 'style'
1451 @current_style = attributes['id']
1452 elsif name == 'prompt'
1453 @server_time = attributes['time'].to_i
1454 @server_time_offset = (Time.now.to_i - @server_time)
1455 $_CLIENT_.puts "\034GSq#{sprintf('%010d', @server_time)}\r\n" if @send_fake_tags
1456 elsif (name == 'compDef') or (name == 'component')
1457 if attributes['id'] == 'room objs'
1458 GameObj.clear_loot
1459 GameObj.clear_npcs
1460 elsif attributes['id'] == 'room players'
1461 GameObj.clear_pcs
1462 elsif attributes['id'] == 'room exits'
1463 @room_exits = Array.new
1464 @room_exits_string = String.new
1465 elsif attributes['id'] == 'room desc'
1466 @room_description = String.new
1467 GameObj.clear_room_desc
1468 elsif attributes['id'] == 'room extra' # DragonRealms
1469 @room_count += 1
1470 $room_count += 1
1471 # elsif attributes['id'] == 'sprite'
1472 end
1473 elsif name == 'clearContainer'
1474 if attributes['id'] == 'stow'
1475 GameObj.clear_container(@stow_container_id)
1476 else
1477 GameObj.clear_container(attributes['id'])
1478 end
1479 elsif name == 'deleteContainer'
1480 GameObj.delete_container(attributes['id'])
1481 elsif name == 'progressBar'
1482 if attributes['id'] == 'pbarStance'
1483 @stance_text = attributes['text'].split.first
1484 @stance_value = attributes['value'].to_i
1485 $_CLIENT_.puts "\034GSg#{sprintf('%010d', @stance_value)}\r\n" if @send_fake_tags
1486 elsif attributes['id'] == 'mana'
1487 last_mana = @mana
1488 @mana, @max_mana = attributes['text'].scan(/-?\d+/).collect { |num| num.to_i }
1489 difference = @mana - last_mana
1490 # fixme: enhancives screw this up
1491 if (difference == noded_pulse) or (difference == unnoded_pulse) or ( (@mana == @max_mana) and (last_mana + noded_pulse > @max_mana) )
1492 @last_pulse = Time.now.to_i
1493 if @send_fake_tags
1494 $_CLIENT_.puts "\034GSZ#{sprintf('%010d',(@mana+1))}\n"
1495 $_CLIENT_.puts "\034GSZ#{sprintf('%010d',@mana)}\n"
1496 end
1497 end
1498 if @send_fake_tags
1499 $_CLIENT_.puts "\034GSV#{sprintf('%010d%010d%010d%010d%010d%010d%010d%010d', @max_health.to_i, @health.to_i, @max_spirit.to_i, @spirit.to_i, @max_mana.to_i, @mana.to_i, @wound_gsl, @scar_gsl)}\r\n"
1500 end
1501 elsif attributes['id'] == 'stamina'
1502 @stamina, @max_stamina = attributes['text'].scan(/-?\d+/).collect { |num| num.to_i }
1503 elsif attributes['id'] == 'mindState'
1504 @mind_text = attributes['text']
1505 @mind_value = attributes['value'].to_i
1506 $_CLIENT_.puts "\034GSr#{MINDMAP[@mind_text]}\r\n" if @send_fake_tags
1507 elsif attributes['id'] == 'health'
1508 @health, @max_health = attributes['text'].scan(/-?\d+/).collect { |num| num.to_i }
1509 $_CLIENT_.puts "\034GSV#{sprintf('%010d%010d%010d%010d%010d%010d%010d%010d', @max_health.to_i, @health.to_i, @max_spirit.to_i, @spirit.to_i, @max_mana.to_i, @mana.to_i, @wound_gsl, @scar_gsl)}\r\n" if @send_fake_tags
1510 elsif attributes['id'] == 'spirit'
1511 @last_spirit = @spirit if @last_spirit
1512 @spirit, @max_spirit = attributes['text'].scan(/-?\d+/).collect { |num| num.to_i }
1513 @last_spirit = @spirit unless @last_spirit
1514 $_CLIENT_.puts "\034GSV#{sprintf('%010d%010d%010d%010d%010d%010d%010d%010d', @max_health.to_i, @health.to_i, @max_spirit.to_i, @spirit.to_i, @max_mana.to_i, @mana.to_i, @wound_gsl, @scar_gsl)}\r\n" if @send_fake_tags
1515 elsif attributes['id'] == 'nextLvlPB'
1516 Gift.pulse unless @next_level_text == attributes['text']
1517 @next_level_value = attributes['value'].to_i
1518 @next_level_text = attributes['text']
1519 elsif attributes['id'] == 'encumlevel'
1520 @encumbrance_value = attributes['value'].to_i
1521 @encumbrance_text = attributes['text']
1522 end
1523 elsif name == 'roundTime'
1524 @roundtime_end = attributes['value'].to_i
1525 $_CLIENT_.puts "\034GSQ#{sprintf('%010d', @roundtime_end)}\r\n" if @send_fake_tags
1526 elsif name == 'castTime'
1527 @cast_roundtime_end = attributes['value'].to_i
1528 elsif name == 'dropDownBox'
1529 if attributes['id'] == 'dDBTarget'
1530 @current_target_ids.clear
1531 attributes['content_value'].split(',').each { |t|
1532 if t =~ /^\#(\-?\d+)(?:,|$)/
1533 @current_target_ids.push($1)
1534 end
1535 }
1536 if attributes['content_value'] =~ /^\#(\-?\d+)(?:,|$)/
1537 @current_target_id = $1
1538 else
1539 @current_target_id = nil
1540 end
1541 end
1542 elsif name == 'indicator'
1543 @indicator[attributes['id']] = attributes['visible']
1544 if @send_fake_tags
1545 if attributes['id'] == 'IconPOISONED'
1546 if attributes['visible'] == 'y'
1547 $_CLIENT_.puts "\034GSJ0000000000000000000100000000001\r\n"
1548 else
1549 $_CLIENT_.puts "\034GSJ0000000000000000000000000000000\r\n"
1550 end
1551 elsif attributes['id'] == 'IconDISEASED'
1552 if attributes['visible'] == 'y'
1553 $_CLIENT_.puts "\034GSK0000000000000000000100000000001\r\n"
1554 else
1555 $_CLIENT_.puts "\034GSK0000000000000000000000000000000\r\n"
1556 end
1557 else
1558 gsl_prompt = String.new; ICONMAP.keys.each { |icon| gsl_prompt += ICONMAP[icon] if @indicator[icon] == 'y' }
1559 $_CLIENT_.puts "\034GSP#{sprintf('%-30s', gsl_prompt)}\r\n"
1560 end
1561 end
1562 elsif (name == 'image') and @active_ids.include?('injuries')
1563 if @injuries.keys.include?(attributes['id'])
1564 if attributes['name'] =~ /Injury/i
1565 @injuries[attributes['id']]['wound'] = attributes['name'].slice(/\d/).to_i
1566 elsif attributes['name'] =~ /Scar/i
1567 @injuries[attributes['id']]['wound'] = 0
1568 @injuries[attributes['id']]['scar'] = attributes['name'].slice(/\d/).to_i
1569 elsif attributes['name'] =~ /Nsys/i
1570 rank = attributes['name'].slice(/\d/).to_i
1571 if rank == 0
1572 @injuries['nsys']['wound'] = 0
1573 @injuries['nsys']['scar'] = 0
1574 else
1575 Thread.new {
1576 wait_while { dead? }
1577 action = proc { |server_string|
1578 if (@nerve_tracker_active == 'maybe')
1579 if @nerve_tracker_active == 'maybe'
1580 if server_string =~ /^You/
1581 @nerve_tracker_active = 'yes'
1582 @injuries['nsys']['wound'] = 0
1583 @injuries['nsys']['scar'] = 0
1584 else
1585 @nerve_tracker_active = 'no'
1586 end
1587 end
1588 end
1589 if @nerve_tracker_active == 'yes'
1590 if server_string =~ /<output class=['"]['"]\/>/
1591 @nerve_tracker_active = 'no'
1592 @nerve_tracker_num -= 1
1593 DownstreamHook.remove('nerve_tracker') if @nerve_tracker_num < 1
1594 $_CLIENT_.puts "\034GSV#{sprintf('%010d%010d%010d%010d%010d%010d%010d%010d', @max_health.to_i, @health.to_i, @max_spirit.to_i, @spirit.to_i, @max_mana.to_i, @mana.to_i, make_wound_gsl, make_scar_gsl)}\r\n" if @send_fake_tags
1595 server_string
1596 elsif server_string =~ /a case of uncontrollable convulsions/
1597 @injuries['nsys']['wound'] = 3
1598 nil
1599 elsif server_string =~ /a case of sporadic convulsions/
1600 @injuries['nsys']['wound'] = 2
1601 nil
1602 elsif server_string =~ /a strange case of muscle twitching/
1603 @injuries['nsys']['wound'] = 1
1604 nil
1605 elsif server_string =~ /a very difficult time with muscle control/
1606 @injuries['nsys']['scar'] = 3
1607 nil
1608 elsif server_string =~ /constant muscle spasms/
1609 @injuries['nsys']['scar'] = 2
1610 nil
1611 elsif server_string =~ /developed slurred speech/
1612 @injuries['nsys']['scar'] = 1
1613 nil
1614 end
1615 else
1616 if server_string =~ /<output class=['"]mono['"]\/>/
1617 @nerve_tracker_active = 'maybe'
1618 end
1619 server_string
1620 end
1621 }
1622 @nerve_tracker_num += 1
1623 DownstreamHook.add('nerve_tracker', action)
1624 Game._puts "#{$cmd_prefix}health"
1625 }
1626 end
1627 else
1628 @injuries[attributes['id']]['wound'] = 0
1629 @injuries[attributes['id']]['scar'] = 0
1630 end
1631 end
1632 $_CLIENT_.puts "\034GSV#{sprintf('%010d%010d%010d%010d%010d%010d%010d%010d', @max_health.to_i, @health.to_i, @max_spirit.to_i, @spirit.to_i, @max_mana.to_i, @mana.to_i, make_wound_gsl, make_scar_gsl)}\r\n" if @send_fake_tags
1633 elsif name == 'compass'
1634 if @current_stream == 'familiar'
1635 @fam_mode = String.new
1636 elsif @room_window_disabled
1637 @room_exits = Array.new
1638 end
1639 elsif @room_window_disabled and (name == 'dir') and @active_tags.include?('compass')
1640 @room_exits.push(LONGDIR[attributes['value']])
1641 elsif name == 'radio'
1642 if attributes['id'] == 'injrRad'
1643 @injury_mode = 0 if attributes['value'] == '1'
1644 elsif attributes['id'] == 'scarRad'
1645 @injury_mode = 1 if attributes['value'] == '1'
1646 elsif attributes['id'] == 'bothRad'
1647 @injury_mode = 2 if attributes['value'] == '1'
1648 end
1649 elsif name == 'label'
1650 if attributes['id'] == 'yourLvl'
1651 @level = Stats.level = attributes['value'].slice(/\d+/).to_i
1652 elsif attributes['id'] == 'encumblurb'
1653 @encumbrance_full_text = attributes['value']
1654 elsif @active_tags[-2] == 'dialogData' and @active_ids[-2] == 'ActiveSpells'
1655 if (name = /^lbl(.+)$/.match(attributes['id']).captures.first) and (value = /^\s*([0-9\:]+)\s*$/.match(attributes['value']).captures.first)
1656 hour, minute = value.split(':')
1657 @active_spells[name] = Time.now + (hour.to_i * 3600) + (minute.to_i * 60)
1658 end
1659 end
1660 elsif (name == 'container') and (attributes['id'] == 'stow')
1661 @stow_container_id = attributes['target'].sub('#', '')
1662 elsif (name == 'clearStream')
1663 if attributes['id'] == 'bounty'
1664 @bounty_task = String.new
1665 end
1666 elsif (name == 'playerID')
1667 @player_id = attributes['id']
1668 unless $frontend =~ /^(?:wizard|avalon)$/
1669 if Lich.inventory_boxes(@player_id)
1670 DownstreamHook.remove('inventory_boxes_off')
1671 end
1672 end
1673 elsif name == 'settingsInfo'
1674 if game = attributes['instance']
1675 if game == 'GS4'
1676 @game = 'GSIV'
1677 elsif (game == 'GSX') or (game == 'GS4X')
1678 @game = 'GSPlat'
1679 else
1680 @game = game
1681 end
1682 end
1683 elsif (name == 'app') and (@name = attributes['char'])
1684 if @game.nil? or @game.empty?
1685 @game = 'unknown'
1686 end
1687 unless File.exists?("#{DATA_DIR}/#{@game}")
1688 Dir.mkdir("#{DATA_DIR}/#{@game}")
1689 end
1690 unless File.exists?("#{DATA_DIR}/#{@game}/#{@name}")
1691 Dir.mkdir("#{DATA_DIR}/#{@game}/#{@name}")
1692 end
1693 if $frontend =~ /^(?:wizard|avalon)$/
1694 Game._puts "#{$cmd_prefix}_flag Display Dialog Boxes 0"
1695 sleep 0.05
1696 Game._puts "#{$cmd_prefix}_injury 2"
1697 sleep 0.05
1698 # fixme: game name hardcoded as Gemstone IV; maybe doesn't make any difference to the client
1699 $_CLIENT_.puts "\034GSB0000000000#{attributes['char']}\r\n\034GSA#{Time.now.to_i.to_s}GemStone IV\034GSD\r\n"
1700 # Sending fake GSL tags to the Wizard FE is disabled until now, because it doesn't accept the tags and just gives errors until initialized with the above line
1701 @send_fake_tags = true
1702 # Send all the tags we missed out on
1703 $_CLIENT_.puts "\034GSV#{sprintf('%010d%010d%010d%010d%010d%010d%010d%010d', @max_health.to_i, @health.to_i, @max_spirit.to_i, @spirit.to_i, @max_mana.to_i, @mana.to_i, make_wound_gsl, make_scar_gsl)}\r\n"
1704 $_CLIENT_.puts "\034GSg#{sprintf('%010d', @stance_value)}\r\n"
1705 $_CLIENT_.puts "\034GSr#{MINDMAP[@mind_text]}\r\n"
1706 gsl_prompt = String.new
1707 @indicator.keys.each { |icon| gsl_prompt += ICONMAP[icon] if @indicator[icon] == 'y' }
1708 $_CLIENT_.puts "\034GSP#{sprintf('%-30s', gsl_prompt)}\r\n"
1709 gsl_prompt = nil
1710 gsl_exits = String.new
1711 @room_exits.each { |exit| gsl_exits.concat(DIRMAP[SHORTDIR[exit]].to_s) }
1712 $_CLIENT_.puts "\034GSj#{sprintf('%-20s', gsl_exits)}\r\n"
1713 gsl_exits = nil
1714 $_CLIENT_.puts "\034GSn#{sprintf('%-14s', @prepared_spell)}\r\n"
1715 $_CLIENT_.puts "\034GSm#{sprintf('%-45s', GameObj.right_hand.name)}\r\n"
1716 $_CLIENT_.puts "\034GSl#{sprintf('%-45s', GameObj.left_hand.name)}\r\n"
1717 $_CLIENT_.puts "\034GSq#{sprintf('%010d', @server_time)}\r\n"
1718 $_CLIENT_.puts "\034GSQ#{sprintf('%010d', @roundtime_end)}\r\n" if @roundtime_end > 0
1719 end
1720 Game._puts("#{$cmd_prefix}_flag Display Inventory Boxes 1")
1721 Script.start('autostart') if Script.exists?('autostart')
1722 if arg = ARGV.find { |a| a=~ /^\-\-start\-scripts=/ }
1723 for script_name in arg.sub('--start-scripts=', '').split(',')
1724 Script.start(script_name)
1725 end
1726 end
1727 end
1728 rescue
1729 $stdout.puts "--- error: XMLParser.tag_start: #{$!}"
1730 Lich.log "error: XMLParser.tag_start: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
1731 sleep 0.1
1732 reset
1733 end
1734 end
1735 def text(text_string)
1736 begin
1737 # fixme: /<stream id="Spells">.*?<\/stream>/m
1738 # $_CLIENT_.write(text_string) unless ($frontend != 'suks') or (@current_stream =~ /^(?:spellfront|inv|bounty|society)$/) or @active_tags.any? { |tag| tag =~ /^(?:compDef|inv|component|right|left|spell)$/ } or (@active_tags.include?('stream') and @active_ids.include?('Spells')) or (text_string == "\n" and (@last_tag =~ /^(?:popStream|prompt|compDef|dialogData|openDialog|switchQuickBar|component)$/))
1739 if @active_tags.include?('inv')
1740 if @active_tags[-1] == 'a'
1741 @obj_name = text_string
1742 elsif @obj_name.nil?
1743 @obj_before_name = text_string.strip
1744 else
1745 @obj_after_name = text_string.strip
1746 end
1747 elsif @active_tags.last == 'prompt'
1748 @prompt = text_string
1749 elsif @active_tags.include?('right')
1750 GameObj.new_right_hand(@obj_exist, @obj_noun, text_string)
1751 $_CLIENT_.puts "\034GSm#{sprintf('%-45s', text_string)}\r\n" if @send_fake_tags
1752 elsif @active_tags.include?('left')
1753 GameObj.new_left_hand(@obj_exist, @obj_noun, text_string)
1754 $_CLIENT_.puts "\034GSl#{sprintf('%-45s', text_string)}\r\n" if @send_fake_tags
1755 elsif @active_tags.include?('spell')
1756 @prepared_spell = text_string
1757 $_CLIENT_.puts "\034GSn#{sprintf('%-14s', text_string)}\r\n" if @send_fake_tags
1758 elsif @active_tags.include?('compDef') or @active_tags.include?('component')
1759 if @active_ids.include?('room objs')
1760 if @active_tags.include?('a')
1761 if @bold
1762 GameObj.new_npc(@obj_exist, @obj_noun, text_string)
1763 else
1764 GameObj.new_loot(@obj_exist, @obj_noun, text_string)
1765 end
1766 elsif (text_string =~ /that (?:is|appears) ([\w\s]+)(?:,| and|\.)/) or (text_string =~ / \(([^\(]+)\)/)
1767 GameObj.npcs[-1].status = $1
1768 end
1769 elsif @active_ids.include?('room players')
1770 if @active_tags.include?('a')
1771 @pc = GameObj.new_pc(@obj_exist, @obj_noun, "#{@player_title}#{text_string}", @player_status)
1772 @player_status = nil
1773 else
1774 if @game =~ /^DR/
1775 GameObj.clear_pcs
1776 text_string.sub(/^Also here\: /, '').sub(/ and ([^,]+)\./) { ", #{$1}" }.split(', ').each { |player|
1777 if player =~ / who is (.+)/
1778 status = $1
1779 player.sub!(/ who is .+/, '')
1780 elsif player =~ / \((.+)\)/
1781 status = $1
1782 player.sub!(/ \(.+\)/, '')
1783 else
1784 status = nil
1785 end
1786 noun = player.slice(/\b[A-Z][a-z]+$/)
1787 if player =~ /the body of /
1788 player.sub!('the body of ', '')
1789 if status
1790 status.concat ' dead'
1791 else
1792 status = 'dead'
1793 end
1794 end
1795 if player =~ /a stunned /
1796 player.sub!('a stunned ', '')
1797 if status
1798 status.concat ' stunned'
1799 else
1800 status = 'stunned'
1801 end
1802 end
1803 GameObj.new_pc(nil, noun, player, status)
1804 }
1805 else
1806 if (text_string =~ /^ who (?:is|appears) ([\w\s]+)(?:,| and|\.|$)/) or (text_string =~ / \(([\w\s]+)\)(?: \(([\w\s]+)\))?/)
1807 if @pc.status
1808 @pc.status.concat " #{$1}"
1809 else
1810 @pc.status = $1
1811 end
1812 @pc.status.concat " #{$2}" if $2
1813 end
1814 if text_string =~ /(?:^Also here: |, )(?:a )?([a-z\s]+)?([\w\s\-!\?',]+)?$/
1815 @player_status = ($1.strip.gsub('the body of', 'dead')) if $1
1816 @player_title = $2
1817 end
1818 end
1819 end
1820 elsif @active_ids.include?('room desc')
1821 if text_string == '[Room window disabled at this location.]'
1822 @room_window_disabled = true
1823 else
1824 @room_window_disabled = false
1825 @room_description.concat(text_string)
1826 if @active_tags.include?('a')
1827 GameObj.new_room_desc(@obj_exist, @obj_noun, text_string)
1828 end
1829 end
1830 elsif @active_ids.include?('room exits')
1831 @room_exits_string.concat(text_string)
1832 @room_exits.push(text_string) if @active_tags.include?('d')
1833 end
1834 elsif @current_stream == 'bounty'
1835 @bounty_task += text_string
1836 elsif @current_stream == 'society'
1837 @society_task = text_string
1838 elsif (@current_stream == 'inv') and @active_tags.include?('a')
1839 GameObj.new_inv(@obj_exist, @obj_noun, text_string, nil)
1840 elsif @current_stream == 'familiar'
1841 # fixme: familiar room tracking does not (can not?) auto update, status of pcs and npcs isn't tracked at all, titles of pcs aren't tracked
1842 if @current_style == 'roomName'
1843 @familiar_room_title = text_string
1844 @familiar_room_description = String.new
1845 @familiar_room_exits = Array.new
1846 GameObj.clear_fam_room_desc
1847 GameObj.clear_fam_loot
1848 GameObj.clear_fam_npcs
1849 GameObj.clear_fam_pcs
1850 @fam_mode = String.new
1851 elsif @current_style == 'roomDesc'
1852 @familiar_room_description.concat(text_string)
1853 if @active_tags.include?('a')
1854 GameObj.new_fam_room_desc(@obj_exist, @obj_noun, text_string)
1855 end
1856 elsif text_string =~ /^You also see/
1857 @fam_mode = 'things'
1858 elsif text_string =~ /^Also here/
1859 @fam_mode = 'people'
1860 elsif text_string =~ /Obvious (?:paths|exits)/
1861 @fam_mode = 'paths'
1862 elsif @fam_mode == 'things'
1863 if @active_tags.include?('a')
1864 if @bold
1865 GameObj.new_fam_npc(@obj_exist, @obj_noun, text_string)
1866 else
1867 GameObj.new_fam_loot(@obj_exist, @obj_noun, text_string)
1868 end
1869 end
1870 # puts 'things: ' + text_string
1871 elsif @fam_mode == 'people' and @active_tags.include?('a')
1872 GameObj.new_fam_pc(@obj_exist, @obj_noun, text_string)
1873 # puts 'people: ' + text_string
1874 elsif (@fam_mode == 'paths') and @active_tags.include?('a')
1875 @familiar_room_exits.push(text_string)
1876 end
1877 elsif @room_window_disabled
1878 if @current_style == 'roomDesc'
1879 @room_description.concat(text_string)
1880 if @active_tags.include?('a')
1881 GameObj.new_room_desc(@obj_exist, @obj_noun, text_string)
1882 end
1883 elsif text_string =~ /^Obvious (?:paths|exits): (?:none)?$/
1884 @room_exits_string = text_string.strip
1885 end
1886 end
1887 rescue
1888 $stdout.puts "--- error: XMLParser.text: #{$!}"
1889 Lich.log "error: XMLParser.text: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
1890 sleep 0.1
1891 reset
1892 end
1893 end
1894 def tag_end(name)
1895 begin
1896 if name == 'inv'
1897 if @obj_exist == @obj_location
1898 if @obj_after_name == 'is closed.'
1899 GameObj.delete_container(@stow_container_id)
1900 end
1901 elsif @obj_exist
1902 GameObj.new_inv(@obj_exist, @obj_noun, @obj_name, @obj_location, @obj_before_name, @obj_after_name)
1903 end
1904 elsif @send_fake_tags and (@active_ids.last == 'room exits')
1905 gsl_exits = String.new
1906 @room_exits.each { |exit| gsl_exits.concat(DIRMAP[SHORTDIR[exit]].to_s) }
1907 $_CLIENT_.puts "\034GSj#{sprintf('%-20s', gsl_exits)}\r\n"
1908 gsl_exits = nil
1909 elsif @room_window_disabled and (name == 'compass')
1910# @room_window_disabled = false
1911 @room_description = @room_description.strip
1912 @room_exits_string.concat " #{@room_exits.join(', ')}" unless @room_exits.empty?
1913 gsl_exits = String.new
1914 @room_exits.each { |exit| gsl_exits.concat(DIRMAP[SHORTDIR[exit]].to_s) }
1915 $_CLIENT_.puts "\034GSj#{sprintf('%-20s', gsl_exits)}\r\n"
1916 gsl_exits = nil
1917 @room_count += 1
1918 $room_count += 1
1919 end
1920 @last_tag = @active_tags.pop
1921 @last_id = @active_ids.pop
1922 rescue
1923 $stdout.puts "--- error: XMLParser.tag_end: #{$!}"
1924 Lich.log "error: XMLParser.tag_end: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
1925 sleep 0.1
1926 reset
1927 end
1928 end
1929 # here for backwards compatibility, but spellfront xml isn't sent by the game anymore
1930 def spellfront
1931 if (Time.now.to_i - @@warned_deprecated_spellfront) > 300
1932 @@warned_deprecated_spellfront = Time.now.to_i
1933 unless script_name = Script.current.name
1934 script_name = 'unknown script'
1935 end
1936 respond "--- warning: #{script_name} is using deprecated method XMLData.spellfront; this method will be removed in a future version of Lich"
1937 end
1938 @active_spells.keys
1939 end
1940end
1941
1942class UpstreamHook
1943 @@upstream_hooks ||= Hash.new
1944 def UpstreamHook.add(name, action)
1945 unless action.class == Proc
1946 echo "UpstreamHook: not a Proc (#{action})"
1947 return false
1948 end
1949 @@upstream_hooks[name] = action
1950 end
1951 def UpstreamHook.run(client_string)
1952 for key in @@upstream_hooks.keys
1953 begin
1954 client_string = @@upstream_hooks[key].call(client_string)
1955 rescue
1956 @@upstream_hooks.delete(key)
1957 respond "--- Lich: UpstreamHook: #{$!}"
1958 respond $!.backtrace.first
1959 end
1960 return nil if client_string.nil?
1961 end
1962 return client_string
1963 end
1964 def UpstreamHook.remove(name)
1965 @@upstream_hooks.delete(name)
1966 end
1967 def UpstreamHook.list
1968 @@upstream_hooks.keys.dup
1969 end
1970end
1971
1972class DownstreamHook
1973 @@downstream_hooks ||= Hash.new
1974 def DownstreamHook.add(name, action)
1975 unless action.class == Proc
1976 echo "DownstreamHook: not a Proc (#{action})"
1977 return false
1978 end
1979 @@downstream_hooks[name] = action
1980 end
1981 def DownstreamHook.run(server_string)
1982 for key in @@downstream_hooks.keys
1983 begin
1984 server_string = @@downstream_hooks[key].call(server_string.dup)
1985 rescue
1986 @@downstream_hooks.delete(key)
1987 respond "--- Lich: DownstreamHook: #{$!}"
1988 respond $!.backtrace.first
1989 end
1990 return nil if server_string.nil?
1991 end
1992 return server_string
1993 end
1994 def DownstreamHook.remove(name)
1995 @@downstream_hooks.delete(name)
1996 end
1997 def DownstreamHook.list
1998 @@downstream_hooks.keys.dup
1999 end
2000end
2001
2002module Setting
2003 @@load = proc { |args|
2004 unless script = Script.current
2005 respond '--- error: Setting.load: calling script is unknown'
2006 respond $!.backtrace[0..2]
2007 next nil
2008 end
2009 if script.class == ExecScript
2010 respond "--- Lich: error: Setting.load: exec scripts can't have settings"
2011 respond $!.backtrace[0..2]
2012 exit
2013 end
2014 if args.empty?
2015 respond '--- error: Setting.load: no setting specified'
2016 respond $!.backtrace[0..2]
2017 exit
2018 end
2019 if args.any? { |a| a.class != String }
2020 respond "--- Lich: error: Setting.load: non-string given as setting name"
2021 respond $!.backtrace[0..2]
2022 exit
2023 end
2024 values = Array.new
2025 for setting in args
2026 begin
2027 v = Lich.db.get_first_value('SELECT value FROM script_setting WHERE script=? AND name=?;', script.name.encode('UTF-8'), setting.encode('UTF-8'))
2028 rescue SQLite3::BusyException
2029 sleep 0.1
2030 retry
2031 end
2032 if v.nil?
2033 values.push(v)
2034 else
2035 begin
2036 values.push(Marshal.load(v))
2037 rescue
2038 respond "--- Lich: error: Setting.load: #{$!}"
2039 respond $!.backtrace[0..2]
2040 exit
2041 end
2042 end
2043 end
2044 if args.length == 1
2045 next values[0]
2046 else
2047 next values
2048 end
2049 }
2050 @@save = proc { |hash|
2051 unless script = Script.current
2052 respond '--- error: Setting.save: calling script is unknown'
2053 respond $!.backtrace[0..2]
2054 next nil
2055 end
2056 if script.class == ExecScript
2057 respond "--- Lich: error: Setting.load: exec scripts can't have settings"
2058 respond $!.backtrace[0..2]
2059 exit
2060 end
2061 if hash.class != Hash
2062 respond "--- Lich: error: Setting.save: invalid arguments: use Setting.save('setting1' => 'value1', 'setting2' => 'value2')"
2063 respond $!.backtrace[0..2]
2064 exit
2065 end
2066 if hash.empty?
2067 next nil
2068 end
2069 if hash.keys.any? { |k| k.class != String }
2070 respond "--- Lich: error: Setting.save: non-string given as a setting name"
2071 respond $!.backtrace[0..2]
2072 exit
2073 end
2074 if hash.length > 1
2075 begin
2076 Lich.db.execute('BEGIN')
2077 rescue SQLite3::BusyException
2078 sleep 0.1
2079 retry
2080 end
2081 end
2082 hash.each { |setting,value|
2083 begin
2084 if value.nil?
2085 begin
2086 Lich.db.execute('DELETE FROM script_setting WHERE script=? AND name=?;', script.name.encode('UTF-8'), setting.encode('UTF-8'))
2087 rescue SQLite3::BusyException
2088 sleep 0.1
2089 retry
2090 end
2091 else
2092 v = SQLite3::Blob.new(Marshal.dump(value))
2093 begin
2094 Lich.db.execute('INSERT OR REPLACE INTO script_setting(script,name,value) VALUES(?,?,?);', script.name.encode('UTF-8'), setting.encode('UTF-8'), v)
2095 rescue SQLite3::BusyException
2096 sleep 0.1
2097 retry
2098 end
2099 end
2100 rescue SQLite3::BusyException
2101 sleep 0.1
2102 retry
2103 end
2104 }
2105 if hash.length > 1
2106 begin
2107 Lich.db.execute('END')
2108 rescue SQLite3::BusyException
2109 sleep 0.1
2110 retry
2111 end
2112 end
2113 true
2114 }
2115 @@list = proc {
2116 unless script = Script.current
2117 respond '--- error: Setting: unknown calling script'
2118 next nil
2119 end
2120 if script.class == ExecScript
2121 respond "--- Lich: error: Setting.load: exec scripts can't have settings"
2122 respond $!.backtrace[0..2]
2123 exit
2124 end
2125 begin
2126 rows = Lich.db.execute('SELECT name FROM script_setting WHERE script=?;', script.name.encode('UTF-8'))
2127 rescue SQLite3::BusyException
2128 sleep 0.1
2129 retry
2130 end
2131 if rows
2132 # fixme
2133 next rows.inspect
2134 else
2135 next nil
2136 end
2137 }
2138 def Setting.load(*args)
2139 @@load.call(args)
2140 end
2141 def Setting.save(hash)
2142 @@save.call(hash)
2143 end
2144 def Setting.list
2145 @@list.call
2146 end
2147end
2148
2149module GameSetting
2150 def GameSetting.load(*args)
2151 Setting.load(args.collect { |a| "#{XMLData.game}:#{a}" })
2152 end
2153 def GameSetting.save(hash)
2154 game_hash = Hash.new
2155 hash.each_pair { |k,v| game_hash["#{XMLData.game}:#{k}"] = v }
2156 Setting.save(game_hash)
2157 end
2158end
2159
2160module CharSetting
2161 def CharSetting.load(*args)
2162 Setting.load(args.collect { |a| "#{XMLData.game}:#{XMLData.name}:#{a}" })
2163 end
2164 def CharSetting.save(hash)
2165 game_hash = Hash.new
2166 hash.each_pair { |k,v| game_hash["#{XMLData.game}:#{XMLData.name}:#{k}"] = v }
2167 Setting.save(game_hash)
2168 end
2169end
2170
2171module Settings
2172 settings = Hash.new
2173 md5_at_load = Hash.new
2174 mutex = Mutex.new
2175 @@settings = proc { |scope|
2176 unless script = Script.current
2177 respond '--- error: Settings: unknown calling script'
2178 next nil
2179 end
2180 unless scope =~ /^#{XMLData.game}\:#{XMLData.name}$|^#{XMLData.game}$|^\:$/
2181 respond '--- error: Settings: invalid scope'
2182 next nil
2183 end
2184 mutex.synchronize {
2185 unless settings[script.name] and settings[script.name][scope]
2186 begin
2187 _hash = Lich.db.get_first_value('SELECT hash FROM script_auto_settings WHERE script=? AND scope=?;', script.name.encode('UTF-8'), scope.encode('UTF-8'))
2188 rescue SQLite3::BusyException
2189 sleep 0.1
2190 retry
2191 end
2192 settings[script.name] ||= Hash.new
2193 if _hash.nil?
2194 settings[script.name][scope] = Hash.new
2195 else
2196 begin
2197 hash = Marshal.load(_hash)
2198 rescue
2199 respond "--- Lich: error: #{$!}"
2200 respond $!.backtrace[0..1]
2201 exit
2202 end
2203 settings[script.name][scope] = hash
2204 end
2205 md5_at_load[script.name] ||= Hash.new
2206 md5_at_load[script.name][scope] = Digest::MD5.hexdigest(settings[script.name][scope].to_s)
2207 end
2208 }
2209 settings[script.name][scope]
2210 }
2211 @@save = proc {
2212 mutex.synchronize {
2213 sql_began = false
2214 settings.each_pair { |script_name,scopedata|
2215 scopedata.each_pair { |scope,data|
2216 if Digest::MD5.hexdigest(data.to_s) != md5_at_load[script_name][scope]
2217 unless sql_began
2218 begin
2219 Lich.db.execute('BEGIN')
2220 rescue SQLite3::BusyException
2221 sleep 0.1
2222 retry
2223 end
2224 sql_began = true
2225 end
2226 blob = SQLite3::Blob.new(Marshal.dump(data))
2227 begin
2228 Lich.db.execute('INSERT OR REPLACE INTO script_auto_settings(script,scope,hash) VALUES(?,?,?);', script_name.encode('UTF-8'), scope.encode('UTF-8'), blob)
2229 rescue SQLite3::BusyException
2230 sleep 0.1
2231 retry
2232 rescue
2233 respond "--- Lich: error: #{$!}"
2234 respond $!.backtrace[0..1]
2235 next
2236 end
2237 end
2238 }
2239 unless Script.running?(script_name)
2240 settings.delete(script_name)
2241 md5_at_load.delete(script_name)
2242 end
2243 }
2244 if sql_began
2245 begin
2246 Lich.db.execute('END')
2247 rescue SQLite3::BusyException
2248 sleep 0.1
2249 retry
2250 end
2251 end
2252 }
2253 }
2254 Thread.new {
2255 loop {
2256 sleep 300
2257 begin
2258 @@save.call
2259 rescue
2260 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
2261 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
2262 end
2263 }
2264 }
2265 def Settings.[](name)
2266 @@settings.call(':')[name]
2267 end
2268 def Settings.[]=(name, value)
2269 @@settings.call(':')[name] = value
2270 end
2271 def Settings.to_hash(scope=':')
2272 @@settings.call(scope)
2273 end
2274 def Settings.char
2275 @@settings.call("#{XMLData.game}:#{XMLData.name}")
2276 end
2277 def Settings.save
2278 @@save.call
2279 end
2280end
2281
2282module GameSettings
2283 def GameSettings.[](name)
2284 Settings.to_hash(XMLData.game)[name]
2285 end
2286 def GameSettings.[]=(name, value)
2287 Settings.to_hash(XMLData.game)[name] = value
2288 end
2289 def GameSettings.to_hash
2290 Settings.to_hash(XMLData.game)
2291 end
2292end
2293
2294module CharSettings
2295 def CharSettings.[](name)
2296 Settings.to_hash("#{XMLData.game}:#{XMLData.name}")[name]
2297 end
2298 def CharSettings.[]=(name, value)
2299 Settings.to_hash("#{XMLData.game}:#{XMLData.name}")[name] = value
2300 end
2301 def CharSettings.to_hash
2302 Settings.to_hash("#{XMLData.game}:#{XMLData.name}")
2303 end
2304end
2305
2306module Vars
2307 @@vars = Hash.new
2308 md5 = nil
2309 mutex = Mutex.new
2310 @@loaded = false
2311 @@load = proc {
2312 mutex.synchronize {
2313 unless @@loaded
2314 begin
2315 h = Lich.db.get_first_value('SELECT hash FROM uservars WHERE scope=?;', "#{XMLData.game}:#{XMLData.name}".encode('UTF-8'))
2316 rescue SQLite3::BusyException
2317 sleep 0.1
2318 retry
2319 end
2320 if h
2321 begin
2322 hash = Marshal.load(h)
2323 hash.each { |k,v| @@vars[k] = v }
2324 md5 = Digest::MD5.hexdigest(hash.to_s)
2325 rescue
2326 respond "--- Lich: error: #{$!}"
2327 respond $!.backtrace[0..2]
2328 end
2329 end
2330 @@loaded = true
2331 end
2332 }
2333 nil
2334 }
2335 @@save = proc {
2336 mutex.synchronize {
2337 if @@loaded
2338 if Digest::MD5.hexdigest(@@vars.to_s) != md5
2339 md5 = Digest::MD5.hexdigest(@@vars.to_s)
2340 blob = SQLite3::Blob.new(Marshal.dump(@@vars))
2341 begin
2342 Lich.db.execute('INSERT OR REPLACE INTO uservars(scope,hash) VALUES(?,?);', "#{XMLData.game}:#{XMLData.name}".encode('UTF-8'), blob)
2343 rescue SQLite3::BusyException
2344 sleep 0.1
2345 retry
2346 end
2347 end
2348 end
2349 }
2350 nil
2351 }
2352 Thread.new {
2353 loop {
2354 sleep 300
2355 begin
2356 @@save.call
2357 rescue
2358 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
2359 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
2360 end
2361 }
2362 }
2363 def Vars.[](name)
2364 @@load.call unless @@loaded
2365 @@vars[name]
2366 end
2367 def Vars.[]=(name, val)
2368 @@load.call unless @@loaded
2369 if val.nil?
2370 @@vars.delete(name)
2371 else
2372 @@vars[name] = val
2373 end
2374 end
2375 def Vars.list
2376 @@load.call unless @@loaded
2377 @@vars.dup
2378 end
2379 def Vars.save
2380 @@save.call
2381 end
2382 def Vars.method_missing(arg1, arg2='')
2383 @@load.call unless @@loaded
2384 if arg1[-1,1] == '='
2385 if arg2.nil?
2386 @@vars.delete(arg1.to_s.chop)
2387 else
2388 @@vars[arg1.to_s.chop] = arg2
2389 end
2390 else
2391 @@vars[arg1.to_s]
2392 end
2393 end
2394end
2395
2396#
2397# script bindings are convoluted, but don't change them without testing if:
2398# class methods such as Script.start and ExecScript.start become accessible without specifying the class name (which is just a syptom of a problem that will break scripts)
2399# local variables become shared between scripts
2400# local variable 'file' is shared between scripts, even though other local variables aren't
2401# defined methods are instantly inaccessible
2402# also, don't put 'untrusted' in the name of the untrusted binding; it shows up in error messages and makes people think the error is caused by not trusting the script
2403#
2404class Scripting
2405 def script
2406 Proc.new {}.binding
2407 end
2408end
2409def _script
2410 Proc.new {}.binding
2411end
2412
2413TRUSTED_SCRIPT_BINDING = proc { _script }
2414
2415class Script
2416 @@elevated_script_start = proc { |args|
2417 if args.empty?
2418 # fixme: error
2419 next nil
2420 elsif args[0].class == String
2421 script_name = args[0]
2422 if args[1]
2423 if args[1].class == String
2424 script_args = args[1]
2425 if args[2]
2426 if args[2].class == Hash
2427 options = args[2]
2428 else
2429 # fixme: error
2430 next nil
2431 end
2432 end
2433 elsif args[1].class == Hash
2434 options = args[1]
2435 script_args = (options[:args] || String.new)
2436 else
2437 # fixme: error
2438 next nil
2439 end
2440 else
2441 options = Hash.new
2442 end
2443 elsif args[0].class == Hash
2444 options = args[0]
2445 if options[:name]
2446 script_name = options[:name]
2447 else
2448 # fixme: error
2449 next nil
2450 end
2451 script_args = (options[:args] || String.new)
2452 end
2453 # fixme: look in wizard script directory
2454 # fixme: allow subdirectories?
2455 file_list = Dir.entries(SCRIPT_DIR).delete_if { |fn| (fn == '.') or (fn == '..') }.sort
2456 if file_name = (file_list.find { |val| val =~ /^#{Regexp.escape(script_name)}\.(?:lic|rb|cmd|wiz)(?:\.gz|\.Z)?$/ || val =~ /^#{Regexp.escape(script_name)}\.(?:lic|rb|cmd|wiz)(?:\.gz|\.Z)?$/i } || file_list.find { |val| val =~ /^#{Regexp.escape(script_name)}[^.]+\.(?i:lic|rb|cmd|wiz)(?:\.gz|\.Z)?$/ } || file_list.find { |val| val =~ /^#{Regexp.escape(script_name)}[^.]+\.(?:lic|rb|cmd|wiz)(?:\.gz|\.Z)?$/i })
2457 script_name = file_name.sub(/\..{1,3}$/, '')
2458 end
2459 file_list = nil
2460 if file_name.nil?
2461 respond "--- Lich: could not find script '#{script_name}' in directory #{SCRIPT_DIR}"
2462 next nil
2463 end
2464 if (options[:force] != true) and (Script.running + Script.hidden).find { |s| s.name =~ /^#{Regexp.escape(script_name)}$/i }
2465 respond "--- Lich: #{script_name} is already running (use #{$clean_lich_char}force [scriptname] if desired)."
2466 next nil
2467 end
2468 begin
2469 if file_name =~ /\.(?:cmd|wiz)(?:\.gz)?$/i
2470 trusted = false
2471 script_obj = WizardScript.new("#{SCRIPT_DIR}/#{file_name}", script_args)
2472 else
2473 if script_obj.labels.length > 1
2474 trusted = false
2475 elsif proc { begin; $SAFE = 3; true; rescue; false; end }.call
2476 begin
2477 trusted = Lich.db.get_first_value('SELECT name FROM trusted_scripts WHERE name=?;', script_name.encode('UTF-8'))
2478 rescue SQLite3::BusyException
2479 sleep 0.1
2480 retry
2481 end
2482 else
2483 trusted = true
2484 end
2485 script_obj = Script.new(:file => "#{SCRIPT_DIR}/#{file_name}", :args => script_args, :quiet => options[:quiet])
2486 end
2487 if trusted
2488 script_binding = TRUSTED_SCRIPT_BINDING.call
2489 else
2490 script_binding = Scripting.new.script
2491 end
2492 rescue
2493 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
2494 next nil
2495 end
2496 unless script_obj
2497 respond "--- Lich: error: failed to start script (#{script_name})"
2498 next nil
2499 end
2500 script_obj.quiet = true if options[:quiet]
2501 new_thread = Thread.new {
2502 100.times { break if Script.current == script_obj; sleep 0.01 }
2503 if script = Script.current
2504 eval('script = Script.current', script_binding, script.name)
2505 Thread.current.priority = 1
2506 respond("--- Lich: #{script.name} active.") unless script.quiet
2507 if trusted
2508 begin
2509 eval(script.labels[script.current_label].to_s, script_binding, script.name)
2510 rescue SystemExit
2511 nil
2512 rescue SyntaxError
2513 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
2514 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
2515 rescue ScriptError
2516 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
2517 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
2518 rescue NoMemoryError
2519 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
2520 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
2521 rescue LoadError
2522 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
2523 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
2524 rescue SecurityError
2525 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
2526 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
2527 rescue ThreadError
2528 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
2529 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
2530 rescue SystemStackError
2531 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
2532 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
2533 rescue Exception
2534 if $! == JUMP
2535 retry if Script.current.get_next_label != JUMP_ERROR
2536 respond "--- label error: `#{Script.current.jump_label}' was not found, and no `LabelError' label was found!"
2537 respond $!.backtrace.first
2538 Lich.log "label error: `#{Script.current.jump_label}' was not found, and no `LabelError' label was found!\n\t#{$!.backtrace.join("\n\t")}"
2539 Script.current.kill
2540 else
2541 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
2542 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
2543 end
2544 rescue
2545 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
2546 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
2547 ensure
2548 Script.current.kill
2549 end
2550 else
2551 begin
2552 while (script = Script.current) and script.current_label
2553 proc { foo = script.labels[script.current_label]; foo.untaint; begin; $SAFE = 3; rescue; nil; end; eval(foo, script_binding, script.name, 1) }.call
2554 Script.current.get_next_label
2555 end
2556 rescue SystemExit
2557 nil
2558 rescue SyntaxError
2559 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
2560 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
2561 rescue ScriptError
2562 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
2563 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
2564 rescue NoMemoryError
2565 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
2566 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
2567 rescue LoadError
2568 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
2569 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
2570 rescue SecurityError
2571 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
2572 if name = Script.current.name
2573 respond "--- Lich: review this script (#{name}) to make sure it isn't malicious, and type #{$clean_lich_char}trust #{name}"
2574 end
2575 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
2576 rescue ThreadError
2577 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
2578 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
2579 rescue SystemStackError
2580 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
2581 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
2582 rescue Exception
2583 if $! == JUMP
2584 retry if Script.current.get_next_label != JUMP_ERROR
2585 respond "--- label error: `#{Script.current.jump_label}' was not found, and no `LabelError' label was found!"
2586 respond $!.backtrace.first
2587 Lich.log "label error: `#{Script.current.jump_label}' was not found, and no `LabelError' label was found!\n\t#{$!.backtrace.join("\n\t")}"
2588 Script.current.kill
2589 else
2590 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
2591 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
2592 end
2593 rescue
2594 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
2595 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
2596 ensure
2597 Script.current.kill
2598 end
2599 end
2600 else
2601 respond '--- error: out of cheese'
2602 end
2603 }
2604 script_obj.thread_group.add(new_thread)
2605 script_obj
2606 }
2607 @@elevated_exists = proc { |script_name|
2608 if script_name =~ /\\|\//
2609 nil
2610 elsif script_name =~ /\.(?:lic|lich|rb|cmd|wiz)(?:\.gz)?$/i
2611 File.exists?("#{SCRIPT_DIR}/#{script_name}")
2612 else
2613 File.exists?("#{SCRIPT_DIR}/#{script_name}.lic") || File.exists?("#{SCRIPT_DIR}/#{script_name}.lich") || File.exists?("#{SCRIPT_DIR}/#{script_name}.rb") || File.exists?("#{SCRIPT_DIR}/#{script_name}.cmd") || File.exists?("#{SCRIPT_DIR}/#{script_name}.wiz") || File.exists?("#{SCRIPT_DIR}/#{script_name}.lic.gz") || File.exists?("#{SCRIPT_DIR}/#{script_name}.rb.gz") || File.exists?("#{SCRIPT_DIR}/#{script_name}.cmd.gz") || File.exists?("#{SCRIPT_DIR}/#{script_name}.wiz.gz")
2614 end
2615 }
2616 @@elevated_log = proc { |data|
2617 if script = Script.current
2618 if script.name =~ /\\|\//
2619 nil
2620 else
2621 begin
2622 Dir.mkdir("#{LICH_DIR}/logs") unless File.exists?("#{LICH_DIR}/logs")
2623 File.open("#{LICH_DIR}/logs/#{script.name}.log", 'a') { |f| f.puts data }
2624 true
2625 rescue
2626 respond "--- Lich: error: Script.log: #{$!}"
2627 false
2628 end
2629 end
2630 else
2631 respond '--- error: Script.log: unable to identify calling script'
2632 false
2633 end
2634 }
2635 @@elevated_db = proc {
2636 if script = Script.current
2637 if script.name =~ /^lich$/i
2638 respond '--- error: Script.db cannot be used by a script named lich'
2639 nil
2640 elsif script.class == ExecScript
2641 respond '--- error: Script.db cannot be used by exec scripts'
2642 nil
2643 else
2644 SQLite3::Database.new("#{DATA_DIR}/#{script.name.gsub(/\/|\\/, '_')}.db3")
2645 end
2646 else
2647 respond '--- error: Script.db called by an unknown script'
2648 nil
2649 end
2650 }
2651 @@elevated_open_file = proc { |ext,mode,block|
2652 if script = Script.current
2653 if script.name =~ /^lich$/i
2654 respond '--- error: Script.open_file cannot be used by a script named lich'
2655 nil
2656 elsif script.name =~ /^entry$/i
2657 respond '--- error: Script.open_file cannot be used by a script named entry'
2658 nil
2659 elsif script.class == ExecScript
2660 respond '--- error: Script.open_file cannot be used by exec scripts'
2661 nil
2662 elsif ext.downcase == 'db3'
2663 SQLite3::Database.new("#{DATA_DIR}/#{script.name.gsub(/\/|\\/, '_')}.db3")
2664# fixme: block gets elevated... why?
2665# elsif block
2666# File.open("#{DATA_DIR}/#{script.name.gsub(/\/|\\/, '_')}.#{ext.gsub(/\/|\\/, '_')}", mode, &block)
2667 else
2668 File.open("#{DATA_DIR}/#{script.name.gsub(/\/|\\/, '_')}.#{ext.gsub(/\/|\\/, '_')}", mode)
2669 end
2670 else
2671 respond '--- error: Script.open_file called by an unknown script'
2672 nil
2673 end
2674 }
2675 @@running = Array.new
2676
2677 attr_reader :name, :vars, :safe, :file_name, :label_order, :at_exit_procs
2678 attr_accessor :quiet, :no_echo, :jump_label, :current_label, :want_downstream, :want_downstream_xml, :want_upstream, :want_script_output, :hidden, :paused, :silent, :no_pause_all, :no_kill_all, :downstream_buffer, :upstream_buffer, :unique_buffer, :die_with, :match_stack_labels, :match_stack_strings, :watchfor, :command_line, :ignore_pause
2679 def Script.list
2680 @@running.dup
2681 end
2682 def Script.current
2683 if script = @@running.find { |s| s.has_thread?(Thread.current) }
2684 sleep 0.2 while script.paused? and not script.ignore_pause
2685 script
2686 else
2687 nil
2688 end
2689 end
2690 def Script.start(*args)
2691 @@elevated_script_start.call(args)
2692 end
2693 def Script.run(*args)
2694 if s = @@elevated_script_start.call(args)
2695 sleep 0.1 while @@running.include?(s)
2696 end
2697 end
2698 def Script.running?(name)
2699 @@running.any? { |i| (i.name =~ /^#{name}$/i) }
2700 end
2701 def Script.pause(name=nil)
2702 if name.nil?
2703 Script.current.pause
2704 Script.current
2705 else
2706 if s = (@@running.find { |i| (i.name == name) and not i.paused? }) || (@@running.find { |i| (i.name =~ /^#{name}$/i) and not i.paused? })
2707 s.pause
2708 true
2709 else
2710 false
2711 end
2712 end
2713 end
2714 def Script.unpause(name)
2715 if s = (@@running.find { |i| (i.name == name) and i.paused? }) || (@@running.find { |i| (i.name =~ /^#{name}$/i) and i.paused? })
2716 s.unpause
2717 true
2718 else
2719 false
2720 end
2721 end
2722 def Script.kill(name)
2723 if s = (@@running.find { |i| i.name == name }) || (@@running.find { |i| i.name =~ /^#{name}$/i })
2724 s.kill
2725 true
2726 else
2727 false
2728 end
2729 end
2730 def Script.paused?(name)
2731 if s = (@@running.find { |i| i.name == name }) || (@@running.find { |i| i.name =~ /^#{name}$/i })
2732 s.paused?
2733 else
2734 nil
2735 end
2736 end
2737 def Script.exists?(script_name)
2738 @@elevated_exists.call(script_name)
2739 end
2740 def Script.new_downstream_xml(line)
2741 for script in @@running
2742 script.downstream_buffer.push(line.chomp) if script.want_downstream_xml
2743 end
2744 end
2745 def Script.new_upstream(line)
2746 for script in @@running
2747 script.upstream_buffer.push(line.chomp) if script.want_upstream
2748 end
2749 end
2750 def Script.new_downstream(line)
2751 @@running.each { |script|
2752 script.downstream_buffer.push(line.chomp) if script.want_downstream
2753 unless script.watchfor.empty?
2754 script.watchfor.each_pair { |trigger,action|
2755 if line =~ trigger
2756 new_thread = Thread.new {
2757 sleep 0.011 until Script.current
2758 begin
2759 action.call
2760 rescue
2761 echo "watchfor error: #{$!}"
2762 end
2763 }
2764 script.thread_group.add(new_thread)
2765 end
2766 }
2767 end
2768 }
2769 end
2770 def Script.new_script_output(line)
2771 for script in @@running
2772 script.downstream_buffer.push(line.chomp) if script.want_script_output
2773 end
2774 end
2775 def Script.log(data)
2776 @@elevated_log.call(data)
2777 end
2778 def Script.db
2779 @@elevated_db.call
2780 end
2781 def Script.open_file(ext, mode='r', &block)
2782 @@elevated_open_file.call(ext, mode, block)
2783 end
2784 def Script.at_exit(&block)
2785 if script = Script.current
2786 script.at_exit(&block)
2787 else
2788 respond "--- Lich: error: Script.at_exit: can't identify calling script"
2789 return false
2790 end
2791 end
2792 def Script.clear_exit_procs
2793 if script = Script.current
2794 script.clear_exit_procs
2795 else
2796 respond "--- Lich: error: Script.clear_exit_procs: can't identify calling script"
2797 return false
2798 end
2799 end
2800 def Script.exit!
2801 if script = Script.current
2802 script.exit!
2803 else
2804 respond "--- Lich: error: Script.exit!: can't identify calling script"
2805 return false
2806 end
2807 end
2808 if (RUBY_VERSION =~ /^2\.[012]\./)
2809 def Script.trust(script_name)
2810 # fixme: case sensitive blah blah
2811 if ($SAFE == 0) and not caller.any? { |c| c =~ /eval|run/ }
2812 begin
2813 Lich.db.execute('INSERT OR REPLACE INTO trusted_scripts(name) values(?);', script_name.encode('UTF-8'))
2814 rescue SQLite3::BusyException
2815 sleep 0.1
2816 retry
2817 end
2818 true
2819 else
2820 respond '--- error: scripts may not trust scripts'
2821 false
2822 end
2823 end
2824 def Script.distrust(script_name)
2825 begin
2826 there = Lich.db.get_first_value('SELECT name FROM trusted_scripts WHERE name=?;', script_name.encode('UTF-8'))
2827 rescue SQLite3::BusyException
2828 sleep 0.1
2829 retry
2830 end
2831 if there
2832 begin
2833 Lich.db.execute('DELETE FROM trusted_scripts WHERE name=?;', script_name.encode('UTF-8'))
2834 rescue SQLite3::BusyException
2835 sleep 0.1
2836 retry
2837 end
2838 true
2839 else
2840 false
2841 end
2842 end
2843 def Script.list_trusted
2844 list = Array.new
2845 begin
2846 Lich.db.execute('SELECT name FROM trusted_scripts;').each { |name| list.push(name[0]) }
2847 rescue SQLite3::BusyException
2848 sleep 0.1
2849 retry
2850 end
2851 list
2852 end
2853 else
2854 def Script.trust(script_name)
2855 true
2856 end
2857 def Script.distrust(script_name)
2858 false
2859 end
2860 def Script.list_trusted
2861 []
2862 end
2863 end
2864 def initialize(args)
2865 @file_name = args[:file]
2866 @name = /.*[\/\\]+([^\.]+)\./.match(@file_name).captures.first
2867 if args[:args].class == String
2868 if args[:args].empty?
2869 @vars = Array.new
2870 else
2871 @vars = [ args[:args] ]
2872 @vars.concat args[:args].scan(/[^\s"]*(?<!\\)"(?:\\"|[^"])+(?<!\\)"[^\s]*|(?:\\"|[^"\s])+/).collect { |s| s.gsub(/(?<!\\)"/,'').gsub('\\"', '"') }
2873 end
2874 elsif args[:args].class == Array
2875 @vars = args[:args] # fixme: set @vars[0] ?
2876 else
2877 @vars = Array.new
2878 end
2879 @quiet = (args[:quiet] ? true : false)
2880 @downstream_buffer = LimitedArray.new
2881 @want_downstream = true
2882 @want_downstream_xml = false
2883 @want_script_output = false
2884 @upstream_buffer = LimitedArray.new
2885 @want_upstream = false
2886 @unique_buffer = LimitedArray.new
2887 @watchfor = Hash.new
2888 @at_exit_procs = Array.new
2889 @die_with = Array.new
2890 @paused = false
2891 @hidden = false
2892 @no_pause_all = false
2893 @no_kill_all = false
2894 @silent = false
2895 @safe = false
2896 @no_echo = false
2897 @match_stack_labels = Array.new
2898 @match_stack_strings = Array.new
2899 @label_order = Array.new
2900 @labels = Hash.new
2901 @killer_mutex = Mutex.new
2902 @ignore_pause = false
2903 data = nil
2904 if @file_name =~ /\.gz$/i
2905 begin
2906 Zlib::GzipReader.open(@file_name) { |f| data = f.readlines.collect { |line| line.chomp } }
2907 rescue
2908 respond "--- Lich: error reading script file (#{@file_name}): #{$!}"
2909 return nil
2910 end
2911 else
2912 begin
2913 File.open(@file_name) { |f| data = f.readlines.collect { |line| line.chomp } }
2914 rescue
2915 respond "--- Lich: error reading script file (#{@file_name}): #{$!}"
2916 return nil
2917 end
2918 end
2919 @quiet = true if data[0] =~ /^[\t\s]*#?[\t\s]*(?:quiet|hush)$/i
2920 @current_label = '~start'
2921 @labels[@current_label] = String.new
2922 @label_order.push(@current_label)
2923 for line in data
2924 if line =~ /^([\d_\w]+):$/
2925 @current_label = $1
2926 @label_order.push(@current_label)
2927 @labels[@current_label] = String.new
2928 else
2929 @labels[@current_label].concat "#{line}\n"
2930 end
2931 end
2932 data = nil
2933 @current_label = @label_order[0]
2934 @thread_group = ThreadGroup.new
2935 @@running.push(self)
2936 return self
2937 end
2938 def kill
2939 Thread.new {
2940 @killer_mutex.synchronize {
2941 if @@running.include?(self)
2942 begin
2943 @thread_group.list.dup.each { |t|
2944 unless t == Thread.current
2945 t.kill rescue nil
2946 end
2947 }
2948 @thread_group.add(Thread.current)
2949 @die_with.each { |script_name| Script.kill(script_name) }
2950 @paused = false
2951 @at_exit_procs.each { |p| report_errors { p.call } }
2952 @die_with = @at_exit_procs = @downstream_buffer = @upstream_buffer = @match_stack_labels = @match_stack_strings = nil
2953 @@running.delete(self)
2954 respond("--- Lich: #{@name} has exited.") unless @quiet
2955 GC.start
2956 rescue
2957 respond "--- Lich: error: #{$!}"
2958 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
2959 end
2960 end
2961 }
2962 }
2963 @name
2964 end
2965 def at_exit(&block)
2966 if block
2967 @at_exit_procs.push(block)
2968 return true
2969 else
2970 respond '--- warning: Script.at_exit called with no code block'
2971 return false
2972 end
2973 end
2974 def clear_exit_procs
2975 @at_exit_procs.clear
2976 true
2977 end
2978 def exit
2979 kill
2980 end
2981 def exit!
2982 @at_exit_procs.clear
2983 kill
2984 end
2985 def instance_variable_get(*a); nil; end
2986 def instance_eval(*a); nil; end
2987 def labels
2988 ($SAFE == 0) ? @labels : nil
2989 end
2990 def thread_group
2991 ($SAFE == 0) ? @thread_group : nil
2992 end
2993 def has_thread?(t)
2994 @thread_group.list.include?(t)
2995 end
2996 def pause
2997 respond "--- Lich: #{@name} paused."
2998 @paused = true
2999 end
3000 def unpause
3001 respond "--- Lich: #{@name} unpaused."
3002 @paused = false
3003 end
3004 def paused?
3005 @paused
3006 end
3007 def get_next_label
3008 if !@jump_label
3009 @current_label = @label_order[@label_order.index(@current_label)+1]
3010 else
3011 if label = @labels.keys.find { |val| val =~ /^#{@jump_label}$/ }
3012 @current_label = label
3013 elsif label = @labels.keys.find { |val| val =~ /^#{@jump_label}$/i }
3014 @current_label = label
3015 elsif label = @labels.keys.find { |val| val =~ /^labelerror$/i }
3016 @current_label = label
3017 else
3018 @current_label = nil
3019 return JUMP_ERROR
3020 end
3021 @jump_label = nil
3022 @current_label
3023 end
3024 end
3025 def clear
3026 to_return = @downstream_buffer.dup
3027 @downstream_buffer.clear
3028 to_return
3029 end
3030 def to_s
3031 @name
3032 end
3033 def gets
3034 # fixme: no xml gets
3035 if @want_downstream or @want_downstream_xml or @want_script_output
3036 sleep 0.05 while @downstream_buffer.empty?
3037 @downstream_buffer.shift
3038 else
3039 echo 'this script is set as unique but is waiting for game data...'
3040 sleep 2
3041 false
3042 end
3043 end
3044 def gets?
3045 if @want_downstream or @want_downstream_xml or @want_script_output
3046 if @downstream_buffer.empty?
3047 nil
3048 else
3049 @downstream_buffer.shift
3050 end
3051 else
3052 echo 'this script is set as unique but is waiting for game data...'
3053 sleep 2
3054 false
3055 end
3056 end
3057 def upstream_gets
3058 sleep 0.05 while @upstream_buffer.empty?
3059 @upstream_buffer.shift
3060 end
3061 def upstream_gets?
3062 if @upstream_buffer.empty?
3063 nil
3064 else
3065 @upstream_buffer.shift
3066 end
3067 end
3068 def unique_gets
3069 sleep 0.05 while @unique_buffer.empty?
3070 @unique_buffer.shift
3071 end
3072 def unique_gets?
3073 if @unique_buffer.empty?
3074 nil
3075 else
3076 @unique_buffer.shift
3077 end
3078 end
3079 def safe?
3080 @safe
3081 end
3082 def feedme_upstream
3083 @want_upstream = !@want_upstream
3084 end
3085 def match_stack_add(label,string)
3086 @match_stack_labels.push(label)
3087 @match_stack_strings.push(string)
3088 end
3089 def match_stack_clear
3090 @match_stack_labels.clear
3091 @match_stack_strings.clear
3092 end
3093end
3094
3095class ExecScript<Script
3096 @@name_exec_mutex = Mutex.new
3097 @@elevated_start = proc { |cmd_data, options|
3098 options[:trusted] = false
3099 unless new_script = ExecScript.new(cmd_data, options)
3100 respond '--- Lich: failed to start exec script'
3101 return false
3102 end
3103 new_thread = Thread.new {
3104 100.times { break if Script.current == new_script; sleep 0.01 }
3105 if script = Script.current
3106 Thread.current.priority = 1
3107 respond("--- Lich: #{script.name} active.") unless script.quiet
3108 begin
3109 script_binding = Scripting.new.script
3110 eval('script = Script.current', script_binding, script.name.to_s)
3111 proc { cmd_data.untaint; $SAFE = 3; eval(cmd_data, script_binding, script.name.to_s) }.call
3112 Script.current.kill
3113 rescue SystemExit
3114 Script.current.kill
3115 rescue SyntaxError
3116 respond "--- SyntaxError: #{$!}"
3117 respond $!.backtrace.first
3118 Lich.log "SyntaxError: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
3119 Script.current.kill
3120 rescue ScriptError
3121 respond "--- ScriptError: #{$!}"
3122 respond $!.backtrace.first
3123 Lich.log "ScriptError: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
3124 Script.current.kill
3125 rescue NoMemoryError
3126 respond "--- NoMemoryError: #{$!}"
3127 respond $!.backtrace.first
3128 Lich.log "NoMemoryError: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
3129 Script.current.kill
3130 rescue LoadError
3131 respond("--- LoadError: #{$!}")
3132 respond "--- LoadError: #{$!}"
3133 respond $!.backtrace.first
3134 Lich.log "LoadError: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
3135 Script.current.kill
3136 rescue SecurityError
3137 respond "--- SecurityError: #{$!}"
3138 respond $!.backtrace[0..1]
3139 Lich.log "SecurityError: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
3140 Script.current.kill
3141 rescue ThreadError
3142 respond "--- ThreadError: #{$!}"
3143 respond $!.backtrace.first
3144 Lich.log "ThreadError: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
3145 Script.current.kill
3146 rescue SystemStackError
3147 respond "--- SystemStackError: #{$!}"
3148 respond $!.backtrace.first
3149 Lich.log "SystemStackError: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
3150 Script.current.kill
3151 rescue Exception
3152 respond "--- Exception: #{$!}"
3153 respond $!.backtrace.first
3154 Lich.log "Exception: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
3155 Script.current.kill
3156 rescue
3157 respond "--- Lich: error: #{$!}"
3158 respond $!.backtrace.first
3159 Lich.log "Error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
3160 Script.current.kill
3161 end
3162 else
3163 respond '--- Lich: error: ExecScript.start: out of cheese'
3164 end
3165 }
3166 new_script.thread_group.add(new_thread)
3167 new_script
3168 }
3169 attr_reader :cmd_data
3170 def ExecScript.start(cmd_data, options={})
3171 options = { :quiet => true } if options == true
3172 if ($SAFE < 2) and (options[:trusted] or (RUBY_VERSION !~ /^2\.[012]\./))
3173 unless new_script = ExecScript.new(cmd_data, options)
3174 respond '--- Lich: failed to start exec script'
3175 return false
3176 end
3177 new_thread = Thread.new {
3178 100.times { break if Script.current == new_script; sleep 0.01 }
3179 if script = Script.current
3180 Thread.current.priority = 1
3181 respond("--- Lich: #{script.name} active.") unless script.quiet
3182 begin
3183 script_binding = TRUSTED_SCRIPT_BINDING.call
3184 eval('script = Script.current', script_binding, script.name.to_s)
3185 eval(cmd_data, script_binding, script.name.to_s)
3186 Script.current.kill
3187 rescue SystemExit
3188 Script.current.kill
3189 rescue SyntaxError
3190 respond "--- SyntaxError: #{$!}"
3191 respond $!.backtrace.first
3192 Lich.log "SyntaxError: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
3193 Script.current.kill
3194 rescue ScriptError
3195 respond "--- ScriptError: #{$!}"
3196 respond $!.backtrace.first
3197 Lich.log "ScriptError: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
3198 Script.current.kill
3199 rescue NoMemoryError
3200 respond "--- NoMemoryError: #{$!}"
3201 respond $!.backtrace.first
3202 Lich.log "NoMemoryError: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
3203 Script.current.kill
3204 rescue LoadError
3205 respond("--- LoadError: #{$!}")
3206 respond "--- LoadError: #{$!}"
3207 respond $!.backtrace.first
3208 Lich.log "LoadError: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
3209 Script.current.kill
3210 rescue SecurityError
3211 respond "--- SecurityError: #{$!}"
3212 respond $!.backtrace[0..1]
3213 Lich.log "SecurityError: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
3214 Script.current.kill
3215 rescue ThreadError
3216 respond "--- ThreadError: #{$!}"
3217 respond $!.backtrace.first
3218 Lich.log "ThreadError: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
3219 Script.current.kill
3220 rescue SystemStackError
3221 respond "--- SystemStackError: #{$!}"
3222 respond $!.backtrace.first
3223 Lich.log "SystemStackError: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
3224 Script.current.kill
3225 rescue Exception
3226 respond "--- Exception: #{$!}"
3227 respond $!.backtrace.first
3228 Lich.log "Exception: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
3229 Script.current.kill
3230 rescue
3231 respond "--- Lich: error: #{$!}"
3232 respond $!.backtrace.first
3233 Lich.log "Error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
3234 Script.current.kill
3235 end
3236 else
3237 respond 'start_exec_script screwed up...'
3238 end
3239 }
3240 new_script.thread_group.add(new_thread)
3241 new_script
3242 else
3243 @@elevated_start.call(cmd_data, options)
3244 end
3245 end
3246 def initialize(cmd_data, flags=Hash.new)
3247 @cmd_data = cmd_data
3248 @vars = Array.new
3249 @downstream_buffer = LimitedArray.new
3250 @killer_mutex = Mutex.new
3251 @want_downstream = true
3252 @want_downstream_xml = false
3253 @upstream_buffer = LimitedArray.new
3254 @want_upstream = false
3255 @at_exit_procs = Array.new
3256 @watchfor = Hash.new
3257 @hidden = false
3258 @paused = false
3259 @silent = false
3260 if flags[:quiet].nil?
3261 @quiet = false
3262 else
3263 @quiet = flags[:quiet]
3264 end
3265 @safe = false
3266 @no_echo = false
3267 @thread_group = ThreadGroup.new
3268 @unique_buffer = LimitedArray.new
3269 @die_with = Array.new
3270 @no_pause_all = false
3271 @no_kill_all = false
3272 @match_stack_labels = Array.new
3273 @match_stack_strings = Array.new
3274 num = '1'; num.succ! while @@running.any? { |s| s.name == "exec#{num}" }
3275 @name = "exec#{num}"
3276 @@running.push(self)
3277 self
3278 end
3279 def get_next_label
3280 echo 'goto labels are not available in exec scripts.'
3281 nil
3282 end
3283end
3284
3285class WizardScript<Script
3286 def initialize(file_name, cli_vars=[])
3287 @name = /.*[\/\\]+([^\.]+)\./.match(file_name).captures.first
3288 @file_name = file_name
3289 @vars = Array.new
3290 @killer_mutex = Mutex.new
3291 unless cli_vars.empty?
3292 if cli_vars.is_a?(String)
3293 cli_vars = cli_vars.split(' ')
3294 end
3295 cli_vars.each_index { |idx| @vars[idx+1] = cli_vars[idx] }
3296 @vars[0] = @vars[1..-1].join(' ')
3297 cli_vars = nil
3298 end
3299 if @vars.first =~ /^quiet$/i
3300 @quiet = true
3301 @vars.shift
3302 else
3303 @quiet = false
3304 end
3305 @downstream_buffer = LimitedArray.new
3306 @want_downstream = true
3307 @want_downstream_xml = false
3308 @upstream_buffer = LimitedArray.new
3309 @want_upstream = false
3310 @unique_buffer = LimitedArray.new
3311 @at_exit_procs = Array.new
3312 @patchfor = Hash.new
3313 @die_with = Array.new
3314 @paused = false
3315 @hidden = false
3316 @no_pause_all = false
3317 @no_kill_all = false
3318 @silent = false
3319 @safe = false
3320 @no_echo = false
3321 @match_stack_labels = Array.new
3322 @match_stack_strings = Array.new
3323 @label_order = Array.new
3324 @labels = Hash.new
3325 data = nil
3326 begin
3327 Zlib::GzipReader.open(file_name) { |f| data = f.readlines.collect { |line| line.chomp } }
3328 rescue
3329 begin
3330 File.open(file_name) { |f| data = f.readlines.collect { |line| line.chomp } }
3331 rescue
3332 respond "--- Lich: error reading script file (#{file_name}): #{$!}"
3333 return nil
3334 end
3335 end
3336 @quiet = true if data[0] =~ /^[\t\s]*#?[\t\s]*(?:quiet|hush)$/i
3337
3338 counter_action = {
3339 'add' => '+',
3340 'sub' => '-',
3341 'subtract' => '-',
3342 'multiply' => '*',
3343 'divide' => '/',
3344 'set' => ''
3345 }
3346
3347 setvars = Array.new
3348 data.each { |line| setvars.push($1) if line =~ /[\s\t]*setvariable\s+([^\s\t]+)[\s\t]/i and not setvars.include?($1) }
3349 has_counter = data.find { |line| line =~ /%c/i }
3350 has_save = data.find { |line| line =~ /%s/i }
3351 has_nextroom = data.find { |line| line =~ /nextroom/i }
3352
3353 fixstring = proc { |str|
3354 while not setvars.empty? and str =~ /%(#{setvars.join('|')})%/io
3355 str.gsub!('%' + $1 + '%', '#{' + $1.downcase + '}')
3356 end
3357 str.gsub!(/%c(?:%)?/i, '#{c}')
3358 str.gsub!(/%s(?:%)?/i, '#{sav}')
3359 while str =~ /%([0-9])(?:%)?/
3360 str.gsub!(/%#{$1}(?:%)?/, '#{script.vars[' + $1 + ']}')
3361 end
3362 str
3363 }
3364
3365 fixline = proc { |line|
3366 if line =~ /^[\s\t]*[A-Za-z0-9_\-']+:/i
3367 line = line.downcase.strip
3368 elsif line =~ /^([\s\t]*)counter\s+(add|sub|subtract|divide|multiply|set)\s+([0-9]+)/i
3369 line = "#{$1}c #{counter_action[$2]}= #{$3}"
3370 elsif line =~ /^([\s\t]*)counter\s+(add|sub|subtract|divide|multiply|set)\s+(.*)/i
3371 indent, action, arg = $1, $2, $3
3372 line = "#{indent}c #{counter_action[action]}= #{fixstring.call(arg.inspect)}.to_i"
3373 elsif line =~ /^([\s\t]*)save[\s\t]+"?(.*?)"?[\s\t]*$/i
3374 indent, arg = $1, $2
3375 line = "#{indent}sav = #{fixstring.call(arg.inspect)}"
3376 elsif line =~ /^([\s\t]*)echo[\s\t]+(.+)/i
3377 indent, arg = $1, $2
3378 line = "#{indent}echo #{fixstring.call(arg.inspect)}"
3379 elsif line =~ /^([\s\t]*)waitfor[\s\t]+(.+)/i
3380 indent, arg = $1, $2
3381 line = "#{indent}waitfor #{fixstring.call(Regexp.escape(arg).inspect.gsub("\\\\ ", ' '))}"
3382 elsif line =~ /^([\s\t]*)put[\s\t]+\.(.+)$/i
3383 indent, arg = $1, $2
3384 if arg.include?(' ')
3385 line = "#{indent}start_script(#{Regexp.escape(fixstring.call(arg.split[0].inspect))}, #{fixstring.call(arg.split[1..-1].join(' ').scan(/"[^"]+"|[^"\s]+/).inspect)})\n#{indent}exit"
3386 else
3387 line = "#{indent}start_script(#{Regexp.escape(fixstring.call(arg.inspect))})\n#{indent}exit"
3388 end
3389 elsif line =~ /^([\s\t]*)put[\s\t]+;(.+)$/i
3390 indent, arg = $1, $2
3391 if arg.include?(' ')
3392 line = "#{indent}start_script(#{Regexp.escape(fixstring.call(arg.split[0].inspect))}, #{fixstring.call(arg.split[1..-1].join(' ').scan(/"[^"]+"|[^"\s]+/).inspect)})"
3393 else
3394 line = "#{indent}start_script(#{Regexp.escape(fixstring.call(arg.inspect))})"
3395 end
3396 elsif line =~ /^([\s\t]*)(put|move)[\s\t]+(.+)/i
3397 indent, cmd, arg = $1, $2, $3
3398 line = "#{indent}waitrt?\n#{indent}clear\n#{indent}#{cmd.downcase} #{fixstring.call(arg.inspect)}"
3399 elsif line =~ /^([\s\t]*)goto[\s\t]+(.+)/i
3400 indent, arg = $1, $2
3401 line = "#{indent}goto #{fixstring.call(arg.inspect).downcase}"
3402 elsif line =~ /^([\s\t]*)waitforre[\s\t]+(.+)/i
3403 indent, arg = $1, $2
3404 line = "#{indent}waitforre #{arg}"
3405 elsif line =~ /^([\s\t]*)pause[\s\t]*(.*)/i
3406 indent, arg = $1, $2
3407 arg = '1' if arg.empty?
3408 arg = '0'+arg.strip if arg.strip =~ /^\.[0-9]+$/
3409 line = "#{indent}pause #{arg}"
3410 elsif line =~ /^([\s\t]*)match[\s\t]+([^\s\t]+)[\s\t]+(.+)/i
3411 indent, label, arg = $1, $2, $3
3412 line = "#{indent}match #{fixstring.call(label.inspect).downcase}, #{fixstring.call(Regexp.escape(arg).inspect.gsub("\\\\ ", ' '))}"
3413 elsif line =~ /^([\s\t]*)matchre[\s\t]+([^\s\t]+)[\s\t]+(.+)/i
3414 indent, label, regex = $1, $2, $3
3415 line = "#{indent}matchre #{fixstring.call(label.inspect).downcase}, #{regex}"
3416 elsif line =~ /^([\s\t]*)setvariable[\s\t]+([^\s\t]+)[\s\t]+(.+)/i
3417 indent, var, arg = $1, $2, $3
3418 line = "#{indent}#{var.downcase} = #{fixstring.call(arg.inspect)}"
3419 elsif line =~ /^([\s\t]*)deletevariable[\s\t]+(.+)/i
3420 line = "#{$1}#{$2.downcase} = nil"
3421 elsif line =~ /^([\s\t]*)(wait|nextroom|exit|echo)\b/i
3422 line = "#{$1}#{$2.downcase}"
3423 elsif line =~ /^([\s\t]*)matchwait\b/i
3424 line = "#{$1}matchwait"
3425 elsif line =~ /^([\s\t]*)if_([0-9])[\s\t]+(.*)/i
3426 indent, num, stuff = $1, $2, $3
3427 line = "#{indent}if script.vars[#{num}]\n#{indent}\t#{fixline.call($3)}\n#{indent}end"
3428 elsif line =~ /^([\s\t]*)shift\b/i
3429 line = "#{$1}script.vars.shift"
3430 else
3431 respond "--- Lich: unknown line: #{line}"
3432 line = '#' + line
3433 end
3434 }
3435
3436 lich_block = false
3437
3438 data.each_index { |idx|
3439 if lich_block
3440 if data[idx] =~ /\}[\s\t]*LICH[\s\t]*$/
3441 data[idx] = data[idx].sub(/\}[\s\t]*LICH[\s\t]*$/, '')
3442 lich_block = false
3443 else
3444 next
3445 end
3446 elsif data[idx] =~ /^[\s\t]*#|^[\s\t]*$/
3447 next
3448 elsif data[idx] =~ /^[\s\t]*LICH[\s\t]*\{/
3449 data[idx] = data[idx].sub(/LICH[\s\t]*\{/, '')
3450 if data[idx] =~ /\}[\s\t]*LICH[\s\t]*$/
3451 data[idx] = data[idx].sub(/\}[\s\t]*LICH[\s\t]*$/, '')
3452 else
3453 lich_block = true
3454 end
3455 else
3456 data[idx] = fixline.call(data[idx])
3457 end
3458 }
3459
3460 if has_counter or has_save or has_nextroom
3461 data.each_index { |idx|
3462 next if data[idx] =~ /^[\s\t]*#/
3463 data.insert(idx, '')
3464 data.insert(idx, 'c = 0') if has_counter
3465 data.insert(idx, "sav = Settings['sav'] || String.new\nbefore_dying { Settings['sav'] = sav }") if has_save
3466 data.insert(idx, "def nextroom\n\troom_count = XMLData.room_count\n\twait_while { room_count == XMLData.room_count }\nend") if has_nextroom
3467 data.insert(idx, '')
3468 break
3469 }
3470 end
3471
3472 @current_label = '~start'
3473 @labels[@current_label] = String.new
3474 @label_order.push(@current_label)
3475 for line in data
3476 if line =~ /^([\d_\w]+):$/
3477 @current_label = $1
3478 @label_order.push(@current_label)
3479 @labels[@current_label] = String.new
3480 else
3481 @labels[@current_label] += "#{line}\n"
3482 end
3483 end
3484 data = nil
3485 @current_label = @label_order[0]
3486 @thread_group = ThreadGroup.new
3487 @@running.push(self)
3488 return self
3489 end
3490end
3491
3492class Watchfor
3493 def initialize(line, theproc=nil, &block)
3494 return nil unless script = Script.current
3495 if line.class == String
3496 line = Regexp.new(Regexp.escape(line))
3497 elsif line.class != Regexp
3498 echo 'watchfor: no string or regexp given'
3499 return nil
3500 end
3501 if block.nil?
3502 if theproc.respond_to? :call
3503 block = theproc
3504 else
3505 echo 'watchfor: no block or proc given'
3506 return nil
3507 end
3508 end
3509 script.watchfor[line] = block
3510 end
3511 def Watchfor.clear
3512 script.watchfor = Hash.new
3513 end
3514end
3515
3516class Map
3517 @@loaded = false
3518 @@load_mutex = Mutex.new
3519 @@list ||= Array.new
3520 @@tags ||= Array.new
3521 @@current_room_mutex = Mutex.new
3522 @@current_room_id ||= 0
3523 @@current_room_count ||= -1
3524 @@fuzzy_room_mutex = Mutex.new
3525 @@fuzzy_room_id ||= 0
3526 @@fuzzy_room_count ||= -1
3527 @@current_location ||= nil
3528 @@current_location_count ||= -1
3529 @@elevated_load = proc { Map.load }
3530 @@elevated_load_dat = proc { Map.load_dat }
3531 @@elevated_load_xml = proc { Map.load_xml }
3532 @@elevated_save = proc { Map.save }
3533 @@elevated_save_xml = proc { Map.save_xml }
3534 attr_reader :id
3535 attr_accessor :title, :description, :paths, :location, :climate, :terrain, :wayto, :timeto, :image, :image_coords, :tags, :check_location, :unique_loot
3536 def initialize(id, title, description, paths, location=nil, climate=nil, terrain=nil, wayto={}, timeto={}, image=nil, image_coords=nil, tags=[], check_location=nil, unique_loot=nil)
3537 @id, @title, @description, @paths, @location, @climate, @terrain, @wayto, @timeto, @image, @image_coords, @tags, @check_location, @unique_loot = id, title, description, paths, location, climate, terrain, wayto, timeto, image, image_coords, tags, check_location, unique_loot
3538 @@list[@id] = self
3539 end
3540 def outside?
3541 @paths.first =~ /Obvious paths:/
3542 end
3543 def to_i
3544 @id
3545 end
3546 def to_s
3547 "##{@id}:\n#{@title[-1]}\n#{@description[-1]}\n#{@paths[-1]}"
3548 end
3549 def inspect
3550 self.instance_variables.collect { |var| var.to_s + "=" + self.instance_variable_get(var).inspect }.join("\n")
3551 end
3552 def Map.get_free_id
3553 Map.load unless @@loaded
3554 free_id = 0
3555 until @@list[free_id].nil?
3556 free_id += 1
3557 end
3558 free_id
3559 end
3560 def Map.list
3561 Map.load unless @@loaded
3562 @@list
3563 end
3564 def Map.[](val)
3565 Map.load unless @@loaded
3566 if (val.class == Fixnum) or (val.class == Bignum) or val =~ /^[0-9]+$/
3567 @@list[val.to_i]
3568 else
3569 chkre = /#{val.strip.sub(/\.$/, '').gsub(/\.(?:\.\.)?/, '|')}/i
3570 chk = /#{Regexp.escape(val.strip)}/i
3571 @@list.find { |room| room.title.find { |title| title =~ chk } } || @@list.find { |room| room.description.find { |desc| desc =~ chk } } || @@list.find { |room| room.description.find { |desc| desc =~ chkre } }
3572 end
3573 end
3574 def Map.get_location
3575 unless XMLData.room_count == @@current_location_count
3576 if script = Script.current
3577 save_want_downstream = script.want_downstream
3578 script.want_downstream = true
3579 waitrt?
3580 location_result = dothistimeout 'location', 15, /^You carefully survey your surroundings and guess that your current location is .*? or somewhere close to it\.$|^You can't do that while submerged under water\.$|^You can't do that\.$|^It would be rude not to give your full attention to the performance\.$|^You can't do that while hanging around up here!$|^You are too distracted by the difficulty of staying alive in these treacherous waters to do that\.$|^You carefully survey your surroundings but are unable to guess your current location\.$|^Not in pitch darkness you don't\.$|^That is too difficult to consider here\.$/
3581 script.want_downstream = save_want_downstream
3582 @@current_location_count = XMLData.room_count
3583 if location_result =~ /^You can't do that while submerged under water\.$|^You can't do that\.$|^It would be rude not to give your full attention to the performance\.$|^You can't do that while hanging around up here!$|^You are too distracted by the difficulty of staying alive in these treacherous waters to do that\.$|^You carefully survey your surroundings but are unable to guess your current location\.$|^Not in pitch darkness you don't\.$|^That is too difficult to consider here\.$/
3584 @@current_location = false
3585 else
3586 @@current_location = /^You carefully survey your surroundings and guess that your current location is (.*?) or somewhere close to it\.$/.match(location_result).captures.first
3587 end
3588 else
3589 nil
3590 end
3591 end
3592 @@current_location
3593 end
3594 def Map.current
3595 Map.load unless @@loaded
3596 if script = Script.current
3597 @@current_room_mutex.synchronize {
3598 if XMLData.room_count == @@current_room_count
3599 if @@current_room_id.nil?
3600 return nil
3601 else
3602 return @@list[@@current_room_id]
3603 end
3604 else
3605 peer_history = Hash.new
3606 need_set_desc_off = false
3607 check_peer_tag = proc { |r|
3608 begin
3609 script.ignore_pause = true
3610 peer_room_count = XMLData.room_count
3611 if peer_tag = r.tags.find { |tag| tag =~ /^(set desc on; )?peer [a-z]+ =~ \/.+\/$/ }
3612 good = false
3613 need_desc, peer_direction, peer_requirement = /^(set desc on; )?peer ([a-z]+) =~ \/(.+)\/$/.match(peer_tag).captures
3614 need_desc = need_desc ? true : false
3615 if peer_history[peer_room_count][peer_direction][need_desc].nil?
3616 if need_desc
3617 unless last_roomdesc = $_SERVERBUFFER_.reverse.find { |line| line =~ /<style id="roomDesc"\/>/ } and (last_roomdesc =~ /<style id="roomDesc"\/>[^<]/)
3618 put 'set description on'
3619 need_set_desc_off = true
3620 end
3621 end
3622 save_want_downstream = script.want_downstream
3623 script.want_downstream = true
3624 squelch_started = false
3625 squelch_proc = proc { |server_string|
3626 if squelch_started
3627 if server_string =~ /<prompt/
3628 DownstreamHook.remove('squelch-peer')
3629 end
3630 nil
3631 elsif server_string =~ /^You peer/
3632 squelch_started = true
3633 nil
3634 else
3635 server_string
3636 end
3637 }
3638 DownstreamHook.add('squelch-peer', squelch_proc)
3639 result = dothistimeout "peer #{peer_direction}", 3, /^You peer|^\[Usage: PEER/
3640 if result =~ /^You peer/
3641 peer_results = Array.new
3642 5.times {
3643 if line = get?
3644 peer_results.push line
3645 break if line =~ /^Obvious/
3646 end
3647 }
3648 if XMLData.room_count == peer_room_count
3649 peer_history[peer_room_count] ||= Hash.new
3650 peer_history[peer_room_count][peer_direction] ||= Hash.new
3651 if need_desc
3652 peer_history[peer_room_count][peer_direction][true] = peer_results
3653 peer_history[peer_room_count][peer_direction][false] = peer_results
3654 else
3655 peer_history[peer_room_count][peer_direction][false] = peer_results
3656 end
3657 end
3658 end
3659 script.want_downstream = save_want_downstream
3660 end
3661 if peer_history[peer_room_count][peer_direction][need_desc].any? { |line| line =~ /#{peer_requirement}/ }
3662 good = true
3663 else
3664 good = false
3665 end
3666 else
3667 good = true
3668 end
3669 ensure
3670 script.ignore_pause = false
3671 end
3672 good
3673 }
3674 begin
3675 1.times {
3676 @@current_room_count = XMLData.room_count
3677 foggy_exits = (XMLData.room_exits_string =~ /^Obvious (?:exits|paths): obscured by a thick fog$/)
3678 if room = @@list.find { |r| r.title.include?(XMLData.room_title) and r.description.include?(XMLData.room_description.strip) and (r.unique_loot.nil? or (r.unique_loot.to_a - GameObj.loot.to_a.collect { |obj| obj.name }).empty?) and (foggy_exits or r.paths.include?(XMLData.room_exits_string.strip) or r.tags.include?('random-paths')) and (not r.check_location or r.location == Map.get_location) and check_peer_tag.call(r) }
3679 redo unless @@current_room_count == XMLData.room_count
3680 @@current_room_id = room.id
3681 return room
3682 else
3683 redo unless @@current_room_count == XMLData.room_count
3684 desc_regex = /#{Regexp.escape(XMLData.room_description.strip.sub(/\.+$/, '')).gsub(/\\\.(?:\\\.\\\.)?/, '|')}/
3685 if room = @@list.find { |r| r.title.include?(XMLData.room_title) and (foggy_exits or r.paths.include?(XMLData.room_exits_string.strip) or r.tags.include?('random-paths')) and (XMLData.room_window_disabled or r.description.any? { |desc| desc =~ desc_regex }) and (r.unique_loot.nil? or (r.unique_loot.to_a - GameObj.loot.to_a.collect { |obj| obj.name }).empty?) and (not r.check_location or r.location == Map.get_location) and check_peer_tag.call(r) }
3686 redo unless @@current_room_count == XMLData.room_count
3687 @@current_room_id = room.id
3688 return room
3689 else
3690 redo unless @@current_room_count == XMLData.room_count
3691 @@current_room_id = nil
3692 return nil
3693 end
3694 end
3695 }
3696 ensure
3697 put 'set description off' if need_set_desc_off
3698 end
3699 end
3700 }
3701 else
3702 @@fuzzy_room_mutex.synchronize {
3703 if XMLData.room_count == @@current_room_count
3704 if @@current_room_id.nil?
3705 return nil
3706 else
3707 return @@list[@@current_room_id]
3708 end
3709 elsif XMLData.room_count == @@fuzzy_room_count
3710 if @@fuzzy_room_id.nil?
3711 return nil
3712 else
3713 return @@list[@@fuzzy_room_id]
3714 end
3715 else
3716 1.times {
3717 @@fuzzy_room_count = XMLData.room_count
3718 foggy_exits = (XMLData.room_exits_string =~ /^Obvious (?:exits|paths): obscured by a thick fog$/)
3719 if (room = @@list.find { |r| r.title.include?(XMLData.room_title) and r.description.include?(XMLData.room_description.strip) and (r.unique_loot.nil? or (r.unique_loot.to_a - GameObj.loot.to_a.collect { |obj| obj.name }).empty?) and (foggy_exits or r.paths.include?(XMLData.room_exits_string.strip) or r.tags.include?('random-paths')) and (not r.check_location or r.location == Map.get_location) })
3720 redo unless @@fuzzy_room_count == XMLData.room_count
3721 if room.tags.any? { |tag| tag =~ /^(set desc on; )?peer [a-z]+ =~ \/.+\/$/ }
3722 @@fuzzy_room_id = nil
3723 return nil
3724 else
3725 @@fuzzy_room_id = room.id
3726 return room
3727 end
3728 else
3729 redo unless @@fuzzy_room_count == XMLData.room_count
3730 desc_regex = /#{Regexp.escape(XMLData.room_description.strip.sub(/\.+$/, '')).gsub(/\\\.(?:\\\.\\\.)?/, '|')}/
3731 if room = @@list.find { |r| r.title.include?(XMLData.room_title) and (foggy_exits or r.paths.include?(XMLData.room_exits_string.strip) or r.tags.include?('random-paths')) and (XMLData.room_window_disabled or r.description.any? { |desc| desc =~ desc_regex }) and (r.unique_loot.nil? or (r.unique_loot.to_a - GameObj.loot.to_a.collect { |obj| obj.name }).empty?) and (not r.check_location or r.location == Map.get_location) }
3732 redo unless @@fuzzy_room_count == XMLData.room_count
3733 if room.tags.any? { |tag| tag =~ /^(set desc on; )?peer [a-z]+ =~ \/.+\/$/ }
3734 @@fuzzy_room_id = nil
3735 return nil
3736 else
3737 @@fuzzy_room_id = room.id
3738 return room
3739 end
3740 else
3741 redo unless @@fuzzy_room_count == XMLData.room_count
3742 @@fuzzy_room_id = nil
3743 return nil
3744 end
3745 end
3746 }
3747 end
3748 }
3749 end
3750 end
3751 def Map.current_or_new
3752 return nil unless Script.current
3753 if XMLData.game =~ /DR/
3754 @@current_room_count = -1
3755 @@fuzzy_room_count = -1
3756 Map.current || Map.new(Map.get_free_id, [ XMLData.room_title ], [ XMLData.room_description.strip ], [ XMLData.room_exits_string.strip ])
3757 else
3758 check_peer_tag = proc { |r|
3759 if peer_tag = r.tags.find { |tag| tag =~ /^(set desc on; )?peer [a-z]+ =~ \/.+\/$/ }
3760 good = false
3761 need_desc, peer_direction, peer_requirement = /^(set desc on; )?peer ([a-z]+) =~ \/(.+)\/$/.match(peer_tag).captures
3762 if need_desc
3763 unless last_roomdesc = $_SERVERBUFFER_.reverse.find { |line| line =~ /<style id="roomDesc"\/>/ } and (last_roomdesc =~ /<style id="roomDesc"\/>[^<]/)
3764 put 'set description on'
3765 end
3766 end
3767 script = Script.current
3768 save_want_downstream = script.want_downstream
3769 script.want_downstream = true
3770 squelch_started = false
3771 squelch_proc = proc { |server_string|
3772 if squelch_started
3773 if server_string =~ /<prompt/
3774 DownstreamHook.remove('squelch-peer')
3775 end
3776 nil
3777 elsif server_string =~ /^You peer/
3778 squelch_started = true
3779 nil
3780 else
3781 server_string
3782 end
3783 }
3784 DownstreamHook.add('squelch-peer', squelch_proc)
3785 result = dothistimeout "peer #{peer_direction}", 3, /^You peer|^\[Usage: PEER/
3786 if result =~ /^You peer/
3787 peer_results = Array.new
3788 5.times {
3789 if line = get?
3790 peer_results.push line
3791 break if line =~ /^Obvious/
3792 end
3793 }
3794 if peer_results.any? { |line| line =~ /#{peer_requirement}/ }
3795 good = true
3796 end
3797 end
3798 script.want_downstream = save_want_downstream
3799 else
3800 good = true
3801 end
3802 good
3803 }
3804 current_location = Map.get_location
3805 if room = @@list.find { |r| (r.location == current_location) and r.title.include?(XMLData.room_title) and r.description.include?(XMLData.room_description.strip) and (r.unique_loot.nil? or (r.unique_loot.to_a - GameObj.loot.to_a.collect { |obj| obj.name }).empty?) and (r.paths.include?(XMLData.room_exits_string.strip) or r.tags.include?('random-paths')) and check_peer_tag.call(r) }
3806 return room
3807 elsif room = @@list.find { |r| r.location.nil? and r.title.include?(XMLData.room_title) and r.description.include?(XMLData.room_description.strip) and (r.unique_loot.nil? or (r.unique_loot.to_a - GameObj.loot.to_a.collect { |obj| obj.name }).empty?) and (r.paths.include?(XMLData.room_exits_string.strip) or r.tags.include?('random-paths')) and check_peer_tag.call(r) }
3808 room.location = current_location
3809 return room
3810 else
3811 title = [ XMLData.room_title ]
3812 description = [ XMLData.room_description.strip ]
3813 paths = [ XMLData.room_exits_string.strip ]
3814 room = Map.new(Map.get_free_id, title, description, paths, current_location)
3815 identical_rooms = @@list.find_all { |r| (r.location != current_location) and r.title.include?(XMLData.room_title) and r.description.include?(XMLData.room_description.strip) and (r.unique_loot.nil? or (r.unique_loot.to_a - GameObj.loot.to_a.collect { |obj| obj.name }).empty?) and (r.paths.include?(XMLData.room_exits_string.strip) or r.tags.include?('random-paths')) }
3816 if identical_rooms.length > 0
3817 room.check_location = true
3818 identical_rooms.each { |r| r.check_location = true }
3819 end
3820 return room
3821 end
3822 end
3823 end
3824 def Map.tags
3825 Map.load unless @@loaded
3826 if @@tags.empty?
3827 @@list.each { |r| r.tags.each { |t| @@tags.push(t) unless @@tags.include?(t) } }
3828 end
3829 @@tags.dup
3830 end
3831 def Map.clear
3832 @@load_mutex.synchronize {
3833 @@list.clear
3834 @@tags.clear
3835 @@loaded = false
3836 GC.start
3837 }
3838 true
3839 end
3840 def Map.reload
3841 Map.clear
3842 Map.load
3843 end
3844 def Map.load(filename=nil)
3845 if $SAFE == 0
3846 if filename.nil?
3847 file_list = Dir.entries("#{DATA_DIR}/#{XMLData.game}").find_all { |filename| filename =~ /^map\-[0-9]+\.(?:dat|xml)$/ }.collect { |filename| "#{DATA_DIR}/#{XMLData.game}/#{filename}" }.sort.reverse
3848 else
3849 file_list = [ filename ]
3850 end
3851 if file_list.empty?
3852 respond "--- Lich: error: no map database found"
3853 return false
3854 end
3855 while filename = file_list.shift
3856 if filename =~ /\.xml$/
3857 if Map.load_xml(filename)
3858 return true
3859 end
3860 else
3861 if Map.load_dat(filename)
3862 return true
3863 end
3864 end
3865 end
3866 return false
3867 else
3868 @@elevated_load.call
3869 end
3870 end
3871 def Map.load_dat(filename=nil)
3872 if $SAFE == 0
3873 @@load_mutex.synchronize {
3874 if @@loaded
3875 return true
3876 else
3877 if filename.nil?
3878 file_list = Dir.entries("#{DATA_DIR}/#{XMLData.game}").find_all { |filename| filename =~ /^map\-[0-9]+\.dat$/ }.collect { |filename| "#{DATA_DIR}/#{XMLData.game}/#{filename}" }.sort.reverse
3879 else
3880 file_list = [ filename ]
3881 end
3882 if file_list.empty?
3883 respond "--- Lich: error: no map database found"
3884 return false
3885 end
3886 error = false
3887 while filename = file_list.shift
3888 begin
3889 @@list = File.open(filename, 'rb') { |f| Marshal.load(f.read) }
3890 respond "--- loaded #{filename}" if error
3891 @@loaded = true
3892 return true
3893 rescue
3894 error = true
3895 if file_list.empty?
3896 respond "--- Lich: error: failed to load #{filename}: #{$!}"
3897 else
3898 respond "--- warning: failed to load #{filename}: #{$!}"
3899 end
3900 end
3901 end
3902 return false
3903 end
3904 }
3905 else
3906 @@elevated_load_dat.call
3907 end
3908 end
3909 def Map.load_xml(filename="#{DATA_DIR}/#{XMLData.game}/map.xml")
3910 if $SAFE == 0
3911 @@load_mutex.synchronize {
3912 if @@loaded
3913 return true
3914 else
3915 unless File.exists?(filename)
3916 raise Exception.exception("MapDatabaseError"), "Fatal error: file `#{filename}' does not exist!"
3917 end
3918 missing_end = false
3919 current_tag = nil
3920 current_attributes = nil
3921 room = nil
3922 buffer = String.new
3923 unescape = { 'lt' => '<', 'gt' => '>', 'quot' => '"', 'apos' => "'", 'amp' => '&' }
3924 tag_start = proc { |element,attributes|
3925 current_tag = element
3926 current_attributes = attributes
3927 if element == 'room'
3928 room = Hash.new
3929 room['id'] = attributes['id'].to_i
3930 room['location'] = attributes['location']
3931 room['climate'] = attributes['climate']
3932 room['terrain'] = attributes['terrain']
3933 room['wayto'] = Hash.new
3934 room['timeto'] = Hash.new
3935 room['title'] = Array.new
3936 room['description'] = Array.new
3937 room['paths'] = Array.new
3938 room['tags'] = Array.new
3939 room['unique_loot'] = Array.new
3940 elsif element =~ /^(?:image|tsoran)$/ and attributes['name'] and attributes['x'] and attributes['y'] and attributes['size']
3941 room['image'] = attributes['name']
3942 room['image_coords'] = [ (attributes['x'].to_i - (attributes['size']/2.0).round), (attributes['y'].to_i - (attributes['size']/2.0).round), (attributes['x'].to_i + (attributes['size']/2.0).round), (attributes['y'].to_i + (attributes['size']/2.0).round) ]
3943 elsif (element == 'image') and attributes['name'] and attributes['coords'] and (attributes['coords'] =~ /[0-9]+,[0-9]+,[0-9]+,[0-9]+/)
3944 room['image'] = attributes['name']
3945 room['image_coords'] = attributes['coords'].split(',').collect { |num| num.to_i }
3946 elsif element == 'map'
3947 missing_end = true
3948 end
3949 }
3950 text = proc { |text_string|
3951 if current_tag == 'tag'
3952 room['tags'].push(text_string)
3953 elsif current_tag =~ /^(?:title|description|paths|tag|unique_loot)$/
3954 room[current_tag].push(text_string)
3955 elsif current_tag == 'exit' and current_attributes['target']
3956 if current_attributes['type'].downcase == 'string'
3957 room['wayto'][current_attributes['target']] = text_string
3958 elsif
3959 room['wayto'][current_attributes['target']] = StringProc.new(text_string)
3960 end
3961 if current_attributes['cost'] =~ /^[0-9\.]+$/
3962 room['timeto'][current_attributes['target']] = current_attributes['cost'].to_f
3963 elsif current_attributes['cost'].length > 0
3964 room['timeto'][current_attributes['target']] = StringProc.new(current_attributes['cost'])
3965 else
3966 room['timeto'][current_attributes['target']] = 0.2
3967 end
3968 end
3969 }
3970 tag_end = proc { |element|
3971 if element == 'room'
3972 room['unique_loot'] = nil if room['unique_loot'].empty?
3973 Map.new(room['id'], room['title'], room['description'], room['paths'], room['location'], room['climate'], room['terrain'], room['wayto'], room['timeto'], room['image'], room['image_coords'], room['tags'], room['check_location'], room['unique_loot'])
3974 elsif element == 'map'
3975 missing_end = false
3976 end
3977 current_tag = nil
3978 }
3979 begin
3980 File.open(filename) { |file|
3981 while line = file.gets
3982 buffer.concat(line)
3983 # fixme: remove (?=<) ?
3984 while str = buffer.slice!(/^<([^>]+)><\/\1>|^[^<]+(?=<)|^<[^<]+>/)
3985 if str[0,1] == '<'
3986 if str[1,1] == '/'
3987 element = /^<\/([^\s>\/]+)/.match(str).captures.first
3988 tag_end.call(element)
3989 else
3990 if str =~ /^<([^>]+)><\/\1>/
3991 element = $1
3992 tag_start.call(element)
3993 text.call('')
3994 tag_end.call(element)
3995 else
3996 element = /^<([^\s>\/]+)/.match(str).captures.first
3997 attributes = Hash.new
3998 str.scan(/([A-z][A-z0-9_\-]*)=(["'])(.*?)\2/).each { |attr| attributes[attr[0]] = attr[2].gsub(/&(#{unescape.keys.join('|')});/) { unescape[$1] } }
3999 tag_start.call(element, attributes)
4000 tag_end.call(element) if str[-2,1] == '/'
4001 end
4002 end
4003 else
4004 text.call(str.gsub(/&(#{unescape.keys.join('|')});/) { unescape[$1] })
4005 end
4006 end
4007 end
4008 }
4009 if missing_end
4010 respond "--- Lich: error: failed to load #{filename}: unexpected end of file"
4011 return false
4012 end
4013 @@tags.clear
4014 @@loaded = true
4015 return true
4016 rescue
4017 respond "--- Lich: error: failed to load #{filename}: #{$!}"
4018 return false
4019 end
4020 end
4021 }
4022 else
4023 @@elevated_load_xml.call
4024 end
4025 end
4026 def Map.save(filename="#{DATA_DIR}/#{XMLData.game}/map-#{Time.now.to_i}.dat")
4027 if $SAFE == 0
4028 if File.exists?(filename)
4029 respond "--- Backing up map database"
4030 begin
4031 # fixme: does this work on all platforms? File.rename(filename, "#{filename}.bak")
4032 File.open(filename, 'rb') { |infile|
4033 File.open("#{filename}.bak", 'wb') { |outfile|
4034 outfile.write(infile.read)
4035 }
4036 }
4037 rescue
4038 respond "--- Lich: error: #{$!}"
4039 end
4040 end
4041 begin
4042 File.open(filename, 'wb') { |f| f.write(Marshal.dump(@@list)) }
4043 @@tags.clear
4044 respond "--- Map database saved"
4045 rescue
4046 respond "--- Lich: error: #{$!}"
4047 end
4048 else
4049 @@elevated_save.call
4050 end
4051 end
4052 def Map.save_xml(filename="#{DATA_DIR}/#{XMLData.game}/map-#{Time.now.to_i}.xml")
4053 if $SAFE == 0
4054 if File.exists?(filename)
4055 respond "File exists! Backing it up before proceeding..."
4056 begin
4057 File.open(filename, 'rb') { |infile|
4058 File.open("#{filename}.bak", "wb") { |outfile|
4059 outfile.write(infile.read)
4060 }
4061 }
4062 rescue
4063 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
4064 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
4065 end
4066 end
4067 begin
4068 escape = { '<' => '<', '>' => '>', '"' => '"', "'" => "'", '&' => '&' }
4069 File.open(filename, 'w') { |file|
4070 file.write "<map>\n"
4071 @@list.each { |room|
4072 next if room == nil
4073 if room.location
4074 location = " location=#{(room.location.gsub(/(<|>|"|'|&)/) { escape[$1] }).inspect}"
4075 else
4076 location = ''
4077 end
4078 if room.climate
4079 climate = " climate=#{(room.climate.gsub(/(<|>|"|'|&)/) { escape[$1] }).inspect}"
4080 else
4081 climate = ''
4082 end
4083 if room.terrain
4084 terrain = " terrain=#{(room.terrain.gsub(/(<|>|"|'|&)/) { escape[$1] }).inspect}"
4085 else
4086 terrain = ''
4087 end
4088 file.write " <room id=\"#{room.id}\"#{location}#{climate}#{terrain}>\n"
4089 room.title.each { |title| file.write " <title>#{title.gsub(/(<|>|"|'|&)/) { escape[$1] }}</title>\n" }
4090 room.description.each { |desc| file.write " <description>#{desc.gsub(/(<|>|"|'|&)/) { escape[$1] }}</description>\n" }
4091 room.paths.each { |paths| file.write " <paths>#{paths.gsub(/(<|>|"|'|&)/) { escape[$1] }}</paths>\n" }
4092 room.tags.each { |tag| file.write " <tag>#{tag.gsub(/(<|>|"|'|&)/) { escape[$1] }}</tag>\n" }
4093 room.unique_loot.to_a.each { |loot| file.write " <unique_loot>#{loot.gsub(/(<|>|"|'|&)/) { escape[$1] }}</unique_loot>\n" }
4094 file.write " <image name=\"#{room.image.gsub(/(<|>|"|'|&)/) { escape[$1] }}\" coords=\"#{room.image_coords.join(',')}\" />\n" if room.image and room.image_coords
4095 room.wayto.keys.each { |target|
4096 if room.timeto[target].class == Proc
4097 cost = " cost=\"#{room.timeto[target]._dump.gsub(/(<|>|"|'|&)/) { escape[$1] }}\""
4098 elsif room.timeto[target]
4099 cost = " cost=\"#{room.timeto[target]}\""
4100 else
4101 cost = ''
4102 end
4103 if room.wayto[target].class == Proc
4104 file.write " <exit target=\"#{target}\" type=\"Proc\"#{cost}>#{room.wayto[target]._dump.gsub(/(<|>|"|'|&)/) { escape[$1] }}</exit>\n"
4105 else
4106 file.write " <exit target=\"#{target}\" type=\"#{room.wayto[target].class}\"#{cost}>#{room.wayto[target].gsub(/(<|>|"|'|&)/) { escape[$1] }}</exit>\n"
4107 end
4108 }
4109 file.write " </room>\n"
4110 }
4111 file.write "</map>\n"
4112 }
4113 @@tags.clear
4114 respond "--- map database saved to: #{filename}"
4115 rescue
4116 respond $!
4117 end
4118 GC.start
4119 else
4120 @@elevated_save_xml.call
4121 end
4122 end
4123 def Map.estimate_time(array)
4124 Map.load unless @@loaded
4125 unless array.class == Array
4126 raise Exception.exception("MapError"), "Map.estimate_time was given something not an array!"
4127 end
4128 time = 0.to_f
4129 until array.length < 2
4130 room = array.shift
4131 if t = Map[room].timeto[array.first.to_s]
4132 if t.class == Proc
4133 time += t.call.to_f
4134 else
4135 time += t.to_f
4136 end
4137 else
4138 time += "0.2".to_f
4139 end
4140 end
4141 time
4142 end
4143 def Map.dijkstra(source, destination=nil)
4144 if source.class == Map
4145 source.dijkstra(destination)
4146 elsif room = Map[source]
4147 room.dijkstra(destination)
4148 else
4149 echo "Map.dijkstra: error: invalid source room"
4150 nil
4151 end
4152 end
4153 def dijkstra(destination=nil)
4154 begin
4155 Map.load unless @@loaded
4156 source = @id
4157 visited = Array.new
4158 shortest_distances = Array.new
4159 previous = Array.new
4160 pq = [ source ]
4161 pq_push = proc { |val|
4162 for i in 0...pq.size
4163 if shortest_distances[val] <= shortest_distances[pq[i]]
4164 pq.insert(i, val)
4165 break
4166 end
4167 end
4168 pq.push(val) if i.nil? or (i == pq.size-1)
4169 }
4170 visited[source] = true
4171 shortest_distances[source] = 0
4172 if destination.nil?
4173 until pq.size == 0
4174 v = pq.shift
4175 visited[v] = true
4176 @@list[v].wayto.keys.each { |adj_room|
4177 adj_room_i = adj_room.to_i
4178 unless visited[adj_room_i]
4179 if @@list[v].timeto[adj_room].class == Proc
4180 nd = @@list[v].timeto[adj_room].call
4181 else
4182 nd = @@list[v].timeto[adj_room]
4183 end
4184 if nd
4185 nd += shortest_distances[v]
4186 if shortest_distances[adj_room_i].nil? or (shortest_distances[adj_room_i] > nd)
4187 shortest_distances[adj_room_i] = nd
4188 previous[adj_room_i] = v
4189 pq_push.call(adj_room_i)
4190 end
4191 end
4192 end
4193 }
4194 end
4195 elsif destination.class == Fixnum
4196 until pq.size == 0
4197 v = pq.shift
4198 break if v == destination
4199 visited[v] = true
4200 @@list[v].wayto.keys.each { |adj_room|
4201 adj_room_i = adj_room.to_i
4202 unless visited[adj_room_i]
4203 if @@list[v].timeto[adj_room].class == Proc
4204 nd = @@list[v].timeto[adj_room].call
4205 else
4206 nd = @@list[v].timeto[adj_room]
4207 end
4208 if nd
4209 nd += shortest_distances[v]
4210 if shortest_distances[adj_room_i].nil? or (shortest_distances[adj_room_i] > nd)
4211 shortest_distances[adj_room_i] = nd
4212 previous[adj_room_i] = v
4213 pq_push.call(adj_room_i)
4214 end
4215 end
4216 end
4217 }
4218 end
4219 elsif destination.class == Array
4220 dest_list = destination.collect { |dest| dest.to_i }
4221 until pq.size == 0
4222 v = pq.shift
4223 break if dest_list.include?(v) and (shortest_distances[v] < 20)
4224 visited[v] = true
4225 @@list[v].wayto.keys.each { |adj_room|
4226 adj_room_i = adj_room.to_i
4227 unless visited[adj_room_i]
4228 if @@list[v].timeto[adj_room].class == Proc
4229 nd = @@list[v].timeto[adj_room].call
4230 else
4231 nd = @@list[v].timeto[adj_room]
4232 end
4233 if nd
4234 nd += shortest_distances[v]
4235 if shortest_distances[adj_room_i].nil? or (shortest_distances[adj_room_i] > nd)
4236 shortest_distances[adj_room_i] = nd
4237 previous[adj_room_i] = v
4238 pq_push.call(adj_room_i)
4239 end
4240 end
4241 end
4242 }
4243 end
4244 end
4245 return previous, shortest_distances
4246 rescue
4247 echo "Map.dijkstra: error: #{$!}"
4248 respond $!.backtrace
4249 nil
4250 end
4251 end
4252 def Map.findpath(source, destination)
4253 if source.class == Map
4254 source.path_to(destination)
4255 elsif room = Map[source]
4256 room.path_to(destination)
4257 else
4258 echo "Map.findpath: error: invalid source room"
4259 nil
4260 end
4261 end
4262 def path_to(destination)
4263 Map.load unless @@loaded
4264 destination = destination.to_i
4265 previous, shortest_distances = dijkstra(destination)
4266 return nil unless previous[destination]
4267 path = [ destination ]
4268 path.push(previous[path[-1]]) until previous[path[-1]] == @id
4269 path.reverse!
4270 path.pop
4271 return path
4272 end
4273 def find_nearest_by_tag(tag_name)
4274 target_list = Array.new
4275 @@list.each { |room| target_list.push(room.id) if room.tags.include?(tag_name) }
4276 previous, shortest_distances = Map.dijkstra(@id, target_list)
4277 if target_list.include?(@id)
4278 @id
4279 else
4280 target_list.delete_if { |room_num| shortest_distances[room_num].nil? }
4281 target_list.sort { |a,b| shortest_distances[a] <=> shortest_distances[b] }.first
4282 end
4283 end
4284 def find_all_nearest_by_tag(tag_name)
4285 target_list = Array.new
4286 @@list.each { |room| target_list.push(room.id) if room.tags.include?(tag_name) }
4287 previous, shortest_distances = Map.dijkstra(@id)
4288 target_list.delete_if { |room_num| shortest_distances[room_num].nil? }
4289 target_list.sort { |a,b| shortest_distances[a] <=> shortest_distances[b] }
4290 end
4291 def find_nearest(target_list)
4292 target_list = target_list.collect { |num| num.to_i }
4293 if target_list.include?(@id)
4294 @id
4295 else
4296 previous, shortest_distances = Map.dijkstra(@id, target_list)
4297 target_list.delete_if { |room_num| shortest_distances[room_num].nil? }
4298 target_list.sort { |a,b| shortest_distances[a] <=> shortest_distances[b] }.first
4299 end
4300 end
4301end
4302
4303class Room < Map
4304# private_class_method :new
4305 def Room.method_missing(*args)
4306 super(*args)
4307 end
4308end
4309
4310def hide_me
4311 Script.current.hidden = !Script.current.hidden
4312end
4313
4314def no_kill_all
4315 script = Script.current
4316 script.no_kill_all = !script.no_kill_all
4317end
4318
4319def no_pause_all
4320 script = Script.current
4321 script.no_pause_all = !script.no_pause_all
4322end
4323
4324def toggle_upstream
4325 unless script = Script.current then echo 'toggle_upstream: cannot identify calling script.'; return nil; end
4326 script.want_upstream = !script.want_upstream
4327end
4328
4329def silence_me
4330 unless script = Script.current then echo 'silence_me: cannot identify calling script.'; return nil; end
4331 if script.safe? then echo("WARNING: 'safe' script attempted to silence itself. Ignoring the request.")
4332 sleep 1
4333 return true
4334 end
4335 script.silent = !script.silent
4336end
4337
4338def toggle_echo
4339 unless script = Script.current then respond('--- toggle_echo: Unable to identify calling script.'); return nil; end
4340 script.no_echo = !script.no_echo
4341end
4342
4343def echo_on
4344 unless script = Script.current then respond('--- echo_on: Unable to identify calling script.'); return nil; end
4345 script.no_echo = false
4346end
4347
4348def echo_off
4349 unless script = Script.current then respond('--- echo_off: Unable to identify calling script.'); return nil; end
4350 script.no_echo = true
4351end
4352
4353def upstream_get
4354 unless script = Script.current then echo 'upstream_get: cannot identify calling script.'; return nil; end
4355 unless script.want_upstream
4356 echo("This script wants to listen to the upstream, but it isn't set as receiving the upstream! This will cause a permanent hang, aborting (ask for the upstream with 'toggle_upstream' in the script)")
4357 sleep 0.3
4358 return false
4359 end
4360 script.upstream_gets
4361end
4362
4363def upstream_get?
4364 unless script = Script.current then echo 'upstream_get: cannot identify calling script.'; return nil; end
4365 unless script.want_upstream
4366 echo("This script wants to listen to the upstream, but it isn't set as receiving the upstream! This will cause a permanent hang, aborting (ask for the upstream with 'toggle_upstream' in the script)")
4367 return false
4368 end
4369 script.upstream_gets?
4370end
4371
4372def echo(*messages)
4373 respond if messages.empty?
4374 if script = Script.current
4375 unless script.no_echo
4376 messages.each { |message| respond("[#{script.name}: #{message.to_s.chomp}]") }
4377 end
4378 else
4379 messages.each { |message| respond("[(unknown script): #{message.to_s.chomp}]") }
4380 end
4381 nil
4382end
4383
4384def _echo(*messages)
4385 _respond if messages.empty?
4386 if script = Script.current
4387 unless script.no_echo
4388 messages.each { |message| _respond("[#{script.name}: #{message.to_s.chomp}]") }
4389 end
4390 else
4391 messages.each { |message| _respond("[(unknown script): #{message.to_s.chomp}]") }
4392 end
4393 nil
4394end
4395
4396def goto(label)
4397 Script.current.jump_label = label.to_s
4398 raise JUMP
4399end
4400
4401def pause_script(*names)
4402 names.flatten!
4403 if names.empty?
4404 Script.current.pause
4405 Script.current
4406 else
4407 names.each { |scr|
4408 fnd = Script.list.find { |nm| nm.name =~ /^#{scr}/i }
4409 fnd.pause unless (fnd.paused || fnd.nil?)
4410 }
4411 end
4412end
4413
4414def unpause_script(*names)
4415 names.flatten!
4416 names.each { |scr|
4417 fnd = Script.list.find { |nm| nm.name =~ /^#{scr}/i }
4418 fnd.unpause if (fnd.paused and not fnd.nil?)
4419 }
4420end
4421
4422def fix_injury_mode
4423 unless XMLData.injury_mode == 2
4424 Game._puts '_injury 2'
4425 150.times { sleep 0.05; break if XMLData.injury_mode == 2 }
4426 end
4427end
4428
4429def hide_script(*args)
4430 args.flatten!
4431 args.each { |name|
4432 if script = Script.running.find { |scr| scr.name == name }
4433 script.hidden = !script.hidden
4434 end
4435 }
4436end
4437
4438def parse_list(string)
4439 string.split_as_list
4440end
4441
4442def waitrt
4443 wait_until { (XMLData.roundtime_end.to_f - Time.now.to_f + XMLData.server_time_offset.to_f) > 0 }
4444 sleep((XMLData.roundtime_end.to_f - Time.now.to_f + XMLData.server_time_offset.to_f + "0.6".to_f).abs)
4445end
4446
4447def waitrt?
4448 rt = XMLData.roundtime_end.to_f - Time.now.to_f + XMLData.server_time_offset.to_f + "0.6".to_f
4449 if rt > 0
4450 sleep rt
4451 end
4452end
4453
4454def waitcastrt
4455 wait_until { (XMLData.cast_roundtime_end.to_f - Time.now.to_f + XMLData.server_time_offset.to_f) > 0 }
4456 sleep((XMLData.cast_roundtime_end.to_f - Time.now.to_f + XMLData.server_time_offset.to_f + "0.6".to_f).abs)
4457end
4458
4459def waitcastrt?
4460 rt = XMLData.cast_roundtime_end.to_f - Time.now.to_f + XMLData.server_time_offset.to_f + "0.6".to_f
4461 if rt > 0
4462 sleep rt
4463 end
4464end
4465
4466def checkrt
4467 [XMLData.roundtime_end.to_f - Time.now.to_f + XMLData.server_time_offset.to_f + "0.6".to_f, 0].max
4468end
4469
4470def checkcastrt
4471 [XMLData.cast_roundtime_end.to_f - Time.now.to_f + XMLData.server_time_offset.to_f + "0.6".to_f, 0].max
4472end
4473
4474def checkpoison
4475 XMLData.indicator['IconPOISONED'] == 'y'
4476end
4477
4478def checkdisease
4479 XMLData.indicator['IconDISEASED'] == 'y'
4480end
4481
4482def checksitting
4483 XMLData.indicator['IconSITTING'] == 'y'
4484end
4485
4486def checkkneeling
4487 XMLData.indicator['IconKNEELING'] == 'y'
4488end
4489
4490def checkstunned
4491 XMLData.indicator['IconSTUNNED'] == 'y'
4492end
4493
4494def checkbleeding
4495 XMLData.indicator['IconBLEEDING'] == 'y'
4496end
4497
4498def checkgrouped
4499 XMLData.indicator['IconJOINED'] == 'y'
4500end
4501
4502def checkdead
4503 XMLData.indicator['IconDEAD'] == 'y'
4504end
4505
4506def checkreallybleeding
4507 checkbleeding and !(Spell[9909].active? or Spell[9905].active?)
4508end
4509
4510def muckled?
4511 muckled = checkwebbed or checkdead or checkstunned
4512 if defined?(checksleeping)
4513 muckled = muckled or checksleeping
4514 end
4515 if defined?(checkbound)
4516 muckled = muckled or checkbound
4517 end
4518 return muckled
4519end
4520
4521def checkhidden
4522 XMLData.indicator['IconHIDDEN'] == 'y'
4523end
4524
4525def checkinvisible
4526 XMLData.indicator['IconINVISIBLE'] == 'y'
4527end
4528
4529def checkwebbed
4530 XMLData.indicator['IconWEBBED'] == 'y'
4531end
4532
4533def checkprone
4534 XMLData.indicator['IconPRONE'] == 'y'
4535end
4536
4537def checknotstanding
4538 XMLData.indicator['IconSTANDING'] == 'n'
4539end
4540
4541def checkstanding
4542 XMLData.indicator['IconSTANDING'] == 'y'
4543end
4544
4545def checkname(*strings)
4546 strings.flatten!
4547 if strings.empty?
4548 XMLData.name
4549 else
4550 XMLData.name =~ /^(?:#{strings.join('|')})/i
4551 end
4552end
4553
4554def checkloot
4555 GameObj.loot.collect { |item| item.noun }
4556end
4557
4558def i_stand_alone
4559 unless script = Script.current then echo 'i_stand_alone: cannot identify calling script.'; return nil; end
4560 script.want_downstream = !script.want_downstream
4561 return !script.want_downstream
4562end
4563
4564def debug(*args)
4565 if $LICH_DEBUG
4566 if block_given?
4567 yield(*args)
4568 else
4569 echo(*args)
4570 end
4571 end
4572end
4573
4574def timetest(*contestants)
4575 contestants.collect { |code| start = Time.now; 5000.times { code.call }; Time.now - start }
4576end
4577
4578def dec2bin(n)
4579 "0" + [n].pack("N").unpack("B32")[0].sub(/^0+(?=\d)/, '')
4580end
4581
4582def bin2dec(n)
4583 [("0"*32+n.to_s)[-32..-1]].pack("B32").unpack("N")[0]
4584end
4585
4586def idle?(time = 60)
4587 Time.now - $_IDLETIMESTAMP_ >= time
4588end
4589
4590def selectput(string, success, failure, timeout = nil)
4591 timeout = timeout.to_f if timeout and !timeout.kind_of?(Numeric)
4592 success = [ success ] if success.kind_of? String
4593 failure = [ failure ] if failure.kind_of? String
4594 if !string.kind_of?(String) or !success.kind_of?(Array) or !failure.kind_of?(Array) or timeout && !timeout.kind_of?(Numeric)
4595 raise ArgumentError, "usage is: selectput(game_command,success_array,failure_array[,timeout_in_secs])"
4596 end
4597 success.flatten!
4598 failure.flatten!
4599 regex = /#{(success + failure).join('|')}/i
4600 successre = /#{success.join('|')}/i
4601 failurere = /#{failure.join('|')}/i
4602 thr = Thread.current
4603
4604 timethr = Thread.new {
4605 timeout -= sleep("0.1".to_f) until timeout <= 0
4606 thr.raise(StandardError)
4607 } if timeout
4608
4609 begin
4610 loop {
4611 fput(string)
4612 response = waitforre(regex)
4613 if successre.match(response.to_s)
4614 timethr.kill if timethr.alive?
4615 break(response.string)
4616 end
4617 yield(response.string) if block_given?
4618 }
4619 rescue
4620 nil
4621 end
4622end
4623
4624def toggle_unique
4625 unless script = Script.current then echo 'toggle_unique: cannot identify calling script.'; return nil; end
4626 script.want_downstream = !script.want_downstream
4627end
4628
4629def die_with_me(*vals)
4630 unless script = Script.current then echo 'die_with_me: cannot identify calling script.'; return nil; end
4631 script.die_with.push vals
4632 script.die_with.flatten!
4633 echo("The following script(s) will now die when I do: #{script.die_with.join(', ')}") unless script.die_with.empty?
4634end
4635
4636def upstream_waitfor(*strings)
4637 strings.flatten!
4638 script = Script.current
4639 unless script.want_upstream then echo("This script wants to listen to the upstream, but it isn't set as receiving the upstream! This will cause a permanent hang, aborting (ask for the upstream with 'toggle_upstream' in the script)") ; return false end
4640 regexpstr = strings.join('|')
4641 while line = script.upstream_gets
4642 if line =~ /#{regexpstr}/i
4643 return line
4644 end
4645 end
4646end
4647
4648def send_to_script(*values)
4649 values.flatten!
4650 if script = Script.list.find { |val| val.name =~ /^#{values.first}/i }
4651 if script.want_downstream
4652 values[1..-1].each { |val| script.downstream_buffer.push(val) }
4653 else
4654 values[1..-1].each { |val| script.unique_buffer.push(val) }
4655 end
4656 echo("Sent to #{script.name} -- '#{values[1..-1].join(' ; ')}'")
4657 return true
4658 else
4659 echo("'#{values.first}' does not match any active scripts!")
4660 return false
4661 end
4662end
4663
4664def unique_send_to_script(*values)
4665 values.flatten!
4666 if script = Script.list.find { |val| val.name =~ /^#{values.first}/i }
4667 values[1..-1].each { |val| script.unique_buffer.push(val) }
4668 echo("sent to #{script}: #{values[1..-1].join(' ; ')}")
4669 return true
4670 else
4671 echo("'#{values.first}' does not match any active scripts!")
4672 return false
4673 end
4674end
4675
4676def unique_waitfor(*strings)
4677 unless script = Script.current then echo 'unique_waitfor: cannot identify calling script.'; return nil; end
4678 strings.flatten!
4679 regexp = /#{strings.join('|')}/
4680 while true
4681 str = script.unique_gets
4682 if str =~ regexp
4683 return str
4684 end
4685 end
4686end
4687
4688def unique_get
4689 unless script = Script.current then echo 'unique_get: cannot identify calling script.'; return nil; end
4690 script.unique_gets
4691end
4692
4693def unique_get?
4694 unless script = Script.current then echo 'unique_get: cannot identify calling script.'; return nil; end
4695 script.unique_gets?
4696end
4697
4698def multimove(*dirs)
4699 dirs.flatten.each { |dir| move(dir) }
4700end
4701
4702def n; 'north'; end
4703def ne; 'northeast'; end
4704def e; 'east'; end
4705def se; 'southeast'; end
4706def s; 'south'; end
4707def sw; 'southwest'; end
4708def w; 'west'; end
4709def nw; 'northwest'; end
4710def u; 'up'; end
4711def up; 'up'; end
4712def down; 'down'; end
4713def d; 'down'; end
4714def o; 'out'; end
4715def out; 'out'; end
4716
4717def move(dir='none', giveup_seconds=30, giveup_lines=30)
4718 #[LNet]-[Private]-Casis: "You begin to make your way up the steep headland pathway. Before traveling very far, however, you lose your footing on the loose stones. You struggle in vain to maintain your balance, then find yourself falling to the bay below!" (20:35:36)
4719 #[LNet]-[Private]-Casis: "You smack into the water with a splash and sink far below the surface." (20:35:50)
4720 # You approach the entrance and identify yourself to the guard. The guard checks over a long scroll of names and says, "I'm sorry, the Guild is open to invitees only. Please do return at a later date when we will be open to the public."
4721 if dir == 'none'
4722 echo 'move: no direction given'
4723 return false
4724 end
4725
4726 need_full_hands = false
4727 tried_open = false
4728 tried_fix_drag = false
4729 line_count = 0
4730 room_count = XMLData.room_count
4731 giveup_time = Time.now.to_i + giveup_seconds.to_i
4732 save_stream = Array.new
4733
4734 put_dir = proc {
4735 if XMLData.room_count > room_count
4736 fill_hands if need_full_hands
4737 Script.current.downstream_buffer.unshift(save_stream)
4738 Script.current.downstream_buffer.flatten!
4739 return true
4740 end
4741 waitrt?
4742 wait_while { stunned? }
4743 giveup_time = Time.now.to_i + giveup_seconds.to_i
4744 line_count = 0
4745 save_stream.push(clear)
4746 put dir
4747 }
4748
4749 put_dir.call
4750
4751 loop {
4752 line = get?
4753 unless line.nil?
4754 save_stream.push(line)
4755 line_count += 1
4756 end
4757 if line.nil?
4758 sleep 0.1
4759 elsif line =~ /^You can't do that while engaged!|^You are engaged to /
4760 # DragonRealms
4761 fput 'retreat'
4762 fput 'retreat'
4763 put_dir.call
4764 elsif line =~ /^You can't enter .+ and remain hidden or invisible\.|if he can't see you!$|^You can't enter .+ when you can't be seen\.$|^You can't do that without being seen\.$|^How do you intend to get .*? attention\? After all, no one can see you right now\.$/
4765 fput 'unhide'
4766 put_dir.call
4767 elsif (line =~ /^You (?:take a few steps toward|trudge up to|limp towards|march up to|sashay gracefully up to|skip happily towards|sneak up to|stumble toward) a rusty doorknob/) and (dir =~ /door/)
4768 which = [ 'first', 'second', 'third', 'fourth', 'fifth', 'sixth', 'seventh', 'eight', 'ninth', 'tenth', 'eleventh', 'twelfth' ]
4769 if dir =~ /\b#{which.join('|')}\b/
4770 dir.sub!(/\b(#{which.join('|')})\b/) { "#{which[which.index($1)+1]}" }
4771 else
4772 dir.sub!('door', 'second door')
4773 end
4774 put_dir.call
4775 elsif line =~ /^You can't go there|^You can't (?:go|swim) in that direction\.|^Where are you trying to go\?|^What were you referring to\?|^I could not find what you were referring to\.|^How do you plan to do that here\?|^You take a few steps towards|^You cannot do that\.|^You settle yourself on|^You shouldn't annoy|^You can't go to|^That's probably not a very good idea|^You can't do that|^Maybe you should look|^You are already|^You walk over to|^You step over to|The [\w\s]+ is too far away|You may not pass\.|become impassable\.|prevents you from entering\.|Please leave promptly\.|is too far above you to attempt that\.$|^Uh, yeah\. Right\.$|^Definitely NOT a good idea\.$|^Your attempt fails|^There doesn't seem to be any way to do that at the moment\.$/
4776 echo 'move: failed'
4777 fill_hands if need_full_hands
4778 Script.current.downstream_buffer.unshift(save_stream)
4779 Script.current.downstream_buffer.flatten!
4780 return false
4781 elsif line =~ /^An unseen force prevents you\.$|^Sorry, you aren't allowed to enter here\.|^That looks like someplace only performers should go\.|^As you climb, your grip gives way and you fall down|^The clerk stops you from entering the partition and says, "I'll need to see your ticket!"$|^The guard stops you, saying, "Only members of registered groups may enter the Meeting Hall\. If you'd like to visit, ask a group officer for a guest pass\."$|^An? .*? reaches over and grasps [A-Z][a-z]+ by the neck preventing (?:him|her) from being dragged anywhere\.$|^You'll have to wait, [A-Z][a-z]+ .* locker|^As you move toward the gate, you carelessly bump into the guard|^You attempt to enter the back of the shop, but a clerk stops you. "Your reputation precedes you!|you notice that thick beams are placed across the entry with a small sign that reads, "Abandoned\."$|appears to be closed, perhaps you should try again later\?$/
4782 echo 'move: failed'
4783 fill_hands if need_full_hands
4784 Script.current.downstream_buffer.unshift(save_stream)
4785 Script.current.downstream_buffer.flatten!
4786 # return nil instead of false to show the direction shouldn't be removed from the map database
4787 return nil
4788 elsif line =~ /^You grab [A-Z][a-z]+ and try to drag h(?:im|er), but s?he (?:is too heavy|doesn't budge)\.$|^Tentatively, you attempt to swim through the nook\. After only a few feet, you begin to sink! Your lungs burn from lack of air, and you begin to panic! You frantically paddle back to safety!$|^Guards(?:wo)?man [A-Z][a-z]+ stops you and says, "(?:Stop\.|Halt!) You need to make sure you check in|^You step into the root, but can see no way to climb the slippery tendrils inside\. After a moment, you step back out\.$|^As you start .*? back to safe ground\.$|^You stumble a bit as you try to enter the pool but feel that your persistence will pay off\.$|^A shimmering field of magical crimson and gold energy flows through the area\.$|^You attempt to navigate your way through the fog, but (?:quickly become entangled|get turned around)/
4789 sleep 1
4790 waitrt?
4791 put_dir.call
4792 elsif line =~ /^Climbing.*(?:plunge|fall)|^Tentatively, you attempt to climb.*(?:fall|slip)|^You start.*but quickly realize|^You.*drop back to the ground|^You leap .* fall unceremoniously to the ground in a heap\.$|^You search for a way to make the climb .*? but without success\.$|^You start to climb .* you fall to the ground|^You attempt to climb .* wrong approach|^You run towards .*? slowly retreat back, reassessing the situation\./
4793 sleep 1
4794 waitrt?
4795 fput 'stand' unless standing?
4796 waitrt?
4797 put_dir.call
4798 elsif line =~ /^You begin to climb up the silvery thread.* you tumble to the ground/
4799 sleep 0.5
4800 waitrt?
4801 fput 'stand' unless standing?
4802 waitrt?
4803 if checkleft or checkright
4804 need_full_hands = true
4805 empty_hands
4806 end
4807 put_dir.call
4808 elsif line == 'You are too injured to be doing any climbing!'
4809 if (resolve = Spell[9704]) and resolve.known?
4810 wait_until { resolve.affordable? }
4811 resove.cast
4812 put_dir.call
4813 else
4814 return nil
4815 end
4816 elsif line =~ /^You(?:'re going to| will) have to climb that\./
4817 dir.gsub!('go', 'climb')
4818 put_dir.call
4819 elsif line =~ /^You can't climb that\./
4820 dir.gsub!('climb', 'go')
4821 put_dir.call
4822 elsif line =~ /^You can't drag/
4823 if tried_fix_drag
4824 fill_hands if need_full_hands
4825 Script.current.downstream_buffer.unshift(save_stream)
4826 Script.current.downstream_buffer.flatten!
4827 return false
4828 elsif (dir =~ /^(?:go|climb) .+$/) and (drag_line = reget.reverse.find { |l| l =~ /^You grab .*?(?:'s body)? and drag|^You are now automatically attempting to drag .*? when/ })
4829 tried_fix_drag = true
4830 name = (/^You grab (.*?)('s body)? and drag/.match(drag_line).captures.first || /^You are now automatically attempting to drag (.*?) when/.match(drag_line).captures.first)
4831 target = /^(?:go|climb) (.+)$/.match(dir).captures.first
4832 fput "drag #{name}"
4833 dir = "drag #{name} #{target}"
4834 put_dir.call
4835 else
4836 tried_fix_drag = true
4837 dir.sub!(/^climb /, 'go ')
4838 put_dir.call
4839 end
4840 elsif line =~ /^Maybe if your hands were empty|^You figure freeing up both hands might help\.|^You can't .+ with your hands full\.$|^You'll need empty hands to climb that\.$|^It's a bit too difficult to swim holding|^You will need both hands free for such a difficult task\./
4841 need_full_hands = true
4842 empty_hands
4843 put_dir.call
4844 elsif line =~ /(?:appears|seems) to be closed\.$|^You cannot quite manage to squeeze between the stone doors\.$/
4845 if tried_open
4846 fill_hands if need_full_hands
4847 Script.current.downstream_buffer.unshift(save_stream)
4848 Script.current.downstream_buffer.flatten!
4849 return false
4850 else
4851 tried_open = true
4852 fput dir.sub(/go|climb/, 'open')
4853 put_dir.call
4854 end
4855 elsif line =~ /^(\.\.\.w|W)ait ([0-9]+) sec(onds)?\.$/
4856 if $2.to_i > 1
4857 sleep ($2.to_i - "0.2".to_f)
4858 else
4859 sleep 0.3
4860 end
4861 put_dir.call
4862 elsif line =~ /will have to stand up first|must be standing first|^You'll have to get up first|^But you're already sitting!|^Shouldn't you be standing first|^Try standing up|^Perhaps you should stand up|^Standing up might help|^You should really stand up first/
4863 fput 'stand'
4864 waitrt?
4865 put_dir.call
4866 elsif line =~ /^Sorry, you may only type ahead/
4867 sleep 1
4868 put_dir.call
4869 elsif line == 'You are still stunned.'
4870 wait_while { stunned? }
4871 put_dir.call
4872 elsif line =~ /you slip (?:on a patch of ice )?and flail uselessly as you land on your rear(?:\.|!)$|You wobble and stumble only for a moment before landing flat on your face!$/
4873 waitrt?
4874 fput 'stand' unless standing?
4875 waitrt?
4876 put_dir.call
4877 elsif line =~ /^You flick your hand (?:up|down)wards and focus your aura on your disk, but your disk only wobbles briefly\.$/
4878 put_dir.call
4879 elsif line =~ /^You dive into the fast-moving river, but the current catches you and whips you back to shore, wet and battered\.$|^Running through the swampy terrain, you notice a wet patch in the bog/
4880 waitrt?
4881 put_dir.call
4882 elsif line == "You don't seem to be able to move to do that."
4883 30.times {
4884 break if clear.include?('You regain control of your senses!')
4885 sleep 0.1
4886 }
4887 put_dir.call
4888 end
4889 if XMLData.room_count > room_count
4890 fill_hands if need_full_hands
4891 Script.current.downstream_buffer.unshift(save_stream)
4892 Script.current.downstream_buffer.flatten!
4893 return true
4894 end
4895 if Time.now.to_i >= giveup_time
4896 echo "move: no recognized response in #{giveup_seconds} seconds. giving up."
4897 fill_hands if need_full_hands
4898 Script.current.downstream_buffer.unshift(save_stream)
4899 Script.current.downstream_buffer.flatten!
4900 return nil
4901 end
4902 if line_count >= giveup_lines
4903 echo "move: no recognized response after #{line_count} lines. giving up."
4904 fill_hands if need_full_hands
4905 Script.current.downstream_buffer.unshift(save_stream)
4906 Script.current.downstream_buffer.flatten!
4907 return nil
4908 end
4909 }
4910end
4911
4912def watchhealth(value, theproc=nil, &block)
4913 value = value.to_i
4914 if block.nil?
4915 if !theproc.respond_to? :call
4916 respond "`watchhealth' was not given a block or a proc to execute!"
4917 return nil
4918 else
4919 block = theproc
4920 end
4921 end
4922 Thread.new {
4923 wait_while { health(value) }
4924 block.call
4925 }
4926end
4927
4928def wait_until(announce=nil)
4929 priosave = Thread.current.priority
4930 Thread.current.priority = 0
4931 unless announce.nil? or yield
4932 respond(announce)
4933 end
4934 until yield
4935 sleep 0.25
4936 end
4937 Thread.current.priority = priosave
4938end
4939
4940def wait_while(announce=nil)
4941 priosave = Thread.current.priority
4942 Thread.current.priority = 0
4943 unless announce.nil? or !yield
4944 respond(announce)
4945 end
4946 while yield
4947 sleep 0.25
4948 end
4949 Thread.current.priority = priosave
4950end
4951
4952def checkpaths(dir="none")
4953 if dir == "none"
4954 if XMLData.room_exits.empty?
4955 return false
4956 else
4957 return XMLData.room_exits.collect { |dir| dir = SHORTDIR[dir] }
4958 end
4959 else
4960 XMLData.room_exits.include?(dir) || XMLData.room_exits.include?(SHORTDIR[dir])
4961 end
4962end
4963
4964def reverse_direction(dir)
4965 if dir == "n" then 's'
4966 elsif dir == "ne" then 'sw'
4967 elsif dir == "e" then 'w'
4968 elsif dir == "se" then 'nw'
4969 elsif dir == "s" then 'n'
4970 elsif dir == "sw" then 'ne'
4971 elsif dir == "w" then 'e'
4972 elsif dir == "nw" then 'se'
4973 elsif dir == "up" then 'down'
4974 elsif dir == "down" then 'up'
4975 elsif dir == "out" then 'out'
4976 elsif dir == 'o' then out
4977 elsif dir == 'u' then 'down'
4978 elsif dir == 'd' then up
4979 elsif dir == n then s
4980 elsif dir == ne then sw
4981 elsif dir == e then w
4982 elsif dir == se then nw
4983 elsif dir == s then n
4984 elsif dir == sw then ne
4985 elsif dir == w then e
4986 elsif dir == nw then se
4987 elsif dir == u then d
4988 elsif dir == d then u
4989 else echo("Cannot recognize direction to properly reverse it!"); false
4990 end
4991end
4992
4993def walk(*boundaries, &block)
4994 boundaries.flatten!
4995 unless block.nil?
4996 until val = yield
4997 walk(*boundaries)
4998 end
4999 return val
5000 end
5001 if $last_dir and !boundaries.empty? and checkroomdescrip =~ /#{boundaries.join('|')}/i
5002 move($last_dir)
5003 $last_dir = reverse_direction($last_dir)
5004 return checknpcs
5005 end
5006 dirs = checkpaths
5007 dirs.delete($last_dir) unless dirs.length < 2
5008 this_time = rand(dirs.length)
5009 $last_dir = reverse_direction(dirs[this_time])
5010 move(dirs[this_time])
5011 checknpcs
5012end
5013
5014def run
5015 loop { break unless walk }
5016end
5017
5018def check_mind(string=nil)
5019 if string.nil?
5020 return XMLData.mind_text
5021 elsif (string.class == String) and (string.to_i == 0)
5022 if string =~ /#{XMLData.mind_text}/i
5023 return true
5024 else
5025 return false
5026 end
5027 elsif string.to_i.between?(0,100)
5028 return string.to_i <= XMLData.mind_value.to_i
5029 else
5030 echo("check_mind error! You must provide an integer ranging from 0-100, the common abbreviation of how full your head is, or provide no input to have check_mind return an abbreviation of how filled your head is.") ; sleep 1
5031 return false
5032 end
5033end
5034
5035def checkmind(string=nil)
5036 if string.nil?
5037 return XMLData.mind_text
5038 elsif string.class == String and string.to_i == 0
5039 if string =~ /#{XMLData.mind_text}/i
5040 return true
5041 else
5042 return false
5043 end
5044 elsif string.to_i.between?(1,8)
5045 mind_state = ['clear as a bell','fresh and clear','clear','muddled','becoming numbed','numbed','must rest','saturated']
5046 if mind_state.index(XMLData.mind_text)
5047 mind = mind_state.index(XMLData.mind_text) + 1
5048 return string.to_i <= mind
5049 else
5050 echo "Bad string in checkmind: mind_state"
5051 nil
5052 end
5053 else
5054 echo("Checkmind error! You must provide an integer ranging from 1-8 (7 is fried, 8 is 100% fried), the common abbreviation of how full your head is, or provide no input to have checkmind return an abbreviation of how filled your head is.") ; sleep 1
5055 return false
5056 end
5057end
5058
5059def percentmind(num=nil)
5060 if num.nil?
5061 XMLData.mind_value
5062 else
5063 XMLData.mind_value >= num.to_i
5064 end
5065end
5066
5067def checkfried
5068 if XMLData.mind_text =~ /must rest|saturated/
5069 true
5070 else
5071 false
5072 end
5073end
5074
5075def checksaturated
5076 if XMLData.mind_text =~ /saturated/
5077 true
5078 else
5079 false
5080 end
5081end
5082
5083def checkmana(num=nil)
5084 if num.nil?
5085 XMLData.mana
5086 else
5087 XMLData.mana >= num.to_i
5088 end
5089end
5090
5091def maxmana
5092 XMLData.max_mana
5093end
5094
5095def percentmana(num=nil)
5096 if XMLData.max_mana == 0
5097 percent = 100
5098 else
5099 percent = ((XMLData.mana.to_f / XMLData.max_mana.to_f) * 100).to_i
5100 end
5101 if num.nil?
5102 percent
5103 else
5104 percent >= num.to_i
5105 end
5106end
5107
5108def checkhealth(num=nil)
5109 if num.nil?
5110 XMLData.health
5111 else
5112 XMLData.health >= num.to_i
5113 end
5114end
5115
5116def maxhealth
5117 XMLData.max_health
5118end
5119
5120def percenthealth(num=nil)
5121 if num.nil?
5122 ((XMLData.health.to_f / XMLData.max_health.to_f) * 100).to_i
5123 else
5124 ((XMLData.health.to_f / XMLData.max_health.to_f) * 100).to_i >= num.to_i
5125 end
5126end
5127
5128def checkspirit(num=nil)
5129 if num.nil?
5130 XMLData.spirit
5131 else
5132 XMLData.spirit >= num.to_i
5133 end
5134end
5135
5136def maxspirit
5137 XMLData.max_spirit
5138end
5139
5140def percentspirit(num=nil)
5141 if num.nil?
5142 ((XMLData.spirit.to_f / XMLData.max_spirit.to_f) * 100).to_i
5143 else
5144 ((XMLData.spirit.to_f / XMLData.max_spirit.to_f) * 100).to_i >= num.to_i
5145 end
5146end
5147
5148def checkstamina(num=nil)
5149 if num.nil?
5150 XMLData.stamina
5151 else
5152 XMLData.stamina >= num.to_i
5153 end
5154end
5155
5156def maxstamina()
5157 XMLData.max_stamina
5158end
5159
5160def percentstamina(num=nil)
5161 if XMLData.max_stamina == 0
5162 percent = 100
5163 else
5164 percent = ((XMLData.stamina.to_f / XMLData.max_stamina.to_f) * 100).to_i
5165 end
5166 if num.nil?
5167 percent
5168 else
5169 percent >= num.to_i
5170 end
5171end
5172
5173def checkstance(num=nil)
5174 if num.nil?
5175 XMLData.stance_text
5176 elsif (num.class == String) and (num.to_i == 0)
5177 if num =~ /off/i
5178 XMLData.stance_value == 0
5179 elsif num =~ /adv/i
5180 XMLData.stance_value.between?(01, 20)
5181 elsif num =~ /for/i
5182 XMLData.stance_value.between?(21, 40)
5183 elsif num =~ /neu/i
5184 XMLData.stance_value.between?(41, 60)
5185 elsif num =~ /gua/i
5186 XMLData.stance_value.between?(61, 80)
5187 elsif num =~ /def/i
5188 XMLData.stance_value == 100
5189 else
5190 echo "checkstance: invalid argument (#{num}). Must be off/adv/for/neu/gua/def or 0-100"
5191 nil
5192 end
5193 elsif (num.class == Fixnum) or (num =~ /^[0-9]+$/ and num = num.to_i)
5194 XMLData.stance_value == num.to_i
5195 else
5196 echo "checkstance: invalid argument (#{num}). Must be off/adv/for/neu/gua/def or 0-100"
5197 nil
5198 end
5199end
5200
5201def percentstance(num=nil)
5202 if num.nil?
5203 XMLData.stance_value
5204 else
5205 XMLData.stance_value >= num.to_i
5206 end
5207end
5208
5209def checkencumbrance(string=nil)
5210 if string.nil?
5211 XMLData.encumbrance_text
5212 elsif (string.class == Fixnum) or (string =~ /^[0-9]+$/ and string = string.to_i)
5213 string <= XMLData.encumbrance_value
5214 else
5215 # fixme
5216 if string =~ /#{XMLData.encumbrance_text}/i
5217 true
5218 else
5219 false
5220 end
5221 end
5222end
5223
5224def percentencumbrance(num=nil)
5225 if num.nil?
5226 XMLData.encumbrance_value
5227 else
5228 num.to_i <= XMLData.encumbrance_value
5229 end
5230end
5231
5232def checkarea(*strings)
5233 strings.flatten!
5234 if strings.empty?
5235 XMLData.room_title.split(',').first.sub('[','')
5236 else
5237 XMLData.room_title.split(',').first =~ /#{strings.join('|')}/i
5238 end
5239end
5240
5241def checkroom(*strings)
5242 strings.flatten!
5243 if strings.empty?
5244 XMLData.room_title.chomp
5245 else
5246 XMLData.room_title =~ /#{strings.join('|')}/i
5247 end
5248end
5249
5250def outside?
5251 if XMLData.room_exits_string =~ /Obvious paths:/
5252 true
5253 else
5254 false
5255 end
5256end
5257
5258def checkfamarea(*strings)
5259 strings.flatten!
5260 if strings.empty? then return XMLData.familiar_room_title.split(',').first.sub('[','') end
5261 XMLData.familiar_room_title.split(',').first =~ /#{strings.join('|')}/i
5262end
5263
5264def checkfampaths(dir="none")
5265 if dir == "none"
5266 if XMLData.familiar_room_exits.empty?
5267 return false
5268 else
5269 return XMLData.familiar_room_exits
5270 end
5271 else
5272 XMLData.familiar_room_exits.include?(dir)
5273 end
5274end
5275
5276def checkfamroom(*strings)
5277 strings.flatten! ; if strings.empty? then return XMLData.familiar_room_title.chomp end
5278 XMLData.familiar_room_title =~ /#{strings.join('|')}/i
5279end
5280
5281def checkfamnpcs(*strings)
5282 parsed = Array.new
5283 XMLData.familiar_npcs.each { |val| parsed.push(val.split.last) }
5284 if strings.empty?
5285 if parsed.empty?
5286 return false
5287 else
5288 return parsed
5289 end
5290 else
5291 if mtch = strings.find { |lookfor| parsed.find { |critter| critter =~ /#{lookfor}/ } }
5292 return mtch
5293 else
5294 return false
5295 end
5296 end
5297end
5298
5299def checkfampcs(*strings)
5300 familiar_pcs = Array.new
5301 XMLData.familiar_pcs.to_s.gsub(/Lord |Lady |Great |High |Renowned |Grand |Apprentice |Novice |Journeyman /,'').split(',').each { |line| familiar_pcs.push(line.slice(/[A-Z][a-z]+/)) }
5302 if familiar_pcs.empty?
5303 return false
5304 elsif strings.empty?
5305 return familiar_pcs
5306 else
5307 regexpstr = strings.join('|\b')
5308 peeps = familiar_pcs.find_all { |val| val =~ /\b#{regexpstr}/i }
5309 if peeps.empty?
5310 return false
5311 else
5312 return peeps
5313 end
5314 end
5315end
5316
5317def checkpcs(*strings)
5318 pcs = GameObj.pcs.collect { |pc| pc.noun }
5319 if pcs.empty?
5320 if strings.empty? then return nil else return false end
5321 end
5322 strings.flatten!
5323 if strings.empty?
5324 pcs
5325 else
5326 regexpstr = strings.join(' ')
5327 pcs.find { |pc| regexpstr =~ /\b#{pc}/i }
5328 end
5329end
5330
5331def checknpcs(*strings)
5332 npcs = GameObj.npcs.collect { |npc| npc.noun }
5333 if npcs.empty?
5334 if strings.empty? then return nil else return false end
5335 end
5336 strings.flatten!
5337 if strings.empty?
5338 npcs
5339 else
5340 regexpstr = strings.join(' ')
5341 npcs.find { |npc| regexpstr =~ /\b#{npc}/i }
5342 end
5343end
5344
5345def count_npcs
5346 checknpcs.length
5347end
5348
5349def checkright(*hand)
5350 if GameObj.right_hand.nil? then return nil end
5351 hand.flatten!
5352 if GameObj.right_hand.name == "Empty" or GameObj.right_hand.name.empty?
5353 nil
5354 elsif hand.empty?
5355 GameObj.right_hand.noun
5356 else
5357 hand.find { |instance| GameObj.right_hand.name =~ /#{instance}/i }
5358 end
5359end
5360
5361def checkleft(*hand)
5362 if GameObj.left_hand.nil? then return nil end
5363 hand.flatten!
5364 if GameObj.left_hand.name == "Empty" or GameObj.left_hand.name.empty?
5365 nil
5366 elsif hand.empty?
5367 GameObj.left_hand.noun
5368 else
5369 hand.find { |instance| GameObj.left_hand.name =~ /#{instance}/i }
5370 end
5371end
5372
5373def checkroomdescrip(*val)
5374 val.flatten!
5375 if val.empty?
5376 return XMLData.room_description
5377 else
5378 return XMLData.room_description =~ /#{val.join('|')}/i
5379 end
5380end
5381
5382def checkfamroomdescrip(*val)
5383 val.flatten!
5384 if val.empty?
5385 return XMLData.familiar_room_description
5386 else
5387 return XMLData.familiar_room_description =~ /#{val.join('|')}/i
5388 end
5389end
5390
5391def checkspell(*spells)
5392 spells.flatten!
5393 return false if Spell.active.empty?
5394 spells.each { |spell| return false unless Spell[spell].active? }
5395 true
5396end
5397
5398def checkprep(spell=nil)
5399 if spell.nil?
5400 XMLData.prepared_spell
5401 elsif spell.class != String
5402 echo("Checkprep error, spell # not implemented! You must use the spell name")
5403 false
5404 else
5405 XMLData.prepared_spell =~ /^#{spell}/i
5406 end
5407end
5408
5409def setpriority(val=nil)
5410 if val.nil? then return Thread.current.priority end
5411 if val.to_i > 3
5412 echo("You're trying to set a script's priority as being higher than the send/recv threads (this is telling Lich to run the script before it even gets data to give the script, and is useless); the limit is 3")
5413 return Thread.current.priority
5414 else
5415 Thread.current.group.list.each { |thr| thr.priority = val.to_i }
5416 return Thread.current.priority
5417 end
5418end
5419
5420def checkbounty
5421 if XMLData.bounty_task
5422 return XMLData.bounty_task
5423 else
5424 return nil
5425 end
5426end
5427
5428def checksleeping
5429 return $infomon_sleeping
5430end
5431def sleeping?
5432 return $infomon_sleeping
5433end
5434def checkbound
5435 return $infomon_bound
5436end
5437def bound?
5438 return $infomon_bound
5439end
5440def checksilenced
5441 $infomon_silenced
5442end
5443def silenced?
5444 $infomon_silenced
5445end
5446def checkcalmed
5447 $infomon_calmed
5448end
5449def calmed?
5450 $infomon_calmed
5451end
5452def checkcutthroat
5453 $infomon_cutthroat
5454end
5455def cutthroat?
5456 $infomon_cutthroat
5457end
5458
5459def variable
5460 unless script = Script.current then echo 'variable: cannot identify calling script.'; return nil; end
5461 script.vars
5462end
5463
5464def pause(num=1)
5465 if num =~ /m/
5466 sleep((num.sub(/m/, '').to_f * 60))
5467 elsif num =~ /h/
5468 sleep((num.sub(/h/, '').to_f * 3600))
5469 elsif num =~ /d/
5470 sleep((num.sub(/d/, '').to_f * 86400))
5471 else
5472 sleep(num.to_f)
5473 end
5474end
5475
5476def cast(spell, target=nil, results_of_interest=nil)
5477 if spell.class == Spell
5478 spell.cast(target, results_of_interest)
5479 elsif ( (spell.class == Fixnum) or (spell.to_s =~ /^[0-9]+$/) ) and (find_spell = Spell[spell.to_i])
5480 find_spell.cast(target, results_of_interest)
5481 elsif (spell.class == String) and (find_spell = Spell[spell])
5482 find_spell.cast(target, results_of_interest)
5483 else
5484 echo "cast: invalid spell (#{spell})"
5485 false
5486 end
5487end
5488
5489def clear(opt=0)
5490 unless script = Script.current then respond('--- clear: Unable to identify calling script.'); return false; end
5491 to_return = script.downstream_buffer.dup
5492 script.downstream_buffer.clear
5493 to_return
5494end
5495
5496def match(label, string)
5497 strings = [ label, string ]
5498 strings.flatten!
5499 unless script = Script.current then echo("An unknown script thread tried to fetch a game line from the queue, but Lich can't process the call without knowing which script is calling! Aborting...") ; Thread.current.kill ; return false end
5500 if strings.empty? then echo("Error! 'match' was given no strings to look for!") ; sleep 1 ; return false end
5501 unless strings.length == 2
5502 while line_in = script.gets
5503 strings.each { |string|
5504 if line_in =~ /#{string}/ then return $~.to_s end
5505 }
5506 end
5507 else
5508 if script.respond_to?(:match_stack_add)
5509 script.match_stack_add(strings.first.to_s, strings.last)
5510 else
5511 script.match_stack_labels.push(strings[0].to_s)
5512 script.match_stack_strings.push(strings[1])
5513 end
5514 end
5515end
5516
5517def matchtimeout(secs, *strings)
5518 unless script = Script.current then echo("An unknown script thread tried to fetch a game line from the queue, but Lich can't process the call without knowing which script is calling! Aborting...") ; Thread.current.kill ; return false end
5519 unless (secs.class == Float || secs.class == Fixnum)
5520 echo('matchtimeout error! You appear to have given it a string, not a #! Syntax: matchtimeout(30, "You stand up")')
5521 return false
5522 end
5523 strings.flatten!
5524 if strings.empty?
5525 echo("matchtimeout without any strings to wait for!")
5526 sleep 1
5527 return false
5528 end
5529 regexpstr = strings.join('|')
5530 end_time = Time.now.to_f + secs
5531 loop {
5532 line = get?
5533 if line.nil?
5534 sleep 0.1
5535 elsif line =~ /#{regexpstr}/i
5536 return line
5537 end
5538 if (Time.now.to_f > end_time)
5539 return false
5540 end
5541 }
5542end
5543
5544def matchbefore(*strings)
5545 strings.flatten!
5546 unless script = Script.current then echo("An unknown script thread tried to fetch a game line from the queue, but Lich can't process the call without knowing which script is calling! Aborting...") ; Thread.current.kill ; return false end
5547 if strings.empty? then echo("matchbefore without any strings to wait for!") ; return false end
5548 regexpstr = strings.join('|')
5549 loop { if (line_in = script.gets) =~ /#{regexpstr}/ then return $`.to_s end }
5550end
5551
5552def matchafter(*strings)
5553 strings.flatten!
5554 unless script = Script.current then echo("An unknown script thread tried to fetch a game line from the queue, but Lich can't process the call without knowing which script is calling! Aborting...") ; Thread.current.kill ; return false end
5555 if strings.empty? then echo("matchafter without any strings to wait for!") ; return end
5556 regexpstr = strings.join('|')
5557 loop { if (line_in = script.gets) =~ /#{regexpstr}/ then return $'.to_s end }
5558end
5559
5560def matchboth(*strings)
5561 strings.flatten!
5562 unless script = Script.current then echo("An unknown script thread tried to fetch a game line from the queue, but Lich can't process the call without knowing which script is calling! Aborting...") ; Thread.current.kill ; return false end
5563 if strings.empty? then echo("matchboth without any strings to wait for!") ; return end
5564 regexpstr = strings.join('|')
5565 loop { if (line_in = script.gets) =~ /#{regexpstr}/ then break end }
5566 return [ $`.to_s, $'.to_s ]
5567end
5568
5569def matchwait(*strings)
5570 unless script = Script.current then respond('--- matchwait: Unable to identify calling script.'); return false; end
5571 strings.flatten!
5572 unless strings.empty?
5573 regexpstr = strings.collect { |str| str.kind_of?(Regexp) ? str.source : str }.join('|')
5574 regexobj = /#{regexpstr}/
5575 while line_in = script.gets
5576 return line_in if line_in =~ regexobj
5577 end
5578 else
5579 strings = script.match_stack_strings
5580 labels = script.match_stack_labels
5581 regexpstr = /#{strings.join('|')}/i
5582 while line_in = script.gets
5583 if mdata = regexpstr.match(line_in)
5584 jmp = labels[strings.index(mdata.to_s) || strings.index(strings.find { |str| line_in =~ /#{str}/i })]
5585 script.match_stack_clear
5586 goto jmp
5587 end
5588 end
5589 end
5590end
5591
5592def waitforre(regexp)
5593 unless script = Script.current then respond('--- waitforre: Unable to identify calling script.'); return false; end
5594 unless regexp.class == Regexp then echo("Script error! You have given 'waitforre' something to wait for, but it isn't a Regular Expression! Use 'waitfor' if you want to wait for a string."); sleep 1; return nil end
5595 regobj = regexp.match(script.gets) until regobj
5596end
5597
5598def waitfor(*strings)
5599 unless script = Script.current then respond('--- waitfor: Unable to identify calling script.'); return false; end
5600 strings.flatten!
5601 if (script.class == WizardScript) and (strings.length == 1) and (strings.first.strip == '>')
5602 return script.gets
5603 end
5604 if strings.empty?
5605 echo 'waitfor: no string to wait for'
5606 return false
5607 end
5608 regexpstr = strings.join('|')
5609 while true
5610 line_in = script.gets
5611 if (line_in =~ /#{regexpstr}/i) then return line_in end
5612 end
5613end
5614
5615def wait
5616 unless script = Script.current then respond('--- wait: unable to identify calling script.'); return false; end
5617 script.clear
5618 return script.gets
5619end
5620
5621def get
5622 Script.current.gets
5623end
5624
5625def get?
5626 Script.current.gets?
5627end
5628
5629def reget(*lines)
5630 unless script = Script.current then respond('--- reget: Unable to identify calling script.'); return false; end
5631 lines.flatten!
5632 if caller.find { |c| c =~ /regetall/ }
5633 history = ($_SERVERBUFFER_.history + $_SERVERBUFFER_).join("\n")
5634 else
5635 history = $_SERVERBUFFER_.dup.join("\n")
5636 end
5637 unless script.want_downstream_xml
5638 history.gsub!(/<pushStream id=["'](?:spellfront|inv|bounty|society)["'][^>]*\/>.*?<popStream[^>]*>/m, '')
5639 history.gsub!(/<stream id="Spells">.*?<\/stream>/m, '')
5640 history.gsub!(/<(compDef|inv|component|right|left|spell|prompt)[^>]*>.*?<\/\1>/m, '')
5641 history.gsub!(/<[^>]+>/, '')
5642 history.gsub!('>', '>')
5643 history.gsub!('<', '<')
5644 end
5645 history = history.split("\n").delete_if { |line| line.nil? or line.empty? or line =~ /^[\r\n\s\t]*$/ }
5646 if lines.first.kind_of?(Numeric) or lines.first.to_i.nonzero?
5647 history = history[-([lines.shift.to_i,history.length].min)..-1]
5648 end
5649 unless lines.empty? or lines.nil?
5650 regex = /#{lines.join('|')}/i
5651 history = history.find_all { |line| line =~ regex }
5652 end
5653 if history.empty?
5654 nil
5655 else
5656 history
5657 end
5658end
5659
5660def regetall(*lines)
5661 reget(*lines)
5662end
5663
5664def multifput(*cmds)
5665 cmds.flatten.compact.each { |cmd| fput(cmd) }
5666end
5667
5668def fput(message, *waitingfor)
5669 unless script = Script.current then respond('--- waitfor: Unable to identify calling script.'); return false; end
5670 waitingfor.flatten!
5671 clear
5672 put(message)
5673
5674 while string = get
5675 if string =~ /(?:\.\.\.wait |Wait )[0-9]+/
5676 hold_up = string.slice(/[0-9]+/).to_i
5677 sleep(hold_up) unless hold_up.nil?
5678 clear
5679 put(message)
5680 next
5681 elsif string =~ /^You.+struggle.+stand/
5682 clear
5683 fput 'stand'
5684 next
5685 elsif string =~ /stunned|can't do that while|cannot seem|^(?!You rummage).*can't seem|don't seem|Sorry, you may only type ahead/
5686 if dead?
5687 echo "You're dead...! You can't do that!"
5688 sleep 1
5689 script.downstream_buffer.unshift(string)
5690 return false
5691 elsif checkstunned
5692 while checkstunned
5693 sleep("0.25".to_f)
5694 end
5695 elsif checkwebbed
5696 while checkwebbed
5697 sleep("0.25".to_f)
5698 end
5699 elsif string =~ /Sorry, you may only type ahead/
5700 sleep 1
5701 else
5702 sleep 0.1
5703 script.downstream_buffer.unshift(string)
5704 return false
5705 end
5706 clear
5707 put(message)
5708 next
5709 else
5710 if waitingfor.empty?
5711 script.downstream_buffer.unshift(string)
5712 return string
5713 else
5714 if foundit = waitingfor.find { |val| string =~ /#{val}/i }
5715 script.downstream_buffer.unshift(string)
5716 return foundit
5717 end
5718 sleep 1
5719 clear
5720 put(message)
5721 next
5722 end
5723 end
5724 end
5725end
5726
5727def put(*messages)
5728 messages.each { |message| Game.puts(message) }
5729end
5730
5731def quiet_exit
5732 script = Script.current
5733 script.quiet = !(script.quiet)
5734end
5735
5736def matchfindexact(*strings)
5737 strings.flatten!
5738 unless script = Script.current then echo("An unknown script thread tried to fetch a game line from the queue, but Lich can't process the call without knowing which script is calling! Aborting...") ; Thread.current.kill ; return false end
5739 if strings.empty? then echo("error! 'matchfind' with no strings to look for!") ; sleep 1 ; return false end
5740 looking = Array.new
5741 strings.each { |str| looking.push(str.gsub('?', '(\b.+\b)')) }
5742 if looking.empty? then echo("matchfind without any strings to wait for!") ; return false end
5743 regexpstr = looking.join('|')
5744 while line_in = script.gets
5745 if gotit = line_in.slice(/#{regexpstr}/)
5746 matches = Array.new
5747 looking.each_with_index { |str,idx|
5748 if gotit =~ /#{str}/i
5749 strings[idx].count('?').times { |n| matches.push(eval("$#{n+1}")) }
5750 end
5751 }
5752 break
5753 end
5754 end
5755 if matches.length == 1
5756 return matches.first
5757 else
5758 return matches.compact
5759 end
5760end
5761
5762def matchfind(*strings)
5763 regex = /#{strings.flatten.join('|').gsub('?', '(.+)')}/i
5764 unless script = Script.current
5765 respond "Unknown script is asking to use matchfind! Cannot process request without identifying the calling script; killing this thread."
5766 Thread.current.kill
5767 end
5768 while true
5769 if reobj = regex.match(script.gets)
5770 ret = reobj.captures.compact
5771 if ret.length < 2
5772 return ret.first
5773 else
5774 return ret
5775 end
5776 end
5777 end
5778end
5779
5780def matchfindword(*strings)
5781 regex = /#{strings.flatten.join('|').gsub('?', '([\w\d]+)')}/i
5782 unless script = Script.current
5783 respond "Unknown script is asking to use matchfindword! Cannot process request without identifying the calling script; killing this thread."
5784 Thread.current.kill
5785 end
5786 while true
5787 if reobj = regex.match(script.gets)
5788 ret = reobj.captures.compact
5789 if ret.length < 2
5790 return ret.first
5791 else
5792 return ret
5793 end
5794 end
5795 end
5796end
5797
5798def send_scripts(*messages)
5799 messages.flatten!
5800 messages.each { |message|
5801 Script.new_downstream(message)
5802 }
5803 true
5804end