· 8 years ago · Nov 30, 2017, 03:38 PM
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.42'
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 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 Lich.db.execute("CREATE TABLE IF NOT EXISTS trusted_scripts (name TEXT NOT NULL);")
664 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));")
665 Lich.db.execute("CREATE TABLE IF NOT EXISTS enable_inventory_boxes (player_id INTEGER NOT NULL, PRIMARY KEY(player_id));")
666 rescue SQLite3::BusyException
667 sleep 0.1
668 retry
669 end
670 end
671 def Lich.class_variable_get(*a); nil; end
672 def Lich.class_eval(*a); nil; end
673 def Lich.module_eval(*a); nil; end
674 def Lich.log(msg)
675 $stderr.puts "#{Time.now.strftime("%Y-%m-%d %H:%M:%S")}: #{msg}"
676 end
677 def Lich.msgbox(args)
678 if defined?(Win32)
679 if args[:buttons] == :ok_cancel
680 buttons = Win32::MB_OKCANCEL
681 elsif args[:buttons] == :yes_no
682 buttons = Win32::MB_YESNO
683 else
684 buttons = Win32::MB_OK
685 end
686 if args[:icon] == :error
687 icon = Win32::MB_ICONERROR
688 elsif args[:icon] == :question
689 icon = Win32::MB_ICONQUESTION
690 elsif args[:icon] == :warning
691 icon = Win32::MB_ICONWARNING
692 else
693 icon = 0
694 end
695 args[:title] ||= "Lich v#{LICH_VERSION}"
696 r = Win32.MessageBox(:lpText => args[:message], :lpCaption => args[:title], :uType => (buttons|icon))
697 if r == Win32::IDIOK
698 return :ok
699 elsif r == Win32::IDICANCEL
700 return :cancel
701 elsif r == Win32::IDIYES
702 return :yes
703 elsif r == Win32::IDINO
704 return :no
705 else
706 return nil
707 end
708 elsif defined?(Gtk)
709 if args[:buttons] == :ok_cancel
710 buttons = Gtk::MessageDialog::BUTTONS_OK_CANCEL
711 elsif args[:buttons] == :yes_no
712 buttons = Gtk::MessageDialog::BUTTONS_YES_NO
713 else
714 buttons = Gtk::MessageDialog::BUTTONS_OK
715 end
716 if args[:icon] == :error
717 type = Gtk::MessageDialog::ERROR
718 elsif args[:icon] == :question
719 type = Gtk::MessageDialog::QUESTION
720 elsif args[:icon] == :warning
721 type = Gtk::MessageDialog::WARNING
722 else
723 type = Gtk::MessageDialog::INFO
724 end
725 dialog = Gtk::MessageDialog.new(nil, Gtk::Dialog::MODAL, type, buttons, args[:message])
726 args[:title] ||= "Lich v#{LICH_VERSION}"
727 dialog.title = args[:title]
728 response = nil
729 dialog.run { |r|
730 response = r
731 dialog.destroy
732 }
733 if response == Gtk::Dialog::RESPONSE_OK
734 return :ok
735 elsif response == Gtk::Dialog::RESPONSE_CANCEL
736 return :cancel
737 elsif response == Gtk::Dialog::RESPONSE_YES
738 return :yes
739 elsif response == Gtk::Dialog::RESPONSE_NO
740 return :no
741 else
742 return nil
743 end
744 elsif $stdout.isatty
745 $stdout.puts(args[:message])
746 return nil
747 end
748 end
749 def Lich.get_simu_launcher
750 if defined?(Win32)
751 begin
752 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]
753 launcher_cmd = Win32.RegQueryValueEx(:hKey => launcher_key, :lpValueName => 'RealCommand')[:lpData]
754 if launcher_cmd.nil? or launcher_cmd.empty?
755 launcher_cmd = Win32.RegQueryValueEx(:hKey => launcher_key)[:lpData]
756 end
757 return launcher_cmd
758 ensure
759 Win32.RegCloseKey(:hKey => launcher_key) rescue nil
760 end
761 elsif defined?(Wine)
762 launcher_cmd = Wine.registry_gets('HKEY_LOCAL_MACHINE\\Software\\Classes\\Simutronics.Autolaunch\\Shell\\Open\\command\\RealCommand')
763 unless launcher_cmd and not launcher_cmd.empty?
764 launcher_cmd = Wine.registry_gets('HKEY_LOCAL_MACHINE\\Software\\Classes\\Simutronics.Autolaunch\\Shell\\Open\\command\\')
765 end
766 return launcher_cmd
767 else
768 return nil
769 end
770 end
771 def Lich.link_to_sge
772 if defined?(Win32)
773 if Win32.admin?
774 begin
775 launcher_key = Win32.RegOpenKeyEx(:hKey => Win32::HKEY_LOCAL_MACHINE, :lpSubKey => 'Software\\Simutronics\\Launcher', :samDesired => (Win32::KEY_ALL_ACCESS|Win32::KEY_WOW64_32KEY))[:phkResult]
776 r = Win32.RegQueryValueEx(:hKey => launcher_key, :lpValueName => 'RealDirectory')
777 if (r[:return] == 0) and not r[:lpData].empty?
778 # already linked
779 return true
780 end
781 r = Win32.GetModuleFileName
782 unless r[:return] > 0
783 # fixme
784 return false
785 end
786 new_launcher_dir = "\"#{r[:lpFilename]}\" \"#{File.expand_path($PROGRAM_NAME)}\" "
787 r = Win32.RegQueryValueEx(:hKey => launcher_key, :lpValueName => 'Directory')
788 launcher_dir = r[:lpData]
789 r = Win32.RegSetValueEx(:hKey => launcher_key, :lpValueName => 'RealDirectory', :dwType => Win32::REG_SZ, :lpData => launcher_dir)
790 return false unless (r == 0)
791 r = Win32.RegSetValueEx(:hKey => launcher_key, :lpValueName => 'Directory', :dwType => Win32::REG_SZ, :lpData => new_launcher_dir)
792 return (r == 0)
793 ensure
794 Win32.RegCloseKey(:hKey => launcher_key) rescue nil
795 end
796 else
797 begin
798 r = Win32.GetModuleFileName
799 file = ((r[:return] > 0) ? r[:lpFilename] : 'rubyw.exe')
800 params = "#{$PROGRAM_NAME.split(/\/|\\/).last} --link-to-sge"
801 r = Win32.ShellExecuteEx(:lpVerb => 'runas', :lpFile => file, :lpDirectory => LICH_DIR.tr("/", "\\"), :lpParameters => params, :fMask => Win32::SEE_MASK_NOCLOSEPROCESS)
802 if r[:return] > 0
803 process_id = r[:hProcess]
804 sleep 0.2 while Win32.GetExitCodeProcess(:hProcess => process_id)[:lpExitCode] == Win32::STILL_ACTIVE
805 sleep 3
806 else
807 Win32.ShellExecute(:lpOperation => 'runas', :lpFile => file, :lpDirectory => LICH_DIR.tr("/", "\\"), :lpParameters => params)
808 sleep 6
809 end
810 rescue
811 Lich.msgbox(:message => $!)
812 end
813 end
814 elsif defined?(Wine)
815 launch_dir = Wine.registry_gets('HKEY_LOCAL_MACHINE\\Software\\Simutronics\\Launcher\\Directory')
816 return false unless launch_dir
817 lich_launch_dir = "#{File.expand_path($PROGRAM_NAME)} --wine=#{Wine::BIN} --wine-prefix=#{Wine::PREFIX} "
818 result = true
819 if launch_dir
820 if launch_dir =~ /lich/i
821 $stdout.puts "--- warning: Lich appears to already be installed to the registry"
822 Lich.log "warning: Lich appears to already be installed to the registry"
823 Lich.log 'info: launch_dir: ' + launch_dir
824 else
825 result = result && Wine.registry_puts('HKEY_LOCAL_MACHINE\\Software\\Simutronics\\Launcher\\RealDirectory', launch_dir)
826 result = result && Wine.registry_puts('HKEY_LOCAL_MACHINE\\Software\\Simutronics\\Launcher\\Directory', lich_launch_dir)
827 end
828 end
829 return result
830 else
831 return false
832 end
833 end
834 def Lich.unlink_from_sge
835 if defined?(Win32)
836 if Win32.admin?
837 begin
838 launcher_key = Win32.RegOpenKeyEx(:hKey => Win32::HKEY_LOCAL_MACHINE, :lpSubKey => 'Software\\Simutronics\\Launcher', :samDesired => (Win32::KEY_ALL_ACCESS|Win32::KEY_WOW64_32KEY))[:phkResult]
839 real_directory = Win32.RegQueryValueEx(:hKey => launcher_key, :lpValueName => 'RealDirectory')[:lpData]
840 if real_directory.nil? or real_directory.empty?
841 # not linked
842 return true
843 end
844 r = Win32.RegSetValueEx(:hKey => launcher_key, :lpValueName => 'Directory', :dwType => Win32::REG_SZ, :lpData => real_directory)
845 return false unless (r == 0)
846 r = Win32.RegDeleteValue(:hKey => launcher_key, :lpValueName => 'RealDirectory')
847 return (r == 0)
848 ensure
849 Win32.RegCloseKey(:hKey => launcher_key) rescue nil
850 end
851 else
852 begin
853 r = Win32.GetModuleFileName
854 file = ((r[:return] > 0) ? r[:lpFilename] : 'rubyw.exe')
855 params = "#{$PROGRAM_NAME.split(/\/|\\/).last} --unlink-from-sge"
856 r = Win32.ShellExecuteEx(:lpVerb => 'runas', :lpFile => file, :lpDirectory => LICH_DIR.tr("/", "\\"), :lpParameters => params, :fMask => Win32::SEE_MASK_NOCLOSEPROCESS)
857 if r[:return] > 0
858 process_id = r[:hProcess]
859 sleep 0.2 while Win32.GetExitCodeProcess(:hProcess => process_id)[:lpExitCode] == Win32::STILL_ACTIVE
860 sleep 3
861 else
862 Win32.ShellExecute(:lpOperation => 'runas', :lpFile => file, :lpDirectory => LICH_DIR.tr("/", "\\"), :lpParameters => params)
863 sleep 6
864 end
865 rescue
866 Lich.msgbox(:message => $!)
867 end
868 end
869 elsif defined?(Wine)
870 real_launch_dir = Wine.registry_gets('HKEY_LOCAL_MACHINE\\Software\\Simutronics\\Launcher\\RealDirectory')
871 result = true
872 if real_launch_dir and not real_launch_dir.empty?
873 result = result && Wine.registry_puts('HKEY_LOCAL_MACHINE\\Software\\Simutronics\\Launcher\\Directory', real_launch_dir)
874 result = result && Wine.registry_puts('HKEY_LOCAL_MACHINE\\Software\\Simutronics\\Launcher\\RealDirectory', '')
875 end
876 return result
877 else
878 return false
879 end
880 end
881 def Lich.link_to_sal
882 if defined?(Win32)
883 if Win32.admin?
884 begin
885 # fixme: 64 bit browsers?
886 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]
887 r = Win32.RegQueryValueEx(:hKey => launcher_key, :lpValueName => 'RealCommand')
888 if (r[:return] == 0) and not r[:lpData].empty?
889 # already linked
890 return true
891 end
892 r = Win32.GetModuleFileName
893 unless r[:return] > 0
894 # fixme
895 return false
896 end
897 new_launcher_cmd = "\"#{r[:lpFilename]}\" \"#{File.expand_path($PROGRAM_NAME)}\" %1"
898 r = Win32.RegQueryValueEx(:hKey => launcher_key)
899 launcher_cmd = r[:lpData]
900 r = Win32.RegSetValueEx(:hKey => launcher_key, :lpValueName => 'RealCommand', :dwType => Win32::REG_SZ, :lpData => launcher_cmd)
901 return false unless (r == 0)
902 r = Win32.RegSetValueEx(:hKey => launcher_key, :dwType => Win32::REG_SZ, :lpData => new_launcher_cmd)
903 return (r == 0)
904 ensure
905 Win32.RegCloseKey(:hKey => launcher_key) rescue nil
906 end
907 else
908 begin
909 r = Win32.GetModuleFileName
910 file = ((r[:return] > 0) ? r[:lpFilename] : 'rubyw.exe')
911 params = "#{$PROGRAM_NAME.split(/\/|\\/).last} --link-to-sal"
912 r = Win32.ShellExecuteEx(:lpVerb => 'runas', :lpFile => file, :lpDirectory => LICH_DIR.tr("/", "\\"), :lpParameters => params, :fMask => Win32::SEE_MASK_NOCLOSEPROCESS)
913 if r[:return] > 0
914 process_id = r[:hProcess]
915 sleep 0.2 while Win32.GetExitCodeProcess(:hProcess => process_id)[:lpExitCode] == Win32::STILL_ACTIVE
916 sleep 3
917 else
918 Win32.ShellExecute(:lpOperation => 'runas', :lpFile => file, :lpDirectory => LICH_DIR.tr("/", "\\"), :lpParameters => params)
919 sleep 6
920 end
921 rescue
922 Lich.msgbox(:message => $!)
923 end
924 end
925 elsif defined?(Wine)
926 launch_cmd = Wine.registry_gets('HKEY_LOCAL_MACHINE\\Software\\Classes\\Simutronics.Autolaunch\\Shell\\Open\\command\\')
927 return false unless launch_cmd
928 new_launch_cmd = "#{File.expand_path($PROGRAM_NAME)} --wine=#{Wine::BIN} --wine-prefix=#{Wine::PREFIX} %1"
929 result = true
930 if launch_cmd
931 if launch_cmd =~ /lich/i
932 $stdout.puts "--- warning: Lich appears to already be installed to the registry"
933 Lich.log "warning: Lich appears to already be installed to the registry"
934 Lich.log 'info: launch_cmd: ' + launch_cmd
935 else
936 result = result && Wine.registry_puts('HKEY_LOCAL_MACHINE\\Software\\Classes\\Simutronics.Autolaunch\\Shell\\Open\\command\\RealCommand', launch_cmd)
937 result = result && Wine.registry_puts('HKEY_LOCAL_MACHINE\\Software\\Classes\\Simutronics.Autolaunch\\Shell\\Open\\command\\', new_launch_cmd)
938 end
939 end
940 return result
941 else
942 return false
943 end
944 end
945 def Lich.unlink_from_sal
946 if defined?(Win32)
947 if Win32.admin?
948 begin
949 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]
950 real_directory = Win32.RegQueryValueEx(:hKey => launcher_key, :lpValueName => 'RealCommand')[:lpData]
951 if real_directory.nil? or real_directory.empty?
952 # not linked
953 return true
954 end
955 r = Win32.RegSetValueEx(:hKey => launcher_key, :dwType => Win32::REG_SZ, :lpData => real_directory)
956 return false unless (r == 0)
957 r = Win32.RegDeleteValue(:hKey => launcher_key, :lpValueName => 'RealCommand')
958 return (r == 0)
959 ensure
960 Win32.RegCloseKey(:hKey => launcher_key) rescue nil
961 end
962 else
963 begin
964 r = Win32.GetModuleFileName
965 file = ((r[:return] > 0) ? r[:lpFilename] : 'rubyw.exe')
966 params = "#{$PROGRAM_NAME.split(/\/|\\/).last} --unlink-from-sal"
967 r = Win32.ShellExecuteEx(:lpVerb => 'runas', :lpFile => file, :lpDirectory => LICH_DIR.tr("/", "\\"), :lpParameters => params, :fMask => Win32::SEE_MASK_NOCLOSEPROCESS)
968 if r[:return] > 0
969 process_id = r[:hProcess]
970 sleep 0.2 while Win32.GetExitCodeProcess(:hProcess => process_id)[:lpExitCode] == Win32::STILL_ACTIVE
971 sleep 3
972 else
973 Win32.ShellExecute(:lpOperation => 'runas', :lpFile => file, :lpDirectory => LICH_DIR.tr("/", "\\"), :lpParameters => params)
974 sleep 6
975 end
976 rescue
977 Lich.msgbox(:message => $!)
978 end
979 end
980 elsif defined?(Wine)
981 real_launch_cmd = Wine.registry_gets('HKEY_LOCAL_MACHINE\\Software\\Classes\\Simutronics.Autolaunch\\Shell\\Open\\command\\RealCommand')
982 result = true
983 if real_launch_cmd and not real_launch_cmd.empty?
984 result = result && Wine.registry_puts('HKEY_LOCAL_MACHINE\\Software\\Classes\\Simutronics.Autolaunch\\Shell\\Open\\command\\', real_launch_cmd)
985 result = result && Wine.registry_puts('HKEY_LOCAL_MACHINE\\Software\\Classes\\Simutronics.Autolaunch\\Shell\\Open\\command\\RealCommand', '')
986 end
987 return result
988 else
989 return false
990 end
991 end
992 def Lich.hosts_file
993 Lich.find_hosts_file if @@hosts_file.nil?
994 return @@hosts_file
995 end
996 def Lich.find_hosts_file
997 if defined?(Win32)
998 begin
999 key = Win32.RegOpenKeyEx(:hKey => Win32::HKEY_LOCAL_MACHINE, :lpSubKey => 'System\\CurrentControlSet\\Services\\Tcpip\\Parameters', :samDesired => Win32::KEY_READ)[:phkResult]
1000 hosts_path = Win32.RegQueryValueEx(:hKey => key, :lpValueName => 'DataBasePath')[:lpData]
1001 ensure
1002 Win32.RegCloseKey(:hKey => key) rescue nil
1003 end
1004 if hosts_path
1005 windir = (ENV['windir'] || ENV['SYSTEMROOT'] || 'c:\windows')
1006 hosts_path.gsub('%SystemRoot%', windir)
1007 hosts_file = "#{hosts_path}\\hosts"
1008 if File.exists?(hosts_file)
1009 return (@@hosts_file = hosts_file)
1010 end
1011 end
1012 if (windir = (ENV['windir'] || ENV['SYSTEMROOT'])) and File.exists?("#{windir}\\system32\\drivers\\etc\\hosts")
1013 return (@@hosts_file = "#{windir}\\system32\\drivers\\etc\\hosts")
1014 end
1015 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']
1016 for windir in ['winnt','windows']
1017 if File.exists?("#{drive}:\\#{windir}\\system32\\drivers\\etc\\hosts")
1018 return (@@hosts_file = "#{drive}:\\#{windir}\\system32\\drivers\\etc\\hosts")
1019 end
1020 end
1021 end
1022 else # Linux/Mac
1023 if File.exists?('/etc/hosts')
1024 return (@@hosts_file = '/etc/hosts')
1025 elsif File.exists?('/private/etc/hosts')
1026 return (@@hosts_file = '/private/etc/hosts')
1027 end
1028 end
1029 return (@@hosts_file = false)
1030 end
1031 def Lich.modify_hosts(game_host)
1032 if Lich.hosts_file and File.exists?(Lich.hosts_file)
1033 at_exit { Lich.restore_hosts }
1034 Lich.restore_hosts
1035 if File.exists?("#{Lich.hosts_file}.bak")
1036 return false
1037 end
1038 begin
1039 # copy hosts to hosts.bak
1040 File.open("#{Lich.hosts_file}.bak", 'w') { |hb| File.open(Lich.hosts_file) { |h| hb.write(h.read) } }
1041 rescue
1042 File.unlink("#{Lich.hosts_file}.bak") if File.exists?("#{Lich.hosts_file}.bak")
1043 return false
1044 end
1045 File.open(Lich.hosts_file, 'a') { |f| f.write "\r\n127.0.0.1\t\t#{game_host}" }
1046 return true
1047 else
1048 return false
1049 end
1050 end
1051 def Lich.restore_hosts
1052 if Lich.hosts_file and File.exists?(Lich.hosts_file)
1053 begin
1054 # fixme: use rename instead? test rename on windows
1055 if File.exists?("#{Lich.hosts_file}.bak")
1056 File.open("#{Lich.hosts_file}.bak") { |infile|
1057 File.open(Lich.hosts_file, 'w') { |outfile|
1058 outfile.write(infile.read)
1059 }
1060 }
1061 File.unlink "#{Lich.hosts_file}.bak"
1062 end
1063 rescue
1064 $stdout.puts "--- error: restore_hosts: #{$!}"
1065 Lich.log "error: restore_hosts: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
1066 exit(1)
1067 end
1068 end
1069 end
1070 def Lich.inventory_boxes(player_id)
1071 begin
1072 v = Lich.db.get_first_value('SELECT player_id FROM enable_inventory_boxes WHERE player_id=?;', player_id.to_i)
1073 rescue SQLite3::BusyException
1074 sleep 0.1
1075 retry
1076 end
1077 if v
1078 true
1079 else
1080 false
1081 end
1082 end
1083 def Lich.set_inventory_boxes(player_id, enabled)
1084 if enabled
1085 begin
1086 Lich.db.execute('INSERT OR REPLACE INTO enable_inventory_boxes values(?);', player_id.to_i)
1087 rescue SQLite3::BusyException
1088 sleep 0.1
1089 retry
1090 end
1091 else
1092 begin
1093 Lich.db.execute('DELETE FROM enable_inventory_boxes where player_id=?;', player_id.to_i)
1094 rescue SQLite3::BusyException
1095 sleep 0.1
1096 retry
1097 end
1098 end
1099 nil
1100 end
1101 def Lich.win32_launch_method
1102 begin
1103 val = Lich.db.get_first_value("SELECT value FROM lich_settings WHERE name='win32_launch_method';")
1104 rescue SQLite3::BusyException
1105 sleep 0.1
1106 retry
1107 end
1108 val
1109 end
1110 def Lich.win32_launch_method=(val)
1111 begin
1112 Lich.db.execute("INSERT OR REPLACE INTO lich_settings(name,value) values('win32_launch_method',?);", val.to_s.encode('UTF-8'))
1113 rescue SQLite3::BusyException
1114 sleep 0.1
1115 retry
1116 end
1117 nil
1118 end
1119 def Lich.fix_game_host_port(gamehost,gameport)
1120 if (gamehost == 'gs-plat.simutronics.net') and (gameport.to_i == 10121)
1121 gamehost = 'storm.gs4.game.play.net'
1122 gameport = 10124
1123 elsif (gamehost == 'gs3.simutronics.net') and (gameport.to_i == 4900)
1124 gamehost = 'storm.gs4.game.play.net'
1125 gameport = 10024
1126 elsif (gamehost == 'gs4.simutronics.net') and (gameport.to_i == 10321)
1127 game_host = 'storm.gs4.game.play.net'
1128 game_port = 10324
1129 elsif (gamehost == 'prime.dr.game.play.net') and (gameport.to_i == 4901)
1130 gamehost = 'dr.simutronics.net'
1131 gameport = 11024
1132 end
1133 [ gamehost, gameport ]
1134 end
1135 def Lich.break_game_host_port(gamehost,gameport)
1136 if (gamehost == 'storm.gs4.game.play.net') and (gameport.to_i == 10324)
1137 gamehost = 'gs4.simutronics.net'
1138 gameport = 10321
1139 elsif (gamehost == 'storm.gs4.game.play.net') and (gameport.to_i == 10124)
1140 gamehost = 'gs-plat.simutronics.net'
1141 gameport = 10121
1142 elsif (gamehost == 'storm.gs4.game.play.net') and (gameport.to_i == 10024)
1143 gamehost = 'gs3.simutronics.net'
1144 gameport = 4900
1145 elsif (gamehost == 'storm.gs4.game.play.net') and (gameport.to_i == 10324)
1146 game_host = 'gs4.simutronics.net'
1147 game_port = 10321
1148 elsif (gamehost == 'dr.simutronics.net') and (gameport.to_i == 11024)
1149 gamehost = 'prime.dr.game.play.net'
1150 gameport = 4901
1151 end
1152 [ gamehost, gameport ]
1153 end
1154end
1155
1156class NilClass
1157 def dup
1158 nil
1159 end
1160 def method_missing(*args)
1161 nil
1162 end
1163 def split(*val)
1164 Array.new
1165 end
1166 def to_s
1167 ""
1168 end
1169 def strip
1170 ""
1171 end
1172 def +(val)
1173 val
1174 end
1175 def closed?
1176 true
1177 end
1178end
1179
1180class Numeric
1181 def as_time
1182 sprintf("%d:%02d:%02d", (self / 60).truncate, self.truncate % 60, ((self % 1) * 60).truncate)
1183 end
1184 def with_commas
1185 self.to_s.reverse.scan(/(?:\d*\.)?\d{1,3}-?/).join(',').reverse
1186 end
1187end
1188
1189class TrueClass
1190 def method_missing(*usersave)
1191 true
1192 end
1193end
1194
1195class FalseClass
1196 def method_missing(*usersave)
1197 nil
1198 end
1199end
1200
1201class String
1202 @@elevated_untaint = proc { |what| what.orig_untaint }
1203 alias :orig_untaint :untaint
1204 def untaint
1205 @@elevated_untaint.call(self)
1206 end
1207 def to_s
1208 self.dup
1209 end
1210 def stream
1211 @stream
1212 end
1213 def stream=(val)
1214 @stream ||= val
1215 end
1216end
1217
1218class StringProc
1219 def initialize(string)
1220 @string = string
1221 @string.untaint
1222 end
1223 def kind_of?(type)
1224 Proc.new {}.kind_of? type
1225 end
1226 def class
1227 Proc
1228 end
1229 def call(*a)
1230 if $SAFE < 3
1231 proc { $SAFE = 3; eval(@string) }.call
1232 else
1233 eval(@string)
1234 end
1235 end
1236 def _dump(d=nil)
1237 @string
1238 end
1239 def inspect
1240 "StringProc.new(#{@string.inspect})"
1241 end
1242end
1243
1244class SynchronizedSocket
1245 def initialize(o)
1246 @delegate = o
1247 @mutex = Mutex.new
1248 self
1249 end
1250 def puts(*args, &block)
1251 @mutex.synchronize {
1252 @delegate.puts *args, &block
1253 }
1254 end
1255 def write(*args, &block)
1256 @mutex.synchronize {
1257 @delegate.write *args, &block
1258 }
1259 end
1260 def method_missing(method, *args, &block)
1261 @delegate.__send__ method, *args, &block
1262 end
1263end
1264
1265class LimitedArray < Array
1266 attr_accessor :max_size
1267 def initialize(size=0, obj=nil)
1268 @max_size = 200
1269 super
1270 end
1271 def push(line)
1272 self.shift while self.length >= @max_size
1273 super
1274 end
1275 def shove(line)
1276 push(line)
1277 end
1278 def history
1279 Array.new
1280 end
1281end
1282
1283class XMLParser
1284 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_id, :room_window_disabled
1285 attr_accessor :send_fake_tags
1286
1287 @@warned_deprecated_spellfront = 0
1288
1289 include REXML::StreamListener
1290
1291 def initialize
1292 @buffer = String.new
1293 @unescape = { 'lt' => '<', 'gt' => '>', 'quot' => '"', 'apos' => "'", 'amp' => '&' }
1294 @bold = false
1295 @active_tags = Array.new
1296 @active_ids = Array.new
1297 @last_tag = String.new
1298 @last_id = String.new
1299 @current_stream = String.new
1300 @current_style = String.new
1301 @stow_container_id = nil
1302 @obj_location = nil
1303 @obj_exist = nil
1304 @obj_noun = nil
1305 @obj_before_name = nil
1306 @obj_name = nil
1307 @obj_after_name = nil
1308 @pc = nil
1309 @last_obj = nil
1310 @in_stream = false
1311 @player_status = nil
1312 @fam_mode = String.new
1313 @room_window_disabled = false
1314 @wound_gsl = String.new
1315 @scar_gsl = String.new
1316 @send_fake_tags = false
1317 @prompt = String.new
1318 @nerve_tracker_num = 0
1319 @nerve_tracker_active = 'no'
1320 @server_time = Time.now.to_i
1321 @server_time_offset = 0
1322 @roundtime_end = 0
1323 @cast_roundtime_end = 0
1324 @last_pulse = Time.now.to_i
1325 @level = 0
1326 @next_level_value = 0
1327 @next_level_text = String.new
1328
1329 @room_count = 0
1330 @room_title = String.new
1331 @room_description = String.new
1332 @room_exits = Array.new
1333 @room_exits_string = String.new
1334
1335 @familiar_room_title = String.new
1336 @familiar_room_description = String.new
1337 @familiar_room_exits = Array.new
1338
1339 @bounty_task = String.new
1340 @society_task = String.new
1341
1342 @name = String.new
1343 @game = String.new
1344 @player_id = String.new
1345 @mana = 0
1346 @max_mana = 0
1347 @health = 0
1348 @max_health = 0
1349 @spirit = 0
1350 @max_spirit = 0
1351 @last_spirit = nil
1352 @stamina = 0
1353 @max_stamina = 0
1354 @stance_text = String.new
1355 @stance_value = 0
1356 @mind_text = String.new
1357 @mind_value = 0
1358 @prepared_spell = 'None'
1359 @encumbrance_text = String.new
1360 @encumbrance_full_text = String.new
1361 @encumbrance_value = 0
1362 @indicator = Hash.new
1363 @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}}
1364 @injury_mode = 0
1365
1366 @active_spells = Hash.new
1367
1368 end
1369
1370 def reset
1371 @active_tags = Array.new
1372 @active_ids = Array.new
1373 @current_stream = String.new
1374 @current_style = String.new
1375 end
1376
1377 def make_wound_gsl
1378 @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'])
1379 end
1380
1381 def make_scar_gsl
1382 @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'])
1383 end
1384
1385 def parse(line)
1386 @buffer.concat(line)
1387 loop {
1388 if str = @buffer.slice!(/^[^<]+/)
1389 text(str.gsub(/&(lt|gt|quot|apos|amp)/) { @unescape[$1] })
1390 elsif str = @buffer.slice!(/^<\/[^<]+>/)
1391 element = /^<\/([^\s>\/]+)/.match(str).captures.first
1392 tag_end(element)
1393 elsif str = @buffer.slice!(/^<[^<]+>/)
1394 element = /^<([^\s>\/]+)/.match(str).captures.first
1395 attributes = Hash.new
1396 str.scan(/([A-z][A-z0-9_\-]*)=(["'])(.*?)\2/).each { |attr| attributes[attr[0]] = attr[2] }
1397 tag_start(element, attributes)
1398 tag_end(element) if str =~ /\/>$/
1399 else
1400 break
1401 end
1402 }
1403 end
1404
1405 def tag_start(name, attributes)
1406 begin
1407 @active_tags.push(name)
1408 @active_ids.push(attributes['id'].to_s)
1409 if name =~ /^(?:a|right|left)$/
1410 @obj_exist = attributes['exist']
1411 @obj_noun = attributes['noun']
1412 elsif name == 'inv'
1413 if attributes['id'] == 'stow'
1414 @obj_location = @stow_container_id
1415 else
1416 @obj_location = attributes['id']
1417 end
1418 @obj_exist = nil
1419 @obj_noun = nil
1420 @obj_name = nil
1421 @obj_before_name = nil
1422 @obj_after_name = nil
1423 elsif name == 'dialogData' and attributes['id'] == 'ActiveSpells' and attributes['clear'] == 't'
1424 @active_spells.clear
1425 elsif name == 'resource' or name == 'nav'
1426 nil
1427 elsif name == 'pushStream'
1428 @in_stream = true
1429 @current_stream = attributes['id'].to_s
1430 GameObj.clear_inv if attributes['id'].to_s == 'inv'
1431 elsif name == 'popStream'
1432 if attributes['id'] == 'room'
1433 unless @room_window_disabled
1434 @room_count += 1
1435 $room_count += 1
1436 end
1437 end
1438 @in_stream = false
1439 if attributes['id'] == 'bounty'
1440 @bounty_task.strip!
1441 end
1442 @current_stream = String.new
1443 elsif name == 'pushBold'
1444 @bold = true
1445 elsif name == 'popBold'
1446 @bold = false
1447 elsif (name == 'streamWindow')
1448 if (attributes['id'] == 'main') and attributes['subtitle']
1449 @room_title = '[' + attributes['subtitle'][3..-1] + ']'
1450 end
1451 elsif name == 'style'
1452 @current_style = attributes['id']
1453 elsif name == 'prompt'
1454 @server_time = attributes['time'].to_i
1455 @server_time_offset = (Time.now.to_i - @server_time)
1456 $_CLIENT_.puts "\034GSq#{sprintf('%010d', @server_time)}\r\n" if @send_fake_tags
1457 elsif (name == 'compDef') or (name == 'component')
1458 if attributes['id'] == 'room objs'
1459 GameObj.clear_loot
1460 GameObj.clear_npcs
1461 elsif attributes['id'] == 'room players'
1462 GameObj.clear_pcs
1463 elsif attributes['id'] == 'room exits'
1464 @room_exits = Array.new
1465 @room_exits_string = String.new
1466 elsif attributes['id'] == 'room desc'
1467 @room_description = String.new
1468 GameObj.clear_room_desc
1469 elsif attributes['id'] == 'room extra' # DragonRealms
1470 @room_count += 1
1471 $room_count += 1
1472 # elsif attributes['id'] == 'sprite'
1473 end
1474 elsif name == 'clearContainer'
1475 if attributes['id'] == 'stow'
1476 GameObj.clear_container(@stow_container_id)
1477 else
1478 GameObj.clear_container(attributes['id'])
1479 end
1480 elsif name == 'deleteContainer'
1481 GameObj.delete_container(attributes['id'])
1482 elsif name == 'progressBar'
1483 if attributes['id'] == 'pbarStance'
1484 @stance_text = attributes['text'].split.first
1485 @stance_value = attributes['value'].to_i
1486 $_CLIENT_.puts "\034GSg#{sprintf('%010d', @stance_value)}\r\n" if @send_fake_tags
1487 elsif attributes['id'] == 'mana'
1488 last_mana = @mana
1489 @mana, @max_mana = attributes['text'].scan(/-?\d+/).collect { |num| num.to_i }
1490 difference = @mana - last_mana
1491 # fixme: enhancives screw this up
1492 if (difference == noded_pulse) or (difference == unnoded_pulse) or ( (@mana == @max_mana) and (last_mana + noded_pulse > @max_mana) )
1493 @last_pulse = Time.now.to_i
1494 if @send_fake_tags
1495 $_CLIENT_.puts "\034GSZ#{sprintf('%010d',(@mana+1))}\n"
1496 $_CLIENT_.puts "\034GSZ#{sprintf('%010d',@mana)}\n"
1497 end
1498 end
1499 if @send_fake_tags
1500 $_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"
1501 end
1502 elsif attributes['id'] == 'stamina'
1503 @stamina, @max_stamina = attributes['text'].scan(/-?\d+/).collect { |num| num.to_i }
1504 elsif attributes['id'] == 'mindState'
1505 @mind_text = attributes['text']
1506 @mind_value = attributes['value'].to_i
1507 $_CLIENT_.puts "\034GSr#{MINDMAP[@mind_text]}\r\n" if @send_fake_tags
1508 elsif attributes['id'] == 'health'
1509 @health, @max_health = attributes['text'].scan(/-?\d+/).collect { |num| num.to_i }
1510 $_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
1511 elsif attributes['id'] == 'spirit'
1512 @last_spirit = @spirit if @last_spirit
1513 @spirit, @max_spirit = attributes['text'].scan(/-?\d+/).collect { |num| num.to_i }
1514 @last_spirit = @spirit unless @last_spirit
1515 $_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
1516 elsif attributes['id'] == 'nextLvlPB'
1517 Gift.pulse unless @next_level_text == attributes['text']
1518 @next_level_value = attributes['value'].to_i
1519 @next_level_text = attributes['text']
1520 elsif attributes['id'] == 'encumlevel'
1521 @encumbrance_value = attributes['value'].to_i
1522 @encumbrance_text = attributes['text']
1523 end
1524 elsif name == 'roundTime'
1525 @roundtime_end = attributes['value'].to_i
1526 $_CLIENT_.puts "\034GSQ#{sprintf('%010d', @roundtime_end)}\r\n" if @send_fake_tags
1527 elsif name == 'castTime'
1528 @cast_roundtime_end = attributes['value'].to_i
1529 elsif name == 'dropDownBox'
1530 if attributes['id'] == 'dDBTarget'
1531 if attributes['content_value'] =~ /^\#(\-?\d+)(?:,|$)/
1532 @current_target_id = $1
1533 else
1534 @current_target_id = nil
1535 end
1536 end
1537 elsif name == 'indicator'
1538 @indicator[attributes['id']] = attributes['visible']
1539 if @send_fake_tags
1540 if attributes['id'] == 'IconPOISONED'
1541 if attributes['visible'] == 'y'
1542 $_CLIENT_.puts "\034GSJ0000000000000000000100000000001\r\n"
1543 else
1544 $_CLIENT_.puts "\034GSJ0000000000000000000000000000000\r\n"
1545 end
1546 elsif attributes['id'] == 'IconDISEASED'
1547 if attributes['visible'] == 'y'
1548 $_CLIENT_.puts "\034GSK0000000000000000000100000000001\r\n"
1549 else
1550 $_CLIENT_.puts "\034GSK0000000000000000000000000000000\r\n"
1551 end
1552 else
1553 gsl_prompt = String.new; ICONMAP.keys.each { |icon| gsl_prompt += ICONMAP[icon] if @indicator[icon] == 'y' }
1554 $_CLIENT_.puts "\034GSP#{sprintf('%-30s', gsl_prompt)}\r\n"
1555 end
1556 end
1557 elsif (name == 'image') and @active_ids.include?('injuries')
1558 if @injuries.keys.include?(attributes['id'])
1559 if attributes['name'] =~ /Injury/i
1560 @injuries[attributes['id']]['wound'] = attributes['name'].slice(/\d/).to_i
1561 elsif attributes['name'] =~ /Scar/i
1562 @injuries[attributes['id']]['wound'] = 0
1563 @injuries[attributes['id']]['scar'] = attributes['name'].slice(/\d/).to_i
1564 elsif attributes['name'] =~ /Nsys/i
1565 rank = attributes['name'].slice(/\d/).to_i
1566 if rank == 0
1567 @injuries['nsys']['wound'] = 0
1568 @injuries['nsys']['scar'] = 0
1569 else
1570 Thread.new {
1571 wait_while { dead? }
1572 action = proc { |server_string|
1573 if (@nerve_tracker_active == 'maybe')
1574 if @nerve_tracker_active == 'maybe'
1575 if server_string =~ /^You/
1576 @nerve_tracker_active = 'yes'
1577 @injuries['nsys']['wound'] = 0
1578 @injuries['nsys']['scar'] = 0
1579 else
1580 @nerve_tracker_active = 'no'
1581 end
1582 end
1583 end
1584 if @nerve_tracker_active == 'yes'
1585 if server_string =~ /<output class=['"]['"]\/>/
1586 @nerve_tracker_active = 'no'
1587 @nerve_tracker_num -= 1
1588 DownstreamHook.remove('nerve_tracker') if @nerve_tracker_num < 1
1589 $_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
1590 server_string
1591 elsif server_string =~ /a case of uncontrollable convulsions/
1592 @injuries['nsys']['wound'] = 3
1593 nil
1594 elsif server_string =~ /a case of sporadic convulsions/
1595 @injuries['nsys']['wound'] = 2
1596 nil
1597 elsif server_string =~ /a strange case of muscle twitching/
1598 @injuries['nsys']['wound'] = 1
1599 nil
1600 elsif server_string =~ /a very difficult time with muscle control/
1601 @injuries['nsys']['scar'] = 3
1602 nil
1603 elsif server_string =~ /constant muscle spasms/
1604 @injuries['nsys']['scar'] = 2
1605 nil
1606 elsif server_string =~ /developed slurred speech/
1607 @injuries['nsys']['scar'] = 1
1608 nil
1609 end
1610 else
1611 if server_string =~ /<output class=['"]mono['"]\/>/
1612 @nerve_tracker_active = 'maybe'
1613 end
1614 server_string
1615 end
1616 }
1617 @nerve_tracker_num += 1
1618 DownstreamHook.add('nerve_tracker', action)
1619 Game._puts "#{$cmd_prefix}health"
1620 }
1621 end
1622 else
1623 @injuries[attributes['id']]['wound'] = 0
1624 @injuries[attributes['id']]['scar'] = 0
1625 end
1626 end
1627 $_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
1628 elsif name == 'compass'
1629 if @current_stream == 'familiar'
1630 @fam_mode = String.new
1631 elsif @room_window_disabled
1632 @room_exits = Array.new
1633 end
1634 elsif @room_window_disabled and (name == 'dir') and @active_tags.include?('compass')
1635 @room_exits.push(LONGDIR[attributes['value']])
1636 elsif name == 'radio'
1637 if attributes['id'] == 'injrRad'
1638 @injury_mode = 0 if attributes['value'] == '1'
1639 elsif attributes['id'] == 'scarRad'
1640 @injury_mode = 1 if attributes['value'] == '1'
1641 elsif attributes['id'] == 'bothRad'
1642 @injury_mode = 2 if attributes['value'] == '1'
1643 end
1644 elsif name == 'label'
1645 if attributes['id'] == 'yourLvl'
1646 @level = Stats.level = attributes['value'].slice(/\d+/).to_i
1647 elsif attributes['id'] == 'encumblurb'
1648 @encumbrance_full_text = attributes['value']
1649 elsif @active_tags[-2] == 'dialogData' and @active_ids[-2] == 'ActiveSpells'
1650 if (name = /^lbl(.+)$/.match(attributes['id']).captures.first) and (value = /^\s*([0-9\:]+)\s*$/.match(attributes['value']).captures.first)
1651 hour, minute = value.split(':')
1652 @active_spells[name] = Time.now + (hour.to_i * 3600) + (minute.to_i * 60)
1653 end
1654 end
1655 elsif (name == 'container') and (attributes['id'] == 'stow')
1656 @stow_container_id = attributes['target'].sub('#', '')
1657 elsif (name == 'clearStream')
1658 if attributes['id'] == 'bounty'
1659 @bounty_task = String.new
1660 end
1661 elsif (name == 'playerID')
1662 @player_id = attributes['id']
1663 unless $frontend =~ /^(?:wizard|avalon)$/
1664 if Lich.inventory_boxes(@player_id)
1665 DownstreamHook.remove('inventory_boxes_off')
1666 end
1667 end
1668 elsif (name == 'app') and (@name = attributes['char'])
1669 @game = attributes['game']
1670 if @game.nil? or @game.empty?
1671 @game = 'unknown'
1672 end
1673 unless File.exists?("#{DATA_DIR}/#{@game}")
1674 Dir.mkdir("#{DATA_DIR}/#{@game}")
1675 end
1676 unless File.exists?("#{DATA_DIR}/#{@game}/#{@name}")
1677 Dir.mkdir("#{DATA_DIR}/#{@game}/#{@name}")
1678 end
1679 if $frontend =~ /^(?:wizard|avalon)$/
1680 Game._puts "#{$cmd_prefix}_flag Display Dialog Boxes 0"
1681 sleep 0.05
1682 Game._puts "#{$cmd_prefix}_injury 2"
1683 sleep 0.05
1684 # fixme: game name hardcoded as Gemstone IV; maybe doesn't make any difference to the client
1685 $_CLIENT_.puts "\034GSB0000000000#{attributes['char']}\r\n\034GSA#{Time.now.to_i.to_s}GemStone IV\034GSD\r\n"
1686 # 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
1687 @send_fake_tags = true
1688 # Send all the tags we missed out on
1689 $_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"
1690 $_CLIENT_.puts "\034GSg#{sprintf('%010d', @stance_value)}\r\n"
1691 $_CLIENT_.puts "\034GSr#{MINDMAP[@mind_text]}\r\n"
1692 gsl_prompt = String.new
1693 @indicator.keys.each { |icon| gsl_prompt += ICONMAP[icon] if @indicator[icon] == 'y' }
1694 $_CLIENT_.puts "\034GSP#{sprintf('%-30s', gsl_prompt)}\r\n"
1695 gsl_prompt = nil
1696 gsl_exits = String.new
1697 @room_exits.each { |exit| gsl_exits.concat(DIRMAP[SHORTDIR[exit]].to_s) }
1698 $_CLIENT_.puts "\034GSj#{sprintf('%-20s', gsl_exits)}\r\n"
1699 gsl_exits = nil
1700 $_CLIENT_.puts "\034GSn#{sprintf('%-14s', @prepared_spell)}\r\n"
1701 $_CLIENT_.puts "\034GSm#{sprintf('%-45s', GameObj.right_hand.name)}\r\n"
1702 $_CLIENT_.puts "\034GSl#{sprintf('%-45s', GameObj.left_hand.name)}\r\n"
1703 $_CLIENT_.puts "\034GSq#{sprintf('%010d', @server_time)}\r\n"
1704 $_CLIENT_.puts "\034GSQ#{sprintf('%010d', @roundtime_end)}\r\n" if @roundtime_end > 0
1705 end
1706 Game._puts("#{$cmd_prefix}_flag Display Inventory Boxes 1")
1707 Script.start('autostart') if Script.exists?('autostart')
1708 if arg = ARGV.find { |a| a=~ /^\-\-start\-scripts=/ }
1709 for script_name in arg.sub('--start-scripts=', '').split(',')
1710 Script.start(script_name)
1711 end
1712 end
1713 end
1714 rescue
1715 $stdout.puts "--- error: XMLParser.tag_start: #{$!}"
1716 Lich.log "error: XMLParser.tag_start: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
1717 sleep 0.1
1718 reset
1719 end
1720 end
1721 def text(text_string)
1722 begin
1723 # fixme: /<stream id="Spells">.*?<\/stream>/m
1724 # $_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)$/))
1725 if @active_tags.include?('inv')
1726 if @active_tags[-1] == 'a'
1727 @obj_name = text_string
1728 elsif @obj_name.nil?
1729 @obj_before_name = text_string.strip
1730 else
1731 @obj_after_name = text_string.strip
1732 end
1733 elsif @active_tags.last == 'prompt'
1734 @prompt = text_string
1735 elsif @active_tags.include?('right')
1736 GameObj.new_right_hand(@obj_exist, @obj_noun, text_string)
1737 $_CLIENT_.puts "\034GSm#{sprintf('%-45s', text_string)}\r\n" if @send_fake_tags
1738 elsif @active_tags.include?('left')
1739 GameObj.new_left_hand(@obj_exist, @obj_noun, text_string)
1740 $_CLIENT_.puts "\034GSl#{sprintf('%-45s', text_string)}\r\n" if @send_fake_tags
1741 elsif @active_tags.include?('spell')
1742 @prepared_spell = text_string
1743 $_CLIENT_.puts "\034GSn#{sprintf('%-14s', text_string)}\r\n" if @send_fake_tags
1744 elsif @active_tags.include?('compDef') or @active_tags.include?('component')
1745 if @active_ids.include?('room objs')
1746 if @active_tags.include?('a')
1747 if @bold
1748 GameObj.new_npc(@obj_exist, @obj_noun, text_string)
1749 else
1750 GameObj.new_loot(@obj_exist, @obj_noun, text_string)
1751 end
1752 elsif (text_string =~ /that (?:is|appears) ([\w\s]+)(?:,| and|\.)/) or (text_string =~ / \(([^\(]+)\)/)
1753 GameObj.npcs[-1].status = $1
1754 end
1755 elsif @active_ids.include?('room players')
1756 if @active_tags.include?('a')
1757 @pc = GameObj.new_pc(@obj_exist, @obj_noun, "#{@player_title}#{text_string}", @player_status)
1758 @player_status = nil
1759 else
1760 if @game =~ /^DR/
1761 GameObj.clear_pcs
1762 text_string.sub(/^Also here\: /, '').sub(/ and ([^,]+)\./) { ", #{$1}" }.split(', ').each { |player|
1763 if player =~ / who is (.+)/
1764 status = $1
1765 player.sub!(/ who is .+/, '')
1766 elsif player =~ / \((.+)\)/
1767 status = $1
1768 player.sub!(/ \(.+\)/, '')
1769 else
1770 status = nil
1771 end
1772 noun = player.slice(/\b[A-Z][a-z]+$/)
1773 if player =~ /the body of /
1774 player.sub!('the body of ', '')
1775 if status
1776 status.concat ' dead'
1777 else
1778 status = 'dead'
1779 end
1780 end
1781 if player =~ /a stunned /
1782 player.sub!('a stunned ', '')
1783 if status
1784 status.concat ' stunned'
1785 else
1786 status = 'stunned'
1787 end
1788 end
1789 GameObj.new_pc(nil, noun, player, status)
1790 }
1791 else
1792 if (text_string =~ /^ who (?:is|appears) ([\w\s]+)(?:,| and|\.|$)/) or (text_string =~ / \(([\w\s]+)\)(?: \(([\w\s]+)\))?/)
1793 if @pc.status
1794 @pc.status.concat " #{$1}"
1795 else
1796 @pc.status = $1
1797 end
1798 @pc.status.concat " #{$2}" if $2
1799 end
1800 if text_string =~ /(?:^Also here: |, )(?:a )?([a-z\s]+)?([\w\s\-!\?',]+)?$/
1801 @player_status = ($1.strip.gsub('the body of', 'dead')) if $1
1802 @player_title = $2
1803 end
1804 end
1805 end
1806 elsif @active_ids.include?('room desc')
1807 if text_string == '[Room window disabled at this location.]'
1808 @room_window_disabled = true
1809 else
1810 @room_window_disabled = false
1811 @room_description.concat(text_string)
1812 if @active_tags.include?('a')
1813 GameObj.new_room_desc(@obj_exist, @obj_noun, text_string)
1814 end
1815 end
1816 elsif @active_ids.include?('room exits')
1817 @room_exits_string.concat(text_string)
1818 @room_exits.push(text_string) if @active_tags.include?('d')
1819 end
1820 elsif @current_stream == 'bounty'
1821 @bounty_task += text_string
1822 elsif @current_stream == 'society'
1823 @society_task = text_string
1824 elsif (@current_stream == 'inv') and @active_tags.include?('a')
1825 GameObj.new_inv(@obj_exist, @obj_noun, text_string, nil)
1826 elsif @current_stream == 'familiar'
1827 # 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
1828 if @current_style == 'roomName'
1829 @familiar_room_title = text_string
1830 @familiar_room_description = String.new
1831 @familiar_room_exits = Array.new
1832 GameObj.clear_fam_room_desc
1833 GameObj.clear_fam_loot
1834 GameObj.clear_fam_npcs
1835 GameObj.clear_fam_pcs
1836 @fam_mode = String.new
1837 elsif @current_style == 'roomDesc'
1838 @familiar_room_description.concat(text_string)
1839 if @active_tags.include?('a')
1840 GameObj.new_fam_room_desc(@obj_exist, @obj_noun, text_string)
1841 end
1842 elsif text_string =~ /^You also see/
1843 @fam_mode = 'things'
1844 elsif text_string =~ /^Also here/
1845 @fam_mode = 'people'
1846 elsif text_string =~ /Obvious (?:paths|exits)/
1847 @fam_mode = 'paths'
1848 elsif @fam_mode == 'things'
1849 if @active_tags.include?('a')
1850 if @bold
1851 GameObj.new_fam_npc(@obj_exist, @obj_noun, text_string)
1852 else
1853 GameObj.new_fam_loot(@obj_exist, @obj_noun, text_string)
1854 end
1855 end
1856 # puts 'things: ' + text_string
1857 elsif @fam_mode == 'people' and @active_tags.include?('a')
1858 GameObj.new_fam_pc(@obj_exist, @obj_noun, text_string)
1859 # puts 'people: ' + text_string
1860 elsif (@fam_mode == 'paths') and @active_tags.include?('a')
1861 @familiar_room_exits.push(text_string)
1862 end
1863 elsif @room_window_disabled
1864 if @current_style == 'roomDesc'
1865 @room_description.concat(text_string)
1866 if @active_tags.include?('a')
1867 GameObj.new_room_desc(@obj_exist, @obj_noun, text_string)
1868 end
1869 elsif text_string =~ /^Obvious (?:paths|exits): (?:none)?$/
1870 @room_exits_string = text_string.strip
1871 end
1872 end
1873 rescue
1874 $stdout.puts "--- error: XMLParser.text: #{$!}"
1875 Lich.log "error: XMLParser.text: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
1876 sleep 0.1
1877 reset
1878 end
1879 end
1880 def tag_end(name)
1881 begin
1882 if name == 'inv'
1883 if @obj_exist == @obj_location
1884 if @obj_after_name == 'is closed.'
1885 GameObj.delete_container(@stow_container_id)
1886 end
1887 elsif @obj_exist
1888 GameObj.new_inv(@obj_exist, @obj_noun, @obj_name, @obj_location, @obj_before_name, @obj_after_name)
1889 end
1890 elsif @send_fake_tags and (@active_ids.last == 'room exits')
1891 gsl_exits = String.new
1892 @room_exits.each { |exit| gsl_exits.concat(DIRMAP[SHORTDIR[exit]].to_s) }
1893 $_CLIENT_.puts "\034GSj#{sprintf('%-20s', gsl_exits)}\r\n"
1894 gsl_exits = nil
1895 elsif @room_window_disabled and (name == 'compass')
1896# @room_window_disabled = false
1897 @room_description = @room_description.strip
1898 @room_exits_string.concat " #{@room_exits.join(', ')}" unless @room_exits.empty?
1899 gsl_exits = String.new
1900 @room_exits.each { |exit| gsl_exits.concat(DIRMAP[SHORTDIR[exit]].to_s) }
1901 $_CLIENT_.puts "\034GSj#{sprintf('%-20s', gsl_exits)}\r\n"
1902 gsl_exits = nil
1903 @room_count += 1
1904 $room_count += 1
1905 end
1906 @last_tag = @active_tags.pop
1907 @last_id = @active_ids.pop
1908 rescue
1909 $stdout.puts "--- error: XMLParser.tag_end: #{$!}"
1910 Lich.log "error: XMLParser.tag_end: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
1911 sleep 0.1
1912 reset
1913 end
1914 end
1915 # here for backwards compatibility, but spellfront xml isn't sent by the game anymore
1916 def spellfront
1917 if (Time.now.to_i - @@warned_deprecated_spellfront) > 300
1918 @@warned_deprecated_spellfront = Time.now.to_i
1919 unless script_name = Script.current.name
1920 script_name = 'unknown script'
1921 end
1922 respond "--- warning: #{script_name} is using deprecated method XMLData.spellfront; this method will be removed in a future version of Lich"
1923 end
1924 @active_spells.keys
1925 end
1926end
1927
1928class UpstreamHook
1929 @@upstream_hooks ||= Hash.new
1930 def UpstreamHook.add(name, action)
1931 unless action.class == Proc
1932 echo "UpstreamHook: not a Proc (#{action})"
1933 return false
1934 end
1935 @@upstream_hooks[name] = action
1936 end
1937 def UpstreamHook.run(client_string)
1938 for key in @@upstream_hooks.keys
1939 begin
1940 client_string = @@upstream_hooks[key].call(client_string)
1941 rescue
1942 @@upstream_hooks.delete(key)
1943 respond "--- Lich: UpstreamHook: #{$!}"
1944 respond $!.backtrace.first
1945 end
1946 return nil if client_string.nil?
1947 end
1948 return client_string
1949 end
1950 def UpstreamHook.remove(name)
1951 @@upstream_hooks.delete(name)
1952 end
1953 def UpstreamHook.list
1954 @@upstream_hooks.keys.dup
1955 end
1956end
1957
1958class DownstreamHook
1959 @@downstream_hooks ||= Hash.new
1960 def DownstreamHook.add(name, action)
1961 unless action.class == Proc
1962 echo "DownstreamHook: not a Proc (#{action})"
1963 return false
1964 end
1965 @@downstream_hooks[name] = action
1966 end
1967 def DownstreamHook.run(server_string)
1968 for key in @@downstream_hooks.keys
1969 begin
1970 server_string = @@downstream_hooks[key].call(server_string.dup)
1971 rescue
1972 @@downstream_hooks.delete(key)
1973 respond "--- Lich: DownstreamHook: #{$!}"
1974 respond $!.backtrace.first
1975 end
1976 return nil if server_string.nil?
1977 end
1978 return server_string
1979 end
1980 def DownstreamHook.remove(name)
1981 @@downstream_hooks.delete(name)
1982 end
1983 def DownstreamHook.list
1984 @@downstream_hooks.keys.dup
1985 end
1986end
1987
1988module Setting
1989 @@load = proc { |args|
1990 unless script = Script.current
1991 respond '--- error: Setting.load: calling script is unknown'
1992 respond $!.backtrace[0..2]
1993 next nil
1994 end
1995 if script.class == ExecScript
1996 respond "--- Lich: error: Setting.load: exec scripts can't have settings"
1997 respond $!.backtrace[0..2]
1998 exit
1999 end
2000 if args.empty?
2001 respond '--- error: Setting.load: no setting specified'
2002 respond $!.backtrace[0..2]
2003 exit
2004 end
2005 if args.any? { |a| a.class != String }
2006 respond "--- Lich: error: Setting.load: non-string given as setting name"
2007 respond $!.backtrace[0..2]
2008 exit
2009 end
2010 values = Array.new
2011 for setting in args
2012 begin
2013 v = Lich.db.get_first_value('SELECT value FROM script_setting WHERE script=? AND name=?;', script.name.encode('UTF-8'), setting.encode('UTF-8'))
2014 rescue SQLite3::BusyException
2015 sleep 0.1
2016 retry
2017 end
2018 if v.nil?
2019 values.push(v)
2020 else
2021 begin
2022 values.push(Marshal.load(v))
2023 rescue
2024 respond "--- Lich: error: Setting.load: #{$!}"
2025 respond $!.backtrace[0..2]
2026 exit
2027 end
2028 end
2029 end
2030 if args.length == 1
2031 next values[0]
2032 else
2033 next values
2034 end
2035 }
2036 @@save = proc { |hash|
2037 unless script = Script.current
2038 respond '--- error: Setting.save: calling script is unknown'
2039 respond $!.backtrace[0..2]
2040 next nil
2041 end
2042 if script.class == ExecScript
2043 respond "--- Lich: error: Setting.load: exec scripts can't have settings"
2044 respond $!.backtrace[0..2]
2045 exit
2046 end
2047 if hash.class != Hash
2048 respond "--- Lich: error: Setting.save: invalid arguments: use Setting.save('setting1' => 'value1', 'setting2' => 'value2')"
2049 respond $!.backtrace[0..2]
2050 exit
2051 end
2052 if hash.empty?
2053 next nil
2054 end
2055 if hash.keys.any? { |k| k.class != String }
2056 respond "--- Lich: error: Setting.save: non-string given as a setting name"
2057 respond $!.backtrace[0..2]
2058 exit
2059 end
2060 if hash.length > 1
2061 begin
2062 Lich.db.execute('BEGIN')
2063 rescue SQLite3::BusyException
2064 sleep 0.1
2065 retry
2066 end
2067 end
2068 hash.each { |setting,value|
2069 begin
2070 if value.nil?
2071 begin
2072 Lich.db.execute('DELETE FROM script_setting WHERE script=? AND name=?;', script.name.encode('UTF-8'), setting.encode('UTF-8'))
2073 rescue SQLite3::BusyException
2074 sleep 0.1
2075 retry
2076 end
2077 else
2078 v = SQLite3::Blob.new(Marshal.dump(value))
2079 begin
2080 Lich.db.execute('INSERT OR REPLACE INTO script_setting(script,name,value) VALUES(?,?,?);', script.name.encode('UTF-8'), setting.encode('UTF-8'), v)
2081 rescue SQLite3::BusyException
2082 sleep 0.1
2083 retry
2084 end
2085 end
2086 rescue SQLite3::BusyException
2087 sleep 0.1
2088 retry
2089 end
2090 }
2091 if hash.length > 1
2092 begin
2093 Lich.db.execute('END')
2094 rescue SQLite3::BusyException
2095 sleep 0.1
2096 retry
2097 end
2098 end
2099 true
2100 }
2101 @@list = proc {
2102 unless script = Script.current
2103 respond '--- error: Setting: unknown calling script'
2104 next nil
2105 end
2106 if script.class == ExecScript
2107 respond "--- Lich: error: Setting.load: exec scripts can't have settings"
2108 respond $!.backtrace[0..2]
2109 exit
2110 end
2111 begin
2112 rows = Lich.db.execute('SELECT name FROM script_setting WHERE script=?;', script.name.encode('UTF-8'))
2113 rescue SQLite3::BusyException
2114 sleep 0.1
2115 retry
2116 end
2117 if rows
2118 # fixme
2119 next rows.inspect
2120 else
2121 next nil
2122 end
2123 }
2124 def Setting.load(*args)
2125 @@load.call(args)
2126 end
2127 def Setting.save(hash)
2128 @@save.call(hash)
2129 end
2130 def Setting.list
2131 @@list.call
2132 end
2133end
2134
2135module GameSetting
2136 def GameSetting.load(*args)
2137 Setting.load(args.collect { |a| "#{XMLData.game}:#{a}" })
2138 end
2139 def GameSetting.save(hash)
2140 game_hash = Hash.new
2141 hash.each_pair { |k,v| game_hash["#{XMLData.game}:#{k}"] = v }
2142 Setting.save(game_hash)
2143 end
2144end
2145
2146module CharSetting
2147 def CharSetting.load(*args)
2148 Setting.load(args.collect { |a| "#{XMLData.game}:#{XMLData.name}:#{a}" })
2149 end
2150 def CharSetting.save(hash)
2151 game_hash = Hash.new
2152 hash.each_pair { |k,v| game_hash["#{XMLData.game}:#{XMLData.name}:#{k}"] = v }
2153 Setting.save(game_hash)
2154 end
2155end
2156
2157module Settings
2158 settings = Hash.new
2159 md5_at_load = Hash.new
2160 mutex = Mutex.new
2161 @@settings = proc { |scope|
2162 unless script = Script.current
2163 respond '--- error: Settings: unknown calling script'
2164 next nil
2165 end
2166 unless scope =~ /^#{XMLData.game}\:#{XMLData.name}$|^#{XMLData.game}$|^\:$/
2167 respond '--- error: Settings: invalid scope'
2168 next nil
2169 end
2170 mutex.synchronize {
2171 unless settings[script.name] and settings[script.name][scope]
2172 begin
2173 _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'))
2174 rescue SQLite3::BusyException
2175 sleep 0.1
2176 retry
2177 end
2178 settings[script.name] ||= Hash.new
2179 if _hash.nil?
2180 settings[script.name][scope] = Hash.new
2181 else
2182 begin
2183 hash = Marshal.load(_hash)
2184 rescue
2185 respond "--- Lich: error: #{$!}"
2186 respond $!.backtrace[0..1]
2187 exit
2188 end
2189 settings[script.name][scope] = hash
2190 end
2191 md5_at_load[script.name] ||= Hash.new
2192 md5_at_load[script.name][scope] = Digest::MD5.hexdigest(settings[script.name][scope].to_s)
2193 end
2194 }
2195 settings[script.name][scope]
2196 }
2197 @@save = proc {
2198 mutex.synchronize {
2199 sql_began = false
2200 settings.each_pair { |script_name,scopedata|
2201 scopedata.each_pair { |scope,data|
2202 if Digest::MD5.hexdigest(data.to_s) != md5_at_load[script_name][scope]
2203 unless sql_began
2204 begin
2205 Lich.db.execute('BEGIN')
2206 rescue SQLite3::BusyException
2207 sleep 0.1
2208 retry
2209 end
2210 sql_began = true
2211 end
2212 blob = SQLite3::Blob.new(Marshal.dump(data))
2213 begin
2214 Lich.db.execute('INSERT OR REPLACE INTO script_auto_settings(script,scope,hash) VALUES(?,?,?);', script_name.encode('UTF-8'), scope.encode('UTF-8'), blob)
2215 rescue SQLite3::BusyException
2216 sleep 0.1
2217 retry
2218 rescue
2219 respond "--- Lich: error: #{$!}"
2220 respond $!.backtrace[0..1]
2221 next
2222 end
2223 end
2224 }
2225 unless Script.running?(script_name)
2226 settings.delete(script_name)
2227 md5_at_load.delete(script_name)
2228 end
2229 }
2230 if sql_began
2231 begin
2232 Lich.db.execute('END')
2233 rescue SQLite3::BusyException
2234 sleep 0.1
2235 retry
2236 end
2237 end
2238 }
2239 }
2240 Thread.new {
2241 loop {
2242 sleep 300
2243 begin
2244 @@save.call
2245 rescue
2246 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
2247 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
2248 end
2249 }
2250 }
2251 def Settings.[](name)
2252 @@settings.call(':')[name]
2253 end
2254 def Settings.[]=(name, value)
2255 @@settings.call(':')[name] = value
2256 end
2257 def Settings.to_hash(scope=':')
2258 @@settings.call(scope)
2259 end
2260 def Settings.char
2261 @@settings.call("#{XMLData.game}:#{XMLData.name}")
2262 end
2263 def Settings.save
2264 @@save.call
2265 end
2266end
2267
2268module GameSettings
2269 def GameSettings.[](name)
2270 Settings.to_hash(XMLData.game)[name]
2271 end
2272 def GameSettings.[]=(name, value)
2273 Settings.to_hash(XMLData.game)[name] = value
2274 end
2275 def GameSettings.to_hash
2276 Settings.to_hash(XMLData.game)
2277 end
2278end
2279
2280module CharSettings
2281 def CharSettings.[](name)
2282 Settings.to_hash("#{XMLData.game}:#{XMLData.name}")[name]
2283 end
2284 def CharSettings.[]=(name, value)
2285 Settings.to_hash("#{XMLData.game}:#{XMLData.name}")[name] = value
2286 end
2287 def CharSettings.to_hash
2288 Settings.to_hash("#{XMLData.game}:#{XMLData.name}")
2289 end
2290end
2291
2292module Vars
2293 @@vars = Hash.new
2294 md5 = nil
2295 mutex = Mutex.new
2296 @@loaded = false
2297 @@load = proc {
2298 mutex.synchronize {
2299 unless @@loaded
2300 begin
2301 h = Lich.db.get_first_value('SELECT hash FROM uservars WHERE scope=?;', "#{XMLData.game}:#{XMLData.name}".encode('UTF-8'))
2302 rescue SQLite3::BusyException
2303 sleep 0.1
2304 retry
2305 end
2306 if h
2307 begin
2308 hash = Marshal.load(h)
2309 hash.each { |k,v| @@vars[k] = v }
2310 md5 = Digest::MD5.hexdigest(hash.to_s)
2311 rescue
2312 respond "--- Lich: error: #{$!}"
2313 respond $!.backtrace[0..2]
2314 end
2315 end
2316 @@loaded = true
2317 end
2318 }
2319 nil
2320 }
2321 @@save = proc {
2322 mutex.synchronize {
2323 if @@loaded
2324 if Digest::MD5.hexdigest(@@vars.to_s) != md5
2325 md5 = Digest::MD5.hexdigest(@@vars.to_s)
2326 blob = SQLite3::Blob.new(Marshal.dump(@@vars))
2327 begin
2328 Lich.db.execute('INSERT OR REPLACE INTO uservars(scope,hash) VALUES(?,?);', "#{XMLData.game}:#{XMLData.name}".encode('UTF-8'), blob)
2329 rescue SQLite3::BusyException
2330 sleep 0.1
2331 retry
2332 end
2333 end
2334 end
2335 }
2336 nil
2337 }
2338 Thread.new {
2339 loop {
2340 sleep 300
2341 begin
2342 @@save.call
2343 rescue
2344 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
2345 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
2346 end
2347 }
2348 }
2349 def Vars.[](name)
2350 @@load.call unless @@loaded
2351 @@vars[name]
2352 end
2353 def Vars.[]=(name, val)
2354 @@load.call unless @@loaded
2355 if val.nil?
2356 @@vars.delete(name)
2357 else
2358 @@vars[name] = val
2359 end
2360 end
2361 def Vars.list
2362 @@load.call unless @@loaded
2363 @@vars.dup
2364 end
2365 def Vars.save
2366 @@save.call
2367 end
2368 def Vars.method_missing(arg1, arg2='')
2369 @@load.call unless @@loaded
2370 if arg1[-1,1] == '='
2371 if arg2.nil?
2372 @@vars.delete(arg1.to_s.chop)
2373 else
2374 @@vars[arg1.to_s.chop] = arg2
2375 end
2376 else
2377 @@vars[arg1.to_s]
2378 end
2379 end
2380end
2381
2382#
2383# script bindings are convoluted, but don't change them without testing if:
2384# 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)
2385# local variables become shared between scripts
2386# local variable 'file' is shared between scripts, even though other local variables aren't
2387# defined methods are instantly inaccessible
2388# 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
2389#
2390class Scripting
2391 def script
2392 Proc.new {}.binding
2393 end
2394end
2395def _script
2396 Proc.new {}.binding
2397end
2398
2399TRUSTED_SCRIPT_BINDING = proc { _script }
2400
2401class Script
2402 @@elevated_script_start = proc { |args|
2403 if args.empty?
2404 # fixme: error
2405 next nil
2406 elsif args[0].class == String
2407 script_name = args[0]
2408 if args[1]
2409 if args[1].class == String
2410 script_args = args[1]
2411 if args[2]
2412 if args[2].class == Hash
2413 options = args[2]
2414 else
2415 # fixme: error
2416 next nil
2417 end
2418 end
2419 elsif args[1].class == Hash
2420 options = args[1]
2421 script_args = (options[:args] || String.new)
2422 else
2423 # fixme: error
2424 next nil
2425 end
2426 else
2427 options = Hash.new
2428 end
2429 elsif args[0].class == Hash
2430 options = args[0]
2431 if options[:name]
2432 script_name = options[:name]
2433 else
2434 # fixme: error
2435 next nil
2436 end
2437 script_args = (options[:args] || String.new)
2438 end
2439 # fixme: look in wizard script directory
2440 # fixme: allow subdirectories?
2441 file_list = Dir.entries(SCRIPT_DIR).delete_if { |fn| (fn == '.') or (fn == '..') }.sort
2442 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 })
2443 script_name = file_name.sub(/\..{1,3}$/, '')
2444 end
2445 file_list = nil
2446 if file_name.nil?
2447 respond "--- Lich: could not find script '#{script_name}' in directory #{SCRIPT_DIR}"
2448 next nil
2449 end
2450 if (options[:force] != true) and (Script.running + Script.hidden).find { |s| s.name =~ /^#{Regexp.escape(script_name)}$/i }
2451 respond "--- Lich: #{script_name} is already running (use #{$clean_lich_char}force [scriptname] if desired)."
2452 next nil
2453 end
2454 begin
2455 if file_name =~ /\.(?:cmd|wiz)(?:\.gz)?$/i
2456 trusted = false
2457 script_obj = WizardScript.new("#{SCRIPT_DIR}/#{file_name}", script_args)
2458 else
2459 begin
2460 trusted = Lich.db.get_first_value('SELECT name FROM trusted_scripts WHERE name=?;', script_name.encode('UTF-8'))
2461 rescue SQLite3::BusyException
2462 sleep 0.1
2463 retry
2464 end
2465 script_obj = Script.new(:file => "#{SCRIPT_DIR}/#{file_name}", :args => script_args, :quiet => options[:quiet])
2466 end
2467 if trusted and not script_obj.labels.length > 1
2468 script_binding = TRUSTED_SCRIPT_BINDING.call
2469 else
2470 script_binding = Scripting.new.script
2471 end
2472 rescue
2473 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
2474 next nil
2475 end
2476 unless script_obj
2477 respond "--- Lich: error: failed to start script (#{script_name})"
2478 next nil
2479 end
2480 script_obj.quiet = true if options[:quiet]
2481 new_thread = Thread.new {
2482 100.times { break if Script.current == script_obj; sleep 0.01 }
2483 if script = Script.current
2484 eval('script = Script.current', script_binding, script.name)
2485 Thread.current.priority = 1
2486 respond("--- Lich: #{script.name} active.") unless script.quiet
2487 if trusted
2488 begin
2489 eval(script.labels[script.current_label].to_s, script_binding, script.name)
2490 rescue SystemExit
2491 nil
2492 rescue SyntaxError
2493 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
2494 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
2495 rescue ScriptError
2496 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
2497 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
2498 rescue NoMemoryError
2499 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
2500 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
2501 rescue LoadError
2502 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
2503 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
2504 rescue SecurityError
2505 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
2506 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
2507 rescue ThreadError
2508 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
2509 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
2510 rescue SystemStackError
2511 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
2512 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
2513 rescue Exception
2514 if $! == JUMP
2515 retry if Script.current.get_next_label != JUMP_ERROR
2516 respond "--- label error: `#{Script.current.jump_label}' was not found, and no `LabelError' label was found!"
2517 respond $!.backtrace.first
2518 Lich.log "label error: `#{Script.current.jump_label}' was not found, and no `LabelError' label was found!\n\t#{$!.backtrace.join("\n\t")}"
2519 Script.current.kill
2520 else
2521 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
2522 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
2523 end
2524 rescue
2525 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
2526 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
2527 ensure
2528 Script.current.kill
2529 end
2530 else
2531 begin
2532 while (script = Script.current) and script.current_label
2533 proc { foo = script.labels[script.current_label]; foo.untaint; $SAFE = 3; eval(foo, script_binding, script.name, 1) }.call
2534 Script.current.get_next_label
2535 end
2536 rescue SystemExit
2537 nil
2538 rescue SyntaxError
2539 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
2540 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
2541 rescue ScriptError
2542 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
2543 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
2544 rescue NoMemoryError
2545 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
2546 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
2547 rescue LoadError
2548 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
2549 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
2550 rescue SecurityError
2551 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
2552 if name = Script.current.name
2553 respond "--- Lich: review this script (#{name}) to make sure it isn't malicious, and type #{$clean_lich_char}trust #{name}"
2554 end
2555 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
2556 rescue ThreadError
2557 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
2558 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
2559 rescue SystemStackError
2560 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
2561 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
2562 rescue Exception
2563 if $! == JUMP
2564 retry if Script.current.get_next_label != JUMP_ERROR
2565 respond "--- label error: `#{Script.current.jump_label}' was not found, and no `LabelError' label was found!"
2566 respond $!.backtrace.first
2567 Lich.log "label error: `#{Script.current.jump_label}' was not found, and no `LabelError' label was found!\n\t#{$!.backtrace.join("\n\t")}"
2568 Script.current.kill
2569 else
2570 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
2571 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
2572 end
2573 rescue
2574 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
2575 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
2576 ensure
2577 Script.current.kill
2578 end
2579 end
2580 else
2581 respond '--- error: out of cheese'
2582 end
2583 }
2584 script_obj.thread_group.add(new_thread)
2585 script_obj
2586 }
2587 @@elevated_exists = proc { |script_name|
2588 if script_name =~ /\\|\//
2589 nil
2590 elsif script_name =~ /\.(?:lic|lich|rb|cmd|wiz)(?:\.gz)?$/i
2591 File.exists?("#{SCRIPT_DIR}/#{script_name}")
2592 else
2593 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")
2594 end
2595 }
2596 @@elevated_log = proc { |data|
2597 if script = Script.current
2598 if script.name =~ /\\|\//
2599 nil
2600 else
2601 begin
2602 Dir.mkdir("#{LICH_DIR}/logs") unless File.exists?("#{LICH_DIR}/logs")
2603 File.open("#{LICH_DIR}/logs/#{script.name}.log", 'a') { |f| f.puts data }
2604 true
2605 rescue
2606 respond "--- Lich: error: Script.log: #{$!}"
2607 false
2608 end
2609 end
2610 else
2611 respond '--- error: Script.log: unable to identify calling script'
2612 false
2613 end
2614 }
2615 @@elevated_db = proc {
2616 if script = Script.current
2617 if script.name =~ /^lich$/i
2618 respond '--- error: Script.db cannot be used by a script named lich'
2619 nil
2620 elsif script.class == ExecScript
2621 respond '--- error: Script.db cannot be used by exec scripts'
2622 nil
2623 else
2624 SQLite3::Database.new("#{DATA_DIR}/#{script.name.gsub(/\/|\\/, '_')}.db3")
2625 end
2626 else
2627 respond '--- error: Script.db called by an unknown script'
2628 nil
2629 end
2630 }
2631 @@elevated_open_file = proc { |ext,mode,block|
2632 if script = Script.current
2633 if script.name =~ /^lich$/i
2634 respond '--- error: Script.open_file cannot be used by a script named lich'
2635 nil
2636 elsif script.name =~ /^entry$/i
2637 respond '--- error: Script.open_file cannot be used by a script named entry'
2638 nil
2639 elsif script.class == ExecScript
2640 respond '--- error: Script.open_file cannot be used by exec scripts'
2641 nil
2642 elsif ext.downcase == 'db3'
2643 SQLite3::Database.new("#{DATA_DIR}/#{script.name.gsub(/\/|\\/, '_')}.db3")
2644# fixme: block gets elevated... why?
2645# elsif block
2646# File.open("#{DATA_DIR}/#{script.name.gsub(/\/|\\/, '_')}.#{ext.gsub(/\/|\\/, '_')}", mode, &block)
2647 else
2648 File.open("#{DATA_DIR}/#{script.name.gsub(/\/|\\/, '_')}.#{ext.gsub(/\/|\\/, '_')}", mode)
2649 end
2650 else
2651 respond '--- error: Script.open_file called by an unknown script'
2652 nil
2653 end
2654 }
2655 @@running = Array.new
2656
2657 attr_reader :name, :vars, :safe, :file_name, :label_order, :at_exit_procs
2658 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
2659 def Script.list
2660 @@running.dup
2661 end
2662 def Script.current
2663 if script = @@running.find { |s| s.has_thread?(Thread.current) }
2664 sleep 0.2 while script.paused? and not script.ignore_pause
2665 script
2666 else
2667 nil
2668 end
2669 end
2670 def Script.start(*args)
2671 @@elevated_script_start.call(args)
2672 end
2673 def Script.run(*args)
2674 if s = @@elevated_script_start.call(args)
2675 sleep 0.1 while @@running.include?(s)
2676 end
2677 end
2678 def Script.running?(name)
2679 @@running.any? { |i| (i.name =~ /^#{name}$/i) }
2680 end
2681 def Script.pause(name=nil)
2682 if name.nil?
2683 Script.current.pause
2684 Script.current
2685 else
2686 if s = (@@running.find { |i| (i.name == name) and not i.paused? }) || (@@running.find { |i| (i.name =~ /^#{name}$/i) and not i.paused? })
2687 s.pause
2688 true
2689 else
2690 false
2691 end
2692 end
2693 end
2694 def Script.unpause(name)
2695 if s = (@@running.find { |i| (i.name == name) and i.paused? }) || (@@running.find { |i| (i.name =~ /^#{name}$/i) and i.paused? })
2696 s.unpause
2697 true
2698 else
2699 false
2700 end
2701 end
2702 def Script.kill(name)
2703 if s = (@@running.find { |i| i.name == name }) || (@@running.find { |i| i.name =~ /^#{name}$/i })
2704 s.kill
2705 true
2706 else
2707 false
2708 end
2709 end
2710 def Script.paused?(name)
2711 if s = (@@running.find { |i| i.name == name }) || (@@running.find { |i| i.name =~ /^#{name}$/i })
2712 s.paused?
2713 else
2714 nil
2715 end
2716 end
2717 def Script.exists?(script_name)
2718 @@elevated_exists.call(script_name)
2719 end
2720 def Script.new_downstream_xml(line)
2721 for script in @@running
2722 script.downstream_buffer.push(line.chomp) if script.want_downstream_xml
2723 end
2724 end
2725 def Script.new_upstream(line)
2726 for script in @@running
2727 script.upstream_buffer.push(line.chomp) if script.want_upstream
2728 end
2729 end
2730 def Script.new_downstream(line)
2731 @@running.each { |script|
2732 script.downstream_buffer.push(line.chomp) if script.want_downstream
2733 unless script.watchfor.empty?
2734 script.watchfor.each_pair { |trigger,action|
2735 if line =~ trigger
2736 new_thread = Thread.new {
2737 sleep 0.011 until Script.current
2738 begin
2739 action.call
2740 rescue
2741 echo "watchfor error: #{$!}"
2742 end
2743 }
2744 script.thread_group.add(new_thread)
2745 end
2746 }
2747 end
2748 }
2749 end
2750 def Script.new_script_output(line)
2751 for script in @@running
2752 script.downstream_buffer.push(line.chomp) if script.want_script_output
2753 end
2754 end
2755 def Script.log(data)
2756 @@elevated_log.call(data)
2757 end
2758 def Script.db
2759 @@elevated_db.call
2760 end
2761 def Script.open_file(ext, mode='r', &block)
2762 @@elevated_open_file.call(ext, mode, block)
2763 end
2764 def Script.at_exit(&block)
2765 if script = Script.current
2766 script.at_exit(&block)
2767 else
2768 respond "--- Lich: error: Script.at_exit: can't identify calling script"
2769 return false
2770 end
2771 end
2772 def Script.clear_exit_procs
2773 if script = Script.current
2774 script.clear_exit_procs
2775 else
2776 respond "--- Lich: error: Script.clear_exit_procs: can't identify calling script"
2777 return false
2778 end
2779 end
2780 def Script.exit!
2781 if script = Script.current
2782 script.exit!
2783 else
2784 respond "--- Lich: error: Script.exit!: can't identify calling script"
2785 return false
2786 end
2787 end
2788 def Script.trust(script_name)
2789 # fixme: case sensitive blah blah
2790 if ($SAFE == 0) and not caller.any? { |c| c =~ /eval|run/ }
2791 begin
2792 Lich.db.execute('INSERT OR REPLACE INTO trusted_scripts(name) values(?);', script_name.encode('UTF-8'))
2793 rescue SQLite3::BusyException
2794 sleep 0.1
2795 retry
2796 end
2797 true
2798 else
2799 respond '--- error: scripts may not trust scripts'
2800 false
2801 end
2802 end
2803 def Script.distrust(script_name)
2804 begin
2805 there = Lich.db.get_first_value('SELECT name FROM trusted_scripts WHERE name=?;', script_name.encode('UTF-8'))
2806 rescue SQLite3::BusyException
2807 sleep 0.1
2808 retry
2809 end
2810 if there
2811 begin
2812 Lich.db.execute('DELETE FROM trusted_scripts WHERE name=?;', script_name.encode('UTF-8'))
2813 rescue SQLite3::BusyException
2814 sleep 0.1
2815 retry
2816 end
2817 true
2818 else
2819 false
2820 end
2821 end
2822 def Script.list_trusted
2823 list = Array.new
2824 begin
2825 Lich.db.execute('SELECT name FROM trusted_scripts;').each { |name| list.push(name[0]) }
2826 rescue SQLite3::BusyException
2827 sleep 0.1
2828 retry
2829 end
2830 list
2831 end
2832 def initialize(args)
2833 @file_name = args[:file]
2834 @name = /.*[\/\\]+([^\.]+)\./.match(@file_name).captures.first
2835 if args[:args].class == String
2836 if args[:args].empty?
2837 @vars = Array.new
2838 else
2839 @vars = [ args[:args] ]
2840 @vars.concat args[:args].scan(/[^\s"]*(?<!\\)"(?:\\"|[^"])+(?<!\\)"[^\s]*|(?:\\"|[^"\s])+/).collect { |s| s.gsub(/(?<!\\)"/,'').gsub('\\"', '"') }
2841 end
2842 elsif args[:args].class == Array
2843 @vars = args[:args] # fixme: set @vars[0] ?
2844 else
2845 @vars = Array.new
2846 end
2847 @quiet = (args[:quiet] ? true : false)
2848 @downstream_buffer = LimitedArray.new
2849 @want_downstream = true
2850 @want_downstream_xml = false
2851 @want_script_output = false
2852 @upstream_buffer = LimitedArray.new
2853 @want_upstream = false
2854 @unique_buffer = LimitedArray.new
2855 @watchfor = Hash.new
2856 @at_exit_procs = Array.new
2857 @die_with = Array.new
2858 @paused = false
2859 @hidden = false
2860 @no_pause_all = false
2861 @no_kill_all = false
2862 @silent = false
2863 @safe = false
2864 @no_echo = false
2865 @match_stack_labels = Array.new
2866 @match_stack_strings = Array.new
2867 @label_order = Array.new
2868 @labels = Hash.new
2869 @killer_mutex = Mutex.new
2870 @ignore_pause = false
2871 data = nil
2872 if @file_name =~ /\.gz$/i
2873 begin
2874 Zlib::GzipReader.open(@file_name) { |f| data = f.readlines.collect { |line| line.chomp } }
2875 rescue
2876 respond "--- Lich: error reading script file (#{@file_name}): #{$!}"
2877 return nil
2878 end
2879 else
2880 begin
2881 File.open(@file_name) { |f| data = f.readlines.collect { |line| line.chomp } }
2882 rescue
2883 respond "--- Lich: error reading script file (#{@file_name}): #{$!}"
2884 return nil
2885 end
2886 end
2887 @quiet = true if data[0] =~ /^[\t\s]*#?[\t\s]*(?:quiet|hush)$/i
2888 @current_label = '~start'
2889 @labels[@current_label] = String.new
2890 @label_order.push(@current_label)
2891 for line in data
2892 if line =~ /^([\d_\w]+):$/
2893 @current_label = $1
2894 @label_order.push(@current_label)
2895 @labels[@current_label] = String.new
2896 else
2897 @labels[@current_label].concat "#{line}\n"
2898 end
2899 end
2900 data = nil
2901 @current_label = @label_order[0]
2902 @thread_group = ThreadGroup.new
2903 @@running.push(self)
2904 return self
2905 end
2906 def kill
2907 Thread.new {
2908 @killer_mutex.synchronize {
2909 if @@running.include?(self)
2910 begin
2911 @thread_group.list.dup.each { |t|
2912 unless t == Thread.current
2913 t.kill rescue nil
2914 end
2915 }
2916 @thread_group.add(Thread.current)
2917 @die_with.each { |script_name| Script.kill(script_name) }
2918 @paused = false
2919 @at_exit_procs.each { |p| report_errors { p.call } }
2920 @die_with = @at_exit_procs = @downstream_buffer = @upstream_buffer = @match_stack_labels = @match_stack_strings = nil
2921 @@running.delete(self)
2922 respond("--- Lich: #{@name} has exited.") unless @quiet
2923 GC.start
2924 rescue
2925 respond "--- Lich: error: #{$!}"
2926 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
2927 end
2928 end
2929 }
2930 }
2931 @name
2932 end
2933 def at_exit(&block)
2934 if block
2935 @at_exit_procs.push(block)
2936 return true
2937 else
2938 respond '--- warning: Script.at_exit called with no code block'
2939 return false
2940 end
2941 end
2942 def clear_exit_procs
2943 @at_exit_procs.clear
2944 true
2945 end
2946 def exit
2947 kill
2948 end
2949 def exit!
2950 @at_exit_procs.clear
2951 kill
2952 end
2953 def instance_variable_get(*a); nil; end
2954 def instance_eval(*a); nil; end
2955 def labels
2956 ($SAFE == 0) ? @labels : nil
2957 end
2958 def thread_group
2959 ($SAFE == 0) ? @thread_group : nil
2960 end
2961 def has_thread?(t)
2962 @thread_group.list.include?(t)
2963 end
2964 def pause
2965 respond "--- Lich: #{@name} paused."
2966 @paused = true
2967 end
2968 def unpause
2969 respond "--- Lich: #{@name} unpaused."
2970 @paused = false
2971 end
2972 def paused?
2973 @paused
2974 end
2975 def get_next_label
2976 if !@jump_label
2977 @current_label = @label_order[@label_order.index(@current_label)+1]
2978 else
2979 if label = @labels.keys.find { |val| val =~ /^#{@jump_label}$/ }
2980 @current_label = label
2981 elsif label = @labels.keys.find { |val| val =~ /^#{@jump_label}$/i }
2982 @current_label = label
2983 elsif label = @labels.keys.find { |val| val =~ /^labelerror$/i }
2984 @current_label = label
2985 else
2986 @current_label = nil
2987 return JUMP_ERROR
2988 end
2989 @jump_label = nil
2990 @current_label
2991 end
2992 end
2993 def clear
2994 to_return = @downstream_buffer.dup
2995 @downstream_buffer.clear
2996 to_return
2997 end
2998 def to_s
2999 @name
3000 end
3001 def gets
3002 # fixme: no xml gets
3003 if @want_downstream or @want_downstream_xml or @want_script_output
3004 sleep 0.05 while @downstream_buffer.empty?
3005 @downstream_buffer.shift
3006 else
3007 echo 'this script is set as unique but is waiting for game data...'
3008 sleep 2
3009 false
3010 end
3011 end
3012 def gets?
3013 if @want_downstream or @want_downstream_xml or @want_script_output
3014 if @downstream_buffer.empty?
3015 nil
3016 else
3017 @downstream_buffer.shift
3018 end
3019 else
3020 echo 'this script is set as unique but is waiting for game data...'
3021 sleep 2
3022 false
3023 end
3024 end
3025 def upstream_gets
3026 sleep 0.05 while @upstream_buffer.empty?
3027 @upstream_buffer.shift
3028 end
3029 def upstream_gets?
3030 if @upstream_buffer.empty?
3031 nil
3032 else
3033 @upstream_buffer.shift
3034 end
3035 end
3036 def unique_gets
3037 sleep 0.05 while @unique_buffer.empty?
3038 @unique_buffer.shift
3039 end
3040 def unique_gets?
3041 if @unique_buffer.empty?
3042 nil
3043 else
3044 @unique_buffer.shift
3045 end
3046 end
3047 def safe?
3048 @safe
3049 end
3050 def feedme_upstream
3051 @want_upstream = !@want_upstream
3052 end
3053 def match_stack_add(label,string)
3054 @match_stack_labels.push(label)
3055 @match_stack_strings.push(string)
3056 end
3057 def match_stack_clear
3058 @match_stack_labels.clear
3059 @match_stack_strings.clear
3060 end
3061end
3062
3063class ExecScript<Script
3064 @@name_exec_mutex = Mutex.new
3065 @@elevated_start = proc { |cmd_data, options|
3066 options[:trusted] = false
3067 unless new_script = ExecScript.new(cmd_data, options)
3068 respond '--- Lich: failed to start exec script'
3069 return false
3070 end
3071 new_thread = Thread.new {
3072 100.times { break if Script.current == new_script; sleep 0.01 }
3073 if script = Script.current
3074 Thread.current.priority = 1
3075 respond("--- Lich: #{script.name} active.") unless script.quiet
3076 begin
3077 script_binding = Scripting.new.script
3078 eval('script = Script.current', script_binding, script.name.to_s)
3079 proc { cmd_data.untaint; $SAFE = 3; eval(cmd_data, script_binding, script.name.to_s) }.call
3080 Script.current.kill
3081 rescue SystemExit
3082 Script.current.kill
3083 rescue SyntaxError
3084 respond "--- SyntaxError: #{$!}"
3085 respond $!.backtrace.first
3086 Lich.log "SyntaxError: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
3087 Script.current.kill
3088 rescue ScriptError
3089 respond "--- ScriptError: #{$!}"
3090 respond $!.backtrace.first
3091 Lich.log "ScriptError: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
3092 Script.current.kill
3093 rescue NoMemoryError
3094 respond "--- NoMemoryError: #{$!}"
3095 respond $!.backtrace.first
3096 Lich.log "NoMemoryError: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
3097 Script.current.kill
3098 rescue LoadError
3099 respond("--- LoadError: #{$!}")
3100 respond "--- LoadError: #{$!}"
3101 respond $!.backtrace.first
3102 Lich.log "LoadError: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
3103 Script.current.kill
3104 rescue SecurityError
3105 respond "--- SecurityError: #{$!}"
3106 respond $!.backtrace[0..1]
3107 Lich.log "SecurityError: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
3108 Script.current.kill
3109 rescue ThreadError
3110 respond "--- ThreadError: #{$!}"
3111 respond $!.backtrace.first
3112 Lich.log "ThreadError: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
3113 Script.current.kill
3114 rescue SystemStackError
3115 respond "--- SystemStackError: #{$!}"
3116 respond $!.backtrace.first
3117 Lich.log "SystemStackError: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
3118 Script.current.kill
3119 rescue Exception
3120 respond "--- Exception: #{$!}"
3121 respond $!.backtrace.first
3122 Lich.log "Exception: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
3123 Script.current.kill
3124 rescue
3125 respond "--- Lich: error: #{$!}"
3126 respond $!.backtrace.first
3127 Lich.log "Error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
3128 Script.current.kill
3129 end
3130 else
3131 respond '--- Lich: error: ExecScript.start: out of cheese'
3132 end
3133 }
3134 new_script.thread_group.add(new_thread)
3135 new_script
3136 }
3137 attr_reader :cmd_data
3138 def ExecScript.start(cmd_data, options={})
3139 options = { :quiet => true } if options == true
3140 if $SAFE > 0
3141 @@elevated_start.call(cmd_data, options)
3142 else
3143 unless new_script = ExecScript.new(cmd_data, options)
3144 respond '--- Lich: failed to start exec script'
3145 return false
3146 end
3147 new_thread = Thread.new {
3148 100.times { break if Script.current == new_script; sleep 0.01 }
3149 if script = Script.current
3150 Thread.current.priority = 1
3151 respond("--- Lich: #{script.name} active.") unless script.quiet
3152 begin
3153 if options[:trusted]
3154 script_binding = TRUSTED_SCRIPT_BINDING.call
3155 eval('script = Script.current', script_binding, script.name.to_s)
3156 eval(cmd_data, script_binding, script.name.to_s)
3157 else
3158 script_binding = Scripting.new.script
3159 eval('script = Script.current', script_binding, script.name.to_s)
3160 proc { cmd_data.untaint; $SAFE = 3; eval(cmd_data, script_binding, script.name.to_s) }.call
3161 end
3162 Script.current.kill
3163 rescue SystemExit
3164 Script.current.kill
3165 rescue SyntaxError
3166 respond "--- SyntaxError: #{$!}"
3167 respond $!.backtrace.first
3168 Lich.log "SyntaxError: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
3169 Script.current.kill
3170 rescue ScriptError
3171 respond "--- ScriptError: #{$!}"
3172 respond $!.backtrace.first
3173 Lich.log "ScriptError: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
3174 Script.current.kill
3175 rescue NoMemoryError
3176 respond "--- NoMemoryError: #{$!}"
3177 respond $!.backtrace.first
3178 Lich.log "NoMemoryError: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
3179 Script.current.kill
3180 rescue LoadError
3181 respond("--- LoadError: #{$!}")
3182 respond "--- LoadError: #{$!}"
3183 respond $!.backtrace.first
3184 Lich.log "LoadError: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
3185 Script.current.kill
3186 rescue SecurityError
3187 respond "--- SecurityError: #{$!}"
3188 respond $!.backtrace[0..1]
3189 Lich.log "SecurityError: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
3190 Script.current.kill
3191 rescue ThreadError
3192 respond "--- ThreadError: #{$!}"
3193 respond $!.backtrace.first
3194 Lich.log "ThreadError: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
3195 Script.current.kill
3196 rescue SystemStackError
3197 respond "--- SystemStackError: #{$!}"
3198 respond $!.backtrace.first
3199 Lich.log "SystemStackError: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
3200 Script.current.kill
3201 rescue Exception
3202 respond "--- Exception: #{$!}"
3203 respond $!.backtrace.first
3204 Lich.log "Exception: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
3205 Script.current.kill
3206 rescue
3207 respond "--- Lich: error: #{$!}"
3208 respond $!.backtrace.first
3209 Lich.log "Error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
3210 Script.current.kill
3211 end
3212 else
3213 respond 'start_exec_script screwed up...'
3214 end
3215 }
3216 new_script.thread_group.add(new_thread)
3217 new_script
3218 end
3219 end
3220 def initialize(cmd_data, flags=Hash.new)
3221 @cmd_data = cmd_data
3222 @vars = Array.new
3223 @downstream_buffer = LimitedArray.new
3224 @killer_mutex = Mutex.new
3225 @want_downstream = true
3226 @want_downstream_xml = false
3227 @upstream_buffer = LimitedArray.new
3228 @want_upstream = false
3229 @at_exit_procs = Array.new
3230 @watchfor = Hash.new
3231 @hidden = false
3232 @paused = false
3233 @silent = false
3234 if flags[:quiet].nil?
3235 @quiet = false
3236 else
3237 @quiet = flags[:quiet]
3238 end
3239 @safe = false
3240 @no_echo = false
3241 @thread_group = ThreadGroup.new
3242 @unique_buffer = LimitedArray.new
3243 @die_with = Array.new
3244 @no_pause_all = false
3245 @no_kill_all = false
3246 @match_stack_labels = Array.new
3247 @match_stack_strings = Array.new
3248 num = '1'; num.succ! while @@running.any? { |s| s.name == "exec#{num}" }
3249 @name = "exec#{num}"
3250 @@running.push(self)
3251 self
3252 end
3253 def get_next_label
3254 echo 'goto labels are not available in exec scripts.'
3255 nil
3256 end
3257end
3258
3259class WizardScript<Script
3260 def initialize(file_name, cli_vars=[])
3261 @name = /.*[\/\\]+([^\.]+)\./.match(file_name).captures.first
3262 @file_name = file_name
3263 @vars = Array.new
3264 @killer_mutex = Mutex.new
3265 unless cli_vars.empty?
3266 if cli_vars.is_a?(String)
3267 cli_vars = cli_vars.split(' ')
3268 end
3269 cli_vars.each_index { |idx| @vars[idx+1] = cli_vars[idx] }
3270 @vars[0] = @vars[1..-1].join(' ')
3271 cli_vars = nil
3272 end
3273 if @vars.first =~ /^quiet$/i
3274 @quiet = true
3275 @vars.shift
3276 else
3277 @quiet = false
3278 end
3279 @downstream_buffer = LimitedArray.new
3280 @want_downstream = true
3281 @want_downstream_xml = false
3282 @upstream_buffer = LimitedArray.new
3283 @want_upstream = false
3284 @unique_buffer = LimitedArray.new
3285 @at_exit_procs = Array.new
3286 @patchfor = Hash.new
3287 @die_with = Array.new
3288 @paused = false
3289 @hidden = false
3290 @no_pause_all = false
3291 @no_kill_all = false
3292 @silent = false
3293 @safe = false
3294 @no_echo = false
3295 @match_stack_labels = Array.new
3296 @match_stack_strings = Array.new
3297 @label_order = Array.new
3298 @labels = Hash.new
3299 data = nil
3300 begin
3301 Zlib::GzipReader.open(file_name) { |f| data = f.readlines.collect { |line| line.chomp } }
3302 rescue
3303 begin
3304 File.open(file_name) { |f| data = f.readlines.collect { |line| line.chomp } }
3305 rescue
3306 respond "--- Lich: error reading script file (#{file_name}): #{$!}"
3307 return nil
3308 end
3309 end
3310 @quiet = true if data[0] =~ /^[\t\s]*#?[\t\s]*(?:quiet|hush)$/i
3311
3312 counter_action = {
3313 'add' => '+',
3314 'sub' => '-',
3315 'subtract' => '-',
3316 'multiply' => '*',
3317 'divide' => '/',
3318 'set' => ''
3319 }
3320
3321 setvars = Array.new
3322 data.each { |line| setvars.push($1) if line =~ /[\s\t]*setvariable\s+([^\s\t]+)[\s\t]/i and not setvars.include?($1) }
3323 has_counter = data.find { |line| line =~ /%c/i }
3324 has_save = data.find { |line| line =~ /%s/i }
3325 has_nextroom = data.find { |line| line =~ /nextroom/i }
3326
3327 fixstring = proc { |str|
3328 while not setvars.empty? and str =~ /%(#{setvars.join('|')})%/io
3329 str.gsub!('%' + $1 + '%', '#{' + $1.downcase + '}')
3330 end
3331 str.gsub!(/%c(?:%)?/i, '#{c}')
3332 str.gsub!(/%s(?:%)?/i, '#{sav}')
3333 while str =~ /%([0-9])(?:%)?/
3334 str.gsub!(/%#{$1}(?:%)?/, '#{script.vars[' + $1 + ']}')
3335 end
3336 str
3337 }
3338
3339 fixline = proc { |line|
3340 if line =~ /^[\s\t]*[A-Za-z0-9_\-']+:/i
3341 line = line.downcase.strip
3342 elsif line =~ /^([\s\t]*)counter\s+(add|sub|subtract|divide|multiply|set)\s+([0-9]+)/i
3343 line = "#{$1}c #{counter_action[$2]}= #{$3}"
3344 elsif line =~ /^([\s\t]*)counter\s+(add|sub|subtract|divide|multiply|set)\s+(.*)/i
3345 indent, action, arg = $1, $2, $3
3346 line = "#{indent}c #{counter_action[action]}= #{fixstring.call(arg.inspect)}.to_i"
3347 elsif line =~ /^([\s\t]*)save[\s\t]+"?(.*?)"?[\s\t]*$/i
3348 indent, arg = $1, $2
3349 line = "#{indent}sav = #{fixstring.call(arg.inspect)}"
3350 elsif line =~ /^([\s\t]*)echo[\s\t]+(.+)/i
3351 indent, arg = $1, $2
3352 line = "#{indent}echo #{fixstring.call(arg.inspect)}"
3353 elsif line =~ /^([\s\t]*)waitfor[\s\t]+(.+)/i
3354 indent, arg = $1, $2
3355 line = "#{indent}waitfor #{fixstring.call(Regexp.escape(arg).inspect.gsub("\\\\ ", ' '))}"
3356 elsif line =~ /^([\s\t]*)put[\s\t]+\.(.+)$/i
3357 indent, arg = $1, $2
3358 if arg.include?(' ')
3359 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"
3360 else
3361 line = "#{indent}start_script(#{Regexp.escape(fixstring.call(arg.inspect))})\n#{indent}exit"
3362 end
3363 elsif line =~ /^([\s\t]*)put[\s\t]+;(.+)$/i
3364 indent, arg = $1, $2
3365 if arg.include?(' ')
3366 line = "#{indent}start_script(#{Regexp.escape(fixstring.call(arg.split[0].inspect))}, #{fixstring.call(arg.split[1..-1].join(' ').scan(/"[^"]+"|[^"\s]+/).inspect)})"
3367 else
3368 line = "#{indent}start_script(#{Regexp.escape(fixstring.call(arg.inspect))})"
3369 end
3370 elsif line =~ /^([\s\t]*)(put|move)[\s\t]+(.+)/i
3371 indent, cmd, arg = $1, $2, $3
3372 line = "#{indent}waitrt?\n#{indent}clear\n#{indent}#{cmd.downcase} #{fixstring.call(arg.inspect)}"
3373 elsif line =~ /^([\s\t]*)goto[\s\t]+(.+)/i
3374 indent, arg = $1, $2
3375 line = "#{indent}goto #{fixstring.call(arg.inspect).downcase}"
3376 elsif line =~ /^([\s\t]*)waitforre[\s\t]+(.+)/i
3377 indent, arg = $1, $2
3378 line = "#{indent}waitforre #{arg}"
3379 elsif line =~ /^([\s\t]*)pause[\s\t]*(.*)/i
3380 indent, arg = $1, $2
3381 arg = '1' if arg.empty?
3382 arg = '0'+arg.strip if arg.strip =~ /^\.[0-9]+$/
3383 line = "#{indent}pause #{arg}"
3384 elsif line =~ /^([\s\t]*)match[\s\t]+([^\s\t]+)[\s\t]+(.+)/i
3385 indent, label, arg = $1, $2, $3
3386 line = "#{indent}match #{fixstring.call(label.inspect).downcase}, #{fixstring.call(Regexp.escape(arg).inspect.gsub("\\\\ ", ' '))}"
3387 elsif line =~ /^([\s\t]*)matchre[\s\t]+([^\s\t]+)[\s\t]+(.+)/i
3388 indent, label, regex = $1, $2, $3
3389 line = "#{indent}matchre #{fixstring.call(label.inspect).downcase}, #{regex}"
3390 elsif line =~ /^([\s\t]*)setvariable[\s\t]+([^\s\t]+)[\s\t]+(.+)/i
3391 indent, var, arg = $1, $2, $3
3392 line = "#{indent}#{var.downcase} = #{fixstring.call(arg.inspect)}"
3393 elsif line =~ /^([\s\t]*)deletevariable[\s\t]+(.+)/i
3394 line = "#{$1}#{$2.downcase} = nil"
3395 elsif line =~ /^([\s\t]*)(wait|nextroom|exit|echo)\b/i
3396 line = "#{$1}#{$2.downcase}"
3397 elsif line =~ /^([\s\t]*)matchwait\b/i
3398 line = "#{$1}matchwait"
3399 elsif line =~ /^([\s\t]*)if_([0-9])[\s\t]+(.*)/i
3400 indent, num, stuff = $1, $2, $3
3401 line = "#{indent}if script.vars[#{num}]\n#{indent}\t#{fixline.call($3)}\n#{indent}end"
3402 elsif line =~ /^([\s\t]*)shift\b/i
3403 line = "#{$1}script.vars.shift"
3404 else
3405 respond "--- Lich: unknown line: #{line}"
3406 line = '#' + line
3407 end
3408 }
3409
3410 lich_block = false
3411
3412 data.each_index { |idx|
3413 if lich_block
3414 if data[idx] =~ /\}[\s\t]*LICH[\s\t]*$/
3415 data[idx] = data[idx].sub(/\}[\s\t]*LICH[\s\t]*$/, '')
3416 lich_block = false
3417 else
3418 next
3419 end
3420 elsif data[idx] =~ /^[\s\t]*#|^[\s\t]*$/
3421 next
3422 elsif data[idx] =~ /^[\s\t]*LICH[\s\t]*\{/
3423 data[idx] = data[idx].sub(/LICH[\s\t]*\{/, '')
3424 if data[idx] =~ /\}[\s\t]*LICH[\s\t]*$/
3425 data[idx] = data[idx].sub(/\}[\s\t]*LICH[\s\t]*$/, '')
3426 else
3427 lich_block = true
3428 end
3429 else
3430 data[idx] = fixline.call(data[idx])
3431 end
3432 }
3433
3434 if has_counter or has_save or has_nextroom
3435 data.each_index { |idx|
3436 next if data[idx] =~ /^[\s\t]*#/
3437 data.insert(idx, '')
3438 data.insert(idx, 'c = 0') if has_counter
3439 data.insert(idx, "sav = Settings['sav'] || String.new\nbefore_dying { Settings['sav'] = sav }") if has_save
3440 data.insert(idx, "def nextroom\n\troom_count = XMLData.room_count\n\twait_while { room_count == XMLData.room_count }\nend") if has_nextroom
3441 data.insert(idx, '')
3442 break
3443 }
3444 end
3445
3446 @current_label = '~start'
3447 @labels[@current_label] = String.new
3448 @label_order.push(@current_label)
3449 for line in data
3450 if line =~ /^([\d_\w]+):$/
3451 @current_label = $1
3452 @label_order.push(@current_label)
3453 @labels[@current_label] = String.new
3454 else
3455 @labels[@current_label] += "#{line}\n"
3456 end
3457 end
3458 data = nil
3459 @current_label = @label_order[0]
3460 @thread_group = ThreadGroup.new
3461 @@running.push(self)
3462 return self
3463 end
3464end
3465
3466class Watchfor
3467 def initialize(line, theproc=nil, &block)
3468 return nil unless script = Script.current
3469 if line.class == String
3470 line = Regexp.new(Regexp.escape(line))
3471 elsif line.class != Regexp
3472 echo 'watchfor: no string or regexp given'
3473 return nil
3474 end
3475 if block.nil?
3476 if theproc.respond_to? :call
3477 block = theproc
3478 else
3479 echo 'watchfor: no block or proc given'
3480 return nil
3481 end
3482 end
3483 script.watchfor[line] = block
3484 end
3485 def Watchfor.clear
3486 script.watchfor = Hash.new
3487 end
3488end
3489
3490class Map
3491 @@loaded = false
3492 @@load_mutex = Mutex.new
3493 @@list ||= Array.new
3494 @@tags ||= Array.new
3495 @@current_room_mutex = Mutex.new
3496 @@current_room_id ||= 0
3497 @@current_room_count ||= -1
3498 @@fuzzy_room_mutex = Mutex.new
3499 @@fuzzy_room_id ||= 0
3500 @@fuzzy_room_count ||= -1
3501 @@current_location ||= nil
3502 @@current_location_count ||= -1
3503 @@elevated_load = proc { Map.load }
3504 @@elevated_load_dat = proc { Map.load_dat }
3505 @@elevated_load_xml = proc { Map.load_xml }
3506 @@elevated_save = proc { Map.save }
3507 @@elevated_save_xml = proc { Map.save_xml }
3508 attr_reader :id
3509 attr_accessor :title, :description, :paths, :location, :climate, :terrain, :wayto, :timeto, :image, :image_coords, :tags, :check_location, :unique_loot
3510 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)
3511 @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
3512 @@list[@id] = self
3513 end
3514 def outside?
3515 @paths.first =~ /Obvious paths:/
3516 end
3517 def to_i
3518 @id
3519 end
3520 def to_s
3521 "##{@id}:\n#{@title[-1]}\n#{@description[-1]}\n#{@paths[-1]}"
3522 end
3523 def inspect
3524 self.instance_variables.collect { |var| var.to_s + "=" + self.instance_variable_get(var).inspect }.join("\n")
3525 end
3526 def Map.get_free_id
3527 Map.load unless @@loaded
3528 free_id = 0
3529 until @@list[free_id].nil?
3530 free_id += 1
3531 end
3532 free_id
3533 end
3534 def Map.list
3535 Map.load unless @@loaded
3536 @@list
3537 end
3538 def Map.[](val)
3539 Map.load unless @@loaded
3540 if (val.class == Fixnum) or (val.class == Bignum) or val =~ /^[0-9]+$/
3541 @@list[val.to_i]
3542 else
3543 chkre = /#{val.strip.sub(/\.$/, '').gsub(/\.(?:\.\.)?/, '|')}/i
3544 chk = /#{Regexp.escape(val.strip)}/i
3545 @@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 } }
3546 end
3547 end
3548 def Map.get_location
3549 unless XMLData.room_count == @@current_location_count
3550 if script = Script.current
3551 save_want_downstream = script.want_downstream
3552 script.want_downstream = true
3553 waitrt?
3554 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\.$/
3555 script.want_downstream = save_want_downstream
3556 @@current_location_count = XMLData.room_count
3557 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\.$/
3558 @@current_location = false
3559 else
3560 @@current_location = /^You carefully survey your surroundings and guess that your current location is (.*?) or somewhere close to it\.$/.match(location_result).captures.first
3561 end
3562 else
3563 nil
3564 end
3565 end
3566 @@current_location
3567 end
3568 def Map.current
3569 Map.load unless @@loaded
3570 if script = Script.current
3571 @@current_room_mutex.synchronize {
3572 if XMLData.room_count == @@current_room_count
3573 if @@current_room_id.nil?
3574 return nil
3575 else
3576 return @@list[@@current_room_id]
3577 end
3578 else
3579 peer_history = Hash.new
3580 need_set_desc_off = false
3581 check_peer_tag = proc { |r|
3582 begin
3583 script.ignore_pause = true
3584 peer_room_count = XMLData.room_count
3585 if peer_tag = r.tags.find { |tag| tag =~ /^(set desc on; )?peer [a-z]+ =~ \/.+\/$/ }
3586 good = false
3587 need_desc, peer_direction, peer_requirement = /^(set desc on; )?peer ([a-z]+) =~ \/(.+)\/$/.match(peer_tag).captures
3588 need_desc = need_desc ? true : false
3589 if peer_history[peer_room_count][peer_direction][need_desc].nil?
3590 if need_desc
3591 unless last_roomdesc = $_SERVERBUFFER_.reverse.find { |line| line =~ /<style id="roomDesc"\/>/ } and (last_roomdesc =~ /<style id="roomDesc"\/>[^<]/)
3592 put 'set description on'
3593 need_set_desc_off = true
3594 end
3595 end
3596 save_want_downstream = script.want_downstream
3597 script.want_downstream = true
3598 squelch_started = false
3599 squelch_proc = proc { |server_string|
3600 if squelch_started
3601 if server_string =~ /<prompt/
3602 DownstreamHook.remove('squelch-peer')
3603 end
3604 nil
3605 elsif server_string =~ /^You peer/
3606 squelch_started = true
3607 nil
3608 else
3609 server_string
3610 end
3611 }
3612 DownstreamHook.add('squelch-peer', squelch_proc)
3613 result = dothistimeout "peer #{peer_direction}", 3, /^You peer|^\[Usage: PEER/
3614 if result =~ /^You peer/
3615 peer_results = Array.new
3616 5.times {
3617 if line = get?
3618 peer_results.push line
3619 break if line =~ /^Obvious/
3620 end
3621 }
3622 if XMLData.room_count == peer_room_count
3623 peer_history[peer_room_count] ||= Hash.new
3624 peer_history[peer_room_count][peer_direction] ||= Hash.new
3625 if need_desc
3626 peer_history[peer_room_count][peer_direction][true] = peer_results
3627 peer_history[peer_room_count][peer_direction][false] = peer_results
3628 else
3629 peer_history[peer_room_count][peer_direction][false] = peer_results
3630 end
3631 end
3632 end
3633 script.want_downstream = save_want_downstream
3634 end
3635 if peer_history[peer_room_count][peer_direction][need_desc].any? { |line| line =~ /#{peer_requirement}/ }
3636 good = true
3637 else
3638 good = false
3639 end
3640 else
3641 good = true
3642 end
3643 ensure
3644 script.ignore_pause = false
3645 end
3646 good
3647 }
3648 begin
3649 1.times {
3650 @@current_room_count = XMLData.room_count
3651 foggy_exits = (XMLData.room_exits_string =~ /^Obvious (?:exits|paths): obscured by a thick fog$/)
3652 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) }
3653 redo unless @@current_room_count == XMLData.room_count
3654 @@current_room_id = room.id
3655 return room
3656 else
3657 redo unless @@current_room_count == XMLData.room_count
3658 desc_regex = /#{Regexp.escape(XMLData.room_description.strip.sub(/\.+$/, '')).gsub(/\\\.(?:\\\.\\\.)?/, '|')}/
3659 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) }
3660 redo unless @@current_room_count == XMLData.room_count
3661 @@current_room_id = room.id
3662 return room
3663 else
3664 redo unless @@current_room_count == XMLData.room_count
3665 @@current_room_id = nil
3666 return nil
3667 end
3668 end
3669 }
3670 ensure
3671 put 'set description off' if need_set_desc_off
3672 end
3673 end
3674 }
3675 else
3676 @@fuzzy_room_mutex.synchronize {
3677 if XMLData.room_count == @@current_room_count
3678 if @@current_room_id.nil?
3679 return nil
3680 else
3681 return @@list[@@current_room_id]
3682 end
3683 elsif XMLData.room_count == @@fuzzy_room_count
3684 if @@fuzzy_room_id.nil?
3685 return nil
3686 else
3687 return @@list[@@fuzzy_room_id]
3688 end
3689 else
3690 1.times {
3691 @@fuzzy_room_count = XMLData.room_count
3692 foggy_exits = (XMLData.room_exits_string =~ /^Obvious (?:exits|paths): obscured by a thick fog$/)
3693 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) })
3694 redo unless @@fuzzy_room_count == XMLData.room_count
3695 if room.tags.any? { |tag| tag =~ /^(set desc on; )?peer [a-z]+ =~ \/.+\/$/ }
3696 @@fuzzy_room_id = nil
3697 return nil
3698 else
3699 @@fuzzy_room_id = room.id
3700 return room
3701 end
3702 else
3703 redo unless @@fuzzy_room_count == XMLData.room_count
3704 desc_regex = /#{Regexp.escape(XMLData.room_description.strip.sub(/\.+$/, '')).gsub(/\\\.(?:\\\.\\\.)?/, '|')}/
3705 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) }
3706 redo unless @@fuzzy_room_count == XMLData.room_count
3707 if room.tags.any? { |tag| tag =~ /^(set desc on; )?peer [a-z]+ =~ \/.+\/$/ }
3708 @@fuzzy_room_id = nil
3709 return nil
3710 else
3711 @@fuzzy_room_id = room.id
3712 return room
3713 end
3714 else
3715 redo unless @@fuzzy_room_count == XMLData.room_count
3716 @@fuzzy_room_id = nil
3717 return nil
3718 end
3719 end
3720 }
3721 end
3722 }
3723 end
3724 end
3725 def Map.current_or_new
3726 return nil unless Script.current
3727 if XMLData.game =~ /DR/
3728 @@current_room_count = -1
3729 @@fuzzy_room_count = -1
3730 Map.current || Map.new(Map.get_free_id, [ XMLData.room_title ], [ XMLData.room_description.strip ], [ XMLData.room_exits_string.strip ])
3731 else
3732 check_peer_tag = proc { |r|
3733 if peer_tag = r.tags.find { |tag| tag =~ /^(set desc on; )?peer [a-z]+ =~ \/.+\/$/ }
3734 good = false
3735 need_desc, peer_direction, peer_requirement = /^(set desc on; )?peer ([a-z]+) =~ \/(.+)\/$/.match(peer_tag).captures
3736 if need_desc
3737 unless last_roomdesc = $_SERVERBUFFER_.reverse.find { |line| line =~ /<style id="roomDesc"\/>/ } and (last_roomdesc =~ /<style id="roomDesc"\/>[^<]/)
3738 put 'set description on'
3739 end
3740 end
3741 script = Script.current
3742 save_want_downstream = script.want_downstream
3743 script.want_downstream = true
3744 squelch_started = false
3745 squelch_proc = proc { |server_string|
3746 if squelch_started
3747 if server_string =~ /<prompt/
3748 DownstreamHook.remove('squelch-peer')
3749 end
3750 nil
3751 elsif server_string =~ /^You peer/
3752 squelch_started = true
3753 nil
3754 else
3755 server_string
3756 end
3757 }
3758 DownstreamHook.add('squelch-peer', squelch_proc)
3759 result = dothistimeout "peer #{peer_direction}", 3, /^You peer|^\[Usage: PEER/
3760 if result =~ /^You peer/
3761 peer_results = Array.new
3762 5.times {
3763 if line = get?
3764 peer_results.push line
3765 break if line =~ /^Obvious/
3766 end
3767 }
3768 if peer_results.any? { |line| line =~ /#{peer_requirement}/ }
3769 good = true
3770 end
3771 end
3772 script.want_downstream = save_want_downstream
3773 else
3774 good = true
3775 end
3776 good
3777 }
3778 current_location = Map.get_location
3779 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) }
3780 return room
3781 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) }
3782 room.location = current_location
3783 return room
3784 else
3785 title = [ XMLData.room_title ]
3786 description = [ XMLData.room_description.strip ]
3787 paths = [ XMLData.room_exits_string.strip ]
3788 room = Map.new(Map.get_free_id, title, description, paths, current_location)
3789 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')) }
3790 if identical_rooms.length > 0
3791 room.check_location = true
3792 identical_rooms.each { |r| r.check_location = true }
3793 end
3794 return room
3795 end
3796 end
3797 end
3798 def Map.tags
3799 Map.load unless @@loaded
3800 if @@tags.empty?
3801 @@list.each { |r| r.tags.each { |t| @@tags.push(t) unless @@tags.include?(t) } }
3802 end
3803 @@tags.dup
3804 end
3805 def Map.clear
3806 @@load_mutex.synchronize {
3807 @@list.clear
3808 @@tags.clear
3809 @@loaded = false
3810 GC.start
3811 }
3812 true
3813 end
3814 def Map.reload
3815 Map.clear
3816 Map.load
3817 end
3818 def Map.load(filename=nil)
3819 if $SAFE == 0
3820 if filename.nil?
3821 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
3822 else
3823 file_list = [ filename ]
3824 end
3825 if file_list.empty?
3826 respond "--- Lich: error: no map database found"
3827 return false
3828 end
3829 while filename = file_list.shift
3830 if filename =~ /\.xml$/
3831 if Map.load_xml(filename)
3832 return true
3833 end
3834 else
3835 if Map.load_dat(filename)
3836 return true
3837 end
3838 end
3839 end
3840 return false
3841 else
3842 @@elevated_load.call
3843 end
3844 end
3845 def Map.load_dat(filename=nil)
3846 if $SAFE == 0
3847 @@load_mutex.synchronize {
3848 if @@loaded
3849 return true
3850 else
3851 if filename.nil?
3852 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
3853 else
3854 file_list = [ filename ]
3855 end
3856 if file_list.empty?
3857 respond "--- Lich: error: no map database found"
3858 return false
3859 end
3860 error = false
3861 while filename = file_list.shift
3862 begin
3863 @@list = File.open(filename, 'rb') { |f| Marshal.load(f.read) }
3864 respond "--- loaded #{filename}" if error
3865 @@loaded = true
3866 return true
3867 rescue
3868 error = true
3869 if file_list.empty?
3870 respond "--- Lich: error: failed to load #{filename}: #{$!}"
3871 else
3872 respond "--- warning: failed to load #{filename}: #{$!}"
3873 end
3874 end
3875 end
3876 return false
3877 end
3878 }
3879 else
3880 @@elevated_load_dat.call
3881 end
3882 end
3883 def Map.load_xml(filename="#{DATA_DIR}/#{XMLData.game}/map.xml")
3884 if $SAFE == 0
3885 @@load_mutex.synchronize {
3886 if @@loaded
3887 return true
3888 else
3889 unless File.exists?(filename)
3890 raise Exception.exception("MapDatabaseError"), "Fatal error: file `#{filename}' does not exist!"
3891 end
3892 missing_end = false
3893 current_tag = nil
3894 current_attributes = nil
3895 room = nil
3896 buffer = String.new
3897 unescape = { 'lt' => '<', 'gt' => '>', 'quot' => '"', 'apos' => "'", 'amp' => '&' }
3898 tag_start = proc { |element,attributes|
3899 current_tag = element
3900 current_attributes = attributes
3901 if element == 'room'
3902 room = Hash.new
3903 room['id'] = attributes['id'].to_i
3904 room['location'] = attributes['location']
3905 room['climate'] = attributes['climate']
3906 room['terrain'] = attributes['terrain']
3907 room['wayto'] = Hash.new
3908 room['timeto'] = Hash.new
3909 room['title'] = Array.new
3910 room['description'] = Array.new
3911 room['paths'] = Array.new
3912 room['tags'] = Array.new
3913 room['unique_loot'] = Array.new
3914 elsif element =~ /^(?:image|tsoran)$/ and attributes['name'] and attributes['x'] and attributes['y'] and attributes['size']
3915 room['image'] = attributes['name']
3916 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) ]
3917 elsif (element == 'image') and attributes['name'] and attributes['coords'] and (attributes['coords'] =~ /[0-9]+,[0-9]+,[0-9]+,[0-9]+/)
3918 room['image'] = attributes['name']
3919 room['image_coords'] = attributes['coords'].split(',').collect { |num| num.to_i }
3920 elsif element == 'map'
3921 missing_end = true
3922 end
3923 }
3924 text = proc { |text_string|
3925 if current_tag == 'tag'
3926 room['tags'].push(text_string)
3927 elsif current_tag =~ /^(?:title|description|paths|tag|unique_loot)$/
3928 room[current_tag].push(text_string)
3929 elsif current_tag == 'exit' and current_attributes['target']
3930 if current_attributes['type'].downcase == 'string'
3931 room['wayto'][current_attributes['target']] = text_string
3932 elsif
3933 room['wayto'][current_attributes['target']] = StringProc.new(text_string)
3934 end
3935 if current_attributes['cost'] =~ /^[0-9\.]+$/
3936 room['timeto'][current_attributes['target']] = current_attributes['cost'].to_f
3937 elsif current_attributes['cost'].length > 0
3938 room['timeto'][current_attributes['target']] = StringProc.new(current_attributes['cost'])
3939 else
3940 room['timeto'][current_attributes['target']] = 0.2
3941 end
3942 end
3943 }
3944 tag_end = proc { |element|
3945 if element == 'room'
3946 room['unique_loot'] = nil if room['unique_loot'].empty?
3947 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'])
3948 elsif element == 'map'
3949 missing_end = false
3950 end
3951 current_tag = nil
3952 }
3953 begin
3954 File.open(filename) { |file|
3955 while line = file.gets
3956 buffer.concat(line)
3957 # fixme: remove (?=<) ?
3958 while str = buffer.slice!(/^<([^>]+)><\/\1>|^[^<]+(?=<)|^<[^<]+>/)
3959 if str[0,1] == '<'
3960 if str[1,1] == '/'
3961 element = /^<\/([^\s>\/]+)/.match(str).captures.first
3962 tag_end.call(element)
3963 else
3964 if str =~ /^<([^>]+)><\/\1>/
3965 element = $1
3966 tag_start.call(element)
3967 text.call('')
3968 tag_end.call(element)
3969 else
3970 element = /^<([^\s>\/]+)/.match(str).captures.first
3971 attributes = Hash.new
3972 str.scan(/([A-z][A-z0-9_\-]*)=(["'])(.*?)\2/).each { |attr| attributes[attr[0]] = attr[2].gsub(/&(#{unescape.keys.join('|')});/) { unescape[$1] } }
3973 tag_start.call(element, attributes)
3974 tag_end.call(element) if str[-2,1] == '/'
3975 end
3976 end
3977 else
3978 text.call(str.gsub(/&(#{unescape.keys.join('|')});/) { unescape[$1] })
3979 end
3980 end
3981 end
3982 }
3983 if missing_end
3984 respond "--- Lich: error: failed to load #{filename}: unexpected end of file"
3985 return false
3986 end
3987 @@tags.clear
3988 @@loaded = true
3989 return true
3990 rescue
3991 respond "--- Lich: error: failed to load #{filename}: #{$!}"
3992 return false
3993 end
3994 end
3995 }
3996 else
3997 @@elevated_load_xml.call
3998 end
3999 end
4000 def Map.save(filename="#{DATA_DIR}/#{XMLData.game}/map-#{Time.now.to_i}.dat")
4001 if $SAFE == 0
4002 if File.exists?(filename)
4003 respond "--- Backing up map database"
4004 begin
4005 # fixme: does this work on all platforms? File.rename(filename, "#{filename}.bak")
4006 File.open(filename, 'rb') { |infile|
4007 File.open("#{filename}.bak", 'wb') { |outfile|
4008 outfile.write(infile.read)
4009 }
4010 }
4011 rescue
4012 respond "--- Lich: error: #{$!}"
4013 end
4014 end
4015 begin
4016 File.open(filename, 'wb') { |f| f.write(Marshal.dump(@@list)) }
4017 @@tags.clear
4018 respond "--- Map database saved"
4019 rescue
4020 respond "--- Lich: error: #{$!}"
4021 end
4022 else
4023 @@elevated_save.call
4024 end
4025 end
4026 def Map.save_xml(filename="#{DATA_DIR}/#{XMLData.game}/map-#{Time.now.to_i}.xml")
4027 if $SAFE == 0
4028 if File.exists?(filename)
4029 respond "File exists! Backing it up before proceeding..."
4030 begin
4031 File.open(filename, 'rb') { |infile|
4032 File.open("#{filename}.bak", "wb") { |outfile|
4033 outfile.write(infile.read)
4034 }
4035 }
4036 rescue
4037 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
4038 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
4039 end
4040 end
4041 begin
4042 escape = { '<' => '<', '>' => '>', '"' => '"', "'" => "'", '&' => '&' }
4043 File.open(filename, 'w') { |file|
4044 file.write "<map>\n"
4045 @@list.each { |room|
4046 next if room == nil
4047 if room.location
4048 location = " location=#{(room.location.gsub(/(<|>|"|'|&)/) { escape[$1] }).inspect}"
4049 else
4050 location = ''
4051 end
4052 if room.climate
4053 climate = " climate=#{(room.climate.gsub(/(<|>|"|'|&)/) { escape[$1] }).inspect}"
4054 else
4055 climate = ''
4056 end
4057 if room.terrain
4058 terrain = " terrain=#{(room.terrain.gsub(/(<|>|"|'|&)/) { escape[$1] }).inspect}"
4059 else
4060 terrain = ''
4061 end
4062 file.write " <room id=\"#{room.id}\"#{location}#{climate}#{terrain}>\n"
4063 room.title.each { |title| file.write " <title>#{title.gsub(/(<|>|"|'|&)/) { escape[$1] }}</title>\n" }
4064 room.description.each { |desc| file.write " <description>#{desc.gsub(/(<|>|"|'|&)/) { escape[$1] }}</description>\n" }
4065 room.paths.each { |paths| file.write " <paths>#{paths.gsub(/(<|>|"|'|&)/) { escape[$1] }}</paths>\n" }
4066 room.tags.each { |tag| file.write " <tag>#{tag.gsub(/(<|>|"|'|&)/) { escape[$1] }}</tag>\n" }
4067 room.unique_loot.to_a.each { |loot| file.write " <unique_loot>#{loot.gsub(/(<|>|"|'|&)/) { escape[$1] }}</unique_loot>\n" }
4068 file.write " <image name=\"#{room.image.gsub(/(<|>|"|'|&)/) { escape[$1] }}\" coords=\"#{room.image_coords.join(',')}\" />\n" if room.image and room.image_coords
4069 room.wayto.keys.each { |target|
4070 if room.timeto[target].class == Proc
4071 cost = " cost=\"#{room.timeto[target]._dump.gsub(/(<|>|"|'|&)/) { escape[$1] }}\""
4072 elsif room.timeto[target]
4073 cost = " cost=\"#{room.timeto[target]}\""
4074 else
4075 cost = ''
4076 end
4077 if room.wayto[target].class == Proc
4078 file.write " <exit target=\"#{target}\" type=\"Proc\"#{cost}>#{room.wayto[target]._dump.gsub(/(<|>|"|'|&)/) { escape[$1] }}</exit>\n"
4079 else
4080 file.write " <exit target=\"#{target}\" type=\"#{room.wayto[target].class}\"#{cost}>#{room.wayto[target].gsub(/(<|>|"|'|&)/) { escape[$1] }}</exit>\n"
4081 end
4082 }
4083 file.write " </room>\n"
4084 }
4085 file.write "</map>\n"
4086 }
4087 @@tags.clear
4088 respond "--- map database saved to: #{filename}"
4089 rescue
4090 respond $!
4091 end
4092 GC.start
4093 else
4094 @@elevated_save_xml.call
4095 end
4096 end
4097 def Map.estimate_time(array)
4098 Map.load unless @@loaded
4099 unless array.class == Array
4100 raise Exception.exception("MapError"), "Map.estimate_time was given something not an array!"
4101 end
4102 time = 0.to_f
4103 until array.length < 2
4104 room = array.shift
4105 if t = Map[room].timeto[array.first.to_s]
4106 if t.class == Proc
4107 time += t.call.to_f
4108 else
4109 time += t.to_f
4110 end
4111 else
4112 time += "0.2".to_f
4113 end
4114 end
4115 time
4116 end
4117 def Map.dijkstra(source, destination=nil)
4118 if source.class == Map
4119 source.dijkstra(destination)
4120 elsif room = Map[source]
4121 room.dijkstra(destination)
4122 else
4123 echo "Map.dijkstra: error: invalid source room"
4124 nil
4125 end
4126 end
4127 def dijkstra(destination=nil)
4128 begin
4129 Map.load unless @@loaded
4130 source = @id
4131 visited = Array.new
4132 shortest_distances = Array.new
4133 previous = Array.new
4134 pq = [ source ]
4135 pq_push = proc { |val|
4136 for i in 0...pq.size
4137 if shortest_distances[val] <= shortest_distances[pq[i]]
4138 pq.insert(i, val)
4139 break
4140 end
4141 end
4142 pq.push(val) if i.nil? or (i == pq.size-1)
4143 }
4144 visited[source] = true
4145 shortest_distances[source] = 0
4146 if destination.nil?
4147 until pq.size == 0
4148 v = pq.shift
4149 visited[v] = true
4150 @@list[v].wayto.keys.each { |adj_room|
4151 adj_room_i = adj_room.to_i
4152 unless visited[adj_room_i]
4153 if @@list[v].timeto[adj_room].class == Proc
4154 nd = @@list[v].timeto[adj_room].call
4155 else
4156 nd = @@list[v].timeto[adj_room]
4157 end
4158 if nd
4159 nd += shortest_distances[v]
4160 if shortest_distances[adj_room_i].nil? or (shortest_distances[adj_room_i] > nd)
4161 shortest_distances[adj_room_i] = nd
4162 previous[adj_room_i] = v
4163 pq_push.call(adj_room_i)
4164 end
4165 end
4166 end
4167 }
4168 end
4169 elsif destination.class == Fixnum
4170 until pq.size == 0
4171 v = pq.shift
4172 break if v == destination
4173 visited[v] = true
4174 @@list[v].wayto.keys.each { |adj_room|
4175 adj_room_i = adj_room.to_i
4176 unless visited[adj_room_i]
4177 if @@list[v].timeto[adj_room].class == Proc
4178 nd = @@list[v].timeto[adj_room].call
4179 else
4180 nd = @@list[v].timeto[adj_room]
4181 end
4182 if nd
4183 nd += shortest_distances[v]
4184 if shortest_distances[adj_room_i].nil? or (shortest_distances[adj_room_i] > nd)
4185 shortest_distances[adj_room_i] = nd
4186 previous[adj_room_i] = v
4187 pq_push.call(adj_room_i)
4188 end
4189 end
4190 end
4191 }
4192 end
4193 elsif destination.class == Array
4194 dest_list = destination.collect { |dest| dest.to_i }
4195 until pq.size == 0
4196 v = pq.shift
4197 break if dest_list.include?(v) and (shortest_distances[v] < 20)
4198 visited[v] = true
4199 @@list[v].wayto.keys.each { |adj_room|
4200 adj_room_i = adj_room.to_i
4201 unless visited[adj_room_i]
4202 if @@list[v].timeto[adj_room].class == Proc
4203 nd = @@list[v].timeto[adj_room].call
4204 else
4205 nd = @@list[v].timeto[adj_room]
4206 end
4207 if nd
4208 nd += shortest_distances[v]
4209 if shortest_distances[adj_room_i].nil? or (shortest_distances[adj_room_i] > nd)
4210 shortest_distances[adj_room_i] = nd
4211 previous[adj_room_i] = v
4212 pq_push.call(adj_room_i)
4213 end
4214 end
4215 end
4216 }
4217 end
4218 end
4219 return previous, shortest_distances
4220 rescue
4221 echo "Map.dijkstra: error: #{$!}"
4222 respond $!.backtrace
4223 nil
4224 end
4225 end
4226 def Map.findpath(source, destination)
4227 if source.class == Map
4228 source.path_to(destination)
4229 elsif room = Map[source]
4230 room.path_to(destination)
4231 else
4232 echo "Map.findpath: error: invalid source room"
4233 nil
4234 end
4235 end
4236 def path_to(destination)
4237 Map.load unless @@loaded
4238 destination = destination.to_i
4239 previous, shortest_distances = dijkstra(destination)
4240 return nil unless previous[destination]
4241 path = [ destination ]
4242 path.push(previous[path[-1]]) until previous[path[-1]] == @id
4243 path.reverse!
4244 path.pop
4245 return path
4246 end
4247 def find_nearest_by_tag(tag_name)
4248 target_list = Array.new
4249 @@list.each { |room| target_list.push(room.id) if room.tags.include?(tag_name) }
4250 previous, shortest_distances = Map.dijkstra(@id, target_list)
4251 if target_list.include?(@id)
4252 @id
4253 else
4254 target_list.delete_if { |room_num| shortest_distances[room_num].nil? }
4255 target_list.sort { |a,b| shortest_distances[a] <=> shortest_distances[b] }.first
4256 end
4257 end
4258 def find_all_nearest_by_tag(tag_name)
4259 target_list = Array.new
4260 @@list.each { |room| target_list.push(room.id) if room.tags.include?(tag_name) }
4261 previous, shortest_distances = Map.dijkstra(@id)
4262 target_list.delete_if { |room_num| shortest_distances[room_num].nil? }
4263 target_list.sort { |a,b| shortest_distances[a] <=> shortest_distances[b] }
4264 end
4265 def find_nearest(target_list)
4266 target_list = target_list.collect { |num| num.to_i }
4267 if target_list.include?(@id)
4268 @id
4269 else
4270 previous, shortest_distances = Map.dijkstra(@id, target_list)
4271 target_list.delete_if { |room_num| shortest_distances[room_num].nil? }
4272 target_list.sort { |a,b| shortest_distances[a] <=> shortest_distances[b] }.first
4273 end
4274 end
4275end
4276
4277class Room < Map
4278# private_class_method :new
4279 def Room.method_missing(*args)
4280 super(*args)
4281 end
4282end
4283
4284def hide_me
4285 Script.current.hidden = !Script.current.hidden
4286end
4287
4288def no_kill_all
4289 script = Script.current
4290 script.no_kill_all = !script.no_kill_all
4291end
4292
4293def no_pause_all
4294 script = Script.current
4295 script.no_pause_all = !script.no_pause_all
4296end
4297
4298def toggle_upstream
4299 unless script = Script.current then echo 'toggle_upstream: cannot identify calling script.'; return nil; end
4300 script.want_upstream = !script.want_upstream
4301end
4302
4303def silence_me
4304 unless script = Script.current then echo 'silence_me: cannot identify calling script.'; return nil; end
4305 if script.safe? then echo("WARNING: 'safe' script attempted to silence itself. Ignoring the request.")
4306 sleep 1
4307 return true
4308 end
4309 script.silent = !script.silent
4310end
4311
4312def toggle_echo
4313 unless script = Script.current then respond('--- toggle_echo: Unable to identify calling script.'); return nil; end
4314 script.no_echo = !script.no_echo
4315end
4316
4317def echo_on
4318 unless script = Script.current then respond('--- echo_on: Unable to identify calling script.'); return nil; end
4319 script.no_echo = false
4320end
4321
4322def echo_off
4323 unless script = Script.current then respond('--- echo_off: Unable to identify calling script.'); return nil; end
4324 script.no_echo = true
4325end
4326
4327def upstream_get
4328 unless script = Script.current then echo 'upstream_get: cannot identify calling script.'; return nil; end
4329 unless script.want_upstream
4330 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)")
4331 sleep 0.3
4332 return false
4333 end
4334 script.upstream_gets
4335end
4336
4337def upstream_get?
4338 unless script = Script.current then echo 'upstream_get: cannot identify calling script.'; return nil; end
4339 unless script.want_upstream
4340 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)")
4341 return false
4342 end
4343 script.upstream_gets?
4344end
4345
4346def echo(*messages)
4347 respond if messages.empty?
4348 if script = Script.current
4349 unless script.no_echo
4350 messages.each { |message| respond("[#{script.name}: #{message.to_s.chomp}]") }
4351 end
4352 else
4353 messages.each { |message| respond("[(unknown script): #{message.to_s.chomp}]") }
4354 end
4355 nil
4356end
4357
4358def _echo(*messages)
4359 _respond if messages.empty?
4360 if script = Script.current
4361 unless script.no_echo
4362 messages.each { |message| _respond("[#{script.name}: #{message.to_s.chomp}]") }
4363 end
4364 else
4365 messages.each { |message| _respond("[(unknown script): #{message.to_s.chomp}]") }
4366 end
4367 nil
4368end
4369
4370def goto(label)
4371 Script.current.jump_label = label.to_s
4372 raise JUMP
4373end
4374
4375def pause_script(*names)
4376 names.flatten!
4377 if names.empty?
4378 Script.current.pause
4379 Script.current
4380 else
4381 names.each { |scr|
4382 fnd = Script.list.find { |nm| nm.name =~ /^#{scr}/i }
4383 fnd.pause unless (fnd.paused || fnd.nil?)
4384 }
4385 end
4386end
4387
4388def unpause_script(*names)
4389 names.flatten!
4390 names.each { |scr|
4391 fnd = Script.list.find { |nm| nm.name =~ /^#{scr}/i }
4392 fnd.unpause if (fnd.paused and not fnd.nil?)
4393 }
4394end
4395
4396def fix_injury_mode
4397 unless XMLData.injury_mode == 2
4398 Game._puts '_injury 2'
4399 150.times { sleep 0.05; break if XMLData.injury_mode == 2 }
4400 end
4401end
4402
4403def hide_script(*args)
4404 args.flatten!
4405 args.each { |name|
4406 if script = Script.running.find { |scr| scr.name == name }
4407 script.hidden = !script.hidden
4408 end
4409 }
4410end
4411
4412def parse_list(string)
4413 string.split_as_list
4414end
4415
4416def waitrt
4417 wait_until { (XMLData.roundtime_end.to_f - Time.now.to_f + XMLData.server_time_offset.to_f) > 0 }
4418 sleep((XMLData.roundtime_end.to_f - Time.now.to_f + XMLData.server_time_offset.to_f + "0.6".to_f).abs)
4419end
4420
4421def waitrt?
4422 rt = XMLData.roundtime_end.to_f - Time.now.to_f + XMLData.server_time_offset.to_f + "0.6".to_f
4423 if rt > 0
4424 sleep rt
4425 end
4426end
4427
4428def waitcastrt
4429 wait_until { (XMLData.cast_roundtime_end.to_f - Time.now.to_f + XMLData.server_time_offset.to_f) > 0 }
4430 sleep((XMLData.cast_roundtime_end.to_f - Time.now.to_f + XMLData.server_time_offset.to_f + "0.6".to_f).abs)
4431end
4432
4433def waitcastrt?
4434 rt = XMLData.cast_roundtime_end.to_f - Time.now.to_f + XMLData.server_time_offset.to_f + "0.6".to_f
4435 if rt > 0
4436 sleep rt
4437 end
4438end
4439
4440def checkrt
4441 [XMLData.roundtime_end.to_f - Time.now.to_f + XMLData.server_time_offset.to_f + "0.6".to_f, 0].max
4442end
4443
4444def checkcastrt
4445 [XMLData.cast_roundtime_end.to_f - Time.now.to_f + XMLData.server_time_offset.to_f + "0.6".to_f, 0].max
4446end
4447
4448def checkpoison
4449 XMLData.indicator['IconPOISONED'] == 'y'
4450end
4451
4452def checkdisease
4453 XMLData.indicator['IconDISEASED'] == 'y'
4454end
4455
4456def checksitting
4457 XMLData.indicator['IconSITTING'] == 'y'
4458end
4459
4460def checkkneeling
4461 XMLData.indicator['IconKNEELING'] == 'y'
4462end
4463
4464def checkstunned
4465 XMLData.indicator['IconSTUNNED'] == 'y'
4466end
4467
4468def checkbleeding
4469 XMLData.indicator['IconBLEEDING'] == 'y'
4470end
4471
4472def checkgrouped
4473 XMLData.indicator['IconJOINED'] == 'y'
4474end
4475
4476def checkdead
4477 XMLData.indicator['IconDEAD'] == 'y'
4478end
4479
4480def checkreallybleeding
4481 checkbleeding and !(Spell[9909].active? or Spell[9905].active?)
4482end
4483
4484def muckled?
4485 muckled = checkwebbed or checkdead or checkstunned
4486 if defined?(checksleeping)
4487 muckled = muckled or checksleeping
4488 end
4489 if defined?(checkbound)
4490 muckled = muckled or checkbound
4491 end
4492 return muckled
4493end
4494
4495def checkhidden
4496 XMLData.indicator['IconHIDDEN'] == 'y'
4497end
4498
4499def checkinvisible
4500 XMLData.indicator['IconINVISIBLE'] == 'y'
4501end
4502
4503def checkwebbed
4504 XMLData.indicator['IconWEBBED'] == 'y'
4505end
4506
4507def checkprone
4508 XMLData.indicator['IconPRONE'] == 'y'
4509end
4510
4511def checknotstanding
4512 XMLData.indicator['IconSTANDING'] == 'n'
4513end
4514
4515def checkstanding
4516 XMLData.indicator['IconSTANDING'] == 'y'
4517end
4518
4519def checkname(*strings)
4520 strings.flatten!
4521 if strings.empty?
4522 XMLData.name
4523 else
4524 XMLData.name =~ /^(?:#{strings.join('|')})/i
4525 end
4526end
4527
4528def checkloot
4529 GameObj.loot.collect { |item| item.noun }
4530end
4531
4532def i_stand_alone
4533 unless script = Script.current then echo 'i_stand_alone: cannot identify calling script.'; return nil; end
4534 script.want_downstream = !script.want_downstream
4535 return !script.want_downstream
4536end
4537
4538def debug(*args)
4539 if $LICH_DEBUG
4540 if block_given?
4541 yield(*args)
4542 else
4543 echo(*args)
4544 end
4545 end
4546end
4547
4548def timetest(*contestants)
4549 contestants.collect { |code| start = Time.now; 5000.times { code.call }; Time.now - start }
4550end
4551
4552def dec2bin(n)
4553 "0" + [n].pack("N").unpack("B32")[0].sub(/^0+(?=\d)/, '')
4554end
4555
4556def bin2dec(n)
4557 [("0"*32+n.to_s)[-32..-1]].pack("B32").unpack("N")[0]
4558end
4559
4560def idle?(time = 60)
4561 Time.now - $_IDLETIMESTAMP_ >= time
4562end
4563
4564def selectput(string, success, failure, timeout = nil)
4565 timeout = timeout.to_f if timeout and !timeout.kind_of?(Numeric)
4566 success = [ success ] if success.kind_of? String
4567 failure = [ failure ] if failure.kind_of? String
4568 if !string.kind_of?(String) or !success.kind_of?(Array) or !failure.kind_of?(Array) or timeout && !timeout.kind_of?(Numeric)
4569 raise ArgumentError, "usage is: selectput(game_command,success_array,failure_array[,timeout_in_secs])"
4570 end
4571 success.flatten!
4572 failure.flatten!
4573 regex = /#{(success + failure).join('|')}/i
4574 successre = /#{success.join('|')}/i
4575 failurere = /#{failure.join('|')}/i
4576 thr = Thread.current
4577
4578 timethr = Thread.new {
4579 timeout -= sleep("0.1".to_f) until timeout <= 0
4580 thr.raise(StandardError)
4581 } if timeout
4582
4583 begin
4584 loop {
4585 fput(string)
4586 response = waitforre(regex)
4587 if successre.match(response.to_s)
4588 timethr.kill if timethr.alive?
4589 break(response.string)
4590 end
4591 yield(response.string) if block_given?
4592 }
4593 rescue
4594 nil
4595 end
4596end
4597
4598def toggle_unique
4599 unless script = Script.current then echo 'toggle_unique: cannot identify calling script.'; return nil; end
4600 script.want_downstream = !script.want_downstream
4601end
4602
4603def die_with_me(*vals)
4604 unless script = Script.current then echo 'die_with_me: cannot identify calling script.'; return nil; end
4605 script.die_with.push vals
4606 script.die_with.flatten!
4607 echo("The following script(s) will now die when I do: #{script.die_with.join(', ')}") unless script.die_with.empty?
4608end
4609
4610def upstream_waitfor(*strings)
4611 strings.flatten!
4612 script = Script.current
4613 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
4614 regexpstr = strings.join('|')
4615 while line = script.upstream_gets
4616 if line =~ /#{regexpstr}/i
4617 return line
4618 end
4619 end
4620end
4621
4622def send_to_script(*values)
4623 values.flatten!
4624 if script = Script.list.find { |val| val.name =~ /^#{values.first}/i }
4625 if script.want_downstream
4626 values[1..-1].each { |val| script.downstream_buffer.push(val) }
4627 else
4628 values[1..-1].each { |val| script.unique_buffer.push(val) }
4629 end
4630 echo("Sent to #{script.name} -- '#{values[1..-1].join(' ; ')}'")
4631 return true
4632 else
4633 echo("'#{values.first}' does not match any active scripts!")
4634 return false
4635 end
4636end
4637
4638def unique_send_to_script(*values)
4639 values.flatten!
4640 if script = Script.list.find { |val| val.name =~ /^#{values.first}/i }
4641 values[1..-1].each { |val| script.unique_buffer.push(val) }
4642 echo("sent to #{script}: #{values[1..-1].join(' ; ')}")
4643 return true
4644 else
4645 echo("'#{values.first}' does not match any active scripts!")
4646 return false
4647 end
4648end
4649
4650def unique_waitfor(*strings)
4651 unless script = Script.current then echo 'unique_waitfor: cannot identify calling script.'; return nil; end
4652 strings.flatten!
4653 regexp = /#{strings.join('|')}/
4654 while true
4655 str = script.unique_gets
4656 if str =~ regexp
4657 return str
4658 end
4659 end
4660end
4661
4662def unique_get
4663 unless script = Script.current then echo 'unique_get: cannot identify calling script.'; return nil; end
4664 script.unique_gets
4665end
4666
4667def unique_get?
4668 unless script = Script.current then echo 'unique_get: cannot identify calling script.'; return nil; end
4669 script.unique_gets?
4670end
4671
4672def multimove(*dirs)
4673 dirs.flatten.each { |dir| move(dir) }
4674end
4675
4676def n; 'north'; end
4677def ne; 'northeast'; end
4678def e; 'east'; end
4679def se; 'southeast'; end
4680def s; 'south'; end
4681def sw; 'southwest'; end
4682def w; 'west'; end
4683def nw; 'northwest'; end
4684def u; 'up'; end
4685def up; 'up'; end
4686def down; 'down'; end
4687def d; 'down'; end
4688def o; 'out'; end
4689def out; 'out'; end
4690
4691def move(dir='none', giveup_seconds=30, giveup_lines=30)
4692 #[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)
4693 #[LNet]-[Private]-Casis: "You smack into the water with a splash and sink far below the surface." (20:35:50)
4694 # 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."
4695 if dir == 'none'
4696 echo 'move: no direction given'
4697 return false
4698 end
4699
4700 need_full_hands = false
4701 tried_open = false
4702 tried_fix_drag = false
4703 line_count = 0
4704 room_count = XMLData.room_count
4705 giveup_time = Time.now.to_i + giveup_seconds.to_i
4706 save_stream = Array.new
4707
4708 put_dir = proc {
4709 if XMLData.room_count > room_count
4710 fill_hands if need_full_hands
4711 Script.current.downstream_buffer.unshift(save_stream)
4712 Script.current.downstream_buffer.flatten!
4713 return true
4714 end
4715 waitrt?
4716 wait_while { stunned? }
4717 giveup_time = Time.now.to_i + giveup_seconds.to_i
4718 line_count = 0
4719 save_stream.push(clear)
4720 put dir
4721 }
4722
4723 put_dir.call
4724
4725 loop {
4726 line = get?
4727 unless line.nil?
4728 save_stream.push(line)
4729 line_count += 1
4730 end
4731 if line.nil?
4732 sleep 0.1
4733 elsif line =~ /^You can't do that while engaged!|^You are engaged to /
4734 # DragonRealms
4735 fput 'retreat'
4736 fput 'retreat'
4737 put_dir.call
4738 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\.$/
4739 fput 'unhide'
4740 put_dir.call
4741 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/)
4742 which = [ 'first', 'second', 'third', 'fourth', 'fifth', 'sixth', 'seventh', 'eight', 'ninth', 'tenth', 'eleventh', 'twelfth' ]
4743 if dir =~ /\b#{which.join('|')}\b/
4744 dir.sub!(/\b(#{which.join('|')})\b/) { "#{which[which.index($1)+1]}" }
4745 else
4746 dir.sub!('door', 'second door')
4747 end
4748 put_dir.call
4749 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\.$/
4750 echo 'move: failed'
4751 fill_hands if need_full_hands
4752 Script.current.downstream_buffer.unshift(save_stream)
4753 Script.current.downstream_buffer.flatten!
4754 return false
4755 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\?$/
4756 echo 'move: failed'
4757 fill_hands if need_full_hands
4758 Script.current.downstream_buffer.unshift(save_stream)
4759 Script.current.downstream_buffer.flatten!
4760 # return nil instead of false to show the direction shouldn't be removed from the map database
4761 return nil
4762 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)/
4763 sleep 1
4764 waitrt?
4765 put_dir.call
4766 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\./
4767 sleep 1
4768 waitrt?
4769 fput 'stand' unless standing?
4770 waitrt?
4771 put_dir.call
4772 elsif line =~ /^You begin to climb up the silvery thread.* you tumble to the ground/
4773 sleep 0.5
4774 waitrt?
4775 fput 'stand' unless standing?
4776 waitrt?
4777 if checkleft or checkright
4778 need_full_hands = true
4779 empty_hands
4780 end
4781 put_dir.call
4782 elsif line == 'You are too injured to be doing any climbing!'
4783 if (resolve = Spell[9704]) and resolve.known?
4784 wait_until { resolve.affordable? }
4785 resove.cast
4786 put_dir.call
4787 else
4788 return nil
4789 end
4790 elsif line =~ /^You(?:'re going to| will) have to climb that\./
4791 dir.gsub!('go', 'climb')
4792 put_dir.call
4793 elsif line =~ /^You can't climb that\./
4794 dir.gsub!('climb', 'go')
4795 put_dir.call
4796 elsif line =~ /^You can't drag/
4797 if tried_fix_drag
4798 fill_hands if need_full_hands
4799 Script.current.downstream_buffer.unshift(save_stream)
4800 Script.current.downstream_buffer.flatten!
4801 return false
4802 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/ })
4803 tried_fix_drag = true
4804 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)
4805 target = /^(?:go|climb) (.+)$/.match(dir).captures.first
4806 fput "drag #{name}"
4807 dir = "drag #{name} #{target}"
4808 put_dir.call
4809 else
4810 tried_fix_drag = true
4811 dir.sub!(/^climb /, 'go ')
4812 put_dir.call
4813 end
4814 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\./
4815 need_full_hands = true
4816 empty_hands
4817 put_dir.call
4818 elsif line =~ /(?:appears|seems) to be closed\.$|^You cannot quite manage to squeeze between the stone doors\.$/
4819 if tried_open
4820 fill_hands if need_full_hands
4821 Script.current.downstream_buffer.unshift(save_stream)
4822 Script.current.downstream_buffer.flatten!
4823 return false
4824 else
4825 tried_open = true
4826 fput dir.sub(/go|climb/, 'open')
4827 put_dir.call
4828 end
4829 elsif line =~ /^(\.\.\.w|W)ait ([0-9]+) sec(onds)?\.$/
4830 if $2.to_i > 1
4831 sleep ($2.to_i - "0.2".to_f)
4832 else
4833 sleep 0.3
4834 end
4835 put_dir.call
4836 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/
4837 fput 'stand'
4838 waitrt?
4839 put_dir.call
4840 elsif line =~ /^Sorry, you may only type ahead/
4841 sleep 1
4842 put_dir.call
4843 elsif line == 'You are still stunned.'
4844 wait_while { stunned? }
4845 put_dir.call
4846 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!$/
4847 waitrt?
4848 fput 'stand' unless standing?
4849 waitrt?
4850 put_dir.call
4851 elsif line =~ /^You flick your hand (?:up|down)wards and focus your aura on your disk, but your disk only wobbles briefly\.$/
4852 put_dir.call
4853 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/
4854 waitrt?
4855 put_dir.call
4856 elsif line == "You don't seem to be able to move to do that."
4857 30.times {
4858 break if clear.include?('You regain control of your senses!')
4859 sleep 0.1
4860 }
4861 put_dir.call
4862 end
4863 if XMLData.room_count > room_count
4864 fill_hands if need_full_hands
4865 Script.current.downstream_buffer.unshift(save_stream)
4866 Script.current.downstream_buffer.flatten!
4867 return true
4868 end
4869 if Time.now.to_i >= giveup_time
4870 echo "move: no recognized response in #{giveup_seconds} seconds. giving up."
4871 fill_hands if need_full_hands
4872 Script.current.downstream_buffer.unshift(save_stream)
4873 Script.current.downstream_buffer.flatten!
4874 return nil
4875 end
4876 if line_count >= giveup_lines
4877 echo "move: no recognized response after #{line_count} lines. giving up."
4878 fill_hands if need_full_hands
4879 Script.current.downstream_buffer.unshift(save_stream)
4880 Script.current.downstream_buffer.flatten!
4881 return nil
4882 end
4883 }
4884end
4885
4886def watchhealth(value, theproc=nil, &block)
4887 value = value.to_i
4888 if block.nil?
4889 if !theproc.respond_to? :call
4890 respond "`watchhealth' was not given a block or a proc to execute!"
4891 return nil
4892 else
4893 block = theproc
4894 end
4895 end
4896 Thread.new {
4897 wait_while { health(value) }
4898 block.call
4899 }
4900end
4901
4902def wait_until(announce=nil)
4903 priosave = Thread.current.priority
4904 Thread.current.priority = 0
4905 unless announce.nil? or yield
4906 respond(announce)
4907 end
4908 until yield
4909 sleep 0.25
4910 end
4911 Thread.current.priority = priosave
4912end
4913
4914def wait_while(announce=nil)
4915 priosave = Thread.current.priority
4916 Thread.current.priority = 0
4917 unless announce.nil? or !yield
4918 respond(announce)
4919 end
4920 while yield
4921 sleep 0.25
4922 end
4923 Thread.current.priority = priosave
4924end
4925
4926def checkpaths(dir="none")
4927 if dir == "none"
4928 if XMLData.room_exits.empty?
4929 return false
4930 else
4931 return XMLData.room_exits.collect { |dir| dir = SHORTDIR[dir] }
4932 end
4933 else
4934 XMLData.room_exits.include?(dir) || XMLData.room_exits.include?(SHORTDIR[dir])
4935 end
4936end
4937
4938def reverse_direction(dir)
4939 if dir == "n" then 's'
4940 elsif dir == "ne" then 'sw'
4941 elsif dir == "e" then 'w'
4942 elsif dir == "se" then 'nw'
4943 elsif dir == "s" then 'n'
4944 elsif dir == "sw" then 'ne'
4945 elsif dir == "w" then 'e'
4946 elsif dir == "nw" then 'se'
4947 elsif dir == "up" then 'down'
4948 elsif dir == "down" then 'up'
4949 elsif dir == "out" then 'out'
4950 elsif dir == 'o' then out
4951 elsif dir == 'u' then 'down'
4952 elsif dir == 'd' then up
4953 elsif dir == n then s
4954 elsif dir == ne then sw
4955 elsif dir == e then w
4956 elsif dir == se then nw
4957 elsif dir == s then n
4958 elsif dir == sw then ne
4959 elsif dir == w then e
4960 elsif dir == nw then se
4961 elsif dir == u then d
4962 elsif dir == d then u
4963 else echo("Cannot recognize direction to properly reverse it!"); false
4964 end
4965end
4966
4967def walk(*boundaries, &block)
4968 boundaries.flatten!
4969 unless block.nil?
4970 until val = yield
4971 walk(*boundaries)
4972 end
4973 return val
4974 end
4975 if $last_dir and !boundaries.empty? and checkroomdescrip =~ /#{boundaries.join('|')}/i
4976 move($last_dir)
4977 $last_dir = reverse_direction($last_dir)
4978 return checknpcs
4979 end
4980 dirs = checkpaths
4981 dirs.delete($last_dir) unless dirs.length < 2
4982 this_time = rand(dirs.length)
4983 $last_dir = reverse_direction(dirs[this_time])
4984 move(dirs[this_time])
4985 checknpcs
4986end
4987
4988def run
4989 loop { break unless walk }
4990end
4991
4992def check_mind(string=nil)
4993 if string.nil?
4994 return XMLData.mind_text
4995 elsif (string.class == String) and (string.to_i == 0)
4996 if string =~ /#{XMLData.mind_text}/i
4997 return true
4998 else
4999 return false
5000 end
5001 elsif string.to_i.between?(0,100)
5002 return string.to_i <= XMLData.mind_value.to_i
5003 else
5004 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
5005 return false
5006 end
5007end
5008
5009def checkmind(string=nil)
5010 if string.nil?
5011 return XMLData.mind_text
5012 elsif string.class == String and string.to_i == 0
5013 if string =~ /#{XMLData.mind_text}/i
5014 return true
5015 else
5016 return false
5017 end
5018 elsif string.to_i.between?(1,8)
5019 mind_state = ['clear as a bell','fresh and clear','clear','muddled','becoming numbed','numbed','must rest','saturated']
5020 if mind_state.index(XMLData.mind_text)
5021 mind = mind_state.index(XMLData.mind_text) + 1
5022 return string.to_i <= mind
5023 else
5024 echo "Bad string in checkmind: mind_state"
5025 nil
5026 end
5027 else
5028 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
5029 return false
5030 end
5031end
5032
5033def percentmind(num=nil)
5034 if num.nil?
5035 XMLData.mind_value
5036 else
5037 XMLData.mind_value >= num.to_i
5038 end
5039end
5040
5041def checkfried
5042 if XMLData.mind_text =~ /must rest|saturated/
5043 true
5044 else
5045 false
5046 end
5047end
5048
5049def checksaturated
5050 if XMLData.mind_text =~ /saturated/
5051 true
5052 else
5053 false
5054 end
5055end
5056
5057def checkmana(num=nil)
5058 if num.nil?
5059 XMLData.mana
5060 else
5061 XMLData.mana >= num.to_i
5062 end
5063end
5064
5065def maxmana
5066 XMLData.max_mana
5067end
5068
5069def percentmana(num=nil)
5070 if XMLData.max_mana == 0
5071 percent = 100
5072 else
5073 percent = ((XMLData.mana.to_f / XMLData.max_mana.to_f) * 100).to_i
5074 end
5075 if num.nil?
5076 percent
5077 else
5078 percent >= num.to_i
5079 end
5080end
5081
5082def checkhealth(num=nil)
5083 if num.nil?
5084 XMLData.health
5085 else
5086 XMLData.health >= num.to_i
5087 end
5088end
5089
5090def maxhealth
5091 XMLData.max_health
5092end
5093
5094def percenthealth(num=nil)
5095 if num.nil?
5096 ((XMLData.health.to_f / XMLData.max_health.to_f) * 100).to_i
5097 else
5098 ((XMLData.health.to_f / XMLData.max_health.to_f) * 100).to_i >= num.to_i
5099 end
5100end
5101
5102def checkspirit(num=nil)
5103 if num.nil?
5104 XMLData.spirit
5105 else
5106 XMLData.spirit >= num.to_i
5107 end
5108end
5109
5110def maxspirit
5111 XMLData.max_spirit
5112end
5113
5114def percentspirit(num=nil)
5115 if num.nil?
5116 ((XMLData.spirit.to_f / XMLData.max_spirit.to_f) * 100).to_i
5117 else
5118 ((XMLData.spirit.to_f / XMLData.max_spirit.to_f) * 100).to_i >= num.to_i
5119 end
5120end
5121
5122def checkstamina(num=nil)
5123 if num.nil?
5124 XMLData.stamina
5125 else
5126 XMLData.stamina >= num.to_i
5127 end
5128end
5129
5130def maxstamina()
5131 XMLData.max_stamina
5132end
5133
5134def percentstamina(num=nil)
5135 if XMLData.max_stamina == 0
5136 percent = 100
5137 else
5138 percent = ((XMLData.stamina.to_f / XMLData.max_stamina.to_f) * 100).to_i
5139 end
5140 if num.nil?
5141 percent
5142 else
5143 percent >= num.to_i
5144 end
5145end
5146
5147def checkstance(num=nil)
5148 if num.nil?
5149 XMLData.stance_text
5150 elsif (num.class == String) and (num.to_i == 0)
5151 if num =~ /off/i
5152 XMLData.stance_value == 0
5153 elsif num =~ /adv/i
5154 XMLData.stance_value.between?(01, 20)
5155 elsif num =~ /for/i
5156 XMLData.stance_value.between?(21, 40)
5157 elsif num =~ /neu/i
5158 XMLData.stance_value.between?(41, 60)
5159 elsif num =~ /gua/i
5160 XMLData.stance_value.between?(61, 80)
5161 elsif num =~ /def/i
5162 XMLData.stance_value == 100
5163 else
5164 echo "checkstance: invalid argument (#{num}). Must be off/adv/for/neu/gua/def or 0-100"
5165 nil
5166 end
5167 elsif (num.class == Fixnum) or (num =~ /^[0-9]+$/ and num = num.to_i)
5168 XMLData.stance_value == num.to_i
5169 else
5170 echo "checkstance: invalid argument (#{num}). Must be off/adv/for/neu/gua/def or 0-100"
5171 nil
5172 end
5173end
5174
5175def percentstance(num=nil)
5176 if num.nil?
5177 XMLData.stance_value
5178 else
5179 XMLData.stance_value >= num.to_i
5180 end
5181end
5182
5183def checkencumbrance(string=nil)
5184 if string.nil?
5185 XMLData.encumbrance_text
5186 elsif (string.class == Fixnum) or (string =~ /^[0-9]+$/ and string = string.to_i)
5187 string <= XMLData.encumbrance_value
5188 else
5189 # fixme
5190 if string =~ /#{XMLData.encumbrance_text}/i
5191 true
5192 else
5193 false
5194 end
5195 end
5196end
5197
5198def percentencumbrance(num=nil)
5199 if num.nil?
5200 XMLData.encumbrance_value
5201 else
5202 num.to_i <= XMLData.encumbrance_value
5203 end
5204end
5205
5206def checkarea(*strings)
5207 strings.flatten!
5208 if strings.empty?
5209 XMLData.room_title.split(',').first.sub('[','')
5210 else
5211 XMLData.room_title.split(',').first =~ /#{strings.join('|')}/i
5212 end
5213end
5214
5215def checkroom(*strings)
5216 strings.flatten!
5217 if strings.empty?
5218 XMLData.room_title.chomp
5219 else
5220 XMLData.room_title =~ /#{strings.join('|')}/i
5221 end
5222end
5223
5224def outside?
5225 if XMLData.room_exits_string =~ /Obvious paths:/
5226 true
5227 else
5228 false
5229 end
5230end
5231
5232def checkfamarea(*strings)
5233 strings.flatten!
5234 if strings.empty? then return XMLData.familiar_room_title.split(',').first.sub('[','') end
5235 XMLData.familiar_room_title.split(',').first =~ /#{strings.join('|')}/i
5236end
5237
5238def checkfampaths(dir="none")
5239 if dir == "none"
5240 if XMLData.familiar_room_exits.empty?
5241 return false
5242 else
5243 return XMLData.familiar_room_exits
5244 end
5245 else
5246 XMLData.familiar_room_exits.include?(dir)
5247 end
5248end
5249
5250def checkfamroom(*strings)
5251 strings.flatten! ; if strings.empty? then return XMLData.familiar_room_title.chomp end
5252 XMLData.familiar_room_title =~ /#{strings.join('|')}/i
5253end
5254
5255def checkfamnpcs(*strings)
5256 parsed = Array.new
5257 XMLData.familiar_npcs.each { |val| parsed.push(val.split.last) }
5258 if strings.empty?
5259 if parsed.empty?
5260 return false
5261 else
5262 return parsed
5263 end
5264 else
5265 if mtch = strings.find { |lookfor| parsed.find { |critter| critter =~ /#{lookfor}/ } }
5266 return mtch
5267 else
5268 return false
5269 end
5270 end
5271end
5272
5273def checkfampcs(*strings)
5274 familiar_pcs = Array.new
5275 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]+/)) }
5276 if familiar_pcs.empty?
5277 return false
5278 elsif strings.empty?
5279 return familiar_pcs
5280 else
5281 regexpstr = strings.join('|\b')
5282 peeps = familiar_pcs.find_all { |val| val =~ /\b#{regexpstr}/i }
5283 if peeps.empty?
5284 return false
5285 else
5286 return peeps
5287 end
5288 end
5289end
5290
5291def checkpcs(*strings)
5292 pcs = GameObj.pcs.collect { |pc| pc.noun }
5293 if pcs.empty?
5294 if strings.empty? then return nil else return false end
5295 end
5296 strings.flatten!
5297 if strings.empty?
5298 pcs
5299 else
5300 regexpstr = strings.join(' ')
5301 pcs.find { |pc| regexpstr =~ /\b#{pc}/i }
5302 end
5303end
5304
5305def checknpcs(*strings)
5306 npcs = GameObj.npcs.collect { |npc| npc.noun }
5307 if npcs.empty?
5308 if strings.empty? then return nil else return false end
5309 end
5310 strings.flatten!
5311 if strings.empty?
5312 npcs
5313 else
5314 regexpstr = strings.join(' ')
5315 npcs.find { |npc| regexpstr =~ /\b#{npc}/i }
5316 end
5317end
5318
5319def count_npcs
5320 checknpcs.length
5321end
5322
5323def checkright(*hand)
5324 if GameObj.right_hand.nil? then return nil end
5325 hand.flatten!
5326 if GameObj.right_hand.name == "Empty" or GameObj.right_hand.name.empty?
5327 nil
5328 elsif hand.empty?
5329 GameObj.right_hand.noun
5330 else
5331 hand.find { |instance| GameObj.right_hand.name =~ /#{instance}/i }
5332 end
5333end
5334
5335def checkleft(*hand)
5336 if GameObj.left_hand.nil? then return nil end
5337 hand.flatten!
5338 if GameObj.left_hand.name == "Empty" or GameObj.left_hand.name.empty?
5339 nil
5340 elsif hand.empty?
5341 GameObj.left_hand.noun
5342 else
5343 hand.find { |instance| GameObj.left_hand.name =~ /#{instance}/i }
5344 end
5345end
5346
5347def checkroomdescrip(*val)
5348 val.flatten!
5349 if val.empty?
5350 return XMLData.room_description
5351 else
5352 return XMLData.room_description =~ /#{val.join('|')}/i
5353 end
5354end
5355
5356def checkfamroomdescrip(*val)
5357 val.flatten!
5358 if val.empty?
5359 return XMLData.familiar_room_description
5360 else
5361 return XMLData.familiar_room_description =~ /#{val.join('|')}/i
5362 end
5363end
5364
5365def checkspell(*spells)
5366 spells.flatten!
5367 return false if Spell.active.empty?
5368 spells.each { |spell| return false unless Spell[spell].active? }
5369 true
5370end
5371
5372def checkprep(spell=nil)
5373 if spell.nil?
5374 XMLData.prepared_spell
5375 elsif spell.class != String
5376 echo("Checkprep error, spell # not implemented! You must use the spell name")
5377 false
5378 else
5379 XMLData.prepared_spell =~ /^#{spell}/i
5380 end
5381end
5382
5383def setpriority(val=nil)
5384 if val.nil? then return Thread.current.priority end
5385 if val.to_i > 3
5386 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")
5387 return Thread.current.priority
5388 else
5389 Thread.current.group.list.each { |thr| thr.priority = val.to_i }
5390 return Thread.current.priority
5391 end
5392end
5393
5394def checkbounty
5395 if XMLData.bounty_task
5396 return XMLData.bounty_task
5397 else
5398 return nil
5399 end
5400end
5401
5402def checksleeping
5403 return $infomon_sleeping
5404end
5405def sleeping?
5406 return $infomon_sleeping
5407end
5408def checkbound
5409 return $infomon_bound
5410end
5411def bound?
5412 return $infomon_bound
5413end
5414def checksilenced
5415 $infomon_silenced
5416end
5417def silenced?
5418 $infomon_silenced
5419end
5420def checkcalmed
5421 $infomon_calmed
5422end
5423def calmed?
5424 $infomon_calmed
5425end
5426def checkcutthroat
5427 $infomon_cutthroat
5428end
5429def cutthroat?
5430 $infomon_cutthroat
5431end
5432
5433def variable
5434 unless script = Script.current then echo 'variable: cannot identify calling script.'; return nil; end
5435 script.vars
5436end
5437
5438def pause(num=1)
5439 if num =~ /m/
5440 sleep((num.sub(/m/, '').to_f * 60))
5441 elsif num =~ /h/
5442 sleep((num.sub(/h/, '').to_f * 3600))
5443 elsif num =~ /d/
5444 sleep((num.sub(/d/, '').to_f * 86400))
5445 else
5446 sleep(num.to_f)
5447 end
5448end
5449
5450def cast(spell, target=nil, results_of_interest=nil)
5451 if spell.class == Spell
5452 spell.cast(target, results_of_interest)
5453 elsif ( (spell.class == Fixnum) or (spell.to_s =~ /^[0-9]+$/) ) and (find_spell = Spell[spell.to_i])
5454 find_spell.cast(target, results_of_interest)
5455 elsif (spell.class == String) and (find_spell = Spell[spell])
5456 find_spell.cast(target, results_of_interest)
5457 else
5458 echo "cast: invalid spell (#{spell})"
5459 false
5460 end
5461end
5462
5463def clear(opt=0)
5464 unless script = Script.current then respond('--- clear: Unable to identify calling script.'); return false; end
5465 to_return = script.downstream_buffer.dup
5466 script.downstream_buffer.clear
5467 to_return
5468end
5469
5470def match(label, string)
5471 strings = [ label, string ]
5472 strings.flatten!
5473 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
5474 if strings.empty? then echo("Error! 'match' was given no strings to look for!") ; sleep 1 ; return false end
5475 unless strings.length == 2
5476 while line_in = script.gets
5477 strings.each { |string|
5478 if line_in =~ /#{string}/ then return $~.to_s end
5479 }
5480 end
5481 else
5482 if script.respond_to?(:match_stack_add)
5483 script.match_stack_add(strings.first.to_s, strings.last)
5484 else
5485 script.match_stack_labels.push(strings[0].to_s)
5486 script.match_stack_strings.push(strings[1])
5487 end
5488 end
5489end
5490
5491def matchtimeout(secs, *strings)
5492 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
5493 unless (secs.class == Float || secs.class == Fixnum)
5494 echo('matchtimeout error! You appear to have given it a string, not a #! Syntax: matchtimeout(30, "You stand up")')
5495 return false
5496 end
5497 strings.flatten!
5498 if strings.empty?
5499 echo("matchtimeout without any strings to wait for!")
5500 sleep 1
5501 return false
5502 end
5503 regexpstr = strings.join('|')
5504 end_time = Time.now.to_f + secs
5505 loop {
5506 line = get?
5507 if line.nil?
5508 sleep 0.1
5509 elsif line =~ /#{regexpstr}/i
5510 return line
5511 end
5512 if (Time.now.to_f > end_time)
5513 return false
5514 end
5515 }
5516end
5517
5518def matchbefore(*strings)
5519 strings.flatten!
5520 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
5521 if strings.empty? then echo("matchbefore without any strings to wait for!") ; return false end
5522 regexpstr = strings.join('|')
5523 loop { if (line_in = script.gets) =~ /#{regexpstr}/ then return $`.to_s end }
5524end
5525
5526def matchafter(*strings)
5527 strings.flatten!
5528 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
5529 if strings.empty? then echo("matchafter without any strings to wait for!") ; return end
5530 regexpstr = strings.join('|')
5531 loop { if (line_in = script.gets) =~ /#{regexpstr}/ then return $'.to_s end }
5532end
5533
5534def matchboth(*strings)
5535 strings.flatten!
5536 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
5537 if strings.empty? then echo("matchboth without any strings to wait for!") ; return end
5538 regexpstr = strings.join('|')
5539 loop { if (line_in = script.gets) =~ /#{regexpstr}/ then break end }
5540 return [ $`.to_s, $'.to_s ]
5541end
5542
5543def matchwait(*strings)
5544 unless script = Script.current then respond('--- matchwait: Unable to identify calling script.'); return false; end
5545 strings.flatten!
5546 unless strings.empty?
5547 regexpstr = strings.collect { |str| str.kind_of?(Regexp) ? str.source : str }.join('|')
5548 regexobj = /#{regexpstr}/
5549 while line_in = script.gets
5550 return line_in if line_in =~ regexobj
5551 end
5552 else
5553 strings = script.match_stack_strings
5554 labels = script.match_stack_labels
5555 regexpstr = /#{strings.join('|')}/i
5556 while line_in = script.gets
5557 if mdata = regexpstr.match(line_in)
5558 jmp = labels[strings.index(mdata.to_s) || strings.index(strings.find { |str| line_in =~ /#{str}/i })]
5559 script.match_stack_clear
5560 goto jmp
5561 end
5562 end
5563 end
5564end
5565
5566def waitforre(regexp)
5567 unless script = Script.current then respond('--- waitforre: Unable to identify calling script.'); return false; end
5568 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
5569 regobj = regexp.match(script.gets) until regobj
5570end
5571
5572def waitfor(*strings)
5573 unless script = Script.current then respond('--- waitfor: Unable to identify calling script.'); return false; end
5574 strings.flatten!
5575 if (script.class == WizardScript) and (strings.length == 1) and (strings.first.strip == '>')
5576 return script.gets
5577 end
5578 if strings.empty?
5579 echo 'waitfor: no string to wait for'
5580 return false
5581 end
5582 regexpstr = strings.join('|')
5583 while true
5584 line_in = script.gets
5585 if (line_in =~ /#{regexpstr}/i) then return line_in end
5586 end
5587end
5588
5589def wait
5590 unless script = Script.current then respond('--- wait: unable to identify calling script.'); return false; end
5591 script.clear
5592 return script.gets
5593end
5594
5595def get
5596 Script.current.gets
5597end
5598
5599def get?
5600 Script.current.gets?
5601end
5602
5603def reget(*lines)
5604 unless script = Script.current then respond('--- reget: Unable to identify calling script.'); return false; end
5605 lines.flatten!
5606 if caller.find { |c| c =~ /regetall/ }
5607 history = ($_SERVERBUFFER_.history + $_SERVERBUFFER_).join("\n")
5608 else
5609 history = $_SERVERBUFFER_.dup.join("\n")
5610 end
5611 unless script.want_downstream_xml
5612 history.gsub!(/<pushStream id=["'](?:spellfront|inv|bounty|society)["'][^>]*\/>.*?<popStream[^>]*>/m, '')
5613 history.gsub!(/<stream id="Spells">.*?<\/stream>/m, '')
5614 history.gsub!(/<(compDef|inv|component|right|left|spell|prompt)[^>]*>.*?<\/\1>/m, '')
5615 history.gsub!(/<[^>]+>/, '')
5616 history.gsub!('>', '>')
5617 history.gsub!('<', '<')
5618 end
5619 history = history.split("\n").delete_if { |line| line.nil? or line.empty? or line =~ /^[\r\n\s\t]*$/ }
5620 if lines.first.kind_of?(Numeric) or lines.first.to_i.nonzero?
5621 history = history[-([lines.shift.to_i,history.length].min)..-1]
5622 end
5623 unless lines.empty? or lines.nil?
5624 regex = /#{lines.join('|')}/i
5625 history = history.find_all { |line| line =~ regex }
5626 end
5627 if history.empty?
5628 nil
5629 else
5630 history
5631 end
5632end
5633
5634def regetall(*lines)
5635 reget(*lines)
5636end
5637
5638def multifput(*cmds)
5639 cmds.flatten.compact.each { |cmd| fput(cmd) }
5640end
5641
5642def fput(message, *waitingfor)
5643 unless script = Script.current then respond('--- waitfor: Unable to identify calling script.'); return false; end
5644 waitingfor.flatten!
5645 clear
5646 put(message)
5647
5648 while string = get
5649 if string =~ /(?:\.\.\.wait |Wait )[0-9]+/
5650 hold_up = string.slice(/[0-9]+/).to_i
5651 sleep(hold_up) unless hold_up.nil?
5652 clear
5653 put(message)
5654 next
5655 elsif string =~ /^You.+struggle.+stand/
5656 clear
5657 fput 'stand'
5658 next
5659 elsif string =~ /stunned|can't do that while|cannot seem|^(?!You rummage).*can't seem|don't seem|Sorry, you may only type ahead/
5660 if dead?
5661 echo "You're dead...! You can't do that!"
5662 sleep 1
5663 script.downstream_buffer.unshift(string)
5664 return false
5665 elsif checkstunned
5666 while checkstunned
5667 sleep("0.25".to_f)
5668 end
5669 elsif checkwebbed
5670 while checkwebbed
5671 sleep("0.25".to_f)
5672 end
5673 elsif string =~ /Sorry, you may only type ahead/
5674 sleep 1
5675 else
5676 sleep 0.1
5677 script.downstream_buffer.unshift(string)
5678 return false
5679 end
5680 clear
5681 put(message)
5682 next
5683 else
5684 if waitingfor.empty?
5685 script.downstream_buffer.unshift(string)
5686 return string
5687 else
5688 if foundit = waitingfor.find { |val| string =~ /#{val}/i }
5689 script.downstream_buffer.unshift(string)
5690 return foundit
5691 end
5692 sleep 1
5693 clear
5694 put(message)
5695 next
5696 end
5697 end
5698 end
5699end
5700
5701def put(*messages)
5702 messages.each { |message| Game.puts(message) }
5703end
5704
5705def quiet_exit
5706 script = Script.current
5707 script.quiet = !(script.quiet)
5708end
5709
5710def matchfindexact(*strings)
5711 strings.flatten!
5712 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
5713 if strings.empty? then echo("error! 'matchfind' with no strings to look for!") ; sleep 1 ; return false end
5714 looking = Array.new
5715 strings.each { |str| looking.push(str.gsub('?', '(\b.+\b)')) }
5716 if looking.empty? then echo("matchfind without any strings to wait for!") ; return false end
5717 regexpstr = looking.join('|')
5718 while line_in = script.gets
5719 if gotit = line_in.slice(/#{regexpstr}/)
5720 matches = Array.new
5721 looking.each_with_index { |str,idx|
5722 if gotit =~ /#{str}/i
5723 strings[idx].count('?').times { |n| matches.push(eval("$#{n+1}")) }
5724 end
5725 }
5726 break
5727 end
5728 end
5729 if matches.length == 1
5730 return matches.first
5731 else
5732 return matches.compact
5733 end
5734end
5735
5736def matchfind(*strings)
5737 regex = /#{strings.flatten.join('|').gsub('?', '(.+)')}/i
5738 unless script = Script.current
5739 respond "Unknown script is asking to use matchfind! Cannot process request without identifying the calling script; killing this thread."
5740 Thread.current.kill
5741 end
5742 while true
5743 if reobj = regex.match(script.gets)
5744 ret = reobj.captures.compact
5745 if ret.length < 2
5746 return ret.first
5747 else
5748 return ret
5749 end
5750 end
5751 end
5752end
5753
5754def matchfindword(*strings)
5755 regex = /#{strings.flatten.join('|').gsub('?', '([\w\d]+)')}/i
5756 unless script = Script.current
5757 respond "Unknown script is asking to use matchfindword! Cannot process request without identifying the calling script; killing this thread."
5758 Thread.current.kill
5759 end
5760 while true
5761 if reobj = regex.match(script.gets)
5762 ret = reobj.captures.compact
5763 if ret.length < 2
5764 return ret.first
5765 else
5766 return ret
5767 end
5768 end
5769 end
5770end
5771
5772def send_scripts(*messages)
5773 messages.flatten!
5774 messages.each { |message|
5775 Script.new_downstream(message)
5776 }
5777 true
5778end
5779
5780def status_tags(onoff="none")
5781 script = Script.current
5782 if onoff == "on"
5783 script.want_downstream = false
5784 script.want_downstream_xml = true
5785 echo("Status tags will be sent to this script.")
5786 elsif onoff == "off"
5787 script.want_downstream = true
5788 script.want_downstream_xml = false
5789 echo("Status tags will no longer be sent to this script.")
5790 elsif script.want_downstream_xml
5791 script.want_downstream = true
5792 script.want_downstream_xml = false
5793 else
5794 script.want_downstream = false
5795 script.want_downstream_xml = true
5796 end
5797end
5798
5799def respond(first = "", *messages)
5800 str = ''
5801 begin
5802 if first.class == Array
5803 first.flatten.each { |ln| str += sprintf("%s\r\n", ln.to_s.chomp) }
5804 else
5805 str += sprintf("%s\r\n", first.to_s.chomp)
5806 end
5807 messages.flatten.each { |message| str += sprintf("%s\r\n", message.to_s.chomp) }
5808 str.split(/\r?\n/).each { |line| Script.new_script_output(line); Buffer.update(line, Buffer::SCRIPT_OUTPUT) }
5809 if $frontend == 'stormfront'
5810 str = "<output class=\"mono\"/>\r\n#{str.gsub('&', '&').gsub('<', '<').gsub('>', '>')}<output class=\"\"/>\r\n"
5811 elsif $frontend == 'profanity'
5812 str = str.gsub('&', '&').gsub('<', '<').gsub('>', '>')
5813 end
5814 wait_while { XMLData.in_stream }
5815 $_CLIENT_.puts(str)
5816 if $_DETACHABLE_CLIENT_
5817 $_DETACHABLE_CLIENT_.puts(str) rescue nil
5818 end
5819 rescue
5820 puts $!
5821 puts $!.backtrace.first
5822 end
5823end
5824
5825def _respond(first = "", *messages)
5826 str = ''
5827 begin
5828 if first.class == Array
5829 first.flatten.each { |ln| str += sprintf("%s\r\n", ln.to_s.chomp) }
5830 else
5831 str += sprintf("%s\r\n", first.to_s.chomp)
5832 end
5833 messages.flatten.each { |message| str += sprintf("%s\r\n", message.to_s.chomp) }
5834 str.split(/\r?\n/).each { |line| Script.new_script_output(line); Buffer.update(line, Buffer::SCRIPT_OUTPUT) } # fixme: strip/separate script output?
5835 wait_while { XMLData.in_stream }
5836 $_CLIENT_.puts(str)
5837 if $_DETACHABLE_CLIENT_
5838 $_DETACHABLE_CLIENT_.puts(str) rescue nil
5839 end
5840 rescue
5841 puts $!
5842 puts $!.backtrace.first
5843 end
5844end
5845
5846def noded_pulse
5847 if Stats.prof =~ /warrior|rogue|sorcerer/i
5848 stats = [ Skills.smc.to_i, Skills.emc.to_i ]
5849 elsif Stats.prof =~ /empath|bard/i
5850 stats = [ Skills.smc.to_i, Skills.mmc.to_i ]
5851 elsif Stats.prof =~ /wizard/i
5852 stats = [ Skills.emc.to_i, 0 ]
5853 elsif Stats.prof =~ /paladin|cleric|ranger/i
5854 stats = [ Skills.smc.to_i, 0 ]
5855 else
5856 stats = [ 0, 0 ]
5857 end
5858 return (maxmana * 25 / 100) + (stats.max/10) + (stats.min/20)
5859end
5860
5861def unnoded_pulse
5862 if Stats.prof =~ /warrior|rogue|sorcerer/i
5863 stats = [ Skills.smc.to_i, Skills.emc.to_i ]
5864 elsif Stats.prof =~ /empath|bard/i
5865 stats = [ Skills.smc.to_i, Skills.mmc.to_i ]
5866 elsif Stats.prof =~ /wizard/i
5867 stats = [ Skills.emc.to_i, 0 ]
5868 elsif Stats.prof =~ /paladin|cleric|ranger/i
5869 stats = [ Skills.smc.to_i, 0 ]
5870 else
5871 stats = [ 0, 0 ]
5872 end
5873 return (maxmana * 15 / 100) + (stats.max/10) + (stats.min/20)
5874end
5875
5876def empty_hands
5877 $fill_hands_actions ||= Array.new
5878 actions = Array.new
5879 right_hand = GameObj.right_hand
5880 left_hand = GameObj.left_hand
5881 if UserVars.lootsack.nil? or UserVars.lootsack.empty?
5882 lootsack = nil
5883 else
5884 lootsack = GameObj.inv.find { |obj| obj.name =~ /#{Regexp.escape(UserVars.lootsack.strip)}/i } || GameObj.inv.find { |obj| obj.name =~ /#{Regexp.escape(UserVars.lootsack).sub(' ', ' .*')}/i }
5885 end
5886 other_containers_var = nil
5887 other_containers = proc {
5888 if other_containers_var.nil?
5889 Script.current.want_downstream = false
5890 Script.current.want_downstream_xml = true
5891 result = dothistimeout 'inventory containers', 5, /^You are wearing/
5892 Script.current.want_downstream_xml = false
5893 Script.current.want_downstream = true
5894 other_containers_ids = result.scan(/exist="(.*?)"/).flatten - [ lootsack.id ]
5895 other_containers_var = GameObj.inv.find_all { |obj| other_containers_ids.include?(obj.id) }
5896 end
5897 other_containers_var
5898 }
5899 if left_hand.id
5900 waitrt?
5901 if (left_hand.noun =~ /shield|buckler|targe|heater|parma|aegis|scutum|greatshield|mantlet|pavis|arbalest|bow|crossbow|yumi|arbalest/) and (wear_result = dothistimeout("wear ##{left_hand.id}", 8, /^You .*#{left_hand.noun}|^You can only wear \w+ items in that location\.$|^You can't wear that\.$/)) and (wear_result !~ /^You can only wear \w+ items in that location\.$|^You can't wear that\.$/)
5902 actions.unshift proc {
5903 dothistimeout "remove ##{left_hand.id}", 3, /^You|^Remove what\?/
5904 20.times { break if GameObj.left_hand.id == left_hand.id or GameObj.right_hand.id == left_hand.id; sleep 0.1 }
5905 if GameObj.right_hand.id == left_hand.id
5906 dothistimeout 'swap', 3, /^You don't have anything to swap!|^You swap/
5907 end
5908 }
5909 else
5910 actions.unshift proc {
5911 dothistimeout "get ##{left_hand.id}", 3, /^You (?:shield the opening of .*? from view as you |discreetly |carefully |deftly )?(?:remove|draw|grab|reach|slip|tuck|retrieve|already have)|^Get what\?$|^Why don't you leave some for others\?$|^You need a free hand/
5912 20.times { break if (GameObj.left_hand.id == left_hand.id) or (GameObj.right_hand.id == left_hand.id); sleep 0.1 }
5913 if GameObj.right_hand.id == left_hand.id
5914 dothistimeout 'swap', 3, /^You don't have anything to swap!|^You swap/
5915 end
5916 }
5917 if lootsack
5918 result = dothistimeout "put ##{left_hand.id} in ##{lootsack.id}", 4, /^You (?:attempt to shield .*? from view as you |discreetly |carefully |absent-mindedly )?(?:put|place|slip|tuck|add|drop|untie your|find an incomplete bundle|wipe off .*? and sheathe|secure)|^A sigh of grateful pleasure can be heard as you feed .*? to your|^As you place|^I could not find what you were referring to\.$|^Your bundle would be too large|^The .+ is too large to be bundled\.|^As you place your|^The .*? is already a bundle|^Your .*? won't fit in .*?\.$|^You can't .+ It's closed!$/
5919 if result =~ /^You can't .+ It's closed!$/
5920 actions.push proc { fput "close ##{lootsack.id}" }
5921 dothistimeout "open ##{lootsack.id}", 3, /^You open|^That is already open\./
5922 result = dothistimeout "put ##{left_hand.id} in ##{lootsack.id}", 3, /^You (?:attempt to shield .*? from view as you |discreetly |carefully |absent-mindedly )?(?:put|place|slip|tuck|add|drop|untie your|find an incomplete bundle|wipe off .*? and sheathe|secure)|^A sigh of grateful pleasure can be heard as you feed .*? to your|^As you place|^I could not find what you were referring to\.$|^Your bundle would be too large|^The .+ is too large to be bundled\.|^As you place your|^The .*? is already a bundle|^Your .*? won't fit in .*?\.$|^You can't .+ It's closed!$/
5923 end
5924 else
5925 result = nil
5926 end
5927 if result.nil? or result =~ /^Your .*? won't fit in .*?\.$/
5928 for container in other_containers.call
5929 result = dothistimeout "put ##{left_hand.id} in ##{container.id}", 4, /^You (?:attempt to shield .*? from view as you |discreetly |carefully |absent-mindedly )?(?:put|place|slip|tuck|add|drop|untie your|find an incomplete bundle|wipe off .*? and sheathe|secure)|^A sigh of grateful pleasure can be heard as you feed .*? to your|^As you place|^I could not find what you were referring to\.$|^Your bundle would be too large|^The .+ is too large to be bundled\.|^As you place your|^The .*? is already a bundle|^Your .*? won't fit in .*?\.$|^You can't .+ It's closed!$/
5930 if result =~ /^You can't .+ It's closed!$/
5931 actions.push proc { fput "close ##{container.id}" }
5932 dothistimeout "open ##{container.id}", 3, /^You open|^That is already open\./
5933 result = dothistimeout "put ##{left_hand.id} in ##{container.id}", 3, /^You (?:attempt to shield .*? from view as you |discreetly |carefully |absent-mindedly )?(?:put|place|slip|tuck|add|drop|untie your|find an incomplete bundle|wipe off .*? and sheathe|secure)|^A sigh of grateful pleasure can be heard as you feed .*? to your|^As you place|^I could not find what you were referring to\.$|^Your bundle would be too large|^The .+ is too large to be bundled\.|^As you place your|^The .*? is already a bundle|^Your .*? won't fit in .*?\.$|^You can't .+ It's closed!$/
5934 end
5935 break if result =~ /^You (?:put|absent-mindedly drop|slip)/
5936 end
5937 end
5938 end
5939 end
5940 if right_hand.id
5941 waitrt?
5942 actions.unshift proc {
5943 dothistimeout "get ##{right_hand.id}", 3, /^You (?:shield the opening of .*? from view as you |discreetly |carefully |deftly )?(?:remove|draw|grab|reach|slip|tuck|retrieve|already have)|^Get what\?$|^Why don't you leave some for others\?$|^You need a free hand/
5944 20.times { break if GameObj.left_hand.id == right_hand.id or GameObj.right_hand.id == right_hand.id; sleep 0.1 }
5945 if GameObj.left_hand.id == right_hand.id
5946 dothistimeout 'swap', 3, /^You don't have anything to swap!|^You swap/
5947 end
5948 }
5949 if UserVars.weapon and UserVars.weaponsack and not UserVars.weapon.empty? and not UserVars.weaponsack.empty? and (right_hand.name =~ /#{Regexp.escape(UserVars.weapon.strip)}/i or right_hand.name =~ /#{Regexp.escape(UserVars.weapon).sub(' ', ' .*')}/i)
5950 weaponsack = GameObj.inv.find { |obj| obj.name =~ /#{Regexp.escape(UserVars.weaponsack.strip)}/i } || GameObj.inv.find { |obj| obj.name =~ /#{Regexp.escape(UserVars.weaponsack).sub(' ', ' .*')}/i }
5951 end
5952 if weaponsack
5953 result = dothistimeout "put ##{right_hand.id} in ##{weaponsack.id}", 4, /^You (?:attempt to shield .*? from view as you |discreetly |carefully |absent-mindedly )?(?:put|place|slip|tuck|add|drop|untie your|find an incomplete bundle|wipe off .*? and sheathe|secure)|^A sigh of grateful pleasure can be heard as you feed .*? to your|^As you place|^I could not find what you were referring to\.$|^Your bundle would be too large|^The .+ is too large to be bundled\.|^As you place your|^The .*? is already a bundle|^Your .*? won't fit in .*?\.$|^You can't .+ It's closed!$/
5954 if result =~ /^You can't .+ It's closed!$/
5955 actions.push proc { fput "close ##{weaponsack.id}" }
5956 dothistimeout "open ##{weaponsack.id}", 3, /^You open|^That is already open\./
5957 result = dothistimeout "put ##{right_hand.id} in ##{weaponsack.id}", 3, /^You (?:attempt to shield .*? from view as you |discreetly |carefully |absent-mindedly )?(?:put|place|slip|tuck|add|drop|untie your|find an incomplete bundle|wipe off .*? and sheathe|secure)|^A sigh of grateful pleasure can be heard as you feed .*? to your|^As you place|^I could not find what you were referring to\.$|^Your bundle would be too large|^The .+ is too large to be bundled\.|^As you place your|^The .*? is already a bundle|^Your .*? won't fit in .*?\.$|^You can't .+ It's closed!$/
5958 end
5959 elsif lootsack
5960 result = dothistimeout "put ##{right_hand.id} in ##{lootsack.id}", 4, /^You (?:attempt to shield .*? from view as you |discreetly |carefully |absent-mindedly )?(?:put|place|slip|tuck|add|drop|untie your|find an incomplete bundle|wipe off .*? and sheathe|secure)|^A sigh of grateful pleasure can be heard as you feed .*? to your|^As you place|^I could not find what you were referring to\.$|^Your bundle would be too large|^The .+ is too large to be bundled\.|^As you place your|^The .*? is already a bundle|^Your .*? won't fit in .*?\.$|^You can't .+ It's closed!$/
5961 if result =~ /^You can't .+ It's closed!$/
5962 actions.push proc { fput "close ##{lootsack.id}" }
5963 dothistimeout "open ##{lootsack.id}", 3, /^You open|^That is already open\./
5964 result = dothistimeout "put ##{right_hand.id} in ##{lootsack.id}", 3, /^You (?:attempt to shield .*? from view as you |discreetly |carefully |absent-mindedly )?(?:put|place|slip|tuck|add|drop|untie your|find an incomplete bundle|wipe off .*? and sheathe|secure)|^A sigh of grateful pleasure can be heard as you feed .*? to your|^As you place|^I could not find what you were referring to\.$|^Your bundle would be too large|^The .+ is too large to be bundled\.|^As you place your|^The .*? is already a bundle|^Your .*? won't fit in .*?\.$|^You can't .+ It's closed!$/
5965 end
5966 else
5967 result = nil
5968 end
5969 if result.nil? or result =~ /^Your .*? won't fit in .*?\.$/
5970 for container in other_containers.call
5971 result = dothistimeout "put ##{right_hand.id} in ##{container.id}", 4, /^You (?:attempt to shield .*? from view as you |discreetly |carefully |absent-mindedly )?(?:put|place|slip|tuck|add|drop|untie your|find an incomplete bundle|wipe off .*? and sheathe|secure)|^A sigh of grateful pleasure can be heard as you feed .*? to your|^As you place|^I could not find what you were referring to\.$|^Your bundle would be too large|^The .+ is too large to be bundled\.|^As you place your|^The .*? is already a bundle|^Your .*? won't fit in .*?\.$|^You can't .+ It's closed!$/
5972 if result =~ /^You can't .+ It's closed!$/
5973 actions.push proc { fput "close ##{container.id}" }
5974 dothistimeout "open ##{container.id}", 3, /^You open|^That is already open\./
5975 result = dothistimeout "put ##{right_hand.id} in ##{container.id}", 3, /^You (?:attempt to shield .*? from view as you |discreetly |carefully |absent-mindedly )?(?:put|place|slip|tuck|add|drop|untie your|find an incomplete bundle|wipe off .*? and sheathe|secure)|^A sigh of grateful pleasure can be heard as you feed .*? to your|^As you place|^I could not find what you were referring to\.$|^Your bundle would be too large|^The .+ is too large to be bundled\.|^As you place your|^The .*? is already a bundle|^Your .*? won't fit in .*?\.$|^You can't .+ It's closed!$/
5976 end
5977 break if result =~ /^You (?:put|absent-mindedly drop|slip)/
5978 end
5979 end
5980 end
5981 $fill_hands_actions.push(actions)
5982end
5983
5984def fill_hands
5985 $fill_hands_actions ||= Array.new
5986 for action in $fill_hands_actions.pop
5987 action.call
5988 end
5989end
5990
5991def empty_hand
5992 $fill_hand_actions ||= Array.new
5993 actions = Array.new
5994 right_hand = GameObj.right_hand
5995 left_hand = GameObj.left_hand
5996 if UserVars.lootsack.nil? or UserVars.lootsack.empty?
5997 lootsack = nil
5998 else
5999 lootsack = GameObj.inv.find { |obj| obj.name =~ /#{Regexp.escape(UserVars.lootsack.strip)}/i } || GameObj.inv.find { |obj| obj.name =~ /#{Regexp.escape(UserVars.lootsack).sub(' ', ' .*')}/i }
6000 end
6001 other_containers_var = nil
6002 other_containers = proc {
6003 if other_containers_var.nil?
6004 Script.current.want_downstream = false
6005 Script.current.want_downstream_xml = true
6006 result = dothistimeout 'inventory containers', 5, /^You are wearing/
6007 Script.current.want_downstream_xml = false
6008 Script.current.want_downstream = true
6009 other_containers_ids = result.scan(/exist="(.*?)"/).flatten - [ lootsack.id ]
6010 other_containers_var = GameObj.inv.find_all { |obj| other_containers_ids.include?(obj.id) }
6011 end
6012 other_containers_var
6013 }
6014 unless (right_hand.id.nil? and ([ Wounds.rightArm, Wounds.rightHand, Scars.rightArm, Scars.rightHand ].max < 3)) or (left_hand.id.nil? and ([ Wounds.leftArm, Wounds.leftHand, Scars.leftArm, Scars.leftHand ].max < 3))
6015 if right_hand.id and ([ Wounds.rightArm, Wounds.rightHand, Scars.rightArm, Scars.rightHand ].max < 3 or [ Wounds.leftArm, Wounds.leftHand, Scars.leftArm, Scars.leftHand ].max = 3)
6016 waitrt?
6017 actions.unshift proc {
6018 dothistimeout "get ##{right_hand.id}", 3, /^You (?:shield the opening of .*? from view as you |discreetly |carefully |deftly )?(?:remove|draw|grab|reach|slip|tuck|retrieve|already have)|^Get what\?$|^Why don't you leave some for others\?$|^You need a free hand/
6019 20.times { break if GameObj.left_hand.id == right_hand.id or GameObj.right_hand.id == right_hand.id; sleep 0.1 }
6020 if GameObj.left_hand.id == right_hand.id
6021 dothistimeout 'swap', 3, /^You don't have anything to swap!|^You swap/
6022 end
6023 }
6024 if UserVars.weapon and UserVars.weaponsack and not UserVars.weapon.empty? and not UserVars.weaponsack.empty? and (right_hand.name =~ /#{Regexp.escape(UserVars.weapon.strip)}/i or right_hand.name =~ /#{Regexp.escape(UserVars.weapon).sub(' ', ' .*')}/i)
6025 weaponsack = GameObj.inv.find { |obj| obj.name =~ /#{Regexp.escape(UserVars.weaponsack.strip)}/i } || GameObj.inv.find { |obj| obj.name =~ /#{Regexp.escape(UserVars.weaponsack).sub(' ', ' .*')}/i }
6026 end
6027 if weaponsack
6028 result = dothistimeout "put ##{right_hand.id} in ##{weaponsack.id}", 4, /^You (?:attempt to shield .*? from view as you |discreetly |carefully |absent-mindedly )?(?:put|place|slip|tuck|add|drop|untie your|find an incomplete bundle|wipe off .*? and sheathe|secure)|^A sigh of grateful pleasure can be heard as you feed .*? to your|^As you place|^I could not find what you were referring to\.$|^Your bundle would be too large|^The .+ is too large to be bundled\.|^As you place your|^The .*? is already a bundle|^Your .*? won't fit in .*?\.$|^You can't .+ It's closed!$/
6029 if result =~ /^You can't .+ It's closed!$/
6030 actions.push proc { fput "close ##{weaponsack.id}" }
6031 dothistimeout "open ##{weaponsack.id}", 3, /^You open|^That is already open\./
6032 result = dothistimeout "put ##{right_hand.id} in ##{weaponsack.id}", 3, /^You (?:attempt to shield .*? from view as you |discreetly |carefully |absent-mindedly )?(?:put|place|slip|tuck|add|drop|untie your|find an incomplete bundle|wipe off .*? and sheathe|secure)|^A sigh of grateful pleasure can be heard as you feed .*? to your|^As you place|^I could not find what you were referring to\.$|^Your bundle would be too large|^The .+ is too large to be bundled\.|^As you place your|^The .*? is already a bundle|^Your .*? won't fit in .*?\.$|^You can't .+ It's closed!$/
6033 end
6034 elsif lootsack
6035 result = dothistimeout "put ##{right_hand.id} in ##{lootsack.id}", 4, /^You (?:attempt to shield .*? from view as you |discreetly |carefully |absent-mindedly )?(?:put|place|slip|tuck|add|drop|untie your|find an incomplete bundle|wipe off .*? and sheathe|secure)|^A sigh of grateful pleasure can be heard as you feed .*? to your|^As you place|^I could not find what you were referring to\.$|^Your bundle would be too large|^The .+ is too large to be bundled\.|^As you place your|^The .*? is already a bundle|^Your .*? won't fit in .*?\.$|^You can't .+ It's closed!$/
6036 if result =~ /^You can't .+ It's closed!$/
6037 actions.push proc { fput "close ##{lootsack.id}" }
6038 dothistimeout "open ##{lootsack.id}", 3, /^You open|^That is already open\./
6039 result = dothistimeout "put ##{right_hand.id} in ##{lootsack.id}", 3, /^You (?:attempt to shield .*? from view as you |discreetly |carefully |absent-mindedly )?(?:put|place|slip|tuck|add|drop|untie your|find an incomplete bundle|wipe off .*? and sheathe|secure)|^A sigh of grateful pleasure can be heard as you feed .*? to your|^As you place|^I could not find what you were referring to\.$|^Your bundle would be too large|^The .+ is too large to be bundled\.|^As you place your|^The .*? is already a bundle|^Your .*? won't fit in .*?\.$|^You can't .+ It's closed!$/
6040 end
6041 else
6042 result = nil
6043 end
6044 if result.nil? or result =~ /^Your .*? won't fit in .*?\.$/
6045 for container in other_containers.call
6046 result = dothistimeout "put ##{right_hand.id} in ##{container.id}", 4, /^You (?:attempt to shield .*? from view as you |discreetly |carefully |absent-mindedly )?(?:put|place|slip|tuck|add|drop|untie your|find an incomplete bundle|wipe off .*? and sheathe|secure)|^A sigh of grateful pleasure can be heard as you feed .*? to your|^As you place|^I could not find what you were referring to\.$|^Your bundle would be too large|^The .+ is too large to be bundled\.|^As you place your|^The .*? is already a bundle|^Your .*? won't fit in .*?\.$|^You can't .+ It's closed!$/
6047 if result =~ /^You can't .+ It's closed!$/
6048 actions.push proc { fput "close ##{container.id}" }
6049 dothistimeout "open ##{container.id}", 3, /^You open|^That is already open\./
6050 result = dothistimeout "put ##{right_hand.id} in ##{container.id}", 3, /^You (?:attempt to shield .*? from view as you |discreetly |carefully |absent-mindedly )?(?:put|place|slip|tuck|add|drop|untie your|find an incomplete bundle|wipe off .*? and sheathe|secure)|^A sigh of grateful pleasure can be heard as you feed .*? to your|^As you place|^I could not find what you were referring to\.$|^Your bundle would be too large|^The .+ is too large to be bundled\.|^As you place your|^The .*? is already a bundle|^Your .*? won't fit in .*?\.$|^You can't .+ It's closed!$/
6051 end
6052 break if result =~ /^You (?:put|absent-mindedly drop|slip)/
6053 end
6054 end
6055 else
6056 waitrt?
6057 if (left_hand.noun =~ /shield|buckler|targe|heater|parma|aegis|scutum|greatshield|mantlet|pavis|arbalest|bow|crossbow|yumi|arbalest/) and (wear_result = dothistimeout("wear ##{left_hand.id}", 8, /^You .*#{left_hand.noun}|^You can only wear \w+ items in that location\.$|^You can't wear that\.$/)) and (wear_result !~ /^You can only wear \w+ items in that location\.$|^You can't wear that\.$/)
6058 actions.unshift proc {
6059 dothistimeout "remove ##{left_hand.id}", 3, /^You|^Remove what\?/
6060 20.times { break if GameObj.left_hand.id == left_hand.id or GameObj.right_hand.id == left_hand.id; sleep 0.1 }
6061 if GameObj.right_hand.id == left_hand.id
6062 dothistimeout 'swap', 3, /^You don't have anything to swap!|^You swap/
6063 end
6064 }
6065 else
6066 actions.unshift proc {
6067 dothistimeout "get ##{left_hand.id}", 3, /^You (?:shield the opening of .*? from view as you |discreetly |carefully |deftly )?(?:remove|draw|grab|reach|slip|tuck|retrieve|already have)|^Get what\?$|^Why don't you leave some for others\?$|^You need a free hand/
6068 20.times { break if GameObj.left_hand.id == left_hand.id or GameObj.right_hand.id == left_hand.id; sleep 0.1 }
6069 if GameObj.right_hand.id == left_hand.id
6070 dothistimeout 'swap', 3, /^You don't have anything to swap!|^You swap/
6071 end
6072 }
6073 if lootsack
6074 result = dothistimeout "put ##{left_hand.id} in ##{lootsack.id}", 4, /^You (?:attempt to shield .*? from view as you |discreetly |carefully |absent-mindedly )?(?:put|place|slip|tuck|add|drop|untie your|find an incomplete bundle|wipe off .*? and sheathe|secure)|^A sigh of grateful pleasure can be heard as you feed .*? to your|^As you place|^I could not find what you were referring to\.$|^Your bundle would be too large|^The .+ is too large to be bundled\.|^As you place your|^The .*? is already a bundle|^Your .*? won't fit in .*?\.$|^You can't .+ It's closed!$/
6075 if result =~ /^You can't .+ It's closed!$/
6076 actions.push proc { fput "close ##{lootsack.id}" }
6077 dothistimeout "open ##{lootsack.id}", 3, /^You open|^That is already open\./
6078 result = dothistimeout "put ##{left_hand.id} in ##{lootsack.id}", 3, /^You (?:attempt to shield .*? from view as you |discreetly |carefully |absent-mindedly )?(?:put|place|slip|tuck|add|drop|untie your|find an incomplete bundle|wipe off .*? and sheathe|secure)|^A sigh of grateful pleasure can be heard as you feed .*? to your|^As you place|^I could not find what you were referring to\.$|^Your bundle would be too large|^The .+ is too large to be bundled\.|^As you place your|^The .*? is already a bundle|^Your .*? won't fit in .*?\.$|^You can't .+ It's closed!$/
6079 end
6080 else
6081 result = nil
6082 end
6083 if result.nil? or result =~ /^Your .*? won't fit in .*?\.$/
6084 for container in other_containers.call
6085 result = dothistimeout "put ##{left_hand.id} in ##{container.id}", 4, /^You (?:attempt to shield .*? from view as you |discreetly |carefully |absent-mindedly )?(?:put|place|slip|tuck|add|drop|untie your|find an incomplete bundle|wipe off .*? and sheathe|secure)|^A sigh of grateful pleasure can be heard as you feed .*? to your|^As you place|^I could not find what you were referring to\.$|^Your bundle would be too large|^The .+ is too large to be bundled\.|^As you place your|^The .*? is already a bundle|^Your .*? won't fit in .*?\.$|^You can't .+ It's closed!$/
6086 if result =~ /^You can't .+ It's closed!$/
6087 actions.push proc { fput "close ##{container.id}" }
6088 dothistimeout "open ##{container.id}", 3, /^You open|^That is already open\./
6089 result = dothistimeout "put ##{left_hand.id} in ##{container.id}", 3, /^You (?:attempt to shield .*? from view as you |discreetly |carefully |absent-mindedly )?(?:put|place|slip|tuck|add|drop|untie your|find an incomplete bundle|wipe off .*? and sheathe|secure)|^A sigh of grateful pleasure can be heard as you feed .*? to your|^As you place|^I could not find what you were referring to\.$|^Your bundle would be too large|^The .+ is too large to be bundled\.|^As you place your|^The .*? is already a bundle|^Your .*? won't fit in .*?\.$|^You can't .+ It's closed!$/
6090 end
6091 break if result =~ /^You (?:put|absent-mindedly drop|slip)/
6092 end
6093 end
6094 end
6095 end
6096 end
6097 $fill_hand_actions.push(actions)
6098end
6099
6100def fill_hand
6101 $fill_hand_actions ||= Array.new
6102 for action in $fill_hand_actions.pop
6103 action.call
6104 end
6105end
6106
6107def empty_right_hand
6108 $fill_right_hand_actions ||= Array.new
6109 actions = Array.new
6110 right_hand = GameObj.right_hand
6111 if UserVars.lootsack.nil? or UserVars.lootsack.empty?
6112 lootsack = nil
6113 else
6114 lootsack = GameObj.inv.find { |obj| obj.name =~ /#{Regexp.escape(UserVars.lootsack.strip)}/i } || GameObj.inv.find { |obj| obj.name =~ /#{Regexp.escape(UserVars.lootsack).sub(' ', ' .*')}/i }
6115 end
6116 other_containers_var = nil
6117 other_containers = proc {
6118 if other_containers_var.nil?
6119 Script.current.want_downstream = false
6120 Script.current.want_downstream_xml = true
6121 result = dothistimeout 'inventory containers', 5, /^You are wearing/
6122 Script.current.want_downstream_xml = false
6123 Script.current.want_downstream = true
6124 other_containers_ids = result.scan(/exist="(.*?)"/).flatten - [ lootsack.id ]
6125 other_containers_var = GameObj.inv.find_all { |obj| other_containers_ids.include?(obj.id) }
6126 end
6127 other_containers_var
6128 }
6129 if right_hand.id
6130 waitrt?
6131 actions.unshift proc {
6132 dothistimeout "get ##{right_hand.id}", 3, /^You (?:shield the opening of .*? from view as you |discreetly |carefully |deftly )?(?:remove|draw|grab|reach|slip|tuck|retrieve|already have)|^Get what\?$|^Why don't you leave some for others\?$|^You need a free hand/
6133 20.times { break if GameObj.left_hand.id == right_hand.id or GameObj.right_hand.id == right_hand.id; sleep 0.1 }
6134 if GameObj.left_hand.id == right_hand.id
6135 dothistimeout 'swap', 3, /^You don't have anything to swap!|^You swap/
6136 end
6137 }
6138 if UserVars.weapon and UserVars.weaponsack and not UserVars.weapon.empty? and not UserVars.weaponsack.empty? and (right_hand.name =~ /#{Regexp.escape(UserVars.weapon.strip)}/i or right_hand.name =~ /#{Regexp.escape(UserVars.weapon).sub(' ', ' .*')}/i)
6139 weaponsack = GameObj.inv.find { |obj| obj.name =~ /#{Regexp.escape(UserVars.weaponsack.strip)}/i } || GameObj.inv.find { |obj| obj.name =~ /#{Regexp.escape(UserVars.weaponsack).sub(' ', ' .*')}/i }
6140 end
6141 if weaponsack
6142 result = dothistimeout "put ##{right_hand.id} in ##{weaponsack.id}", 4, /^You (?:attempt to shield .*? from view as you |discreetly |carefully |absent-mindedly )?(?:put|place|slip|tuck|add|drop|untie your|find an incomplete bundle|wipe off .*? and sheathe|secure)|^A sigh of grateful pleasure can be heard as you feed .*? to your|^As you place|^I could not find what you were referring to\.$|^Your bundle would be too large|^The .+ is too large to be bundled\.|^As you place your|^The .*? is already a bundle|^Your .*? won't fit in .*?\.$|^You can't .+ It's closed!$/
6143 if result =~ /^You can't .+ It's closed!$/
6144 actions.push proc { fput "close ##{weaponsack.id}" }
6145 dothistimeout "open ##{weaponsack.id}", 3, /^You open|^That is already open\./
6146 result = dothistimeout "put ##{right_hand.id} in ##{weaponsack.id}", 3, /^You (?:attempt to shield .*? from view as you |discreetly |carefully |absent-mindedly )?(?:put|place|slip|tuck|add|drop|untie your|find an incomplete bundle|wipe off .*? and sheathe|secure)|^A sigh of grateful pleasure can be heard as you feed .*? to your|^As you place|^I could not find what you were referring to\.$|^Your bundle would be too large|^The .+ is too large to be bundled\.|^As you place your|^The .*? is already a bundle|^Your .*? won't fit in .*?\.$|^You can't .+ It's closed!$/
6147 end
6148 elsif lootsack
6149 result = dothistimeout "put ##{right_hand.id} in ##{lootsack.id}", 4, /^You (?:attempt to shield .*? from view as you |discreetly |carefully |absent-mindedly )?(?:put|place|slip|tuck|add|drop|untie your|find an incomplete bundle|wipe off .*? and sheathe|secure)|^A sigh of grateful pleasure can be heard as you feed .*? to your|^As you place|^I could not find what you were referring to\.$|^Your bundle would be too large|^The .+ is too large to be bundled\.|^As you place your|^The .*? is already a bundle|^Your .*? won't fit in .*?\.$|^You can't .+ It's closed!$/
6150 if result =~ /^You can't .+ It's closed!$/
6151 actions.push proc { fput "close ##{lootsack.id}" }
6152 dothistimeout "open ##{lootsack.id}", 3, /^You open|^That is already open\./
6153 result = dothistimeout "put ##{right_hand.id} in ##{lootsack.id}", 3, /^You (?:attempt to shield .*? from view as you |discreetly |carefully |absent-mindedly )?(?:put|place|slip|tuck|add|drop|untie your|find an incomplete bundle|wipe off .*? and sheathe|secure)|^A sigh of grateful pleasure can be heard as you feed .*? to your|^As you place|^I could not find what you were referring to\.$|^Your bundle would be too large|^The .+ is too large to be bundled\.|^As you place your|^The .*? is already a bundle|^Your .*? won't fit in .*?\.$|^You can't .+ It's closed!$/
6154 end
6155 else
6156 result = nil
6157 end
6158 if result.nil? or result =~ /^Your .*? won't fit in .*?\.$/
6159 for container in other_containers.call
6160 result = dothistimeout "put ##{right_hand.id} in ##{container.id}", 4, /^You (?:attempt to shield .*? from view as you |discreetly |carefully |absent-mindedly )?(?:put|place|slip|tuck|add|drop|untie your|find an incomplete bundle|wipe off .*? and sheathe|secure)|^A sigh of grateful pleasure can be heard as you feed .*? to your|^As you place|^I could not find what you were referring to\.$|^Your bundle would be too large|^The .+ is too large to be bundled\.|^As you place your|^The .*? is already a bundle|^Your .*? won't fit in .*?\.$|^You can't .+ It's closed!$/
6161 if result =~ /^You can't .+ It's closed!$/
6162 actions.push proc { fput "close ##{container.id}" }
6163 dothistimeout "open ##{container.id}", 3, /^You open|^That is already open\./
6164 result = dothistimeout "put ##{right_hand.id} in ##{container.id}", 3, /^You (?:attempt to shield .*? from view as you |discreetly |carefully |absent-mindedly )?(?:put|place|slip|tuck|add|drop|untie your|find an incomplete bundle|wipe off .*? and sheathe|secure)|^A sigh of grateful pleasure can be heard as you feed .*? to your|^As you place|^I could not find what you were referring to\.$|^Your bundle would be too large|^The .+ is too large to be bundled\.|^As you place your|^The .*? is already a bundle|^Your .*? won't fit in .*?\.$|^You can't .+ It's closed!$/
6165 end
6166 break if result =~ /^You (?:put|absent-mindedly drop|slip)/
6167 end
6168 end
6169 end
6170 $fill_right_hand_actions.push(actions)
6171end
6172
6173def fill_right_hand
6174 $fill_right_hand_actions ||= Array.new
6175 for action in $fill_right_hand_actions.pop
6176 action.call
6177 end
6178end
6179
6180def empty_left_hand
6181 $fill_left_hand_actions ||= Array.new
6182 actions = Array.new
6183 left_hand = GameObj.left_hand
6184 if UserVars.lootsack.nil? or UserVars.lootsack.empty?
6185 lootsack = nil
6186 else
6187 lootsack = GameObj.inv.find { |obj| obj.name =~ /#{Regexp.escape(UserVars.lootsack.strip)}/i } || GameObj.inv.find { |obj| obj.name =~ /#{Regexp.escape(UserVars.lootsack).sub(' ', ' .*')}/i }
6188 end
6189 other_containers_var = nil
6190 other_containers = proc {
6191 if other_containers_var.nil?
6192 Script.current.want_downstream = false
6193 Script.current.want_downstream_xml = true
6194 result = dothistimeout 'inventory containers', 5, /^You are wearing/
6195 Script.current.want_downstream_xml = false
6196 Script.current.want_downstream = true
6197 other_containers_ids = result.scan(/exist="(.*?)"/).flatten - [ lootsack.id ]
6198 other_containers_var = GameObj.inv.find_all { |obj| other_containers_ids.include?(obj.id) }
6199 end
6200 other_containers_var
6201 }
6202 if left_hand.id
6203 waitrt?
6204 if (left_hand.noun =~ /shield|buckler|targe|heater|parma|aegis|scutum|greatshield|mantlet|pavis|arbalest|bow|crossbow|yumi|arbalest/) and (wear_result = dothistimeout("wear ##{left_hand.id}", 8, /^You .*#{left_hand.noun}|^You can only wear \w+ items in that location\.$|^You can't wear that\.$/)) and (wear_result !~ /^You can only wear \w+ items in that location\.$|^You can't wear that\.$/)
6205 actions.unshift proc {
6206 dothistimeout "remove ##{left_hand.id}", 3, /^You|^Remove what\?/
6207 20.times { break if GameObj.left_hand.id == left_hand.id or GameObj.right_hand.id == left_hand.id; sleep 0.1 }
6208 if GameObj.right_hand.id == left_hand.id
6209 dothistimeout 'swap', 3, /^You don't have anything to swap!|^You swap/
6210 end
6211 }
6212 else
6213 actions.unshift proc {
6214 dothistimeout "get ##{left_hand.id}", 3, /^You (?:shield the opening of .*? from view as you |discreetly |carefully |deftly )?(?:remove|draw|grab|reach|slip|tuck|retrieve|already have)|^Get what\?$|^Why don't you leave some for others\?$|^You need a free hand/
6215 20.times { break if GameObj.left_hand.id == left_hand.id or GameObj.right_hand.id == left_hand.id; sleep 0.1 }
6216 if GameObj.right_hand.id == left_hand.id
6217 dothistimeout 'swap', 3, /^You don't have anything to swap!|^You swap/
6218 end
6219 }
6220 if lootsack
6221 result = dothistimeout "put ##{left_hand.id} in ##{lootsack.id}", 4, /^You (?:attempt to shield .*? from view as you |discreetly |carefully |absent-mindedly )?(?:put|place|slip|tuck|add|drop|untie your|find an incomplete bundle|wipe off .*? and sheathe|secure)|^A sigh of grateful pleasure can be heard as you feed .*? to your|^As you place|^I could not find what you were referring to\.$|^Your bundle would be too large|^The .+ is too large to be bundled\.|^As you place your|^The .*? is already a bundle|^Your .*? won't fit in .*?\.$|^You can't .+ It's closed!$/
6222 if result =~ /^You can't .+ It's closed!$/
6223 actions.push proc { fput "close ##{lootsack.id}" }
6224 dothistimeout "open ##{lootsack.id}", 3, /^You open|^That is already open\./
6225 dothistimeout "put ##{left_hand.id} in ##{lootsack.id}", 3, /^You (?:attempt to shield .*? from view as you |discreetly |carefully |absent-mindedly )?(?:put|place|slip|tuck|add|drop|untie your|find an incomplete bundle|wipe off .*? and sheathe|secure)|^A sigh of grateful pleasure can be heard as you feed .*? to your|^As you place|^I could not find what you were referring to\.$|^Your bundle would be too large|^The .+ is too large to be bundled\.|^As you place your|^The .*? is already a bundle|^Your .*? won't fit in .*?\.$|^You can't .+ It's closed!$/
6226 end
6227 else
6228 result = nil
6229 end
6230 if result.nil? or result =~ /^Your .*? won't fit in .*?\.$/
6231 for container in other_containers.call
6232 result = dothistimeout "put ##{left_hand.id} in ##{container.id}", 4, /^You (?:attempt to shield .*? from view as you |discreetly |carefully |absent-mindedly )?(?:put|place|slip|tuck|add|drop|untie your|find an incomplete bundle|wipe off .*? and sheathe|secure)|^A sigh of grateful pleasure can be heard as you feed .*? to your|^As you place|^I could not find what you were referring to\.$|^Your bundle would be too large|^The .+ is too large to be bundled\.|^As you place your|^The .*? is already a bundle|^Your .*? won't fit in .*?\.$|^You can't .+ It's closed!$/
6233 if result =~ /^You can't .+ It's closed!$/
6234 actions.push proc { fput "close ##{container.id}" }
6235 dothistimeout "open ##{container.id}", 3, /^You open|^That is already open\./
6236 result = dothistimeout "put ##{left_hand.id} in ##{container.id}", 3, /^You (?:attempt to shield .*? from view as you |discreetly |carefully |absent-mindedly )?(?:put|place|slip|tuck|add|drop|untie your|find an incomplete bundle|wipe off .*? and sheathe|secure)|^A sigh of grateful pleasure can be heard as you feed .*? to your|^As you place|^I could not find what you were referring to\.$|^Your bundle would be too large|^The .+ is too large to be bundled\.|^As you place your|^The .*? is already a bundle|^Your .*? won't fit in .*?\.$|^You can't .+ It's closed!$/
6237 end
6238 break if result =~ /^You (?:put|absent-mindedly drop|slip)/
6239 end
6240 end
6241 end
6242 end
6243 $fill_left_hand_actions.push(actions)
6244end
6245
6246def fill_left_hand
6247 $fill_left_hand_actions ||= Array.new
6248 for action in $fill_left_hand_actions.pop
6249 action.call
6250 end
6251end
6252
6253def dothis (action, success_line)
6254 loop {
6255 Script.current.clear
6256 put action
6257 loop {
6258 line = get
6259 if line =~ success_line
6260 return line
6261 elsif line =~ /^(\.\.\.w|W)ait ([0-9]+) sec(onds)?\.$/
6262 if $2.to_i > 1
6263 sleep ($2.to_i - "0.5".to_f)
6264 else
6265 sleep 0.3
6266 end
6267 break
6268 elsif line == 'Sorry, you may only type ahead 1 command.'
6269 sleep 1
6270 break
6271 elsif line == 'You are still stunned.'
6272 wait_while { stunned? }
6273 break
6274 elsif line == 'That is impossible to do while unconscious!'
6275 100.times {
6276 unless line = get?
6277 sleep 0.1
6278 else
6279 break if line =~ /Your thoughts slowly come back to you as you find yourself lying on the ground\. You must have been sleeping\.$|^You wake up from your slumber\.$/
6280 end
6281 }
6282 break
6283 elsif line == "You don't seem to be able to move to do that."
6284 100.times {
6285 unless line = get?
6286 sleep 0.1
6287 else
6288 break if line == 'The restricting force that envelops you dissolves away.'
6289 end
6290 }
6291 break
6292 elsif line == "You can't do that while entangled in a web."
6293 wait_while { checkwebbed }
6294 break
6295 elsif line == 'You find that impossible under the effects of the lullabye.'
6296 100.times {
6297 unless line = get?
6298 sleep 0.1
6299 else
6300 # fixme
6301 break if line == 'You shake off the effects of the lullabye.'
6302 end
6303 }
6304 break
6305 end
6306 }
6307 }
6308end
6309
6310def dothistimeout (action, timeout, success_line)
6311 end_time = Time.now.to_f + timeout
6312 line = nil
6313 loop {
6314 Script.current.clear
6315 put action unless action.nil?
6316 loop {
6317 line = get?
6318 if line.nil?
6319 sleep 0.1
6320 elsif line =~ success_line
6321 return line
6322 elsif line =~ /^(\.\.\.w|W)ait ([0-9]+) sec(onds)?\.$/
6323 if $2.to_i > 1
6324 sleep ($2.to_i - "0.5".to_f)
6325 else
6326 sleep 0.3
6327 end
6328 end_time = Time.now.to_f + timeout
6329 break
6330 elsif line == 'Sorry, you may only type ahead 1 command.'
6331 sleep 1
6332 end_time = Time.now.to_f + timeout
6333 break
6334 elsif line == 'You are still stunned.'
6335 wait_while { stunned? }
6336 end_time = Time.now.to_f + timeout
6337 break
6338 elsif line == 'That is impossible to do while unconscious!'
6339 100.times {
6340 unless line = get?
6341 sleep 0.1
6342 else
6343 break if line =~ /Your thoughts slowly come back to you as you find yourself lying on the ground\. You must have been sleeping\.$|^You wake up from your slumber\.$/
6344 end
6345 }
6346 break
6347 elsif line == "You don't seem to be able to move to do that."
6348 100.times {
6349 unless line = get?
6350 sleep 0.1
6351 else
6352 break if line == 'The restricting force that envelops you dissolves away.'
6353 end
6354 }
6355 break
6356 elsif line == "You can't do that while entangled in a web."
6357 wait_while { checkwebbed }
6358 break
6359 elsif line == 'You find that impossible under the effects of the lullabye.'
6360 100.times {
6361 unless line = get?
6362 sleep 0.1
6363 else
6364 # fixme
6365 break if line == 'You shake off the effects of the lullabye.'
6366 end
6367 }
6368 break
6369 end
6370 if Time.now.to_f >= end_time
6371 return nil
6372 end
6373 }
6374 }
6375end
6376
6377$link_highlight_start = ''
6378$link_highlight_end = ''
6379$speech_highlight_start = ''
6380$speech_highlight_end = ''
6381
6382def sf_to_wiz(line)
6383 begin
6384 return line if line == "\r\n"
6385
6386 if $sftowiz_multiline
6387 $sftowiz_multiline = $sftowiz_multiline + line
6388 line = $sftowiz_multiline
6389 end
6390 if (line.scan(/<pushStream[^>]*\/>/).length > line.scan(/<popStream[^>]*\/>/).length)
6391 $sftowiz_multiline = line
6392 return nil
6393 end
6394 if (line.scan(/<style id="\w+"[^>]*\/>/).length > line.scan(/<style id=""[^>]*\/>/).length)
6395 $sftowiz_multiline = line
6396 return nil
6397 end
6398 $sftowiz_multiline = nil
6399 if line =~ /<LaunchURL src="(.*?)" \/>/
6400 $_CLIENT_.puts "\034GSw00005\r\nhttps://www.play.net#{$1}\r\n"
6401 end
6402 if line =~ /<preset id='speech'>(.*?)<\/preset>/m
6403 line = line.sub(/<preset id='speech'>.*?<\/preset>/m, "#{$speech_highlight_start}#{$1}#{$speech_highlight_end}")
6404 end
6405 if line =~ /<pushStream id="thoughts"[^>]*>(?:<a[^>]*>)?([A-Z][a-z]+)(?:<\/a>)?\s*([\s\[\]\(\)A-z]+)?:(.*?)<popStream\/>/m
6406 line = line.sub(/<pushStream id="thoughts"[^>]*>(?:<a[^>]*>)?[A-Z][a-z]+(?:<\/a>)?\s*[\s\[\]\(\)A-z]+:.*?<popStream\/>/m, "You hear the faint thoughts of #{$1} echo in your mind:\r\n#{$2}#{$3}")
6407 end
6408 if line =~ /<pushStream id="voln"[^>]*>\[Voln \- (?:<a[^>]*>)?([A-Z][a-z]+)(?:<\/a>)?\]\s*(".*")[\r\n]*<popStream\/>/m
6409 line = line.sub(/<pushStream id="voln"[^>]*>\[Voln \- (?:<a[^>]*>)?([A-Z][a-z]+)(?:<\/a>)?\]\s*(".*")[\r\n]*<popStream\/>/m, "The Symbol of Thought begins to burn in your mind and you hear #{$1} thinking, #{$2}\r\n")
6410 end
6411 if line =~ /<stream id="thoughts"[^>]*>([^:]+): (.*?)<\/stream>/m
6412 line = line.sub(/<stream id="thoughts"[^>]*>.*?<\/stream>/m, "You hear the faint thoughts of #{$1} echo in your mind:\r\n#{$2}")
6413 end
6414 if line =~ /<pushStream id="familiar"[^>]*>(.*)<popStream\/>/m
6415 line = line.sub(/<pushStream id="familiar"[^>]*>.*<popStream\/>/m, "\034GSe\r\n#{$1}\034GSf\r\n")
6416 end
6417 if line =~ /<pushStream id="death"\/>(.*?)<popStream\/>/m
6418 line = line.sub(/<pushStream id="death"\/>.*?<popStream\/>/m, "\034GSw00003\r\n#{$1}\034GSw00004\r\n")
6419 end
6420 if line =~ /<style id="roomName" \/>(.*?)<style id=""\/>/m
6421 line = line.sub(/<style id="roomName" \/>.*?<style id=""\/>/m, "\034GSo\r\n#{$1}\034GSp\r\n")
6422 end
6423 line.gsub!(/<style id="roomDesc"\/><style id=""\/>\r?\n/, '')
6424 if line =~ /<style id="roomDesc"\/>(.*?)<style id=""\/>/m
6425 desc = $1.gsub(/<a[^>]*>/, $link_highlight_start).gsub("</a>", $link_highlight_end)
6426 line = line.sub(/<style id="roomDesc"\/>.*?<style id=""\/>/m, "\034GSH\r\n#{desc}\034GSI\r\n")
6427 end
6428 line = line.gsub("</prompt>\r\n", "</prompt>")
6429 line = line.gsub("<pushBold/>", "\034GSL\r\n")
6430 line = line.gsub("<popBold/>", "\034GSM\r\n")
6431 line = line.gsub(/<pushStream id=["'](?:spellfront|inv|bounty|society|speech|talk)["'][^>]*\/>.*?<popStream[^>]*>/m, '')
6432 line = line.gsub(/<stream id="Spells">.*?<\/stream>/m, '')
6433 line = line.gsub(/<(compDef|inv|component|right|left|spell|prompt)[^>]*>.*?<\/\1>/m, '')
6434 line = line.gsub(/<[^>]+>/, '')
6435 line = line.gsub('>', '>')
6436 line = line.gsub('<', '<')
6437 return nil if line.gsub("\r\n", '').length < 1
6438 return line
6439 rescue
6440 $_CLIENT_.puts "--- Error: sf_to_wiz: #{$!}"
6441 $_CLIENT_.puts '$_SERVERSTRING_: ' + $_SERVERSTRING_.to_s
6442 end
6443end
6444
6445def strip_xml(line)
6446 return line if line == "\r\n"
6447
6448 if $strip_xml_multiline
6449 $strip_xml_multiline = $strip_xml_multiline + line
6450 line = $strip_xml_multiline
6451 end
6452 if (line.scan(/<pushStream[^>]*\/>/).length > line.scan(/<popStream[^>]*\/>/).length)
6453 $strip_xml_multiline = line
6454 return nil
6455 end
6456 $strip_xml_multiline = nil
6457
6458 line = line.gsub(/<pushStream id=["'](?:spellfront|inv|bounty|society|speech|talk)["'][^>]*\/>.*?<popStream[^>]*>/m, '')
6459 line = line.gsub(/<stream id="Spells">.*?<\/stream>/m, '')
6460 line = line.gsub(/<(compDef|inv|component|right|left|spell|prompt)[^>]*>.*?<\/\1>/m, '')
6461 line = line.gsub(/<[^>]+>/, '')
6462 line = line.gsub('>', '>')
6463 line = line.gsub('<', '<')
6464
6465 return nil if line.gsub("\n", '').gsub("\r", '').gsub(' ', '').length < 1
6466 return line
6467end
6468
6469def monsterbold_start
6470 if $frontend =~ /^(?:wizard|avalon)$/
6471 "\034GSL\r\n"
6472 elsif $frontend == 'stormfront'
6473 '<pushBold/>'
6474 elsif $frontend == 'profanity'
6475 '<b>'
6476 else
6477 ''
6478 end
6479end
6480
6481def monsterbold_end
6482 if $frontend =~ /^(?:wizard|avalon)$/
6483 "\034GSM\r\n"
6484 elsif $frontend == 'stormfront'
6485 '<popBold/>'
6486 elsif $frontend == 'profanity'
6487 '</b>'
6488 else
6489 ''
6490 end
6491end
6492
6493def do_client(client_string)
6494 client_string.strip!
6495# Buffer.update(client_string, Buffer::UPSTREAM)
6496 client_string = UpstreamHook.run(client_string)
6497# Buffer.update(client_string, Buffer::UPSTREAM_MOD)
6498 return nil if client_string.nil?
6499 if client_string =~ /^(?:<c>)?#{$lich_char}(.+)$/
6500 cmd = $1
6501 if cmd =~ /^k$|^kill$|^stop$/
6502 if Script.running.empty?
6503 respond '--- Lich: no scripts to kill'
6504 else
6505 Script.running.last.kill
6506 end
6507 elsif cmd =~ /^p$|^pause$/
6508 if s = Script.running.reverse.find { |s| not s.paused? }
6509 s.pause
6510 else
6511 respond '--- Lich: no scripts to pause'
6512 end
6513 s = nil
6514 elsif cmd =~ /^u$|^unpause$/
6515 if s = Script.running.reverse.find { |s| s.paused? }
6516 s.unpause
6517 else
6518 respond '--- Lich: no scripts to unpause'
6519 end
6520 s = nil
6521 elsif cmd =~ /^ka$|^kill\s?all$|^stop\s?all$/
6522 did_something = false
6523 Script.running.find_all { |s| not s.no_kill_all }.each { |s| s.kill; did_something = true }
6524 respond('--- Lich: no scripts to kill') unless did_something
6525 elsif cmd =~ /^pa$|^pause\s?all$/
6526 did_something = false
6527 Script.running.find_all { |s| not s.paused? and not s.no_pause_all }.each { |s| s.pause; did_something = true }
6528 respond('--- Lich: no scripts to pause') unless did_something
6529 elsif cmd =~ /^ua$|^unpause\s?all$/
6530 did_something = false
6531 Script.running.find_all { |s| s.paused? and not s.no_pause_all }.each { |s| s.unpause; did_something = true }
6532 respond('--- Lich: no scripts to unpause') unless did_something
6533 elsif cmd =~ /^(k|kill|stop|p|pause|u|unpause)\s(.+)/
6534 action = $1
6535 target = $2
6536 script = Script.running.find { |s| s.name == target } || Script.hidden.find { |s| s.name == target } || Script.running.find { |s| s.name =~ /^#{target}/i } || Script.hidden.find { |s| s.name =~ /^#{target}/i }
6537 if script.nil?
6538 respond "--- Lich: #{target} does not appear to be running! Use ';list' or ';listall' to see what's active."
6539 elsif action =~ /^(?:k|kill|stop)$/
6540 script.kill
6541 elsif action =~/^(?:p|pause)$/
6542 script.pause
6543 elsif action =~/^(?:u|unpause)$/
6544 script.unpause
6545 end
6546 action = target = script = nil
6547 elsif cmd =~ /^list\s?(?:all)?$|^l(?:a)?$/i
6548 if cmd =~ /a(?:ll)?/i
6549 list = Script.running + Script.hidden
6550 else
6551 list = Script.running
6552 end
6553 if list.empty?
6554 respond '--- Lich: no active scripts'
6555 else
6556 respond "--- Lich: #{list.collect { |s| s.paused? ? "#{s.name} (paused)" : s.name }.join(", ")}"
6557 end
6558 list = nil
6559 elsif cmd =~ /^force\s+[^\s]+/
6560 if cmd =~ /^force\s+([^\s]+)\s+(.+)$/
6561 Script.start($1, $2, :force => true)
6562 elsif cmd =~ /^force\s+([^\s]+)/
6563 Script.start($1, :force => true)
6564 end
6565 elsif cmd =~ /^send |^s /
6566 if cmd.split[1] == "to"
6567 script = (Script.running + Script.hidden).find { |scr| scr.name == cmd.split[2].chomp.strip } || script = (Script.running + Script.hidden).find { |scr| scr.name =~ /^#{cmd.split[2].chomp.strip}/i }
6568 if script
6569 msg = cmd.split[3..-1].join(' ').chomp
6570 if script.want_downstream
6571 script.downstream_buffer.push(msg)
6572 else
6573 script.unique_buffer.push(msg)
6574 end
6575 respond "--- sent to '#{script.name}': #{msg}"
6576 else
6577 respond "--- Lich: '#{cmd.split[2].chomp.strip}' does not match any active script!"
6578 end
6579 script = nil
6580 else
6581 if Script.running.empty? and Script.hidden.empty?
6582 respond('--- Lich: no active scripts to send to.')
6583 else
6584 msg = cmd.split[1..-1].join(' ').chomp
6585 respond("--- sent: #{msg}")
6586 Script.new_downstream(msg)
6587 end
6588 end
6589 elsif cmd =~ /^(?:exec|e)(q)? (.+)$/
6590 cmd_data = $2
6591 if $1.nil?
6592 ExecScript.start(cmd_data, flags={ :quiet => false, :trusted => true })
6593 else
6594 ExecScript.start(cmd_data, flags={ :quiet => true, :trusted => true })
6595 end
6596 elsif cmd =~ /^trust\s+(.*)/i
6597 script_name = $1
6598 if File.exists?("#{SCRIPT_DIR}/#{script_name}.lic")
6599 if Script.trust(script_name)
6600 respond "--- Lich: '#{script_name}' is now a trusted script."
6601 end
6602 else
6603 respond "--- Lich: could not find script: #{script_name}"
6604 end
6605 elsif cmd =~ /^(?:dis|un)trust\s+(.*)/i
6606 script_name = $1
6607 if Script.distrust(script_name)
6608 respond "--- Lich: '#{script_name}' is no longer a trusted script."
6609 else
6610 respond "--- Lich: '#{script_name}' was not found in the trusted script list."
6611 end
6612 elsif cmd =~ /^list\s?(?:un)?trust(?:ed)?$|^lt$/i
6613 list = Script.list_trusted
6614 if list.empty?
6615 respond "--- Lich: no scripts are trusted"
6616 else
6617 respond "--- Lich: trusted scripts: #{list.join(', ')}"
6618 end
6619 list = nil
6620 elsif cmd =~ /^help$/i
6621 respond
6622 respond "Lich v#{LICH_VERSION}"
6623 respond
6624 respond 'built-in commands:'
6625 respond " #{$clean_lich_char}<script name> start a script"
6626 respond " #{$clean_lich_char}force <script name> start a script even if it's already running"
6627 respond " #{$clean_lich_char}pause <script name> pause a script"
6628 respond " #{$clean_lich_char}p <script name> ''"
6629 respond " #{$clean_lich_char}unpause <script name> unpause a script"
6630 respond " #{$clean_lich_char}u <script name> ''"
6631 respond " #{$clean_lich_char}kill <script name> kill a script"
6632 respond " #{$clean_lich_char}k <script name> ''"
6633 respond " #{$clean_lich_char}pause pause the most recently started script that isn't aready paused"
6634 respond " #{$clean_lich_char}p ''"
6635 respond " #{$clean_lich_char}unpause unpause the most recently started script that is paused"
6636 respond " #{$clean_lich_char}u ''"
6637 respond " #{$clean_lich_char}kill kill the most recently started script"
6638 respond " #{$clean_lich_char}k ''"
6639 respond " #{$clean_lich_char}list show running scripts (except hidden ones)"
6640 respond " #{$clean_lich_char}l ''"
6641 respond " #{$clean_lich_char}pause all pause all scripts"
6642 respond " #{$clean_lich_char}pa ''"
6643 respond " #{$clean_lich_char}unpause all unpause all scripts"
6644 respond " #{$clean_lich_char}ua ''"
6645 respond " #{$clean_lich_char}kill all kill all scripts"
6646 respond " #{$clean_lich_char}ka ''"
6647 respond " #{$clean_lich_char}list all show all running scripts"
6648 respond " #{$clean_lich_char}la ''"
6649 respond
6650 respond " #{$clean_lich_char}exec <code> executes the code as if it was in a script"
6651 respond " #{$clean_lich_char}e <code> ''"
6652 respond " #{$clean_lich_char}execq <code> same as #{$clean_lich_char}exec but without the script active and exited messages"
6653 respond " #{$clean_lich_char}eq <code> ''"
6654 respond
6655 respond " #{$clean_lich_char}trust <script name> let the script do whatever it wants"
6656 respond " #{$clean_lich_char}distrust <script name> restrict the script from doing things that might harm your computer"
6657 respond " #{$clean_lich_char}list trusted show what scripts are trusted"
6658 respond " #{$clean_lich_char}lt ''"
6659 respond
6660 respond " #{$clean_lich_char}send <line> send a line to all scripts as if it came from the game"
6661 respond " #{$clean_lich_char}send to <script> <line> send a line to a specific script"
6662 respond
6663 respond 'If you liked this help message, you might also enjoy:'
6664 respond " #{$clean_lich_char}lnet help"
6665 respond " #{$clean_lich_char}magic help (infomon must be running)"
6666 respond " #{$clean_lich_char}go2 help"
6667 respond " #{$clean_lich_char}repository help"
6668 respond " #{$clean_lich_char}alias help"
6669 respond " #{$clean_lich_char}vars help"
6670 respond " #{$clean_lich_char}autostart help"
6671 respond
6672 else
6673 if cmd =~ /^([^\s]+)\s+(.+)/
6674 Script.start($1, $2)
6675 else
6676 Script.start(cmd)
6677 end
6678 end
6679 else
6680 if $offline_mode
6681 respond "--- Lich: offline mode: ignoring #{client_string}"
6682 else
6683 client_string = "#{$cmd_prefix}bbs" if ($frontend =~ /^(?:wizard|avalon)$/) and (client_string == "#{$cmd_prefix}\egbbk\n") # launch forum
6684 Game._puts client_string
6685 end
6686 $_CLIENTBUFFER_.push client_string
6687 end
6688 Script.new_upstream(client_string)
6689end
6690
6691def report_errors(&block)
6692 begin
6693 block.call
6694 rescue
6695 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
6696 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
6697 rescue SyntaxError
6698 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
6699 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
6700 rescue SystemExit
6701 nil
6702 rescue SecurityError
6703 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
6704 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
6705 rescue ThreadError
6706 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
6707 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
6708 rescue SystemStackError
6709 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
6710 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
6711 rescue Exception
6712 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
6713 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
6714 rescue ScriptError
6715 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
6716 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
6717 rescue LoadError
6718 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
6719 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
6720 rescue NoMemoryError
6721 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
6722 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
6723 rescue
6724 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
6725 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
6726 end
6727end
6728
6729module Buffer
6730 DOWNSTREAM_STRIPPED = 1
6731 DOWNSTREAM_RAW = 2
6732 DOWNSTREAM_MOD = 4
6733 UPSTREAM = 8
6734 UPSTREAM_MOD = 16
6735 SCRIPT_OUTPUT = 32
6736 @@index = Hash.new
6737 @@streams = Hash.new
6738 @@mutex = Mutex.new
6739 @@offset = 0
6740 @@buffer = Array.new
6741 @@max_size = 3000
6742 def Buffer.gets
6743 thread_id = Thread.current.object_id
6744 if @@index[thread_id].nil?
6745 @@mutex.synchronize {
6746 @@index[thread_id] = (@@offset + @@buffer.length)
6747 @@streams[thread_id] ||= DOWNSTREAM_STRIPPED
6748 }
6749 end
6750 line = nil
6751 loop {
6752 if (@@index[thread_id] - @@offset) >= @@buffer.length
6753 sleep 0.05 while ((@@index[thread_id] - @@offset) >= @@buffer.length)
6754 end
6755 @@mutex.synchronize {
6756 if @@index[thread_id] < @@offset
6757 @@index[thread_id] = @@offset
6758 end
6759 line = @@buffer[@@index[thread_id] - @@offset]
6760 }
6761 @@index[thread_id] += 1
6762 break if ((line.stream & @@streams[thread_id]) != 0)
6763 }
6764 return line
6765 end
6766 def Buffer.gets?
6767 thread_id = Thread.current.object_id
6768 if @@index[thread_id].nil?
6769 @@mutex.synchronize {
6770 @@index[thread_id] = (@@offset + @@buffer.length)
6771 @@streams[thread_id] ||= DOWNSTREAM_STRIPPED
6772 }
6773 end
6774 line = nil
6775 loop {
6776 if (@@index[thread_id] - @@offset) >= @@buffer.length
6777 return nil
6778 end
6779 @@mutex.synchronize {
6780 if @@index[thread_id] < @@offset
6781 @@index[thread_id] = @@offset
6782 end
6783 line = @@buffer[@@index[thread_id] - @@offset]
6784 }
6785 @@index[thread_id] += 1
6786 break if ((line.stream & @@streams[thread_id]) != 0)
6787 }
6788 return line
6789 end
6790 def Buffer.rewind
6791 thread_id = Thread.current.object_id
6792 @@index[thread_id] = @@offset
6793 @@streams[thread_id] ||= DOWNSTREAM_STRIPPED
6794 return self
6795 end
6796 def Buffer.clear
6797 thread_id = Thread.current.object_id
6798 if @@index[thread_id].nil?
6799 @@mutex.synchronize {
6800 @@index[thread_id] = (@@offset + @@buffer.length)
6801 @@streams[thread_id] ||= DOWNSTREAM_STRIPPED
6802 }
6803 end
6804 lines = Array.new
6805 loop {
6806 if (@@index[thread_id] - @@offset) >= @@buffer.length
6807 return lines
6808 end
6809 line = nil
6810 @@mutex.synchronize {
6811 if @@index[thread_id] < @@offset
6812 @@index[thread_id] = @@offset
6813 end
6814 line = @@buffer[@@index[thread_id] - @@offset]
6815 }
6816 @@index[thread_id] += 1
6817 lines.push(line) if ((line.stream & @@streams[thread_id]) != 0)
6818 }
6819 return lines
6820 end
6821 def Buffer.update(line, stream=nil)
6822 @@mutex.synchronize {
6823 frozen_line = line.dup
6824 unless stream.nil?
6825 frozen_line.stream = stream
6826 end
6827 frozen_line.freeze
6828 @@buffer.push(frozen_line)
6829 while (@@buffer.length > @@max_size)
6830 @@buffer.shift
6831 @@offset += 1
6832 end
6833 }
6834 return self
6835 end
6836 def Buffer.streams
6837 @@streams[Thread.current.object_id]
6838 end
6839 def Buffer.streams=(val)
6840 if (val.class != Fixnum) or ((val & 63) == 0)
6841 respond "--- Lich: error: invalid streams value\n\t#{$!.caller[0..2].join("\n\t")}"
6842 return nil
6843 end
6844 @@streams[Thread.current.object_id] = val
6845 end
6846 def Buffer.cleanup
6847 @@index.delete_if { |k,v| not Thread.list.any? { |t| t.object_id == k } }
6848 @@streams.delete_if { |k,v| not Thread.list.any? { |t| t.object_id == k } }
6849 return self
6850 end
6851end
6852
6853class SharedBuffer
6854 attr_accessor :max_size
6855 def initialize(args={})
6856 @buffer = Array.new
6857 @buffer_offset = 0
6858 @buffer_index = Hash.new
6859 @buffer_mutex = Mutex.new
6860 @max_size = args[:max_size] || 500
6861 return self
6862 end
6863 def gets
6864 thread_id = Thread.current.object_id
6865 if @buffer_index[thread_id].nil?
6866 @buffer_mutex.synchronize { @buffer_index[thread_id] = (@buffer_offset + @buffer.length) }
6867 end
6868 if (@buffer_index[thread_id] - @buffer_offset) >= @buffer.length
6869 sleep 0.05 while ((@buffer_index[thread_id] - @buffer_offset) >= @buffer.length)
6870 end
6871 line = nil
6872 @buffer_mutex.synchronize {
6873 if @buffer_index[thread_id] < @buffer_offset
6874 @buffer_index[thread_id] = @buffer_offset
6875 end
6876 line = @buffer[@buffer_index[thread_id] - @buffer_offset]
6877 }
6878 @buffer_index[thread_id] += 1
6879 return line
6880 end
6881 def gets?
6882 thread_id = Thread.current.object_id
6883 if @buffer_index[thread_id].nil?
6884 @buffer_mutex.synchronize { @buffer_index[thread_id] = (@buffer_offset + @buffer.length) }
6885 end
6886 if (@buffer_index[thread_id] - @buffer_offset) >= @buffer.length
6887 return nil
6888 end
6889 line = nil
6890 @buffer_mutex.synchronize {
6891 if @buffer_index[thread_id] < @buffer_offset
6892 @buffer_index[thread_id] = @buffer_offset
6893 end
6894 line = @buffer[@buffer_index[thread_id] - @buffer_offset]
6895 }
6896 @buffer_index[thread_id] += 1
6897 return line
6898 end
6899 def clear
6900 thread_id = Thread.current.object_id
6901 if @buffer_index[thread_id].nil?
6902 @buffer_mutex.synchronize { @buffer_index[thread_id] = (@buffer_offset + @buffer.length) }
6903 return Array.new
6904 end
6905 if (@buffer_index[thread_id] - @buffer_offset) >= @buffer.length
6906 return Array.new
6907 end
6908 lines = Array.new
6909 @buffer_mutex.synchronize {
6910 if @buffer_index[thread_id] < @buffer_offset
6911 @buffer_index[thread_id] = @buffer_offset
6912 end
6913 lines = @buffer[(@buffer_index[thread_id] - @buffer_offset)..-1]
6914 @buffer_index[thread_id] = (@buffer_offset + @buffer.length)
6915 }
6916 return lines
6917 end
6918 def rewind
6919 @buffer_index[Thread.current.object_id] = @buffer_offset
6920 return self
6921 end
6922 def update(line)
6923 @buffer_mutex.synchronize {
6924 fline = line.dup
6925 fline.freeze
6926 @buffer.push(fline)
6927 while (@buffer.length > @max_size)
6928 @buffer.shift
6929 @buffer_offset += 1
6930 end
6931 }
6932 return self
6933 end
6934 def cleanup_threads
6935 @buffer_index.delete_if { |k,v| not Thread.list.any? { |t| t.object_id == k } }
6936 return self
6937 end
6938end
6939
6940class SpellRanks
6941 @@list ||= Array.new
6942 @@timestamp ||= 0
6943 @@loaded ||= false
6944 @@elevated_load = proc { SpellRanks.load }
6945 @@elevated_save = proc { SpellRanks.save }
6946 attr_reader :name
6947 attr_accessor :minorspiritual, :majorspiritual, :cleric, :minorelemental, :majorelemental, :minormental, :ranger, :sorcerer, :wizard, :bard, :empath, :paladin, :arcanesymbols, :magicitemuse, :monk
6948 def SpellRanks.load
6949 if $SAFE == 0
6950 if File.exists?("#{DATA_DIR}/#{XMLData.game}/spell-ranks.dat")
6951 begin
6952 File.open("#{DATA_DIR}/#{XMLData.game}/spell-ranks.dat", 'rb') { |f|
6953 @@timestamp, @@list = Marshal.load(f.read)
6954 }
6955 # minor mental circle added 2012-07-18; old data files will have @minormental as nil
6956 @@list.each { |rank_info| rank_info.minormental ||= 0 }
6957 # monk circle added 2013-01-15; old data files will have @minormental as nil
6958 @@list.each { |rank_info| rank_info.monk ||= 0 }
6959 @@loaded = true
6960 rescue
6961 respond "--- Lich: error: SpellRanks.load: #{$!}"
6962 Lich.log "error: SpellRanks.load: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
6963 @@list = Array.new
6964 @@timestamp = 0
6965 @@loaded = true
6966 end
6967 else
6968 @@loaded = true
6969 end
6970 else
6971 @@elevated_load.call
6972 end
6973 end
6974 def SpellRanks.save
6975 if $SAFE == 0
6976 begin
6977 File.open("#{DATA_DIR}/#{XMLData.game}/spell-ranks.dat", 'wb') { |f|
6978 f.write(Marshal.dump([@@timestamp, @@list]))
6979 }
6980 rescue
6981 respond "--- Lich: error: SpellRanks.save: #{$!}"
6982 Lich.log "error: SpellRanks.save: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
6983 end
6984 else
6985 @@elevated_save.call
6986 end
6987 end
6988 def SpellRanks.timestamp
6989 SpellRanks.load unless @@loaded
6990 @@timestamp
6991 end
6992 def SpellRanks.timestamp=(val)
6993 SpellRanks.load unless @@loaded
6994 @@timestamp = val
6995 end
6996 def SpellRanks.[](name)
6997 SpellRanks.load unless @@loaded
6998 @@list.find { |n| n.name == name }
6999 end
7000 def SpellRanks.list
7001 SpellRanks.load unless @@loaded
7002 @@list
7003 end
7004 def SpellRanks.method_missing(arg=nil)
7005 echo "error: unknown method #{arg} for class SpellRanks"
7006 respond caller[0..1]
7007 end
7008 def initialize(name)
7009 SpellRanks.load unless @@loaded
7010 @name = name
7011 @minorspiritual, @majorspiritual, @cleric, @minorelemental, @majorelemental, @ranger, @sorcerer, @wizard, @bard, @empath, @paladin, @minormental, @arcanesymbols, @magicitemuse = 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
7012 @@list.push(self)
7013 end
7014end
7015
7016
7017module Games
7018 module Unknown
7019 module Game
7020 end
7021 end
7022 module Gemstone
7023 module Game
7024 @@socket = nil
7025 @@mutex = Mutex.new
7026 @@last_recv = nil
7027 @@thread = nil
7028 @@buffer = SharedBuffer.new
7029 @@_buffer = SharedBuffer.new
7030 @@_buffer.max_size = 1000
7031 def Game.open(host, port)
7032 @@socket = TCPSocket.open(host, port)
7033 begin
7034 @@socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true)
7035 rescue
7036 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
7037 rescue Exception
7038 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
7039 end
7040 @@socket.sync = true
7041
7042 Thread.new {
7043 @@last_recv = Time.now
7044 loop {
7045 if (@@last_recv + 300) < Time.now
7046 Lich.log "#{Time.now}: error: nothing recieved from game server in 5 minutes"
7047 @@thread.kill rescue nil
7048 break
7049 end
7050 sleep (300 - (Time.now - @@last_recv))
7051 sleep 1
7052 }
7053 }
7054
7055 @@thread = Thread.new {
7056 begin
7057 atmospherics = false
7058 while $_SERVERSTRING_ = @@socket.gets
7059 @@last_recv = Time.now
7060 @@_buffer.update($_SERVERSTRING_) if TESTING
7061 begin
7062 $cmd_prefix = String.new if $_SERVERSTRING_ =~ /^\034GSw/
7063 # The Rift, Scatter is broken...
7064 if $_SERVERSTRING_ =~ /<compDef id='room text'><\/compDef>/
7065 $_SERVERSTRING_.sub!(/(.*)\s\s<compDef id='room text'><\/compDef>/) { "<compDef id='room desc'>#{$1}</compDef>" }
7066 end
7067 if atmospherics
7068 atmospherics = false
7069 $_SERVERSTRING.prepend('<popStream id="atmospherics" \/>') unless $_SERVERSTRING =~ /<popStream id="atmospherics" \/>/
7070 end
7071 if $_SERVERSTRING_ =~ /<pushStream id="familiar" \/><prompt time="[0-9]+">><\/prompt>/ # Cry For Help spell is broken...
7072 $_SERVERSTRING_.sub!('<pushStream id="familiar" />', '')
7073 elsif $_SERVERSTRING_ =~ /<pushStream id="atmospherics" \/><prompt time="[0-9]+">><\/prompt>/ # pet pigs in DragonRealms are broken...
7074 $_SERVERSTRING_.sub!('<pushStream id="atmospherics" />', '')
7075 elsif ($_SERVERSTRING_ =~ /<pushStream id="atmospherics" \/>/)
7076 atmospherics = true
7077 end
7078# while $_SERVERSTRING_.scan('<pushStream').length > $_SERVERSTRING_.scan('<popStream').length
7079# $_SERVERSTRING_.concat(@@socket.gets)
7080# end
7081 $_SERVERBUFFER_.push($_SERVERSTRING_)
7082 if alt_string = DownstreamHook.run($_SERVERSTRING_)
7083# Buffer.update(alt_string, Buffer::DOWNSTREAM_MOD)
7084 if $_DETACHABLE_CLIENT_
7085 begin
7086 $_DETACHABLE_CLIENT_.write(alt_string)
7087 rescue
7088 $_DETACHABLE_CLIENT_.close rescue nil
7089 $_DETACHABLE_CLIENT_ = nil
7090 respond "--- Lich: error: client_thread: #{$!}"
7091 respond $!.backtrace.first
7092 Lich.log "error: client_thread: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
7093 end
7094 end
7095 if $frontend =~ /^(?:wizard|avalon)$/
7096 alt_string = sf_to_wiz(alt_string)
7097 end
7098 $_CLIENT_.write(alt_string)
7099 end
7100 unless $_SERVERSTRING_ =~ /^<setting/
7101 begin
7102 REXML::Document.parse_stream($_SERVERSTRING_, XMLData)
7103 # XMLData.parse($_SERVERSTRING_)
7104 rescue
7105 unless $!.to_s =~ /invalid byte sequence/
7106 if $_SERVERSTRING_ =~ /<[^>]+='[^=>'\\]+'[^=>']+'[\s>]/
7107 # Simu has a nasty habbit of bad quotes in XML. <tag attr='this's that'>
7108 $_SERVERSTRING_.gsub!(/(<[^>]+=)'([^=>'\\]+'[^=>']+)'([\s>])/) { "#{$1}\"#{$2}\"#{$3}" }
7109 retry
7110 end
7111 $stdout.puts "--- error: server_thread: #{$!}"
7112 Lich.log "error: server_thread: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
7113 end
7114 XMLData.reset
7115 end
7116 Script.new_downstream_xml($_SERVERSTRING_)
7117 stripped_server = strip_xml($_SERVERSTRING_)
7118 stripped_server.split("\r\n").each { |line|
7119 @@buffer.update(line) if TESTING
7120 unless line =~ /^\s\*\s[A-Z][a-z]+ (?:returns home from a hard day of adventuring\.|joins the adventure\.|(?:is off to a rough start! (?:H|She) )?just bit the dust!|was just incinerated!|was just vaporized!|has been vaporized!|has disconnected\.)$|^ \* The death cry of [A-Z][a-z]+ echoes in your mind!$|^\r*\n*$/
7121 Script.new_downstream(line) unless line.empty?
7122 end
7123 }
7124 end
7125 rescue
7126 $stdout.puts "--- error: server_thread: #{$!}"
7127 Lich.log "error: server_thread: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
7128 end
7129 end
7130 rescue Exception
7131 Lich.log "error: server_thread: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
7132 $stdout.puts "--- error: server_thread: #{$!}"
7133 sleep 0.2
7134 retry unless $_CLIENT_.closed? or @@socket.closed? or ($!.to_s =~ /invalid argument|A connection attempt failed|An existing connection was forcibly closed|An established connection was aborted by the software in your host machine./i)
7135 rescue
7136 Lich.log "error: server_thread: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
7137 $stdout.puts "--- error: server_thread: #{$!}"
7138 sleep 0.2
7139 retry unless $_CLIENT_.closed? or @@socket.closed? or ($!.to_s =~ /invalid argument|A connection attempt failed|An existing connection was forcibly closed|An established connection was aborted by the software in your host machine./i)
7140 end
7141 }
7142 @@thread.priority = 4
7143 $_SERVER_ = @@socket # deprecated
7144 end
7145 def Game.thread
7146 @@thread
7147 end
7148 def Game.closed?
7149 if @@socket.nil?
7150 true
7151 else
7152 @@socket.closed?
7153 end
7154 end
7155 def Game.close
7156 if @@socket
7157 @@socket.close rescue nil
7158 @@thread.kill rescue nil
7159 end
7160 end
7161 def Game._puts(str)
7162 @@mutex.synchronize {
7163 @@socket.puts(str)
7164 }
7165 end
7166 def Game.puts(str)
7167 $_SCRIPTIDLETIMESTAMP_ = Time.now
7168 if script = Script.current
7169 script_name = script.name
7170 else
7171 script_name = '(unknown script)'
7172 end
7173 $_CLIENTBUFFER_.push "[#{script_name}]#{$SEND_CHARACTER}#{$cmd_prefix}#{str}\r\n"
7174 if script.nil? or not script.silent
7175 respond "[#{script_name}]#{$SEND_CHARACTER}#{str}\r\n"
7176 end
7177 Game._puts "#{$cmd_prefix}#{str}"
7178 $_LASTUPSTREAM_ = "[#{script_name}]#{$SEND_CHARACTER}#{str}"
7179 end
7180 def Game.gets
7181 @@buffer.gets
7182 end
7183 def Game.buffer
7184 @@buffer
7185 end
7186 def Game._gets
7187 @@_buffer.gets
7188 end
7189 def Game._buffer
7190 @@_buffer
7191 end
7192 end
7193 class Char
7194 @@name ||= nil
7195 @@citizenship ||= nil
7196 private_class_method :new
7197 def Char.init(blah)
7198 echo 'Char.init is no longer used. Update or fix your script.'
7199 end
7200 def Char.name
7201 XMLData.name
7202 end
7203 def Char.name=(name)
7204 nil
7205 end
7206 def Char.health(*args)
7207 health(*args)
7208 end
7209 def Char.mana(*args)
7210 checkmana(*args)
7211 end
7212 def Char.spirit(*args)
7213 checkspirit(*args)
7214 end
7215 def Char.maxhealth
7216 Object.module_eval { maxhealth }
7217 end
7218 def Char.maxmana
7219 Object.module_eval { maxmana }
7220 end
7221 def Char.maxspirit
7222 Object.module_eval { maxspirit }
7223 end
7224 def Char.stamina(*args)
7225 checkstamina(*args)
7226 end
7227 def Char.maxstamina
7228 Object.module_eval { maxstamina }
7229 end
7230 def Char.cha(val=nil)
7231 nil
7232 end
7233 def Char.dump_info
7234 Marshal.dump([
7235 Spell.detailed?,
7236 Spell.serialize,
7237 Spellsong.serialize,
7238 Stats.serialize,
7239 Skills.serialize,
7240 Spells.serialize,
7241 Gift.serialize,
7242 Society.serialize,
7243 ])
7244 end
7245 def Char.load_info(string)
7246 save = Char.dump_info
7247 begin
7248 Spell.load_detailed,
7249 Spell.load_active,
7250 Spellsong.load_serialized,
7251 Stats.load_serialized,
7252 Skills.load_serialized,
7253 Spells.load_serialized,
7254 Gift.load_serialized,
7255 Society.load_serialized = Marshal.load(string)
7256 rescue
7257 raise $! if string == save
7258 string = save
7259 retry
7260 end
7261 end
7262 def Char.method_missing(meth, *args)
7263 [ Stats, Skills, Spellsong, Society ].each { |klass|
7264 begin
7265 result = klass.__send__(meth, *args)
7266 return result
7267 rescue
7268 end
7269 }
7270 respond 'missing method: ' + meth
7271 raise NoMethodError
7272 end
7273 def Char.info
7274 ary = []
7275 ary.push sprintf("Name: %s Race: %s Profession: %s", XMLData.name, Stats.race, Stats.prof)
7276 ary.push sprintf("Gender: %s Age: %d Expr: %d Level: %d", Stats.gender, Stats.age, Stats.exp, Stats.level)
7277 ary.push sprintf("%017.17s Normal (Bonus) ... Enhanced (Bonus)", "")
7278 %w[ Strength Constitution Dexterity Agility Discipline Aura Logic Intuition Wisdom Influence ].each { |stat|
7279 val, bon = Stats.send(stat[0..2].downcase)
7280 spc = " " * (4 - bon.to_s.length)
7281 ary.push sprintf("%012s (%s): %05s (%d) %s ... %05s (%d)", stat, stat[0..2].upcase, val, bon, spc, val, bon)
7282 }
7283 ary.push sprintf("Mana: %04s", mana)
7284 ary
7285 end
7286 def Char.skills
7287 ary = []
7288 ary.push sprintf("%s (at level %d), your current skill bonuses and ranks (including all modifiers) are:", XMLData.name, Stats.level)
7289 ary.push sprintf(" %-035s| Current Current", 'Skill Name')
7290 ary.push sprintf(" %-035s|%08s%08s", '', 'Bonus', 'Ranks')
7291 fmt = [ [ 'Two Weapon Combat', 'Armor Use', 'Shield Use', 'Combat Maneuvers', 'Edged Weapons', 'Blunt Weapons', 'Two-Handed Weapons', 'Ranged Weapons', 'Thrown Weapons', 'Polearm Weapons', 'Brawling', 'Ambush', 'Multi Opponent Combat', 'Combat Leadership', 'Physical Fitness', 'Dodging', 'Arcane Symbols', 'Magic Item Use', 'Spell Aiming', 'Harness Power', 'Elemental Mana Control', 'Mental Mana Control', 'Spirit Mana Control', 'Elemental Lore - Air', 'Elemental Lore - Earth', 'Elemental Lore - Fire', 'Elemental Lore - Water', 'Spiritual Lore - Blessings', 'Spiritual Lore - Religion', 'Spiritual Lore - Summoning', 'Sorcerous Lore - Demonology', 'Sorcerous Lore - Necromancy', 'Mental Lore - Divination', 'Mental Lore - Manipulation', 'Mental Lore - Telepathy', 'Mental Lore - Transference', 'Mental Lore - Transformation', 'Survival', 'Disarming Traps', 'Picking Locks', 'Stalking and Hiding', 'Perception', 'Climbing', 'Swimming', 'First Aid', 'Trading', 'Pickpocketing' ], [ 'twoweaponcombat', 'armoruse', 'shielduse', 'combatmaneuvers', 'edgedweapons', 'bluntweapons', 'twohandedweapons', 'rangedweapons', 'thrownweapons', 'polearmweapons', 'brawling', 'ambush', 'multiopponentcombat', 'combatleadership', 'physicalfitness', 'dodging', 'arcanesymbols', 'magicitemuse', 'spellaiming', 'harnesspower', 'emc', 'mmc', 'smc', 'elair', 'elearth', 'elfire', 'elwater', 'slblessings', 'slreligion', 'slsummoning', 'sldemonology', 'slnecromancy', 'mldivination', 'mlmanipulation', 'mltelepathy', 'mltransference', 'mltransformation', 'survival', 'disarmingtraps', 'pickinglocks', 'stalkingandhiding', 'perception', 'climbing', 'swimming', 'firstaid', 'trading', 'pickpocketing' ] ]
7292 0.upto(fmt.first.length - 1) { |n|
7293 dots = '.' * (35 - fmt[0][n].length)
7294 rnk = Skills.send(fmt[1][n])
7295 ary.push sprintf(" %s%s|%08s%08s", fmt[0][n], dots, Skills.to_bonus(rnk), rnk) unless rnk.zero?
7296 }
7297 %[Minor Elemental,Major Elemental,Minor Spirit,Major Spirit,Minor Mental,Bard,Cleric,Empath,Paladin,Ranger,Sorcerer,Wizard].split(',').each { |circ|
7298 rnk = Spells.send(circ.gsub(" ", '').downcase)
7299 if rnk.nonzero?
7300 ary.push ''
7301 ary.push "Spell Lists"
7302 dots = '.' * (35 - circ.length)
7303 ary.push sprintf(" %s%s|%016s", circ, dots, rnk)
7304 end
7305 }
7306 ary
7307 end
7308 def Char.citizenship
7309 @@citizenship
7310 end
7311 def Char.citizenship=(val)
7312 @@citizenship = val.to_s
7313 end
7314 end
7315
7316 class Society
7317 @@status ||= String.new
7318 @@rank ||= 0
7319 def Society.serialize
7320 [@@status,@@rank]
7321 end
7322 def Society.load_serialized=(val)
7323 @@status,@@rank = val
7324 end
7325 def Society.status=(val)
7326 @@status = val
7327 end
7328 def Society.status
7329 @@status.dup
7330 end
7331 def Society.rank=(val)
7332 if val =~ /Master/
7333 if @@status =~ /Voln/
7334 @@rank = 26
7335 elsif @@status =~ /Council of Light|Guardians of Sunfist/
7336 @@rank = 20
7337 else
7338 @@rank = val.to_i
7339 end
7340 else
7341 @@rank = val.slice(/[0-9]+/).to_i
7342 end
7343 end
7344 def Society.step
7345 @@rank
7346 end
7347 def Society.member
7348 @@status.dup
7349 end
7350 def Society.rank
7351 @@rank
7352 end
7353 def Society.task
7354 XMLData.society_task
7355 end
7356 end
7357
7358 class Spellsong
7359 @@renewed ||= Time.at(Time.now.to_i - 1200)
7360 def Spellsong.renewed
7361 @@renewed = Time.now
7362 end
7363 def Spellsong.renewed=(val)
7364 @@renewed = val
7365 end
7366 def Spellsong.renewed_at
7367 @@renewed
7368 end
7369 def Spellsong.timeleft
7370 (Spellsong.duration - ((Time.now - @@renewed) % Spellsong.duration)) / 60.to_f
7371 end
7372 def Spellsong.serialize
7373 Spellsong.timeleft
7374 end
7375 def Spellsong.load_serialized=(old)
7376 Thread.new {
7377 n = 0
7378 while Stats.level == 0
7379 sleep 0.25
7380 n += 1
7381 break if n >= 4
7382 end
7383 unless n >= 4
7384 @@renewed = Time.at(Time.now.to_f - (Spellsong.duration - old * 60.to_f))
7385 else
7386 @@renewed = Time.now
7387 end
7388 }
7389 nil
7390 end
7391 def Spellsong.duration
7392 total = 120
7393 1.upto(Stats.level.to_i) { |n|
7394 if n < 26
7395 total += 4
7396 elsif n < 51
7397 total += 3
7398 elsif n < 76
7399 total += 2
7400 else
7401 total += 1
7402 end
7403 }
7404 total + Stats.log[1].to_i + (Stats.inf[1].to_i * 3) + (Skills.mltelepathy.to_i * 2)
7405 end
7406 def Spellsong.renew_cost
7407 # fixme: multi-spell penalty?
7408 total = num_active = 0
7409 [ 1003, 1006, 1009, 1010, 1012, 1014, 1018, 1019, 1025 ].each { |song_num|
7410 if song = Spell[song_num]
7411 if song.active?
7412 total += song.renew_cost
7413 num_active += 1
7414 end
7415 else
7416 echo "Spellsong.renew_cost: warning: can't find song number #{song_num}"
7417 end
7418 }
7419 return total
7420 end
7421 def Spellsong.sonicarmordurability
7422 210 + (Stats.level / 2).round + Skills.to_bonus(Skills.elair)
7423 end
7424 def Spellsong.sonicbladedurability
7425 160 + (Stats.level / 2).round + Skills.to_bonus(Skills.elair)
7426 end
7427 def Spellsong.sonicweapondurability
7428 Spellsong.sonicbladedurability
7429 end
7430 def Spellsong.sonicshielddurability
7431 125 + (Stats.level / 2).round + Skills.to_bonus(Skills.elair)
7432 end
7433 def Spellsong.tonishastebonus
7434 bonus = -1
7435 thresholds = [30,75]
7436 thresholds.each { |val| if Skills.elair >= val then bonus -= 1 end }
7437 bonus
7438 end
7439 def Spellsong.depressionpushdown
7440 20 + Skills.mltelepathy
7441 end
7442 def Spellsong.depressionslow
7443 thresholds = [10,25,45,70,100]
7444 bonus = -2
7445 thresholds.each { |val| if Skills.mltelepathy >= val then bonus -= 1 end }
7446 bonus
7447 end
7448 def Spellsong.holdingtargets
7449 1 + ((Spells.bard - 1) / 7).truncate
7450 end
7451 end
7452
7453 class Skills
7454 @@twoweaponcombat ||= 0
7455 @@armoruse ||= 0
7456 @@shielduse ||= 0
7457 @@combatmaneuvers ||= 0
7458 @@edgedweapons ||= 0
7459 @@bluntweapons ||= 0
7460 @@twohandedweapons ||= 0
7461 @@rangedweapons ||= 0
7462 @@thrownweapons ||= 0
7463 @@polearmweapons ||= 0
7464 @@brawling ||= 0
7465 @@ambush ||= 0
7466 @@multiopponentcombat ||= 0
7467 @@combatleadership ||= 0
7468 @@physicalfitness ||= 0
7469 @@dodging ||= 0
7470 @@arcanesymbols ||= 0
7471 @@magicitemuse ||= 0
7472 @@spellaiming ||= 0
7473 @@harnesspower ||= 0
7474 @@emc ||= 0
7475 @@mmc ||= 0
7476 @@smc ||= 0
7477 @@elair ||= 0
7478 @@elearth ||= 0
7479 @@elfire ||= 0
7480 @@elwater ||= 0
7481 @@slblessings ||= 0
7482 @@slreligion ||= 0
7483 @@slsummoning ||= 0
7484 @@sldemonology ||= 0
7485 @@slnecromancy ||= 0
7486 @@mldivination ||= 0
7487 @@mlmanipulation ||= 0
7488 @@mltelepathy ||= 0
7489 @@mltransference ||= 0
7490 @@mltransformation ||= 0
7491 @@survival ||= 0
7492 @@disarmingtraps ||= 0
7493 @@pickinglocks ||= 0
7494 @@stalkingandhiding ||= 0
7495 @@perception ||= 0
7496 @@climbing ||= 0
7497 @@swimming ||= 0
7498 @@firstaid ||= 0
7499 @@trading ||= 0
7500 @@pickpocketing ||= 0
7501
7502 def Skills.twoweaponcombat; @@twoweaponcombat; end
7503 def Skills.twoweaponcombat=(val); @@twoweaponcombat=val; end
7504 def Skills.armoruse; @@armoruse; end
7505 def Skills.armoruse=(val); @@armoruse=val; end
7506 def Skills.shielduse; @@shielduse; end
7507 def Skills.shielduse=(val); @@shielduse=val; end
7508 def Skills.combatmaneuvers; @@combatmaneuvers; end
7509 def Skills.combatmaneuvers=(val); @@combatmaneuvers=val; end
7510 def Skills.edgedweapons; @@edgedweapons; end
7511 def Skills.edgedweapons=(val); @@edgedweapons=val; end
7512 def Skills.bluntweapons; @@bluntweapons; end
7513 def Skills.bluntweapons=(val); @@bluntweapons=val; end
7514 def Skills.twohandedweapons; @@twohandedweapons; end
7515 def Skills.twohandedweapons=(val); @@twohandedweapons=val; end
7516 def Skills.rangedweapons; @@rangedweapons; end
7517 def Skills.rangedweapons=(val); @@rangedweapons=val; end
7518 def Skills.thrownweapons; @@thrownweapons; end
7519 def Skills.thrownweapons=(val); @@thrownweapons=val; end
7520 def Skills.polearmweapons; @@polearmweapons; end
7521 def Skills.polearmweapons=(val); @@polearmweapons=val; end
7522 def Skills.brawling; @@brawling; end
7523 def Skills.brawling=(val); @@brawling=val; end
7524 def Skills.ambush; @@ambush; end
7525 def Skills.ambush=(val); @@ambush=val; end
7526 def Skills.multiopponentcombat; @@multiopponentcombat; end
7527 def Skills.multiopponentcombat=(val); @@multiopponentcombat=val; end
7528 def Skills.combatleadership; @@combatleadership; end
7529 def Skills.combatleadership=(val); @@combatleadership=val; end
7530 def Skills.physicalfitness; @@physicalfitness; end
7531 def Skills.physicalfitness=(val); @@physicalfitness=val; end
7532 def Skills.dodging; @@dodging; end
7533 def Skills.dodging=(val); @@dodging=val; end
7534 def Skills.arcanesymbols; @@arcanesymbols; end
7535 def Skills.arcanesymbols=(val); @@arcanesymbols=val; end
7536 def Skills.magicitemuse; @@magicitemuse; end
7537 def Skills.magicitemuse=(val); @@magicitemuse=val; end
7538 def Skills.spellaiming; @@spellaiming; end
7539 def Skills.spellaiming=(val); @@spellaiming=val; end
7540 def Skills.harnesspower; @@harnesspower; end
7541 def Skills.harnesspower=(val); @@harnesspower=val; end
7542 def Skills.emc; @@emc; end
7543 def Skills.emc=(val); @@emc=val; end
7544 def Skills.mmc; @@mmc; end
7545 def Skills.mmc=(val); @@mmc=val; end
7546 def Skills.smc; @@smc; end
7547 def Skills.smc=(val); @@smc=val; end
7548 def Skills.elair; @@elair; end
7549 def Skills.elair=(val); @@elair=val; end
7550 def Skills.elearth; @@elearth; end
7551 def Skills.elearth=(val); @@elearth=val; end
7552 def Skills.elfire; @@elfire; end
7553 def Skills.elfire=(val); @@elfire=val; end
7554 def Skills.elwater; @@elwater; end
7555 def Skills.elwater=(val); @@elwater=val; end
7556 def Skills.slblessings; @@slblessings; end
7557 def Skills.slblessings=(val); @@slblessings=val; end
7558 def Skills.slreligion; @@slreligion; end
7559 def Skills.slreligion=(val); @@slreligion=val; end
7560 def Skills.slsummoning; @@slsummoning; end
7561 def Skills.slsummoning=(val); @@slsummoning=val; end
7562 def Skills.sldemonology; @@sldemonology; end
7563 def Skills.sldemonology=(val); @@sldemonology=val; end
7564 def Skills.slnecromancy; @@slnecromancy; end
7565 def Skills.slnecromancy=(val); @@slnecromancy=val; end
7566 def Skills.mldivination; @@mldivination; end
7567 def Skills.mldivination=(val); @@mldivination=val; end
7568 def Skills.mlmanipulation; @@mlmanipulation; end
7569 def Skills.mlmanipulation=(val); @@mlmanipulation=val; end
7570 def Skills.mltelepathy; @@mltelepathy; end
7571 def Skills.mltelepathy=(val); @@mltelepathy=val; end
7572 def Skills.mltransference; @@mltransference; end
7573 def Skills.mltransference=(val); @@mltransference=val; end
7574 def Skills.mltransformation; @@mltransformation; end
7575 def Skills.mltransformation=(val); @@mltransformation=val; end
7576 def Skills.survival; @@survival; end
7577 def Skills.survival=(val); @@survival=val; end
7578 def Skills.disarmingtraps; @@disarmingtraps; end
7579 def Skills.disarmingtraps=(val); @@disarmingtraps=val; end
7580 def Skills.pickinglocks; @@pickinglocks; end
7581 def Skills.pickinglocks=(val); @@pickinglocks=val; end
7582 def Skills.stalkingandhiding; @@stalkingandhiding; end
7583 def Skills.stalkingandhiding=(val); @@stalkingandhiding=val; end
7584 def Skills.perception; @@perception; end
7585 def Skills.perception=(val); @@perception=val; end
7586 def Skills.climbing; @@climbing; end
7587 def Skills.climbing=(val); @@climbing=val; end
7588 def Skills.swimming; @@swimming; end
7589 def Skills.swimming=(val); @@swimming=val; end
7590 def Skills.firstaid; @@firstaid; end
7591 def Skills.firstaid=(val); @@firstaid=val; end
7592 def Skills.trading; @@trading; end
7593 def Skills.trading=(val); @@trading=val; end
7594 def Skills.pickpocketing; @@pickpocketing; end
7595 def Skills.pickpocketing=(val); @@pickpocketing=val; end
7596
7597 def Skills.serialize
7598 [@@twoweaponcombat, @@armoruse, @@shielduse, @@combatmaneuvers, @@edgedweapons, @@bluntweapons, @@twohandedweapons, @@rangedweapons, @@thrownweapons, @@polearmweapons, @@brawling, @@ambush, @@multiopponentcombat, @@combatleadership, @@physicalfitness, @@dodging, @@arcanesymbols, @@magicitemuse, @@spellaiming, @@harnesspower, @@emc, @@mmc, @@smc, @@elair, @@elearth, @@elfire, @@elwater, @@slblessings, @@slreligion, @@slsummoning, @@sldemonology, @@slnecromancy, @@mldivination, @@mlmanipulation, @@mltelepathy, @@mltransference, @@mltransformation, @@survival, @@disarmingtraps, @@pickinglocks, @@stalkingandhiding, @@perception, @@climbing, @@swimming, @@firstaid, @@trading, @@pickpocketing]
7599 end
7600 def Skills.load_serialized=(array)
7601 @@twoweaponcombat, @@armoruse, @@shielduse, @@combatmaneuvers, @@edgedweapons, @@bluntweapons, @@twohandedweapons, @@rangedweapons, @@thrownweapons, @@polearmweapons, @@brawling, @@ambush, @@multiopponentcombat, @@combatleadership, @@physicalfitness, @@dodging, @@arcanesymbols, @@magicitemuse, @@spellaiming, @@harnesspower, @@emc, @@mmc, @@smc, @@elair, @@elearth, @@elfire, @@elwater, @@slblessings, @@slreligion, @@slsummoning, @@sldemonology, @@slnecromancy, @@mldivination, @@mlmanipulation, @@mltelepathy, @@mltransference, @@mltransformation, @@survival, @@disarmingtraps, @@pickinglocks, @@stalkingandhiding, @@perception, @@climbing, @@swimming, @@firstaid, @@trading, @@pickpocketing = array
7602 end
7603 def Skills.to_bonus(ranks)
7604 bonus = 0
7605 while ranks > 0
7606 if ranks > 40
7607 bonus += (ranks - 40)
7608 ranks = 40
7609 elsif ranks > 30
7610 bonus += (ranks - 30) * 2
7611 ranks = 30
7612 elsif ranks > 20
7613 bonus += (ranks - 20) * 3
7614 ranks = 20
7615 elsif ranks > 10
7616 bonus += (ranks - 10) * 4
7617 ranks = 10
7618 else
7619 bonus += (ranks * 5)
7620 ranks = 0
7621 end
7622 end
7623 bonus
7624 end
7625 end
7626
7627 class Spells
7628 @@minorelemental ||= 0
7629 @@minormental ||= 0
7630 @@majorelemental ||= 0
7631 @@minorspiritual ||= 0
7632 @@majorspiritual ||= 0
7633 @@wizard ||= 0
7634 @@sorcerer ||= 0
7635 @@ranger ||= 0
7636 @@paladin ||= 0
7637 @@empath ||= 0
7638 @@cleric ||= 0
7639 @@bard ||= 0
7640 def Spells.minorelemental=(val); @@minorelemental = val; end
7641 def Spells.minorelemental; @@minorelemental; end
7642 def Spells.minormental=(val); @@minormental = val; end
7643 def Spells.minormental; @@minormental; end
7644 def Spells.majorelemental=(val); @@majorelemental = val; end
7645 def Spells.majorelemental; @@majorelemental; end
7646 def Spells.minorspiritual=(val); @@minorspiritual = val; end
7647 def Spells.minorspiritual; @@minorspiritual; end
7648 def Spells.minorspirit=(val); @@minorspiritual = val; end
7649 def Spells.minorspirit; @@minorspiritual; end
7650 def Spells.majorspiritual=(val); @@majorspiritual = val; end
7651 def Spells.majorspiritual; @@majorspiritual; end
7652 def Spells.majorspirit=(val); @@majorspiritual = val; end
7653 def Spells.majorspirit; @@majorspiritual; end
7654 def Spells.wizard=(val); @@wizard = val; end
7655 def Spells.wizard; @@wizard; end
7656 def Spells.sorcerer=(val); @@sorcerer = val; end
7657 def Spells.sorcerer; @@sorcerer; end
7658 def Spells.ranger=(val); @@ranger = val; end
7659 def Spells.ranger; @@ranger; end
7660 def Spells.paladin=(val); @@paladin = val; end
7661 def Spells.paladin; @@paladin; end
7662 def Spells.empath=(val); @@empath = val; end
7663 def Spells.empath; @@empath; end
7664 def Spells.cleric=(val); @@cleric = val; end
7665 def Spells.cleric; @@cleric; end
7666 def Spells.bard=(val); @@bard = val; end
7667 def Spells.bard; @@bard; end
7668 def Spells.get_circle_name(num)
7669 val = num.to_s
7670 if val == '1'
7671 'Minor Spirit'
7672 elsif val == '2'
7673 'Major Spirit'
7674 elsif val == '3'
7675 'Cleric'
7676 elsif val == '4'
7677 'Minor Elemental'
7678 elsif val == '5'
7679 'Major Elemental'
7680 elsif val == '6'
7681 'Ranger'
7682 elsif val == '7'
7683 'Sorcerer'
7684 elsif val == '9'
7685 'Wizard'
7686 elsif val == '10'
7687 'Bard'
7688 elsif val == '11'
7689 'Empath'
7690 elsif val == '12'
7691 'Minor Mental'
7692 elsif val == '16'
7693 'Paladin'
7694 elsif val == '17'
7695 'Arcane'
7696 elsif val == '66'
7697 'Death'
7698 elsif val == '65'
7699 'Imbedded Enchantment'
7700 elsif val == '90'
7701 'Miscellaneous'
7702 elsif val == '95'
7703 'Armor Specialization'
7704 elsif val == '96'
7705 'Combat Maneuvers'
7706 elsif val == '97'
7707 'Guardians of Sunfist'
7708 elsif val == '98'
7709 'Order of Voln'
7710 elsif val == '99'
7711 'Council of Light'
7712 else
7713 'Unknown Circle'
7714 end
7715 end
7716 def Spells.active
7717 Spell.active
7718 end
7719 def Spells.known
7720 known_spells = Array.new
7721 Spell.list.each { |spell| known_spells.push(spell) if spell.known? }
7722 return known_spells
7723 end
7724 def Spells.serialize
7725 [@@minorelemental,@@majorelemental,@@minorspiritual,@@majorspiritual,@@wizard,@@sorcerer,@@ranger,@@paladin,@@empath,@@cleric,@@bard,@@minormental]
7726 end
7727 def Spells.load_serialized=(val)
7728 @@minorelemental,@@majorelemental,@@minorspiritual,@@majorspiritual,@@wizard,@@sorcerer,@@ranger,@@paladin,@@empath,@@cleric,@@bard,@@minormental = val
7729 # new spell circle added 2012-07-18; old data files will make @@minormental nil
7730 @@minormental ||= 0
7731 end
7732 end
7733
7734 class Spell
7735 @@list ||= Array.new
7736 @@loaded ||= false
7737 @@cast_lock ||= Array.new
7738 @@bonus_list ||= Array.new
7739 @@cost_list ||= Array.new
7740 @@load_mutex = Mutex.new
7741 @@elevated_load = proc { Spell.load }
7742 @@after_stance = nil
7743 attr_reader :num, :name, :timestamp, :msgup, :msgdn, :circle, :active, :type, :cast_proc, :real_time, :persist_on_death, :availability, :no_incant
7744 attr_accessor :stance, :channel
7745 def initialize(xml_spell)
7746 @num = xml_spell.attributes['number'].to_i
7747 @name = xml_spell.attributes['name']
7748 @type = xml_spell.attributes['type']
7749 @no_incant = ((xml_spell.attributes['incant'] == 'no') ? true : false)
7750 if xml_spell.attributes['availability'] == 'all'
7751 @availability = 'all'
7752 elsif xml_spell.attributes['availability'] == 'group'
7753 @availability = 'group'
7754 else
7755 @availability = 'self-cast'
7756 end
7757 @bonus = Hash.new
7758 xml_spell.elements.find_all { |e| e.name == 'bonus' }.each { |e|
7759 @bonus[e.attributes['type']] = e.text
7760 @bonus[e.attributes['type']].untaint
7761 }
7762 @msgup = xml_spell.elements.find_all { |e| (e.name == 'message') and (e.attributes['type'].downcase == 'start') }.collect { |e| e.text }.join('$|^')
7763 @msgup = nil if @msgup.empty?
7764 @msgdn = xml_spell.elements.find_all { |e| (e.name == 'message') and (e.attributes['type'].downcase == 'end') }.collect { |e| e.text }.join('$|^')
7765 @msgdn = nil if @msgdn.empty?
7766 @stance = ((xml_spell.attributes['stance'] =~ /^(yes|true)$/i) ? true : false)
7767 @channel = ((xml_spell.attributes['channel'] =~ /^(yes|true)$/i) ? true : false)
7768 @cost = Hash.new
7769 xml_spell.elements.find_all { |e| e.name == 'cost' }.each { |xml_cost|
7770 @cost[xml_cost.attributes['type'].downcase] ||= Hash.new
7771 if xml_cost.attributes['cast-type'].downcase == 'target'
7772 @cost[xml_cost.attributes['type'].downcase]['target'] = xml_cost.text
7773 else
7774 @cost[xml_cost.attributes['type'].downcase]['self'] = xml_cost.text
7775 end
7776 }
7777 @duration = Hash.new
7778 xml_spell.elements.find_all { |e| e.name == 'duration' }.each { |xml_duration|
7779 if xml_duration.attributes['cast-type'].downcase == 'target'
7780 cast_type = 'target'
7781 else
7782 cast_type = 'self'
7783 if xml_duration.attributes['real-time'] =~ /^(yes|true)$/i
7784 @real_time = true
7785 else
7786 @real_time = false
7787 end
7788 end
7789 @duration[cast_type] = Hash.new
7790 @duration[cast_type][:duration] = xml_duration.text
7791 @duration[cast_type][:stackable] = (xml_duration.attributes['span'].downcase == 'stackable')
7792 @duration[cast_type][:refreshable] = (xml_duration.attributes['span'].downcase == 'refreshable')
7793 if xml_duration.attributes['multicastable'] =~ /^(yes|true)$/i
7794 @duration[cast_type][:multicastable] = true
7795 else
7796 @duration[cast_type][:multicastable] = false
7797 end
7798 if xml_duration.attributes['persist-on-death'] =~ /^(yes|true)$/i
7799 @persist_on_death = true
7800 else
7801 @persist_on_death = false
7802 end
7803 if xml_duration.attributes['max']
7804 @duration[cast_type][:max_duration] = xml_duration.attributes['max'].to_f
7805 else
7806 @duration[cast_type][:max_duration] = 250.0
7807 end
7808 }
7809 @cast_proc = xml_spell.elements['cast-proc'].text
7810 @cast_proc.untaint
7811 @timestamp = Time.now
7812 @timeleft = 0
7813 @active = false
7814 @circle = (num.to_s.length == 3 ? num.to_s[0..0] : num.to_s[0..1])
7815 @@list.push(self) unless @@list.find { |spell| spell.num == @num }
7816 self
7817 end
7818 def Spell.after_stance=(val)
7819 @@after_stance = val
7820 end
7821 def Spell.load(filename=nil)
7822 if $SAFE == 0
7823 if filename.nil?
7824 if File.exists?("#{DATA_DIR}/spell-list.xml")
7825 filename = "#{DATA_DIR}/spell-list.xml"
7826 elsif File.exists?("#{SCRIPT_DIR}/spell-list.xml") # deprecated
7827 filename = "#{SCRIPT_DIR}/spell-list.xml"
7828 else
7829 filename = "#{DATA_DIR}/spell-list.xml"
7830 end
7831 end
7832 script = Script.current
7833 @@load_mutex.synchronize {
7834 return true if @loaded
7835 begin
7836 spell_times = Hash.new
7837 # reloading spell data should not reset spell tracking...
7838 unless @@list.empty?
7839 @@list.each { |spell| spell_times[spell.num] = spell.timeleft if spell.active? }
7840 @@list.clear
7841 end
7842 File.open(filename) { |file|
7843 xml_doc = REXML::Document.new(file)
7844 xml_root = xml_doc.root
7845 xml_root.elements.each { |xml_spell| Spell.new(xml_spell) }
7846 }
7847 @@list.each { |spell|
7848 if spell_times[spell.num]
7849 spell.timeleft = spell_times[spell.num]
7850 spell.active = true
7851 end
7852 }
7853 @@bonus_list = @@list.collect { |spell| spell._bonus.keys }.flatten
7854 @@bonus_list = @@bonus_list | @@bonus_list
7855 @@cost_list = @@list.collect { |spell| spell._cost.keys }.flatten
7856 @@cost_list = @@cost_list | @@cost_list
7857 @@loaded = true
7858 return true
7859 rescue
7860 respond "--- Lich: error: Spell.load: #{$!}"
7861 Lich.log "error: Spell.load: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
7862 @@loaded = false
7863 return false
7864 end
7865 }
7866 else
7867 @@elevated_load.call
7868 end
7869 end
7870 def Spell.[](val)
7871 Spell.load unless @@loaded
7872 if val.class == Spell
7873 val
7874 elsif (val.class == Fixnum) or (val.class == String and val =~ /^[0-9]+$/)
7875 @@list.find { |spell| spell.num == val.to_i }
7876 else
7877 (@@list.find { |s| s.name =~ /^#{val}$/i } || @@list.find { |s| s.name =~ /^#{val}/i } || @@list.find { |s| s.msgup =~ /#{val}/i or s.msgdn =~ /#{val}/i })
7878 end
7879 end
7880 def Spell.active
7881 Spell.load unless @@loaded
7882 active = Array.new
7883 @@list.each { |spell| active.push(spell) if spell.active? }
7884 active
7885 end
7886 def Spell.active?(val)
7887 Spell.load unless @@loaded
7888 Spell[val].active?
7889 end
7890 def Spell.list
7891 Spell.load unless @@loaded
7892 @@list
7893 end
7894 def Spell.upmsgs
7895 Spell.load unless @@loaded
7896 @@list.collect { |spell| spell.msgup }.compact
7897 end
7898 def Spell.dnmsgs
7899 Spell.load unless @@loaded
7900 @@list.collect { |spell| spell.msgdn }.compact
7901 end
7902 def time_per_formula(options={})
7903 activator_modifier = { 'tap' => 0.5, 'rub' => 1, 'wave' => 1, 'raise' => 1.33, 'drink' => 0, 'bite' => 0, 'eat' => 0, 'gobble' => 0 }
7904 can_haz_spell_ranks = /Spells\.(?:minorelemental|majorelemental|minorspiritual|majorspiritual|wizard|sorcerer|ranger|paladin|empath|cleric|bard|minormental)/
7905 skills = [ 'Spells.minorelemental', 'Spells.majorelemental', 'Spells.minorspiritual', 'Spells.majorspiritual', 'Spells.wizard', 'Spells.sorcerer', 'Spells.ranger', 'Spells.paladin', 'Spells.empath', 'Spells.cleric', 'Spells.bard', 'Spells.minormental', 'Skills.magicitemuse', 'Skills.arancesymbols' ]
7906 if options[:caster] and (options[:caster] !~ /^(?:self|#{XMLData.name})$/i)
7907 if options[:target] and (options[:target].downcase == options[:caster].downcase)
7908 formula = @duration['self'][:duration].to_s.dup
7909 else
7910 formula = @duration['target'][:duration].dup || @duration['self'][:duration].to_s.dup
7911 end
7912 if options[:activator] =~ /^(#{activator_modifier.keys.join('|')})$/i
7913 if formula =~ can_haz_spell_ranks
7914 skills.each { |skill_name| formula.gsub!(skill_name, "(SpellRanks['#{options[:caster]}'].magicitemuse * #{activator_modifier[options[:activator]]}).to_i") }
7915 formula = "(#{formula})/2.0"
7916 elsif formula =~ /Skills\.(?:magicitemuse|arancesymbols)/
7917 skills.each { |skill_name| formula.gsub!(skill_name, "(SpellRanks['#{options[:caster]}'].magicitemuse * #{activator_modifier[options[:activator]]}).to_i") }
7918 end
7919 elsif options[:activator] =~ /^(invoke|scroll)$/i
7920 if formula =~ can_haz_spell_ranks
7921 skills.each { |skill_name| formula.gsub!(skill_name, "SpellRanks['#{options[:caster]}'].arcanesymbols.to_i") }
7922 formula = "(#{formula})/2.0"
7923 elsif formula =~ /Skills\.(?:magicitemuse|arancesymbols)/
7924 skills.each { |skill_name| formula.gsub!(skill_name, "SpellRanks['#{options[:caster]}'].arcanesymbols.to_i") }
7925 end
7926 else
7927 skills.each { |skill_name| formula.gsub!(skill_name, "SpellRanks[#{options[:caster].to_s.inspect}].#{skill_name.sub(/^(?:Spells|Skills)\./, '')}.to_i") }
7928 end
7929 else
7930 if options[:target] and (options[:target] !~ /^(?:self|#{XMLData.name})$/i)
7931 formula = @duration['target'][:duration].dup || @duration['self'][:duration].to_s.dup
7932 else
7933 formula = @duration['self'][:duration].to_s.dup
7934 end
7935 if options[:activator] =~ /^(#{activator_modifier.keys.join('|')})$/i
7936 if formula =~ can_haz_spell_ranks
7937 skills.each { |skill_name| formula.gsub!(skill_name, "(Skills.magicitemuse * #{activator_modifier[options[:activator]]}).to_i") }
7938 formula = "(#{formula})/2.0"
7939 elsif formula =~ /Skills\.(?:magicitemuse|arancesymbols)/
7940 skills.each { |skill_name| formula.gsub!(skill_name, "(Skills.magicitemuse * #{activator_modifier[options[:activator]]}).to_i") }
7941 end
7942 elsif options[:activator] =~ /^(invoke|scroll)$/i
7943 if formula =~ can_haz_spell_ranks
7944 skills.each { |skill_name| formula.gsub!(skill_name, "Skills.arcanesymbols.to_i") }
7945 formula = "(#{formula})/2.0"
7946 elsif formula =~ /Skills\.(?:magicitemuse|arancesymbols)/
7947 skills.each { |skill_name| formula.gsub!(skill_name, "Skills.arcanesymbols.to_i") }
7948 end
7949 end
7950 end
7951 formula.untaint
7952 formula
7953 end
7954 def time_per(options={})
7955 formula = self.time_per_formula(options)
7956 if options[:line]
7957 line = options[:line]
7958 end
7959 if $SAFE < 3
7960 proc { $SAFE = 3; eval(formula) }.call.to_f
7961 else
7962 eval(formula).to_f
7963 end
7964 end
7965 def timeleft=(val)
7966 @timeleft = val
7967 @timestamp = Time.now
7968 end
7969 def timeleft
7970 if self.time_per_formula.to_s == 'Spellsong.timeleft'
7971 @timeleft = Spellsong.timeleft
7972 else
7973 @timeleft = @timeleft - ((Time.now - @timestamp) / 60.to_f)
7974 if @timeleft <= 0
7975 self.putdown
7976 return 0.to_f
7977 end
7978 end
7979 @timestamp = Time.now
7980 @timeleft
7981 end
7982 def minsleft
7983 self.timeleft
7984 end
7985 def secsleft
7986 self.timeleft * 60
7987 end
7988 def active=(val)
7989 @active = val
7990 end
7991 def active?
7992 (self.timeleft > 0) and @active
7993 end
7994 def stackable?(options={})
7995 if options[:caster] and (options[:caster] !~ /^(?:self|#{XMLData.name})$/i)
7996 if options[:target] and (options[:target].downcase == options[:caster].downcase)
7997 @duration['self'][:stackable]
7998 else
7999 if @duration['target'][:stackable].nil?
8000 @duration['self'][:stackable]
8001 else
8002 @duration['target'][:stackable]
8003 end
8004 end
8005 else
8006 if options[:target] and (options[:target] !~ /^(?:self|#{XMLData.name})$/i)
8007 if @duration['target'][:stackable].nil?
8008 @duration['self'][:stackable]
8009 else
8010 @duration['target'][:stackable]
8011 end
8012 else
8013 @duration['self'][:stackable]
8014 end
8015 end
8016 end
8017 def refreshable?(options={})
8018 if options[:caster] and (options[:caster] !~ /^(?:self|#{XMLData.name})$/i)
8019 if options[:target] and (options[:target].downcase == options[:caster].downcase)
8020 @duration['self'][:refreshable]
8021 else
8022 if @duration['target'][:refreshable].nil?
8023 @duration['self'][:refreshable]
8024 else
8025 @duration['target'][:refreshable]
8026 end
8027 end
8028 else
8029 if options[:target] and (options[:target] !~ /^(?:self|#{XMLData.name})$/i)
8030 if @duration['target'][:refreshable].nil?
8031 @duration['self'][:refreshable]
8032 else
8033 @duration['target'][:refreshable]
8034 end
8035 else
8036 @duration['self'][:refreshable]
8037 end
8038 end
8039 end
8040 def multicastable?(options={})
8041 if options[:caster] and (options[:caster] !~ /^(?:self|#{XMLData.name})$/i)
8042 if options[:target] and (options[:target].downcase == options[:caster].downcase)
8043 @duration['self'][:multicastable]
8044 else
8045 if @duration['target'][:multicastable].nil?
8046 @duration['self'][:multicastable]
8047 else
8048 @duration['target'][:multicastable]
8049 end
8050 end
8051 else
8052 if options[:target] and (options[:target] !~ /^(?:self|#{XMLData.name})$/i)
8053 if @duration['target'][:multicastable].nil?
8054 @duration['self'][:multicastable]
8055 else
8056 @duration['target'][:multicastable]
8057 end
8058 else
8059 @duration['self'][:multicastable]
8060 end
8061 end
8062 end
8063 def known?
8064 if @num.to_s.length == 3
8065 circle_num = @num.to_s[0..0].to_i
8066 elsif @num.to_s.length == 4
8067 circle_num = @num.to_s[0..1].to_i
8068 else
8069 return false
8070 end
8071 if circle_num == 1
8072 ranks = [ Spells.minorspiritual, XMLData.level ].min
8073 elsif circle_num == 2
8074 ranks = [ Spells.majorspiritual, XMLData.level ].min
8075 elsif circle_num == 3
8076 ranks = [ Spells.cleric, XMLData.level ].min
8077 elsif circle_num == 4
8078 ranks = [ Spells.minorelemental, XMLData.level ].min
8079 elsif circle_num == 5
8080 ranks = [ Spells.majorelemental, XMLData.level ].min
8081 elsif circle_num == 6
8082 ranks = [ Spells.ranger, XMLData.level ].min
8083 elsif circle_num == 7
8084 ranks = [ Spells.sorcerer, XMLData.level ].min
8085 elsif circle_num == 9
8086 ranks = [ Spells.wizard, XMLData.level ].min
8087 elsif circle_num == 10
8088 ranks = [ Spells.bard, XMLData.level ].min
8089 elsif circle_num == 11
8090 ranks = [ Spells.empath, XMLData.level ].min
8091 elsif circle_num == 12
8092 ranks = [ Spells.minormental, XMLData.level ].min
8093 elsif circle_num == 16
8094 ranks = [ Spells.paladin, XMLData.level ].min
8095 elsif circle_num == 17
8096 if (@num == 1700) and (Char.prof =~ /^(?:Wizard|Cleric|Empath|Sorcerer|Savant)$/)
8097 return true
8098 else
8099 return false
8100 end
8101 elsif (circle_num == 97) and (Society.status == 'Guardians of Sunfist')
8102 ranks = Society.rank
8103 elsif (circle_num == 98) and (Society.status == 'Order of Voln')
8104 ranks = Society.rank
8105 elsif (circle_num == 99) and (Society.status == 'Council of Light')
8106 ranks = Society.rank
8107 elsif (circle_num == 96)
8108 if CMan[@name].to_i > 0
8109 return true
8110 else
8111 return false
8112 end
8113 else
8114 return false
8115 end
8116 if (@num % 100) <= ranks
8117 return true
8118 else
8119 return false
8120 end
8121 end
8122 def available?(options={})
8123 if self.known?
8124 if options[:caster] and (options[:caster] !~ /^(?:self|#{XMLData.name})$/i)
8125 if options[:target] and (options[:target].downcase == options[:caster].downcase)
8126 true
8127 else
8128 @availability == 'all'
8129 end
8130 else
8131 if options[:target] and (options[:target] !~ /^(?:self|#{XMLData.name})$/i)
8132 @availability == 'all'
8133 else
8134 true
8135 end
8136 end
8137 else
8138 false
8139 end
8140 end
8141 def to_s
8142 @name.to_s
8143 end
8144 def max_duration(options={})
8145 if options[:caster] and (options[:caster] !~ /^(?:self|#{XMLData.name})$/i)
8146 if options[:target] and (options[:target].downcase == options[:caster].downcase)
8147 @duration['self'][:max_duration]
8148 else
8149 @duration['target'][:max_duration] || @duration['self'][:max_duration]
8150 end
8151 else
8152 if options[:target] and (options[:target] !~ /^(?:self|#{XMLData.name})$/i)
8153 @duration['target'][:max_duration] || @duration['self'][:max_duration]
8154 else
8155 @duration['self'][:max_duration]
8156 end
8157 end
8158 end
8159 def putup(options={})
8160 if stackable?(options)
8161 self.timeleft = [ self.timeleft + self.time_per(options), self.max_duration(options) ].min
8162 else
8163 self.timeleft = [ self.time_per(options), self.max_duration(options) ].min
8164 end
8165 @active = true
8166 end
8167 def putdown
8168 self.timeleft = 0
8169 @active = false
8170 end
8171 def remaining
8172 self.timeleft.as_time
8173 end
8174 def affordable?(options={})
8175 # fixme: deal with them dirty bards!
8176 release_options = options.dup
8177 release_options[:multicast] = nil
8178 if (self.mana_cost(options) > 0) and ( !checkmana(self.mana_cost(options)) or (Spell[515].active? and !checkmana(self.mana_cost(options) + [self.mana_cost(release_options)/4, 1].max)) )
8179 false
8180 elsif (self.stamina_cost(options) > 0) and (Spell[9699].active? or not checkstamina(self.stamina_cost(options)))
8181 false
8182 elsif (self.spirit_cost(options) > 0) and not checkspirit(self.spirit_cost(options) + 1 + [ 9912, 9913, 9914, 9916, 9916, 9916 ].delete_if { |num| !Spell[num].active? }.length)
8183 false
8184 else
8185 true
8186 end
8187 end
8188 def Spell.lock_cast
8189 script = Script.current
8190 @@cast_lock.push(script)
8191 until (@@cast_lock.first == script) or @@cast_lock.empty?
8192 sleep 0.1
8193 Script.current # allows this loop to be paused
8194 @@cast_lock.delete_if { |s| s.paused or not Script.list.include?(s) }
8195 end
8196 end
8197 def Spell.unlock_cast
8198 @@cast_lock.delete(Script.current)
8199 end
8200 def cast(target=nil, results_of_interest=nil)
8201 # fixme: find multicast in target and check mana for it
8202 script = Script.current
8203 if @type.nil?
8204 echo "cast: spell missing type (#{@name})"
8205 sleep 0.1
8206 return false
8207 end
8208 unless (self.mana_cost <= 0) or checkmana(self.mana_cost)
8209 echo 'cast: not enough mana'
8210 sleep 0.1
8211 return false
8212 end
8213 unless (self.spirit_cost > 0) or checkspirit(self.spirit_cost + 1 + [ 9912, 9913, 9914, 9916, 9916, 9916 ].delete_if { |num| !Spell[num].active? }.length)
8214 echo 'cast: not enough spirit'
8215 sleep 0.1
8216 return false
8217 end
8218 unless (self.stamina_cost <= 0) or checkstamina(self.stamina_cost)
8219 echo 'cast: not enough stamina'
8220 sleep 0.1
8221 return false
8222 end
8223 begin
8224 save_want_downstream = script.want_downstream
8225 save_want_downstream_xml = script.want_downstream_xml
8226 script.want_downstream = true
8227 script.want_downstream_xml = false
8228 @@cast_lock.push(script)
8229 until (@@cast_lock.first == script) or @@cast_lock.empty?
8230 sleep 0.1
8231 Script.current # allows this loop to be paused
8232 @@cast_lock.delete_if { |s| s.paused or not Script.list.include?(s) }
8233 end
8234 unless (self.mana_cost <= 0) or checkmana(self.mana_cost)
8235 echo 'cast: not enough mana'
8236 sleep 0.1
8237 return false
8238 end
8239 unless (self.spirit_cost > 0) or checkspirit(self.spirit_cost + 1 + [ 9912, 9913, 9914, 9916, 9916, 9916 ].delete_if { |num| !Spell[num].active? }.length)
8240 echo 'cast: not enough spirit'
8241 sleep 0.1
8242 return false
8243 end
8244 unless (self.stamina_cost <= 0) or checkstamina(self.stamina_cost)
8245 echo 'cast: not enough stamina'
8246 sleep 0.1
8247 return false
8248 end
8249 if @cast_proc
8250 waitrt?
8251 waitcastrt?
8252 unless (self.mana_cost <= 0) or checkmana(self.mana_cost)
8253 echo 'cast: not enough mana'
8254 sleep 0.1
8255 return false
8256 end
8257 unless (self.spirit_cost > 0) or checkspirit(self.spirit_cost + 1 + [ 9912, 9913, 9914, 9916, 9916, 9916 ].delete_if { |num| !Spell[num].active? }.length)
8258 echo 'cast: not enough spirit'
8259 sleep 0.1
8260 return false
8261 end
8262 unless (self.stamina_cost <= 0) or checkstamina(self.stamina_cost)
8263 echo 'cast: not enough stamina'
8264 sleep 0.1
8265 return false
8266 end
8267 begin
8268 if $SAFE < 3
8269 proc { $SAFE = 3; eval(@cast_proc) }.call
8270 else
8271 eval(@cast_proc)
8272 end
8273 rescue
8274 echo "cast: error: #{$!}"
8275 respond $!.backtrace[0..2]
8276 return false
8277 end
8278 else
8279 if @channel
8280 cast_cmd = 'channel'
8281 else
8282 cast_cmd = 'cast'
8283 end
8284 if (target.nil? or target.to_s.empty?) and not @no_incant
8285 cast_cmd = "incant #{@num}"
8286 elsif (target.nil? or target.to_s.empty?) and (@type =~ /attack/i) and not [410,435,525,912,909,609].include?(@num)
8287 cast_cmd += ' target'
8288 elsif target.class == GameObj
8289 cast_cmd += " ##{target.id}"
8290 elsif target.class == Fixnum
8291 cast_cmd += " ##{target}"
8292 else
8293 cast_cmd += " #{target}"
8294 end
8295 cast_result = nil
8296 loop {
8297 waitrt?
8298 if cast_cmd =~ /^incant/
8299 if (checkprep != @name) and (checkprep != 'None')
8300 dothistimeout 'release', 5, /^You feel the magic of your spell rush away from you\.$|^You don't have a prepared spell to release!$/
8301 end
8302 else
8303 unless checkprep == @name
8304 unless checkprep == 'None'
8305 dothistimeout 'release', 5, /^You feel the magic of your spell rush away from you\.$|^You don't have a prepared spell to release!$/
8306 unless (self.mana_cost <= 0) or checkmana(self.mana_cost)
8307 echo 'cast: not enough mana'
8308 sleep 0.1
8309 return false
8310 end
8311 unless (self.spirit_cost <= 0) or checkspirit(self.spirit_cost + 1 + (if checkspell(9912) then 1 else 0 end) + (if checkspell(9913) then 1 else 0 end) + (if checkspell(9914) then 1 else 0 end) + (if checkspell(9916) then 5 else 0 end))
8312 echo 'cast: not enough spirit'
8313 sleep 0.1
8314 return false
8315 end
8316 unless (self.stamina_cost <= 0) or checkstamina(self.stamina_cost)
8317 echo 'cast: not enough stamina'
8318 sleep 0.1
8319 return false
8320 end
8321 end
8322 loop {
8323 waitrt?
8324 waitcastrt?
8325 prepare_result = dothistimeout "prepare #{@num}", 8, /^You already have a spell readied! You must RELEASE it if you wish to prepare another!$|^Your spell(?:song)? is ready\.|^You can't think clearly enough to prepare a spell!$|^You are concentrating too intently .*?to prepare a spell\.$|^You are too injured to make that dextrous of a movement|^The searing pain in your throat makes that impossible|^But you don't have any mana!\.$|^You can't make that dextrous of a move!$|^As you begin to prepare the spell the wind blows small objects at you thwarting your attempt\.$|^You do not know that spell!$|^All you manage to do is cough up some blood\.$|The incantations of countless spells swirl through your mind as a golden light flashes before your eyes\./
8326 if prepare_result =~ /^Your spell(?:song)? is ready\./
8327 break
8328 elsif prepare_result == 'You already have a spell readied! You must RELEASE it if you wish to prepare another!'
8329 dothistimeout 'release', 5, /^You feel the magic of your spell rush away from you\.$|^You don't have a prepared spell to release!$/
8330 unless (self.mana_cost <= 0) or checkmana(self.mana_cost)
8331 echo 'cast: not enough mana'
8332 sleep 0.1
8333 return false
8334 end
8335 elsif prepare_result =~ /^You can't think clearly enough to prepare a spell!$|^You are concentrating too intently .*?to prepare a spell\.$|^You are too injured to make that dextrous of a movement|^The searing pain in your throat makes that impossible|^But you don't have any mana!\.$|^You can't make that dextrous of a move!$|^As you begin to prepare the spell the wind blows small objects at you thwarting your attempt\.$|^You do not know that spell!$|^All you manage to do is cough up some blood\.$|The incantations of countless spells swirl through your mind as a golden light flashes before your eyes\./
8336 sleep 0.1
8337 return prepare_result
8338 end
8339 }
8340 end
8341 end
8342 waitcastrt?
8343 if @stance and checkstance != 'offensive'
8344 put 'stance offensive'
8345 # dothistimeout 'stance offensive', 5, /^You (?:are now in|move into) an? offensive stance|^You are unable to change your stance\.$/
8346 end
8347 if results_of_interest.class == Regexp
8348 results_regex = /^(?:Cast|Sing) Roundtime [0-9]+ Seconds?\.$|^Cast at what\?$|^But you don't have any mana!$|^\[Spell Hindrance for|^You don't have a spell prepared!$|keeps? the spell from working\.|^Be at peace my child, there is no need for spells of war in here\.$|Spells of War cannot be cast|^As you focus on your magic, your vision swims with a swirling haze of crimson\.$|^Your magic fizzles ineffectually\.$|^All you manage to do is cough up some blood\.$|^And give yourself away! Never!$|^You are unable to do that right now\.$|^You feel a sudden rush of power as you absorb [0-9]+ mana!$|^You are unable to drain it!$|leaving you casting at nothing but thin air!$|^You don't seem to be able to move to do that\.$|^Provoking a GameMaster is not such a good idea\.$|^You can't think clearly enough to prepare a spell!$|^You do not currently have a target\.$|The incantations of countless spells swirl through your mind as a golden light flashes before your eyes\.|#{results_of_interest.to_s}/
8349 else
8350 results_regex = /^(?:Cast|Sing) Roundtime [0-9]+ Seconds?\.$|^Cast at what\?$|^But you don't have any mana!$|^\[Spell Hindrance for|^You don't have a spell prepared!$|keeps? the spell from working\.|^Be at peace my child, there is no need for spells of war in here\.$|Spells of War cannot be cast|^As you focus on your magic, your vision swims with a swirling haze of crimson\.$|^Your magic fizzles ineffectually\.$|^All you manage to do is cough up some blood\.$|^And give yourself away! Never!$|^You are unable to do that right now\.$|^You feel a sudden rush of power as you absorb [0-9]+ mana!$|^You are unable to drain it!$|leaving you casting at nothing but thin air!$|^You don't seem to be able to move to do that\.$|^Provoking a GameMaster is not such a good idea\.$|^You can't think clearly enough to prepare a spell!$|^You do not currently have a target\.$|The incantations of countless spells swirl through your mind as a golden light flashes before your eyes\./
8351 end
8352 cast_result = dothistimeout cast_cmd, 5, results_regex
8353 if cast_result == "You don't seem to be able to move to do that."
8354 100.times { break if clear.any? { |line| line =~ /^You regain control of your senses!$/ }; sleep 0.1 }
8355 cast_result = dothistimeout cast_cmd, 5, results_regex
8356 end
8357 if @stance
8358 if @@after_stance
8359 if checkstance !~ /#{@@after_stance}/
8360 waitrt?
8361 dothistimeout "stance #{@@after_stance}", 3, /^You (?:are now in|move into) an? \w+ stance|^You are unable to change your stance\.$/
8362 end
8363 elsif checkstance !~ /^guarded$|^defensive$/
8364 waitrt?
8365 if checkcastrt > 0
8366 dothistimeout 'stance guarded', 3, /^You (?:are now in|move into) an? \w+ stance|^You are unable to change your stance\.$/
8367 else
8368 dothistimeout 'stance defensive', 3, /^You (?:are now in|move into) an? \w+ stance|^You are unable to change your stance\.$/
8369 end
8370 end
8371 end
8372 if cast_result =~ /^Cast at what\?$|^Be at peace my child, there is no need for spells of war in here\.$|^Provoking a GameMaster is not such a good idea\.$/
8373 dothistimeout 'release', 5, /^You feel the magic of your spell rush away from you\.$|^You don't have a prepared spell to release!$/
8374 end
8375 break unless (@circle.to_i == 10) and (cast_result =~ /^\[Spell Hindrance for/)
8376 }
8377 cast_result
8378 end
8379 ensure
8380 script.want_downstream = save_want_downstream
8381 script.want_downstream_xml = save_want_downstream_xml
8382 @@cast_lock.delete(script)
8383 end
8384 end
8385 def _bonus
8386 @bonus.dup
8387 end
8388 def _cost
8389 @cost.dup
8390 end
8391 def method_missing(*args)
8392 if @@bonus_list.include?(args[0].to_s.gsub('_', '-'))
8393 if @bonus[args[0].to_s.gsub('_', '-')]
8394 if $SAFE < 3
8395 proc { $SAFE = 3; eval(@bonus[args[0].to_s.gsub('_', '-')]) }.call.to_i
8396 else
8397 eval(@bonus[args[0].to_s.gsub('_', '-')]).to_i
8398 end
8399 else
8400 0
8401 end
8402 elsif @@bonus_list.include?(args[0].to_s.sub(/_formula$/, '').gsub('_', '-'))
8403 @bonus[args[0].to_s.sub(/_formula$/, '').gsub('_', '-')].dup
8404 elsif (args[0].to_s =~ /_cost(?:_formula)?$/) and @@cost_list.include?(args[0].to_s.sub(/_formula$/, '').sub(/_cost$/, ''))
8405 options = args[1].to_hash
8406 if options[:caster] and (options[:caster] !~ /^(?:self|#{XMLData.name})$/i)
8407 if options[:target] and (options[:target].downcase == options[:caster].downcase)
8408 formula = @cost[args[0].to_s.sub(/_formula$/, '').sub(/_cost$/, '')]['self'].dup
8409 else
8410 formula = @cost[args[0].to_s.sub(/_formula$/, '').sub(/_cost$/, '')]['target'].dup || @cost[args[0].to_s.gsub('_', '-')]['self'].dup
8411 end
8412 skills = { 'Spells.minorelemental' => "SpellRanks['#{options[:caster]}'].minorelemental.to_i", 'Spells.majorelemental' => "SpellRanks['#{options[:caster]}'].majorelemental.to_i", 'Spells.minorspiritual' => "SpellRanks['#{options[:caster]}'].minorspiritual.to_i", 'Spells.majorspiritual' => "SpellRanks['#{options[:caster]}'].majorspiritual.to_i", 'Spells.wizard' => "SpellRanks['#{options[:caster]}'].wizard.to_i", 'Spells.sorcerer' => "SpellRanks['#{options[:caster]}'].sorcerer.to_i", 'Spells.ranger' => "SpellRanks['#{options[:caster]}'].ranger.to_i", 'Spells.paladin' => "SpellRanks['#{options[:caster]}'].paladin.to_i", 'Spells.empath' => "SpellRanks['#{options[:caster]}'].empath.to_i", 'Spells.cleric' => "SpellRanks['#{options[:caster]}'].cleric.to_i", 'Spells.bard' => "SpellRanks['#{options[:caster]}'].bard.to_i", 'Stats.level' => '100' }
8413 skills.each_pair { |a, b| formula.gsub!(a, b) }
8414 else
8415 if options[:target] and (options[:target] !~ /^(?:self|#{XMLData.name})$/i)
8416 formula = @cost[args[0].to_s.sub(/_formula$/, '').sub(/_cost$/, '')]['target'].dup || @cost[args[0].to_s.gsub('_', '-')]['self'].dup
8417 else
8418 formula = @cost[args[0].to_s.sub(/_formula$/, '').sub(/_cost$/, '')]['self'].dup
8419 end
8420 end
8421 if args[0].to_s =~ /mana/ and Spell[597].active? # Rapid Fire Penalty
8422 formula = "#{formula}+5"
8423 end
8424 if options[:multicast].to_i > 1
8425 formula = "(#{formula})*#{options[:multicast].to_i}"
8426 end
8427 if args[0].to_s =~ /_formula$/
8428 formula.dup
8429 else
8430 if formula
8431 formula.untaint if formula.tainted?
8432 if $SAFE < 3
8433 proc { $SAFE = 3; eval(formula) }.call.to_i
8434 else
8435 eval(formula).to_i
8436 end
8437 else
8438 0
8439 end
8440 end
8441 else
8442 respond 'missing method: ' + args.inspect.to_s
8443 raise NoMethodError
8444 end
8445 end
8446 def circle_name
8447 Spells.get_circle_name(@circle)
8448 end
8449 def clear_on_death
8450 !@persist_on_death
8451 end
8452 # for backwards compatiblity
8453 def duration; self.time_per_formula; end
8454 def cost; self.mana_cost_formula || '0'; end
8455 def manaCost; self.mana_cost_formula || '0'; end
8456 def spiritCost; self.spirit_cost_formula || '0'; end
8457 def staminaCost; self.stamina_cost_formula || '0'; end
8458 def boltAS; self.bolt_as_formula; end
8459 def physicalAS; self.physical_as_formula; end
8460 def boltDS; self.bolt_ds_formula; end
8461 def physicalDS; self.physical_ds_formula; end
8462 def elementalCS; self.elemental_cs_formula; end
8463 def mentalCS; self.mental_cs_formula; end
8464 def spiritCS; self.spirit_cs_formula; end
8465 def sorcererCS; self.sorcerer_cs_formula; end
8466 def elementalTD; self.elemental_td_formula; end
8467 def mentalTD; self.mental_td_formula; end
8468 def spiritTD; self.spirit_td_formula; end
8469 def sorcererTD; self.sorcerer_td_formula; end
8470 def castProc; @cast_proc; end
8471 def stacks; self.stackable? end
8472 def command; nil; end
8473 def circlename; self.circle_name; end
8474 def selfonly; @availability != 'all'; end
8475 end
8476
8477 class CMan
8478 @@bearhug ||= 0
8479 @@berserk ||= 0
8480 @@block_mastery ||= 0
8481 @@bull_rush ||= 0
8482 @@charge ||= 0
8483 @@cheapshots ||= 0
8484 @@combat_focus ||= 0
8485 @@combat_mastery ||= 0
8486 @@combat_mobility ||= 0
8487 @@combat_movement ||= 0
8488 @@combat_toughness ||= 0
8489 @@coup_de_grace ||= 0
8490 @@crowd_press ||= 0
8491 @@cunning_defense ||= 0
8492 @@cutthroat ||= 0
8493 @@dirtkick ||= 0
8494 @@disarm_weapon ||= 0
8495 @@divert ||= 0
8496 @@dust_shroud ||= 0
8497 @@evade_mastery ||= 0
8498 @@feint ||= 0
8499 @@garrote ||= 0
8500 @@groin_kick ||= 0
8501 @@hamstring ||= 0
8502 @@haymaker ||= 0
8503 @@headbutt ||= 0
8504 @@mighty_blow ||= 0
8505 @@multi_fire ||= 0
8506 @@parry_mastery ||= 0
8507 @@precision ||= 0
8508 @@quickstrike ||= 0
8509 @@shadow_mastery ||= 0
8510 @@shield_bash ||= 0
8511 @@shield_charge ||= 0
8512 @@side_by_side ||= 0
8513 @@silent_strike ||= 0
8514 @@specialization_i ||= 0
8515 @@specialization_ii ||= 0
8516 @@specialization_iii ||= 0
8517 @@spin_attack ||= 0
8518 @@staggering_blow ||= 0
8519 @@stun_maneuvers ||= 0
8520 @@subdual_strike ||= 0
8521 @@subdue ||= 0
8522 @@sucker_punch ||= 0
8523 @@sunder_shield ||= 0
8524 @@surge_of_strength ||= 0
8525 @@sweep ||= 0
8526 @@tackle ||= 0
8527 @@trip ||= 0
8528 @@truehand ||= 0
8529 @@twin_hammerfists ||= 0
8530 @@weapon_bonding ||= 0
8531 @@vanish ||= 0
8532 @@duck_and_weave ||= 0
8533 @@slippery_mind ||= 0
8534 @@predators_eye ||= 0
8535 @@burst_of_swiftness ||= 0
8536 @@rolling_krynch_stance ||= 0
8537 @@stance_of_the_mongoose ||= 0
8538 @@slippery_mind ||= 0
8539 @@flurry_of_blows ||= 0
8540 @@inner_harmony ||= 0
8541
8542 def CMan.bearhug; @@bearhug; end
8543 def CMan.berserk; @@berserk; end
8544 def CMan.block_mastery; @@block_mastery; end
8545 def CMan.bull_rush; @@bull_rush; end
8546 def CMan.burst_of_swiftness; @@burst_of_swiftness; end
8547 def CMan.charge; @@charge; end
8548 def CMan.cheapshots; @@cheapshots; end
8549 def CMan.combat_focus; @@combat_focus; end
8550 def CMan.combat_mastery; @@combat_mastery; end
8551 def CMan.combat_mobility; @@combat_mobility; end
8552 def CMan.combat_movement; @@combat_movement; end
8553 def CMan.combat_toughness; @@combat_toughness; end
8554 def CMan.coup_de_grace; @@coup_de_grace; end
8555 def CMan.crowd_press; @@crowd_press; end
8556 def CMan.cunning_defense; @@cunning_defense; end
8557 def CMan.cutthroat; @@cutthroat; end
8558 def CMan.dirtkick; @@dirtkick; end
8559 def CMan.disarm_weapon; @@disarm_weapon; end
8560 def CMan.divert; @@divert; end
8561 def CMan.dust_shroud; @@dust_shroud; end
8562 def CMan.evade_mastery; @@evade_mastery; end
8563 def CMan.feint; @@feint; end
8564 def CMan.garrote; @@garrote; end
8565 def CMan.groin_kick; @@groin_kick; end
8566 def CMan.hamstring; @@hamstring; end
8567 def CMan.haymaker; @@haymaker; end
8568 def CMan.headbutt; @@headbutt; end
8569 def CMan.mighty_blow; @@mighty_blow; end
8570 def CMan.multi_fire; @@multi_fire; end
8571 def CMan.parry_mastery; @@parry_mastery; end
8572 def CMan.precision; @@precision; end
8573 def CMan.quickstrike; @@quickstrike; end
8574 def CMan.shadow_mastery; @@shadow_mastery; end
8575 def CMan.shield_bash; @@shield_bash; end
8576 def CMan.shield_charge; @@shield_charge; end
8577 def CMan.side_by_side; @@side_by_side; end
8578 def CMan.silent_strike; @@silent_strike; end
8579 def CMan.specialization_i; @@specialization_i; end
8580 def CMan.specialization_ii; @@specialization_ii; end
8581 def CMan.specialization_iii; @@specialization_iii; end
8582 def CMan.spin_attack; @@spin_attack; end
8583 def CMan.staggering_blow; @@staggering_blow; end
8584 def CMan.stun_maneuvers; @@stun_maneuvers; end
8585 def CMan.subdual_strike; @@subdual_strike; end
8586 def CMan.subdue; @@subdue; end
8587 def CMan.sucker_punch; @@sucker_punch; end
8588 def CMan.sunder_shield; @@sunder_shield; end
8589 def CMan.surge_of_strength; @@surge_of_strength; end
8590 def CMan.sweep; @@sweep; end
8591 def CMan.tackle; @@tackle; end
8592 def CMan.trip; @@trip; end
8593 def CMan.truehand; @@truehand; end
8594 def CMan.twin_hammerfists; @@twin_hammerfists; end
8595 def CMan.weapon_bonding; @@weapon_bonding; end
8596 def CMan.vanish; @@vanish; end
8597 def CMan.duck_and_weave; @@duck_and_weave; end
8598 def CMan.slippery_mind; @@slippery_mind; end
8599 def CMan.predators_eye; @@predators_eye; end
8600
8601 def CMan.bearhug=(val); @@bearhug=val; end
8602 def CMan.berserk=(val); @@berserk=val; end
8603 def CMan.block_mastery=(val); @@block_mastery=val; end
8604 def CMan.bull_rush=(val); @@bull_rush=val; end
8605 def CMan.burst_of_swiftness=(val); @@burst_of_swiftness=val; end
8606 def CMan.charge=(val); @@charge=val; end
8607 def CMan.cheapshots=(val); @@cheapshots=val; end
8608 def CMan.combat_focus=(val); @@combat_focus=val; end
8609 def CMan.combat_mastery=(val); @@combat_mastery=val; end
8610 def CMan.combat_mobility=(val); @@combat_mobility=val; end
8611 def CMan.combat_movement=(val); @@combat_movement=val; end
8612 def CMan.combat_toughness=(val); @@combat_toughness=val; end
8613 def CMan.coup_de_grace=(val); @@coup_de_grace=val; end
8614 def CMan.crowd_press=(val); @@crowd_press=val; end
8615 def CMan.cunning_defense=(val); @@cunning_defense=val; end
8616 def CMan.cutthroat=(val); @@cutthroat=val; end
8617 def CMan.dirtkick=(val); @@dirtkick=val; end
8618 def CMan.disarm_weapon=(val); @@disarm_weapon=val; end
8619 def CMan.divert=(val); @@divert=val; end
8620 def CMan.dust_shroud=(val); @@dust_shroud=val; end
8621 def CMan.evade_mastery=(val); @@evade_mastery=val; end
8622 def CMan.feint=(val); @@feint=val; end
8623 def CMan.garrote=(val); @@garrote=val; end
8624 def CMan.groin_kick=(val); @@groin_kick=val; end
8625 def CMan.hamstring=(val); @@hamstring=val; end
8626 def CMan.haymaker=(val); @@haymaker=val; end
8627 def CMan.headbutt=(val); @@headbutt=val; end
8628 def CMan.mighty_blow=(val); @@mighty_blow=val; end
8629 def CMan.multi_fire=(val); @@multi_fire=val; end
8630 def CMan.parry_mastery=(val); @@parry_mastery=val; end
8631 def CMan.precision=(val); @@precision=val; end
8632 def CMan.quickstrike=(val); @@quickstrike=val; end
8633 def CMan.shadow_mastery=(val); @@shadow_mastery=val; end
8634 def CMan.shield_bash=(val); @@shield_bash=val; end
8635 def CMan.shield_charge=(val); @@shield_charge=val; end
8636 def CMan.side_by_side=(val); @@side_by_side=val; end
8637 def CMan.silent_strike=(val); @@silent_strike=val; end
8638 def CMan.specialization_i=(val); @@specialization_i=val; end
8639 def CMan.specialization_ii=(val); @@specialization_ii=val; end
8640 def CMan.specialization_iii=(val); @@specialization_iii=val; end
8641 def CMan.spin_attack=(val); @@spin_attack=val; end
8642 def CMan.staggering_blow=(val); @@staggering_blow=val; end
8643 def CMan.stun_maneuvers=(val); @@stun_maneuvers=val; end
8644 def CMan.subdual_strike=(val); @@subdual_strike=val; end
8645 def CMan.subdue=(val); @@subdue=val; end
8646 def CMan.sucker_punch=(val); @@sucker_punch=val; end
8647 def CMan.sunder_shield=(val); @@sunder_shield=val; end
8648 def CMan.surge_of_strength=(val); @@surge_of_strength=val; end
8649 def CMan.sweep=(val); @@sweep=val; end
8650 def CMan.tackle=(val); @@tackle=val; end
8651 def CMan.trip=(val); @@trip=val; end
8652 def CMan.truehand=(val); @@truehand=val; end
8653 def CMan.twin_hammerfists=(val); @@twin_hammerfists=val; end
8654 def CMan.weapon_bonding=(val); @@weapon_bonding=val; end
8655 def CMan.vanish=(val); @@vanish=val; end
8656 def CMan.duck_and_weave=(val); @@duck_and_weave=val; end
8657 def CMan.slippery_mind=(val); @@slippery_mind=val; end
8658 def CMan.predators_eye=(val); @@predators_eye=val; end
8659
8660 def CMan.method_missing(arg1, arg2=nil)
8661 nil
8662 end
8663 def CMan.[](name)
8664 CMan.send(name.gsub(/[\s\-]/, '_').gsub("'", "").downcase)
8665 end
8666 def CMan.[]=(name,val)
8667 CMan.send("#{name.gsub(/[\s\-]/, '_').gsub("'", "").downcase}=", val.to_i)
8668 end
8669 end
8670
8671 class Stats
8672 @@race ||= 'unknown'
8673 @@prof ||= 'unknown'
8674 @@gender ||= 'unknown'
8675 @@age ||= 0
8676 @@level ||= 0
8677 @@str ||= [0,0]
8678 @@con ||= [0,0]
8679 @@dex ||= [0,0]
8680 @@agi ||= [0,0]
8681 @@dis ||= [0,0]
8682 @@aur ||= [0,0]
8683 @@log ||= [0,0]
8684 @@int ||= [0,0]
8685 @@wis ||= [0,0]
8686 @@inf ||= [0,0]
8687 def Stats.race; @@race; end
8688 def Stats.race=(val); @@race=val; end
8689 def Stats.prof; @@prof; end
8690 def Stats.prof=(val); @@prof=val; end
8691 def Stats.gender; @@gender; end
8692 def Stats.gender=(val); @@gender=val; end
8693 def Stats.age; @@age; end
8694 def Stats.age=(val); @@age=val; end
8695 def Stats.level; @@level; end
8696 def Stats.level=(val); @@level=val; end
8697 def Stats.str; @@str; end
8698 def Stats.str=(val); @@str=val; end
8699 def Stats.con; @@con; end
8700 def Stats.con=(val); @@con=val; end
8701 def Stats.dex; @@dex; end
8702 def Stats.dex=(val); @@dex=val; end
8703 def Stats.agi; @@agi; end
8704 def Stats.agi=(val); @@agi=val; end
8705 def Stats.dis; @@dis; end
8706 def Stats.dis=(val); @@dis=val; end
8707 def Stats.aur; @@aur; end
8708 def Stats.aur=(val); @@aur=val; end
8709 def Stats.log; @@log; end
8710 def Stats.log=(val); @@log=val; end
8711 def Stats.int; @@int; end
8712 def Stats.int=(val); @@int=val; end
8713 def Stats.wis; @@wis; end
8714 def Stats.wis=(val); @@wis=val; end
8715 def Stats.inf; @@inf; end
8716 def Stats.inf=(val); @@inf=val; end
8717 def Stats.exp
8718 if XMLData.next_level_text =~ /until next level/
8719 exp_threshold = [ 2500, 5000, 10000, 17500, 27500, 40000, 55000, 72500, 92500, 115000, 140000, 167000, 197500, 230000, 265000, 302000, 341000, 382000, 425000, 470000, 517000, 566000, 617000, 670000, 725000, 781500, 839500, 899000, 960000, 1022500, 1086500, 1152000, 1219000, 1287500, 1357500, 1429000, 1502000, 1576500, 1652500, 1730000, 1808500, 1888000, 1968500, 2050000, 2132500, 2216000, 2300500, 2386000, 2472500, 2560000, 2648000, 2736500, 2825500, 2915000, 3005000, 3095500, 3186500, 3278000, 3370000, 3462500, 3555500, 3649000, 3743000, 3837500, 3932500, 4028000, 4124000, 4220500, 4317500, 4415000, 4513000, 4611500, 4710500, 4810000, 4910000, 5010500, 5111500, 5213000, 5315000, 5417500, 5520500, 5624000, 5728000, 5832500, 5937500, 6043000, 6149000, 6255500, 6362500, 6470000, 6578000, 6686500, 6795500, 6905000, 7015000, 7125500, 7236500, 7348000, 7460000, 7572500 ]
8720 exp_threshold[XMLData.level] - XMLData.next_level_text.slice(/[0-9]+/).to_i
8721 else
8722 XMLData.next_level_text.slice(/[0-9]+/).to_i
8723 end
8724 end
8725 def Stats.exp=(val); nil; end
8726 def Stats.serialize
8727 [@@race,@@prof,@@gender,@@age,Stats.exp,@@level,@@str,@@con,@@dex,@@agi,@@dis,@@aur,@@log,@@int,@@wis,@@inf]
8728 end
8729 def Stats.load_serialized=(array)
8730 @@race,@@prof,@@gender,@@age = array[0..3]
8731 @@level,@@str,@@con,@@dex,@@agi,@@dis,@@aur,@@log,@@int,@@wis,@@inf = array[5..15]
8732 end
8733 end
8734
8735 class Gift
8736 @@gift_start ||= Time.now
8737 @@pulse_count ||= 0
8738 def Gift.started
8739 @@gift_start = Time.now
8740 @@pulse_count = 0
8741 end
8742 def Gift.pulse
8743 @@pulse_count += 1
8744 end
8745 def Gift.remaining
8746 ([360 - @@pulse_count, 0].max * 60).to_f
8747 end
8748 def Gift.restarts_on
8749 @@gift_start + 594000
8750 end
8751 def Gift.serialize
8752 [@@gift_start, @@pulse_count]
8753 end
8754 def Gift.load_serialized=(array)
8755 @@gift_start = array[0]
8756 @@pulse_count = array[1].to_i
8757 end
8758 def Gift.ended
8759 @@pulse_count = 360
8760 end
8761 def Gift.stopwatch
8762 nil
8763 end
8764 end
8765
8766 class Wounds
8767 def Wounds.leftEye; fix_injury_mode; XMLData.injuries['leftEye']['wound']; end
8768 def Wounds.leye; fix_injury_mode; XMLData.injuries['leftEye']['wound']; end
8769 def Wounds.rightEye; fix_injury_mode; XMLData.injuries['rightEye']['wound']; end
8770 def Wounds.reye; fix_injury_mode; XMLData.injuries['rightEye']['wound']; end
8771 def Wounds.head; fix_injury_mode; XMLData.injuries['head']['wound']; end
8772 def Wounds.neck; fix_injury_mode; XMLData.injuries['neck']['wound']; end
8773 def Wounds.back; fix_injury_mode; XMLData.injuries['back']['wound']; end
8774 def Wounds.chest; fix_injury_mode; XMLData.injuries['chest']['wound']; end
8775 def Wounds.abdomen; fix_injury_mode; XMLData.injuries['abdomen']['wound']; end
8776 def Wounds.abs; fix_injury_mode; XMLData.injuries['abdomen']['wound']; end
8777 def Wounds.leftArm; fix_injury_mode; XMLData.injuries['leftArm']['wound']; end
8778 def Wounds.larm; fix_injury_mode; XMLData.injuries['leftArm']['wound']; end
8779 def Wounds.rightArm; fix_injury_mode; XMLData.injuries['rightArm']['wound']; end
8780 def Wounds.rarm; fix_injury_mode; XMLData.injuries['rightArm']['wound']; end
8781 def Wounds.rightHand; fix_injury_mode; XMLData.injuries['rightHand']['wound']; end
8782 def Wounds.rhand; fix_injury_mode; XMLData.injuries['rightHand']['wound']; end
8783 def Wounds.leftHand; fix_injury_mode; XMLData.injuries['leftHand']['wound']; end
8784 def Wounds.lhand; fix_injury_mode; XMLData.injuries['leftHand']['wound']; end
8785 def Wounds.leftLeg; fix_injury_mode; XMLData.injuries['leftLeg']['wound']; end
8786 def Wounds.lleg; fix_injury_mode; XMLData.injuries['leftLeg']['wound']; end
8787 def Wounds.rightLeg; fix_injury_mode; XMLData.injuries['rightLeg']['wound']; end
8788 def Wounds.rleg; fix_injury_mode; XMLData.injuries['rightLeg']['wound']; end
8789 def Wounds.leftFoot; fix_injury_mode; XMLData.injuries['leftFoot']['wound']; end
8790 def Wounds.rightFoot; fix_injury_mode; XMLData.injuries['rightFoot']['wound']; end
8791 def Wounds.nsys; fix_injury_mode; XMLData.injuries['nsys']['wound']; end
8792 def Wounds.nerves; fix_injury_mode; XMLData.injuries['nsys']['wound']; end
8793 def Wounds.arms
8794 fix_injury_mode
8795 [XMLData.injuries['leftArm']['wound'],XMLData.injuries['rightArm']['wound'],XMLData.injuries['leftHand']['wound'],XMLData.injuries['rightHand']['wound']].max
8796 end
8797 def Wounds.limbs
8798 fix_injury_mode
8799 [XMLData.injuries['leftArm']['wound'],XMLData.injuries['rightArm']['wound'],XMLData.injuries['leftHand']['wound'],XMLData.injuries['rightHand']['wound'],XMLData.injuries['leftLeg']['wound'],XMLData.injuries['rightLeg']['wound']].max
8800 end
8801 def Wounds.torso
8802 fix_injury_mode
8803 [XMLData.injuries['rightEye']['wound'],XMLData.injuries['leftEye']['wound'],XMLData.injuries['chest']['wound'],XMLData.injuries['abdomen']['wound'],XMLData.injuries['back']['wound']].max
8804 end
8805 def Wounds.method_missing(arg=nil)
8806 echo "Wounds: Invalid area, try one of these: arms, limbs, torso, #{XMLData.injuries.keys.join(', ')}"
8807 nil
8808 end
8809 end
8810
8811 class Scars
8812 def Scars.leftEye; fix_injury_mode; XMLData.injuries['leftEye']['scar']; end
8813 def Scars.leye; fix_injury_mode; XMLData.injuries['leftEye']['scar']; end
8814 def Scars.rightEye; fix_injury_mode; XMLData.injuries['rightEye']['scar']; end
8815 def Scars.reye; fix_injury_mode; XMLData.injuries['rightEye']['scar']; end
8816 def Scars.head; fix_injury_mode; XMLData.injuries['head']['scar']; end
8817 def Scars.neck; fix_injury_mode; XMLData.injuries['neck']['scar']; end
8818 def Scars.back; fix_injury_mode; XMLData.injuries['back']['scar']; end
8819 def Scars.chest; fix_injury_mode; XMLData.injuries['chest']['scar']; end
8820 def Scars.abdomen; fix_injury_mode; XMLData.injuries['abdomen']['scar']; end
8821 def Scars.abs; fix_injury_mode; XMLData.injuries['abdomen']['scar']; end
8822 def Scars.leftArm; fix_injury_mode; XMLData.injuries['leftArm']['scar']; end
8823 def Scars.larm; fix_injury_mode; XMLData.injuries['leftArm']['scar']; end
8824 def Scars.rightArm; fix_injury_mode; XMLData.injuries['rightArm']['scar']; end
8825 def Scars.rarm; fix_injury_mode; XMLData.injuries['rightArm']['scar']; end
8826 def Scars.rightHand; fix_injury_mode; XMLData.injuries['rightHand']['scar']; end
8827 def Scars.rhand; fix_injury_mode; XMLData.injuries['rightHand']['scar']; end
8828 def Scars.leftHand; fix_injury_mode; XMLData.injuries['leftHand']['scar']; end
8829 def Scars.lhand; fix_injury_mode; XMLData.injuries['leftHand']['scar']; end
8830 def Scars.leftLeg; fix_injury_mode; XMLData.injuries['leftLeg']['scar']; end
8831 def Scars.lleg; fix_injury_mode; XMLData.injuries['leftLeg']['scar']; end
8832 def Scars.rightLeg; fix_injury_mode; XMLData.injuries['rightLeg']['scar']; end
8833 def Scars.rleg; fix_injury_mode; XMLData.injuries['rightLeg']['scar']; end
8834 def Scars.leftFoot; fix_injury_mode; XMLData.injuries['leftFoot']['scar']; end
8835 def Scars.rightFoot; fix_injury_mode; XMLData.injuries['rightFoot']['scar']; end
8836 def Scars.nsys; fix_injury_mode; XMLData.injuries['nsys']['scar']; end
8837 def Scars.nerves; fix_injury_mode; XMLData.injuries['nsys']['scar']; end
8838 def Scars.arms
8839 fix_injury_mode
8840 [XMLData.injuries['leftArm']['scar'],XMLData.injuries['rightArm']['scar'],XMLData.injuries['leftHand']['scar'],XMLData.injuries['rightHand']['scar']].max
8841 end
8842 def Scars.limbs
8843 fix_injury_mode
8844 [XMLData.injuries['leftArm']['scar'],XMLData.injuries['rightArm']['scar'],XMLData.injuries['leftHand']['scar'],XMLData.injuries['rightHand']['scar'],XMLData.injuries['leftLeg']['scar'],XMLData.injuries['rightLeg']['scar']].max
8845 end
8846 def Scars.torso
8847 fix_injury_mode
8848 [XMLData.injuries['rightEye']['scar'],XMLData.injuries['leftEye']['scar'],XMLData.injuries['chest']['scar'],XMLData.injuries['abdomen']['scar'],XMLData.injuries['back']['scar']].max
8849 end
8850 def Scars.method_missing(arg=nil)
8851 echo "Scars: Invalid area, try one of these: arms, limbs, torso, #{XMLData.injuries.keys.join(', ')}"
8852 nil
8853 end
8854 end
8855 class GameObj
8856 @@loot = Array.new
8857 @@npcs = Array.new
8858 @@npc_status = Hash.new
8859 @@pcs = Array.new
8860 @@pc_status = Hash.new
8861 @@inv = Array.new
8862 @@contents = Hash.new
8863 @@right_hand = nil
8864 @@left_hand = nil
8865 @@room_desc = Array.new
8866 @@fam_loot = Array.new
8867 @@fam_npcs = Array.new
8868 @@fam_pcs = Array.new
8869 @@fam_room_desc = Array.new
8870 @@type_data = Hash.new
8871 @@sellable_data = Hash.new
8872 @@elevated_load = proc { GameObj.load_data }
8873
8874 attr_reader :id
8875 attr_accessor :noun, :name, :before_name, :after_name
8876 def initialize(id, noun, name, before=nil, after=nil)
8877 @id = id
8878 @noun = noun
8879 @noun = 'lapis' if @noun == 'lapis lazuli'
8880 @noun = 'hammer' if @noun == "Hammer of Kai"
8881 @noun = 'mother-of-pearl' if (@noun == 'pearl') and (@name =~ /mother\-of\-pearl/)
8882 @name = name
8883 @before_name = before
8884 @after_name = after
8885 end
8886 def type
8887 GameObj.load_data if @@type_data.empty?
8888 list = @@type_data.keys.find_all { |t| (@name =~ @@type_data[t][:name] or @noun =~ @@type_data[t][:noun]) and (@@type_data[t][:exclude].nil? or @name !~ @@type_data[t][:exclude]) }
8889 if list.empty?
8890 nil
8891 else
8892 list.join(',')
8893 end
8894 end
8895 def sellable
8896 GameObj.load_data if @@sellable_data.empty?
8897 list = @@sellable_data.keys.find_all { |t| (@name =~ @@sellable_data[t][:name] or @noun =~ @@sellable_data[t][:noun]) and (@@sellable_data[t][:exclude].nil? or @name !~ @@sellable_data[t][:exclude]) }
8898 if list.empty?
8899 nil
8900 else
8901 list.join(',')
8902 end
8903 end
8904 def status
8905 if @@npc_status.keys.include?(@id)
8906 @@npc_status[@id]
8907 elsif @@pc_status.keys.include?(@id)
8908 @@pc_status[@id]
8909 elsif @@loot.find { |obj| obj.id == @id } or @@inv.find { |obj| obj.id == @id } or @@room_desc.find { |obj| obj.id == @id } or @@fam_loot.find { |obj| obj.id == @id } or @@fam_npcs.find { |obj| obj.id == @id } or @@fam_pcs.find { |obj| obj.id == @id } or @@fam_room_desc.find { |obj| obj.id == @id } or (@@right_hand.id == @id) or (@@left_hand.id == @id) or @@contents.values.find { |list| list.find { |obj| obj.id == @id } }
8910 nil
8911 else
8912 'gone'
8913 end
8914 end
8915 def status=(val)
8916 if @@npcs.any? { |npc| npc.id == @id }
8917 @@npc_status[@id] = val
8918 elsif @@pcs.any? { |pc| pc.id == @id }
8919 @@pc_status[@id] = val
8920 else
8921 nil
8922 end
8923 end
8924 def to_s
8925 @noun
8926 end
8927 def empty?
8928 false
8929 end
8930 def contents
8931 @@contents[@id].dup
8932 end
8933 def GameObj.[](val)
8934 if val.class == String
8935 if val =~ /^\-?[0-9]+$/
8936 obj = @@inv.find { |o| o.id == val } || @@loot.find { |o| o.id == val } || @@npcs.find { |o| o.id == val } || @@pcs.find { |o| o.id == val } || [ @@right_hand, @@left_hand ].find { |o| o.id == val } || @@room_desc.find { |o| o.id == val }
8937 elsif val.split(' ').length == 1
8938 obj = @@inv.find { |o| o.noun == val } || @@loot.find { |o| o.noun == val } || @@npcs.find { |o| o.noun == val } || @@pcs.find { |o| o.noun == val } || [ @@right_hand, @@left_hand ].find { |o| o.noun == val } || @@room_desc.find { |o| o.noun == val }
8939 else
8940 obj = @@inv.find { |o| o.name == val } || @@loot.find { |o| o.name == val } || @@npcs.find { |o| o.name == val } || @@pcs.find { |o| o.name == val } || [ @@right_hand, @@left_hand ].find { |o| o.name == val } || @@room_desc.find { |o| o.name == val } || @@inv.find { |o| o.name =~ /\b#{Regexp.escape(val.strip)}$/i } || @@loot.find { |o| o.name =~ /\b#{Regexp.escape(val.strip)}$/i } || @@npcs.find { |o| o.name =~ /\b#{Regexp.escape(val.strip)}$/i } || @@pcs.find { |o| o.name =~ /\b#{Regexp.escape(val.strip)}$/i } || [ @@right_hand, @@left_hand ].find { |o| o.name =~ /\b#{Regexp.escape(val.strip)}$/i } || @@room_desc.find { |o| o.name =~ /\b#{Regexp.escape(val.strip)}$/i } || @@inv.find { |o| o.name =~ /\b#{Regexp.escape(val).sub(' ', ' .*')}$/i } || @@loot.find { |o| o.name =~ /\b#{Regexp.escape(val).sub(' ', ' .*')}$/i } || @@npcs.find { |o| o.name =~ /\b#{Regexp.escape(val).sub(' ', ' .*')}$/i } || @@pcs.find { |o| o.name =~ /\b#{Regexp.escape(val).sub(' ', ' .*')}$/i } || [ @@right_hand, @@left_hand ].find { |o| o.name =~ /\b#{Regexp.escape(val).sub(' ', ' .*')}$/i } || @@room_desc.find { |o| o.name =~ /\b#{Regexp.escape(val).sub(' ', ' .*')}$/i }
8941 end
8942 elsif val.class == Regexp
8943 obj = @@inv.find { |o| o.name =~ val } || @@loot.find { |o| o.name =~ val } || @@npcs.find { |o| o.name =~ val } || @@pcs.find { |o| o.name =~ val } || [ @@right_hand, @@left_hand ].find { |o| o.name =~ val } || @@room_desc.find { |o| o.name =~ val }
8944 end
8945 end
8946 def GameObj
8947 @noun
8948 end
8949 def full_name
8950 "#{@before_name}#{' ' unless @before_name.nil? or @before_name.empty?}#{name}#{' ' unless @after_name.nil? or @after_name.empty?}#{@after_name}"
8951 end
8952 def GameObj.new_npc(id, noun, name, status=nil)
8953 obj = GameObj.new(id, noun, name)
8954 @@npcs.push(obj)
8955 @@npc_status[id] = status
8956 obj
8957 end
8958 def GameObj.new_loot(id, noun, name)
8959 obj = GameObj.new(id, noun, name)
8960 @@loot.push(obj)
8961 obj
8962 end
8963 def GameObj.new_pc(id, noun, name, status=nil)
8964 obj = GameObj.new(id, noun, name)
8965 @@pcs.push(obj)
8966 @@pc_status[id] = status
8967 obj
8968 end
8969 def GameObj.new_inv(id, noun, name, container=nil, before=nil, after=nil)
8970 obj = GameObj.new(id, noun, name, before, after)
8971 if container
8972 @@contents[container].push(obj)
8973 else
8974 @@inv.push(obj)
8975 end
8976 obj
8977 end
8978 def GameObj.new_room_desc(id, noun, name)
8979 obj = GameObj.new(id, noun, name)
8980 @@room_desc.push(obj)
8981 obj
8982 end
8983 def GameObj.new_fam_room_desc(id, noun, name)
8984 obj = GameObj.new(id, noun, name)
8985 @@fam_room_desc.push(obj)
8986 obj
8987 end
8988 def GameObj.new_fam_loot(id, noun, name)
8989 obj = GameObj.new(id, noun, name)
8990 @@fam_loot.push(obj)
8991 obj
8992 end
8993 def GameObj.new_fam_npc(id, noun, name)
8994 obj = GameObj.new(id, noun, name)
8995 @@fam_npcs.push(obj)
8996 obj
8997 end
8998 def GameObj.new_fam_pc(id, noun, name)
8999 obj = GameObj.new(id, noun, name)
9000 @@fam_pcs.push(obj)
9001 obj
9002 end
9003 def GameObj.new_right_hand(id, noun, name)
9004 @@right_hand = GameObj.new(id, noun, name)
9005 end
9006 def GameObj.right_hand
9007 @@right_hand.dup
9008 end
9009 def GameObj.new_left_hand(id, noun, name)
9010 @@left_hand = GameObj.new(id, noun, name)
9011 end
9012 def GameObj.left_hand
9013 @@left_hand.dup
9014 end
9015 def GameObj.clear_loot
9016 @@loot.clear
9017 end
9018 def GameObj.clear_npcs
9019 @@npcs.clear
9020 @@npc_status.clear
9021 end
9022 def GameObj.clear_pcs
9023 @@pcs.clear
9024 @@pc_status.clear
9025 end
9026 def GameObj.clear_inv
9027 @@inv.clear
9028 end
9029 def GameObj.clear_room_desc
9030 @@room_desc.clear
9031 end
9032 def GameObj.clear_fam_room_desc
9033 @@fam_room_desc.clear
9034 end
9035 def GameObj.clear_fam_loot
9036 @@fam_loot.clear
9037 end
9038 def GameObj.clear_fam_npcs
9039 @@fam_npcs.clear
9040 end
9041 def GameObj.clear_fam_pcs
9042 @@fam_pcs.clear
9043 end
9044 def GameObj.npcs
9045 if @@npcs.empty?
9046 nil
9047 else
9048 @@npcs.dup
9049 end
9050 end
9051 def GameObj.loot
9052 if @@loot.empty?
9053 nil
9054 else
9055 @@loot.dup
9056 end
9057 end
9058 def GameObj.pcs
9059 if @@pcs.empty?
9060 nil
9061 else
9062 @@pcs.dup
9063 end
9064 end
9065 def GameObj.inv
9066 if @@inv.empty?
9067 nil
9068 else
9069 @@inv.dup
9070 end
9071 end
9072 def GameObj.room_desc
9073 if @@room_desc.empty?
9074 nil
9075 else
9076 @@room_desc.dup
9077 end
9078 end
9079 def GameObj.fam_room_desc
9080 if @@fam_room_desc.empty?
9081 nil
9082 else
9083 @@fam_room_desc.dup
9084 end
9085 end
9086 def GameObj.fam_loot
9087 if @@fam_loot.empty?
9088 nil
9089 else
9090 @@fam_loot.dup
9091 end
9092 end
9093 def GameObj.fam_npcs
9094 if @@fam_npcs.empty?
9095 nil
9096 else
9097 @@fam_npcs.dup
9098 end
9099 end
9100 def GameObj.fam_pcs
9101 if @@fam_pcs.empty?
9102 nil
9103 else
9104 @@fam_pcs.dup
9105 end
9106 end
9107 def GameObj.clear_container(container_id)
9108 @@contents[container_id] = Array.new
9109 end
9110 def GameObj.delete_container(container_id)
9111 @@contents.delete(container_id)
9112 end
9113 def GameObj.dead
9114 dead_list = Array.new
9115 for obj in @@npcs
9116 dead_list.push(obj) if obj.status == "dead"
9117 end
9118 return nil if dead_list.empty?
9119 return dead_list
9120 end
9121 def GameObj.containers
9122 @@contents.dup
9123 end
9124 def GameObj.load_data(filename=nil)
9125 if $SAFE == 0
9126 if filename.nil?
9127 if File.exists?("#{DATA_DIR}/gameobj-data.xml")
9128 filename = "#{DATA_DIR}/gameobj-data.xml"
9129 elsif File.exists?("#{SCRIPT_DIR}/gameobj-data.xml") # deprecated
9130 filename = "#{SCRIPT_DIR}/gameobj-data.xml"
9131 else
9132 filename = "#{DATA_DIR}/gameobj-data.xml"
9133 end
9134 end
9135 if File.exists?(filename)
9136 begin
9137 @@type_data = Hash.new
9138 @@sellable_data = Hash.new
9139 File.open(filename) { |file|
9140 doc = REXML::Document.new(file.read)
9141 doc.elements.each('data/type') { |e|
9142 if type = e.attributes['name']
9143 @@type_data[type] = Hash.new
9144 @@type_data[type][:name] = Regexp.new(e.elements['name'].text) unless e.elements['name'].text.nil? or e.elements['name'].text.empty?
9145 @@type_data[type][:noun] = Regexp.new(e.elements['noun'].text) unless e.elements['noun'].text.nil? or e.elements['noun'].text.empty?
9146 @@type_data[type][:exclude] = Regexp.new(e.elements['exclude'].text) unless e.elements['exclude'].text.nil? or e.elements['exclude'].text.empty?
9147 end
9148 }
9149 doc.elements.each('data/sellable') { |e|
9150 if sellable = e.attributes['name']
9151 @@sellable_data[sellable] = Hash.new
9152 @@sellable_data[sellable][:name] = Regexp.new(e.elements['name'].text) unless e.elements['name'].text.nil? or e.elements['name'].text.empty?
9153 @@sellable_data[sellable][:noun] = Regexp.new(e.elements['noun'].text) unless e.elements['noun'].text.nil? or e.elements['noun'].text.empty?
9154 @@sellable_data[sellable][:exclude] = Regexp.new(e.elements['exclude'].text) unless e.elements['exclude'].text.nil? or e.elements['exclude'].text.empty?
9155 end
9156 }
9157 }
9158 true
9159 rescue
9160 @@type_data = nil
9161 @@sellable_data = nil
9162 echo "error: GameObj.load_data: #{$!}"
9163 respond $!.backtrace[0..1]
9164 false
9165 end
9166 else
9167 @@type_data = nil
9168 @@sellable_data = nil
9169 echo "error: GameObj.load_data: file does not exist: #{filename}"
9170 false
9171 end
9172 else
9173 @@elevated_load.call
9174 end
9175 end
9176 def GameObj.type_data
9177 @@type_data
9178 end
9179 def GameObj.sellable_data
9180 @@sellable_data
9181 end
9182 end
9183 #
9184 # start deprecated stuff
9185 #
9186 class RoomObj < GameObj
9187 end
9188 #
9189 # end deprecated stuff
9190 #
9191 end
9192 module DragonRealms
9193 # fixme
9194 end
9195end
9196
9197include Games::Gemstone
9198
9199JUMP = Exception.exception('JUMP')
9200JUMP_ERROR = Exception.exception('JUMP_ERROR')
9201
9202DIRMAP = {
9203 'out' => 'K',
9204 'ne' => 'B',
9205 'se' => 'D',
9206 'sw' => 'F',
9207 'nw' => 'H',
9208 'up' => 'I',
9209 'down' => 'J',
9210 'n' => 'A',
9211 'e' => 'C',
9212 's' => 'E',
9213 'w' => 'G',
9214}
9215SHORTDIR = {
9216 'out' => 'out',
9217 'northeast' => 'ne',
9218 'southeast' => 'se',
9219 'southwest' => 'sw',
9220 'northwest' => 'nw',
9221 'up' => 'up',
9222 'down' => 'down',
9223 'north' => 'n',
9224 'east' => 'e',
9225 'south' => 's',
9226 'west' => 'w',
9227}
9228LONGDIR = {
9229 'out' => 'out',
9230 'ne' => 'northeast',
9231 'se' => 'southeast',
9232 'sw' => 'southwest',
9233 'nw' => 'northwest',
9234 'up' => 'up',
9235 'down' => 'down',
9236 'n' => 'north',
9237 'e' => 'east',
9238 's' => 'south',
9239 'w' => 'west',
9240}
9241MINDMAP = {
9242 'clear as a bell' => 'A',
9243 'fresh and clear' => 'B',
9244 'clear' => 'C',
9245 'muddled' => 'D',
9246 'becoming numbed' => 'E',
9247 'numbed' => 'F',
9248 'must rest' => 'G',
9249 'saturated' => 'H',
9250}
9251ICONMAP = {
9252 'IconKNEELING' => 'GH',
9253 'IconPRONE' => 'G',
9254 'IconSITTING' => 'H',
9255 'IconSTANDING' => 'T',
9256 'IconSTUNNED' => 'I',
9257 'IconHIDDEN' => 'N',
9258 'IconINVISIBLE' => 'D',
9259 'IconDEAD' => 'B',
9260 'IconWEBBED' => 'C',
9261 'IconJOINED' => 'P',
9262 'IconBLEEDING' => 'O',
9263}
9264
9265XMLData = XMLParser.new
9266
9267reconnect_if_wanted = proc {
9268 if ARGV.include?('--reconnect') and ARGV.include?('--login') and not $_CLIENTBUFFER_.any? { |cmd| cmd =~ /^(?:\[.*?\])?(?:<c>)?(?:quit|exit)/i }
9269 if reconnect_arg = ARGV.find { |arg| arg =~ /^\-\-reconnect\-delay=[0-9]+(?:\+[0-9]+)?$/ }
9270 reconnect_arg =~ /^\-\-reconnect\-delay=([0-9]+)(\+[0-9]+)?/
9271 reconnect_delay = $1.to_i
9272 reconnect_step = $2.to_i
9273 else
9274 reconnect_delay = 60
9275 reconnect_step = 0
9276 end
9277 Lich.log "info: waiting #{reconnect_delay} seconds to reconnect..."
9278 sleep reconnect_delay
9279 Lich.log 'info: reconnecting...'
9280 if (RUBY_PLATFORM =~ /mingw|win/i) and (RUBY_PLATFORM !~ /darwin/i)
9281 if $frontend == 'stormfront'
9282 system 'taskkill /FI "WINDOWTITLE eq [GSIV: ' + Char.name + '*"'
9283 end
9284 args = [ 'start rubyw.exe' ]
9285 else
9286 args = [ 'ruby' ]
9287 end
9288 args.push $PROGRAM_NAME.slice(/[^\\\/]+$/)
9289 args.concat ARGV
9290 args.push '--reconnected' unless args.include?('--reconnected')
9291 if reconnect_step > 0
9292 args.delete(reconnect_arg)
9293 args.concat ["--reconnect-delay=#{reconnect_delay+reconnect_step}+#{reconnect_step}"]
9294 end
9295 Lich.log "exec args.join(' '): exec #{args.join(' ')}"
9296 exec args.join(' ')
9297 end
9298}
9299
9300#
9301# Start deprecated stuff
9302#
9303
9304$version = LICH_VERSION
9305$room_count = 0
9306$psinet = false
9307$stormfront = true
9308
9309module Lich
9310 @@last_warn_deprecated = 0
9311 def Lich.method_missing(arg1, arg2='')
9312 if (Time.now.to_i - @@last_warn_deprecated) > 300
9313 respond "--- warning: Lich.* variables will stop working in a future version of Lich. Use Vars.* (offending script: #{Script.current.name || 'unknown'})"
9314 @@last_warn_deprecated = Time.now.to_i
9315 end
9316 Vars.method_missing(arg1, arg2)
9317 end
9318end
9319
9320class Script
9321 def Script.self
9322 Script.current
9323 end
9324 def Script.running
9325 list = Array.new
9326 for script in @@running
9327 list.push(script) unless script.hidden
9328 end
9329 return list
9330 end
9331 def Script.index
9332 Script.running
9333 end
9334 def Script.hidden
9335 list = Array.new
9336 for script in @@running
9337 list.push(script) if script.hidden
9338 end
9339 return list
9340 end
9341 def Script.namescript_incoming(line)
9342 Script.new_downstream(line)
9343 end
9344end
9345
9346class Spellsong
9347 def Spellsong.cost
9348 Spellsong.renew_cost
9349 end
9350 def Spellsong.tonisdodgebonus
9351 thresholds = [1,2,3,5,8,10,14,17,21,26,31,36,42,49,55,63,70,78,87,96]
9352 bonus = 20
9353 thresholds.each { |val| if Skills.elair >= val then bonus += 1 end }
9354 bonus
9355 end
9356 def Spellsong.mirrorsdodgebonus
9357 20 + ((Spells.bard - 19) / 2).round
9358 end
9359 def Spellsong.mirrorscost
9360 [19 + ((Spells.bard - 19) / 5).truncate, 8 + ((Spells.bard - 19) / 10).truncate]
9361 end
9362 def Spellsong.sonicbonus
9363 (Spells.bard / 2).round
9364 end
9365 def Spellsong.sonicarmorbonus
9366 Spellsong.sonicbonus + 15
9367 end
9368 def Spellsong.sonicbladebonus
9369 Spellsong.sonicbonus + 10
9370 end
9371 def Spellsong.sonicweaponbonus
9372 Spellsong.sonicbladebonus
9373 end
9374 def Spellsong.sonicshieldbonus
9375 Spellsong.sonicbonus + 10
9376 end
9377 def Spellsong.valorbonus
9378 10 + (([Spells.bard, Stats.level].min - 10) / 2).round
9379 end
9380 def Spellsong.valorcost
9381 [10 + (Spellsong.valorbonus / 2), 3 + (Spellsong.valorbonus / 5)]
9382 end
9383 def Spellsong.luckcost
9384 [6 + ((Spells.bard - 6) / 4),(6 + ((Spells.bard - 6) / 4) / 2).round]
9385 end
9386 def Spellsong.manacost
9387 [18,15]
9388 end
9389 def Spellsong.fortcost
9390 [3,1]
9391 end
9392 def Spellsong.shieldcost
9393 [9,4]
9394 end
9395 def Spellsong.weaponcost
9396 [12,4]
9397 end
9398 def Spellsong.armorcost
9399 [14,5]
9400 end
9401 def Spellsong.swordcost
9402 [25,15]
9403 end
9404end
9405
9406class Map
9407 def desc
9408 @description
9409 end
9410 def map_name
9411 @image
9412 end
9413 def map_x
9414 if @image_coords.nil?
9415 nil
9416 else
9417 ((image_coords[0] + image_coords[2])/2.0).round
9418 end
9419 end
9420 def map_y
9421 if @image_coords.nil?
9422 nil
9423 else
9424 ((image_coords[1] + image_coords[3])/2.0).round
9425 end
9426 end
9427 def map_roomsize
9428 if @image_coords.nil?
9429 nil
9430 else
9431 image_coords[2] - image_coords[0]
9432 end
9433 end
9434 def geo
9435 nil
9436 end
9437end
9438
9439def start_script(script_name, cli_vars=[], flags=Hash.new)
9440 if flags == true
9441 flags = { :quiet => true }
9442 end
9443 Script.start(script_name, cli_vars.join(' '), flags)
9444end
9445
9446def start_scripts(*script_names)
9447 script_names.flatten.each { |script_name|
9448 start_script(script_name)
9449 sleep 0.02
9450 }
9451end
9452
9453def force_start_script(script_name,cli_vars=[], flags={})
9454 flags = Hash.new unless flags.class == Hash
9455 flags[:force] = true
9456 start_script(script_name,cli_vars,flags)
9457end
9458
9459def survivepoison?
9460 echo 'survivepoison? called, but there is no XML for poison rate'
9461 return true
9462end
9463
9464def survivedisease?
9465 echo 'survivepoison? called, but there is no XML for disease rate'
9466 return true
9467end
9468
9469def before_dying(&code)
9470 Script.at_exit(&code)
9471end
9472
9473def undo_before_dying
9474 Script.clear_exit_procs
9475end
9476
9477def abort!
9478 Script.exit!
9479end
9480
9481def fetchloot(userbagchoice=UserVars.lootsack)
9482 if GameObj.loot.empty?
9483 return false
9484 end
9485 if UserVars.excludeloot.empty?
9486 regexpstr = nil
9487 else
9488 regexpstr = UserVars.excludeloot.split(', ').join('|')
9489 end
9490 if checkright and checkleft
9491 stowed = GameObj.right_hand.noun
9492 fput "put my #{stowed} in my #{UserVars.lootsack}"
9493 else
9494 stowed = nil
9495 end
9496 GameObj.loot.each { |loot|
9497 unless not regexpstr.nil? and loot.name =~ /#{regexpstr}/
9498 fput "get #{loot.noun}"
9499 fput("put my #{loot.noun} in my #{userbagchoice}") if (checkright || checkleft)
9500 end
9501 }
9502 if stowed
9503 fput "take my #{stowed} from my #{UserVars.lootsack}"
9504 end
9505end
9506
9507def take(*items)
9508 items.flatten!
9509 if (righthand? && lefthand?)
9510 weap = checkright
9511 fput "put my #{checkright} in my #{UserVars.lootsack}"
9512 unsh = true
9513 else
9514 unsh = false
9515 end
9516 items.each { |trinket|
9517 fput "take #{trinket}"
9518 fput("put my #{trinket} in my #{UserVars.lootsack}") if (righthand? || lefthand?)
9519 }
9520 if unsh then fput("take my #{weap} from my #{UserVars.lootsack}") end
9521end
9522
9523def stop_script(*target_names)
9524 numkilled = 0
9525 target_names.each { |target_name|
9526 condemned = Script.list.find { |s_sock| s_sock.name =~ /^#{target_name}/i }
9527 if condemned.nil?
9528 respond("--- Lich: '#{Script.current}' tried to stop '#{target_name}', but it isn't running!")
9529 else
9530 if condemned.name =~ /^#{Script.current.name}$/i
9531 exit
9532 end
9533 condemned.kill
9534 respond("--- Lich: '#{condemned}' has been stopped by #{Script.current}.")
9535 numkilled += 1
9536 end
9537 }
9538 if numkilled == 0
9539 return false
9540 else
9541 return numkilled
9542 end
9543end
9544
9545def running?(*snames)
9546 snames.each { |checking| (return false) unless (Script.running.find { |lscr| lscr.name =~ /^#{checking}$/i } || Script.running.find { |lscr| lscr.name =~ /^#{checking}/i } || Script.hidden.find { |lscr| lscr.name =~ /^#{checking}$/i } || Script.hidden.find { |lscr| lscr.name =~ /^#{checking}/i }) }
9547 true
9548end
9549
9550module Settings
9551 def Settings.load; end
9552 def Settings.save_all; end
9553 def Settings.clear; end
9554 def Settings.auto=(val); end
9555 def Settings.auto; end
9556 def Settings.autoload; end
9557end
9558
9559module GameSettings
9560 def GameSettings.load; end
9561 def GameSettings.save; end
9562 def GameSettings.save_all; end
9563 def GameSettings.clear; end
9564 def GameSettings.auto=(val); end
9565 def GameSettings.auto; end
9566 def GameSettings.autoload; end
9567end
9568
9569module CharSettings
9570 def CharSettings.load; end
9571 def CharSettings.save; end
9572 def CharSettings.save_all; end
9573 def CharSettings.clear; end
9574 def CharSettings.auto=(val); end
9575 def CharSettings.auto; end
9576 def CharSettings.autoload; end
9577end
9578
9579module UserVars
9580 def UserVars.list
9581 Vars.list
9582 end
9583 def UserVars.method_missing(arg1, arg2='')
9584 Vars.method_missing(arg1, arg2)
9585 end
9586 def UserVars.change(var_name, value, t=nil)
9587 Vars[var_name] = value
9588 end
9589 def UserVars.add(var_name, value, t=nil)
9590 Vars[var_name] = Vars[var_name].split(', ').push(value).join(', ')
9591 end
9592 def UserVars.delete(var_name, t=nil)
9593 Vars[var_name] = nil
9594 end
9595 def UserVars.list_global
9596 Array.new
9597 end
9598 def UserVars.list_char
9599 Vars.list
9600 end
9601end
9602
9603def start_exec_script(cmd_data, options=Hash.new)
9604 ExecScript.start(cmd_data, options)
9605end
9606
9607module Setting
9608 def Setting.[](name)
9609 Settings[name]
9610 end
9611 def Setting.[]=(name, value)
9612 Settings[name] = value
9613 end
9614 def Setting.to_hash(scope=':')
9615 Settings.to_hash
9616 end
9617end
9618module GameSetting
9619 def GameSetting.[](name)
9620 GameSettings[name]
9621 end
9622 def GameSetting.[]=(name, value)
9623 GameSettings[name] = value
9624 end
9625 def GameSetting.to_hash(scope=':')
9626 GameSettings.to_hash
9627 end
9628end
9629module CharSetting
9630 def CharSetting.[](name)
9631 CharSettings[name]
9632 end
9633 def CharSetting.[]=(name, value)
9634 CharSettings[name] = value
9635 end
9636 def CharSetting.to_hash(scope=':')
9637 CharSettings.to_hash
9638 end
9639end
9640module Vars
9641 def Vars.save
9642 end
9643end
9644class StringProc
9645 def StringProc._load(string)
9646 StringProc.new(string)
9647 end
9648end
9649class String
9650 def to_a # for compatibility with Ruby 1.8
9651 [self]
9652 end
9653 def silent
9654 false
9655 end
9656 def split_as_list
9657 string = self
9658 string.sub!(/^You (?:also see|notice) |^In the .+ you see /, ',')
9659 string.sub('.','').sub(/ and (an?|some|the)/, ', \1').split(',').reject { |str| str.strip.empty? }.collect { |str| str.lstrip }
9660 end
9661end
9662#
9663# End deprecated stuff
9664#
9665
9666undef :abort
9667alias :mana :checkmana
9668alias :mana? :checkmana
9669alias :max_mana :maxmana
9670alias :health :checkhealth
9671alias :health? :checkhealth
9672alias :spirit :checkspirit
9673alias :spirit? :checkspirit
9674alias :stamina :checkstamina
9675alias :stamina? :checkstamina
9676alias :stunned? :checkstunned
9677alias :bleeding? :checkbleeding
9678alias :reallybleeding? :checkreallybleeding
9679alias :dead? :checkdead
9680alias :hiding? :checkhidden
9681alias :hidden? :checkhidden
9682alias :hidden :checkhidden
9683alias :checkhiding :checkhidden
9684alias :invisible? :checkinvisible
9685alias :standing? :checkstanding
9686alias :kneeling? :checkkneeling
9687alias :sitting? :checksitting
9688alias :stance? :checkstance
9689alias :stance :checkstance
9690alias :joined? :checkgrouped
9691alias :checkjoined :checkgrouped
9692alias :group? :checkgrouped
9693alias :myname? :checkname
9694alias :active? :checkspell
9695alias :righthand? :checkright
9696alias :lefthand? :checkleft
9697alias :righthand :checkright
9698alias :lefthand :checkleft
9699alias :mind? :checkmind
9700alias :checkactive :checkspell
9701alias :forceput :fput
9702alias :send_script :send_scripts
9703alias :stop_scripts :stop_script
9704alias :kill_scripts :stop_script
9705alias :kill_script :stop_script
9706alias :fried? :checkfried
9707alias :saturated? :checksaturated
9708alias :webbed? :checkwebbed
9709alias :pause_scripts :pause_script
9710alias :roomdescription? :checkroomdescrip
9711alias :prepped? :checkprep
9712alias :checkprepared :checkprep
9713alias :unpause_scripts :unpause_script
9714alias :priority? :setpriority
9715alias :checkoutside :outside?
9716alias :toggle_status :status_tags
9717alias :encumbrance? :checkencumbrance
9718alias :bounty? :checkbounty
9719
9720
9721
9722#
9723# Program start
9724#
9725
9726ARGV.delete_if { |arg| arg =~ /launcher\.exe/i } # added by Simutronics Game Entry
9727
9728argv_options = Hash.new
9729bad_args = Array.new
9730
9731for arg in ARGV
9732 if (arg == '-h') or (arg == '--help')
9733 puts "
9734 -h, --help Display this message and exit
9735 -v, --version Display version number and credits and exit
9736
9737 --home=<directory> Set home directory for Lich (default: location of this file)
9738 --scripts=<directory> Set directory for script files (default: home/scripts)
9739 --data=<directory> Set directory for data files (default: home/data)
9740 --temp=<directory> Set directory for temp files (default: home/temp)
9741 --logs=<directory> Set directory for log files (default: home/logs)
9742 --maps=<directory> Set directory for map images (default: home/maps)
9743 --backup=<directory> Set directory for backups (default: home/backup)
9744
9745 --start-scripts=<script1,script2,etc> Start the specified scripts after login
9746
9747"
9748 exit
9749 elsif (arg == '-v') or (arg == '--version')
9750 puts "The Lich, version #{LICH_VERSION}"
9751 puts ' (an implementation of the Ruby interpreter by Yukihiro Matsumoto designed to be a \'script engine\' for text-based MUDs)'
9752 puts ''
9753 puts '- The Lich program and all material collectively referred to as "The Lich project" is copyright (C) 2005-2006 Murray Miron.'
9754 puts '- The Gemstone IV and DragonRealms games are copyright (C) Simutronics Corporation.'
9755 puts '- The Wizard front-end and the StormFront front-end are also copyrighted by the Simutronics Corporation.'
9756 puts '- Ruby is (C) Yukihiro \'Matz\' Matsumoto.'
9757 puts ''
9758 puts 'Thanks to all those who\'ve reported bugs and helped me track down problems on both Windows and Linux.'
9759 exit
9760 elsif arg == '--link-to-sge'
9761 result = Lich.link_to_sge
9762 if $stdout.isatty
9763 if result
9764 $stdout.puts "Successfully linked to SGE."
9765 else
9766 $stdout.puts "Failed to link to SGE."
9767 end
9768 end
9769 exit
9770 elsif arg == '--unlink-from-sge'
9771 result = Lich.unlink_from_sge
9772 if $stdout.isatty
9773 if result
9774 $stdout.puts "Successfully unlinked from SGE."
9775 else
9776 $stdout.puts "Failed to unlink from SGE."
9777 end
9778 end
9779 exit
9780 elsif arg == '--link-to-sal'
9781 result = Lich.link_to_sal
9782 if $stdout.isatty
9783 if result
9784 $stdout.puts "Successfully linked to SAL files."
9785 else
9786 $stdout.puts "Failed to link to SAL files."
9787 end
9788 end
9789 exit
9790 elsif arg == '--unlink-from-sal'
9791 result = Lich.unlink_from_sal
9792 if $stdout.isatty
9793 if result
9794 $stdout.puts "Successfully unlinked from SAL files."
9795 else
9796 $stdout.puts "Failed to unlink from SAL files."
9797 end
9798 end
9799 exit
9800 elsif arg == '--install' # deprecated
9801 if Lich.link_to_sge and Lich.link_to_sal
9802 $stdout.puts 'Install was successful.'
9803 Lich.log 'Install was successful.'
9804 else
9805 $stdout.puts 'Install failed.'
9806 Lich.log 'Install failed.'
9807 end
9808 exit
9809 elsif arg == '--uninstall' # deprecated
9810 if Lich.unlink_from_sge and Lich.unlink_from_sal
9811 $stdout.puts 'Uninstall was successful.'
9812 Lich.log 'Uninstall was successful.'
9813 else
9814 $stdout.puts 'Uninstall failed.'
9815 Lich.log 'Uninstall failed.'
9816 end
9817 exit
9818 elsif arg =~ /^--(?:home)=(.+)$/i
9819 LICH_DIR = $1.sub(/[\\\/]$/, '')
9820 elsif arg =~ /^--temp=(.+)$/i
9821 TEMP_DIR = $1.sub(/[\\\/]$/, '')
9822 elsif arg =~ /^--scripts=(.+)$/i
9823 SCRIPT_DIR = $1.sub(/[\\\/]$/, '')
9824 elsif arg =~ /^--maps=(.+)$/i
9825 MAP_DIR = $1.sub(/[\\\/]$/, '')
9826 elsif arg =~ /^--logs=(.+)$/i
9827 LOG_DIR = $1.sub(/[\\\/]$/, '')
9828 elsif arg =~ /^--backup=(.+)$/i
9829 BACKUP_DIR = $1.sub(/[\\\/]$/, '')
9830 elsif arg =~ /^--data=(.+)$/i
9831 DATA_DIR = $1.sub(/[\\\/]$/, '')
9832 elsif arg =~ /^--start-scripts=(.+)$/i
9833 argv_options[:start_scripts] = $1
9834 elsif arg =~ /^--reconnect$/i
9835 argv_options[:reconnect] = true
9836 elsif arg =~ /^--reconnect-delay=(.+)$/i
9837 argv_options[:reconnect_delay] = $1
9838 elsif arg =~ /^--host=(.+):(.+)$/
9839 argv_options[:host] = { :domain => $1, :port => $2.to_i }
9840 elsif arg =~ /^--hosts-file=(.+)$/i
9841 argv_options[:hosts_file] = $1
9842 elsif arg =~ /^--gui$/i
9843 argv_options[:gui] = true
9844 elsif arg =~ /^--game=(.+)$/i
9845 argv_options[:game] = $1
9846 elsif arg =~ /^--account=(.+)$/i
9847 argv_options[:account] = $1
9848 elsif arg =~ /^--password=(.+)$/i
9849 argv_options[:password] = $1
9850 elsif arg =~ /^--character=(.+)$/i
9851 argv_options[:character] = $1
9852 elsif arg =~ /^--frontend=(.+)$/i
9853 argv_options[:frontend] = $1
9854 elsif arg =~ /^--frontend-command=(.+)$/i
9855 argv_options[:frontend_command] = $1
9856 elsif arg =~ /^--save$/i
9857 argv_options[:save] = true
9858 elsif arg =~ /^--wine(?:\-prefix)?=.+$/i
9859 nil # already used when defining the Wine module
9860 elsif arg =~ /\.sal$|Gse\.~xt$/i
9861 argv_options[:sal] = arg
9862 unless File.exists?(argv_options[:sal])
9863 if ARGV.join(' ') =~ /([A-Z]:\\.+?\.(?:sal|~xt))/i
9864 argv_options[:sal] = $1
9865 end
9866 end
9867 unless File.exists?(argv_options[:sal])
9868 if defined?(Wine)
9869 argv_options[:sal] = "#{Wine::PREFIX}/drive_c/#{argv_options[:sal][3..-1].split('\\').join('/')}"
9870 end
9871 end
9872 bad_args.clear
9873 else
9874 bad_args.push(arg)
9875 end
9876end
9877
9878LICH_DIR ||= File.dirname(File.expand_path($PROGRAM_NAME))
9879TEMP_DIR ||= "#{LICH_DIR}/temp"
9880DATA_DIR ||= "#{LICH_DIR}/data"
9881SCRIPT_DIR ||= "#{LICH_DIR}/scripts"
9882MAP_DIR ||= "#{LICH_DIR}/maps"
9883LOG_DIR ||= "#{LICH_DIR}/logs"
9884BACKUP_DIR ||= "#{LICH_DIR}/backup"
9885
9886unless File.exists?(LICH_DIR)
9887 begin
9888 Dir.mkdir(LICH_DIR)
9889 rescue
9890 message = "An error occured while attempting to create directory #{LICH_DIR}\n\n"
9891 if not File.exists?(LICH_DIR.sub(/[\\\/]$/, '').slice(/^.+[\\\/]/).chop)
9892 message.concat "This was likely because the parent directory (#{LICH_DIR.sub(/[\\\/]$/, '').slice(/^.+[\\\/]/).chop}) doesn't exist."
9893 elsif defined?(Win32) and (Win32.GetVersionEx[:dwMajorVersion] >= 6) and (dir !~ /^[A-z]\:\\(Users|Documents and Settings)/)
9894 message.concat "This was likely because Lich doesn't have permission to create files and folders here. It is recommended to put Lich in your Documents folder."
9895 else
9896 message.concat $!
9897 end
9898 Lich.msgbox(:message => message, :icon => :error)
9899 exit
9900 end
9901end
9902
9903Dir.chdir(LICH_DIR)
9904
9905unless File.exists?(TEMP_DIR)
9906 begin
9907 Dir.mkdir(TEMP_DIR)
9908 rescue
9909 message = "An error occured while attempting to create directory #{TEMP_DIR}\n\n"
9910 if not File.exists?(TEMP_DIR.sub(/[\\\/]$/, '').slice(/^.+[\\\/]/).chop)
9911 message.concat "This was likely because the parent directory (#{TEMP_DIR.sub(/[\\\/]$/, '').slice(/^.+[\\\/]/).chop}) doesn't exist."
9912 elsif defined?(Win32) and (Win32.GetVersionEx[:dwMajorVersion] >= 6) and (dir !~ /^[A-z]\:\\(Users|Documents and Settings)/)
9913 message.concat "This was likely because Lich doesn't have permission to create files and folders here. It is recommended to put Lich in your Documents folder."
9914 else
9915 message.concat $!
9916 end
9917 Lich.msgbox(:message => message, :icon => :error)
9918 exit
9919 end
9920end
9921
9922begin
9923 debug_filename = "#{TEMP_DIR}/debug-#{Time.now.strftime("%Y-%m-%d-%H-%M-%S")}.log"
9924 $stderr = File.open(debug_filename, 'w')
9925rescue
9926 message = "An error occured while attempting to create file #{debug_filename}\n\n"
9927 if defined?(Win32) and (TEMP_DIR !~ /^[A-z]\:\\(Users|Documents and Settings)/) and not Win32.isXP?
9928 message.concat "This was likely because Lich doesn't have permission to create files and folders here. It is recommended to put Lich in your Documents folder."
9929 else
9930 message.concat $!
9931 end
9932 Lich.msgbox(:message => message, :icon => :error)
9933 exit
9934end
9935
9936$stderr.sync = true
9937Lich.log "info: Lich #{LICH_VERSION}"
9938Lich.log "info: Ruby #{RUBY_VERSION}"
9939Lich.log "info: #{RUBY_PLATFORM}"
9940Lich.log early_gtk_error if early_gtk_error
9941early_gtk_error = nil
9942
9943unless File.exists?(DATA_DIR)
9944 begin
9945 Dir.mkdir(DATA_DIR)
9946 rescue
9947 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
9948 Lich.msgbox(:message => "An error occured while attempting to create directory #{DATA_DIR}\n\n#{$!}", :icon => :error)
9949 exit
9950 end
9951end
9952unless File.exists?(SCRIPT_DIR)
9953 begin
9954 Dir.mkdir(SCRIPT_DIR)
9955 rescue
9956 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
9957 Lich.msgbox(:message => "An error occured while attempting to create directory #{SCRIPT_DIR}\n\n#{$!}", :icon => :error)
9958 exit
9959 end
9960end
9961unless File.exists?(MAP_DIR)
9962 begin
9963 Dir.mkdir(MAP_DIR)
9964 rescue
9965 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
9966 Lich.msgbox(:message => "An error occured while attempting to create directory #{MAP_DIR}\n\n#{$!}", :icon => :error)
9967 exit
9968 end
9969end
9970unless File.exists?(LOG_DIR)
9971 begin
9972 Dir.mkdir(LOG_DIR)
9973 rescue
9974 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
9975 Lich.msgbox(:message => "An error occured while attempting to create directory #{LOG_DIR}\n\n#{$!}", :icon => :error)
9976 exit
9977 end
9978end
9979unless File.exists?(BACKUP_DIR)
9980 begin
9981 Dir.mkdir(BACKUP_DIR)
9982 rescue
9983 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
9984 Lich.msgbox(:message => "An error occured while attempting to create directory #{BACKUP_DIR}\n\n#{$!}", :icon => :error)
9985 exit
9986 end
9987end
9988
9989Lich.init_db
9990
9991# deprecated
9992$lich_dir = "#{LICH_DIR}/"
9993$temp_dir = "#{TEMP_DIR}/"
9994$script_dir = "#{SCRIPT_DIR}/"
9995$data_dir = "#{DATA_DIR}/"
9996
9997#
9998# only keep the last 20 debug files
9999#
10000Dir.entries(TEMP_DIR).find_all { |fn| fn =~ /^debug-\d+-\d+-\d+-\d+-\d+-\d+\.log$/ }.sort.reverse[20..-1].each { |oldfile|
10001 begin
10002 File.delete("#{TEMP_DIR}/#{oldfile}")
10003 rescue
10004 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
10005 end
10006}
10007
10008
10009
10010
10011
10012
10013
10014
10015begin
10016 did_trusted_defaults = Lich.db.get_first_value("SELECT value FROM lich_settings WHERE name='did_trusted_defaults';")
10017rescue SQLite3::BusyException
10018 sleep 0.1
10019 retry
10020end
10021if did_trusted_defaults.nil?
10022 Script.trust('repository')
10023 Script.trust('lnet')
10024 Script.trust('narost')
10025 begin
10026 Lich.db.execute("INSERT INTO lich_settings(name,value) VALUES('did_trusted_defaults', 'yes');")
10027 rescue SQLite3::BusyException
10028 sleep 0.1
10029 retry
10030 end
10031end
10032
10033if ARGV.any? { |arg| (arg == '-h') or (arg == '--help') }
10034 puts 'Usage: lich [OPTION]'
10035 puts ''
10036 puts 'Options are:'
10037 puts ' -h, --help Display this list.'
10038 puts ' -V, --version Display the program version number and credits.'
10039 puts ''
10040 puts ' -d, --directory Set the main Lich program directory.'
10041 puts ' --script-dir Set the directoy where Lich looks for scripts.'
10042 puts ' --data-dir Set the directory where Lich will store script data.'
10043 puts ' --temp-dir Set the directory where Lich will store temporary files.'
10044 puts ''
10045 puts ' -w, --wizard Run in Wizard mode (default)'
10046 puts ' -s, --stormfront Run in StormFront mode.'
10047 puts ' --avalon Run in Avalon mode.'
10048 puts ''
10049 puts ' --gemstone Connect to the Gemstone IV Prime server (default).'
10050 puts ' --dragonrealms Connect to the DragonRealms server.'
10051 puts ' --platinum Connect to the Gemstone IV/DragonRealms Platinum server.'
10052 puts ' -g, --game Set the IP address and port of the game. See example below.'
10053 puts ''
10054 puts ' --install Edits the Windows/WINE registry so that Lich is started when logging in using the website or SGE.'
10055 puts ' --uninstall Removes Lich from the registry.'
10056 puts ''
10057 puts 'The majority of Lich\'s built-in functionality was designed and implemented with Simutronics MUDs in mind (primarily Gemstone IV): as such, many options/features provided by Lich may not be applicable when it is used with a non-Simutronics MUD. In nearly every aspect of the program, users who are not playing a Simutronics game should be aware that if the description of a feature/option does not sound applicable and/or compatible with the current game, it should be assumed that the feature/option is not. This particularly applies to in-script methods (commands) that depend heavily on the data received from the game conforming to specific patterns (for instance, it\'s extremely unlikely Lich will know how much "health" your character has left in a non-Simutronics game, and so the "health" script command will most likely return a value of 0).'
10058 puts ''
10059 puts 'The level of increase in efficiency when Lich is run in "bare-bones mode" (i.e. started with the --bare argument) depends on the data stream received from a given game, but on average results in a moderate improvement and it\'s recommended that Lich be run this way for any game that does not send "status information" in a format consistent with Simutronics\' GSL or XML encoding schemas.'
10060 puts ''
10061 puts ''
10062 puts 'Examples:'
10063 puts ' lich -w -d /usr/bin/lich/ (run Lich in Wizard mode using the dir \'/usr/bin/lich/\' as the program\'s home)'
10064 puts ' lich -g gs3.simutronics.net:4000 (run Lich using the IP address \'gs3.simutronics.net\' and the port number \'4000\')'
10065 puts ' lich --script-dir /mydir/scripts (run Lich with its script directory set to \'/mydir/scripts\')'
10066 puts ' lich --bare -g skotos.net:5555 (run in bare-bones mode with the IP address and port of the game set to \'skotos.net:5555\')'
10067 puts ''
10068 exit
10069end
10070
10071
10072
10073if arg = ARGV.find { |a| a == '--hosts-dir' }
10074 i = ARGV.index(arg)
10075 ARGV.delete_at(i)
10076 hosts_dir = ARGV[i]
10077 ARGV.delete_at(i)
10078 if hosts_dir and File.exists?(hosts_dir)
10079 hosts_dir = hosts_dir.tr('\\', '/')
10080 hosts_dir += '/' unless hosts_dir[-1..-1] == '/'
10081 else
10082 $stdout.puts "warning: given hosts directory does not exist: #{hosts_dir}"
10083 hosts_dir = nil
10084 end
10085else
10086 hosts_dir = nil
10087end
10088
10089detachable_client_port = nil
10090if arg = ARGV.find { |a| a =~ /^\-\-detachable\-client=[0-9]+$/ }
10091 detachable_client_port = /^\-\-detachable\-client=([0-9]+)$/.match(arg).captures.first
10092end
10093
10094
10095
10096#
10097# import Lich 4.4 settings to Lich 4.6
10098#
10099begin
10100 did_import = Lich.db.get_first_value("SELECT value FROM lich_settings WHERE name='imported_44_data';")
10101rescue SQLite3::BusyException
10102 sleep 0.1
10103 retry
10104end
10105if did_import.nil?
10106 begin
10107 Lich.db.execute('BEGIN')
10108 rescue SQLite3::BusyException
10109 sleep 0.1
10110 retry
10111 end
10112 begin
10113 Lich.db.execute("INSERT INTO lich_settings(name,value) VALUES('imported_44_data', 'yes');")
10114 rescue SQLite3::BusyException
10115 sleep 0.1
10116 retry
10117 end
10118 backup_dir = 'data44/'
10119 Dir.mkdir(backup_dir) unless File.exists?(backup_dir)
10120 Dir.entries(DATA_DIR).find_all { |fn| fn =~ /\.sav$/i }.each { |fn|
10121 next if fn == 'lich.sav'
10122 s = fn.match(/^(.+)\.sav$/i).captures.first
10123 data = File.open("#{DATA_DIR}/#{fn}", 'rb') { |f| f.read }
10124 blob = SQLite3::Blob.new(data)
10125 begin
10126 Lich.db.execute("INSERT OR REPLACE INTO script_auto_settings(script,scope,hash) VALUES(?,':',?);", s.encode('UTF-8'), blob)
10127 rescue SQLite3::BusyException
10128 sleep 0.1
10129 retry
10130 end
10131 File.rename("#{DATA_DIR}/#{fn}", "#{backup_dir}#{fn}")
10132 File.rename("#{DATA_DIR}/#{fn}~", "#{backup_dir}#{fn}~") if File.exists?("#{DATA_DIR}/#{fn}~")
10133 }
10134 Dir.entries(DATA_DIR).find_all { |fn| File.directory?("#{DATA_DIR}/#{fn}") and fn !~ /^\.\.?$/}.each { |game|
10135 Dir.mkdir("#{backup_dir}#{game}") unless File.exists?("#{backup_dir}#{game}")
10136 Dir.entries("#{DATA_DIR}/#{game}").find_all { |fn| fn =~ /\.sav$/i }.each { |fn|
10137 s = fn.match(/^(.+)\.sav$/i).captures.first
10138 data = File.open("#{DATA_DIR}/#{game}/#{fn}", 'rb') { |f| f.read }
10139 blob = SQLite3::Blob.new(data)
10140 begin
10141 Lich.db.execute('INSERT OR REPLACE INTO script_auto_settings(script,scope,hash) VALUES(?,?,?);', s.encode('UTF-8'), game.encode('UTF-8'), blob)
10142 rescue SQLite3::BusyException
10143 sleep 0.1
10144 retry
10145 end
10146 File.rename("#{DATA_DIR}/#{game}/#{fn}", "#{backup_dir}#{game}/#{fn}")
10147 File.rename("#{DATA_DIR}/#{game}/#{fn}~", "#{backup_dir}#{game}/#{fn}~") if File.exists?("#{DATA_DIR}/#{game}/#{fn}~")
10148 }
10149 Dir.entries("#{DATA_DIR}/#{game}").find_all { |fn| File.directory?("#{DATA_DIR}/#{game}/#{fn}") and fn !~ /^\.\.?$/ }.each { |char|
10150 Dir.mkdir("#{backup_dir}#{game}/#{char}") unless File.exists?("#{backup_dir}#{game}/#{char}")
10151 Dir.entries("#{DATA_DIR}/#{game}/#{char}").find_all { |fn| fn =~ /\.sav$/i }.each { |fn|
10152 s = fn.match(/^(.+)\.sav$/i).captures.first
10153 data = File.open("#{DATA_DIR}/#{game}/#{char}/#{fn}", 'rb') { |f| f.read }
10154 blob = SQLite3::Blob.new(data)
10155 begin
10156 Lich.db.execute('INSERT OR REPLACE INTO script_auto_settings(script,scope,hash) VALUES(?,?,?);', s.encode('UTF-8'), "#{game}:#{char}".encode('UTF-8'), blob)
10157 rescue SQLite3::BusyException
10158 sleep 0.1
10159 retry
10160 end
10161 File.rename("#{DATA_DIR}/#{game}/#{char}/#{fn}", "#{backup_dir}#{game}/#{char}/#{fn}")
10162 File.rename("#{DATA_DIR}/#{game}/#{char}/#{fn}~", "#{backup_dir}#{game}/#{char}/#{fn}~") if File.exists?("#{DATA_DIR}/#{game}/#{char}/#{fn}~")
10163 }
10164 if File.exists?("#{DATA_DIR}/#{game}/#{char}/uservars.dat")
10165 blob = SQLite3::Blob.new(File.open("#{DATA_DIR}/#{game}/#{char}/uservars.dat", 'rb') { |f| f.read })
10166 begin
10167 Lich.db.execute('INSERT OR REPLACE INTO uservars(scope,hash) VALUES(?,?);', "#{game}:#{char}".encode('UTF-8'), blob)
10168 rescue SQLite3::BusyException
10169 sleep 0.1
10170 retry
10171 end
10172 blob = nil
10173 File.rename("#{DATA_DIR}/#{game}/#{char}/uservars.dat", "#{backup_dir}#{game}/#{char}/uservars.dat")
10174 end
10175 }
10176 }
10177 begin
10178 Lich.db.execute('END')
10179 rescue SQLite3::BusyException
10180 sleep 0.1
10181 retry
10182 end
10183 backup_dir = nil
10184 characters = Array.new
10185 begin
10186 Lich.db.execute("SELECT DISTINCT(scope) FROM script_auto_settings;").each { |row| characters.push(row[0]) if row[0] =~ /^.+:.+$/ }
10187 rescue SQLite3::BusyException
10188 sleep 0.1
10189 retry
10190 end
10191 if File.exists?("#{DATA_DIR}/lich.sav")
10192 data = File.open("#{DATA_DIR}/lich.sav", 'rb') { |f| Marshal.load(f.read) }
10193 favs = data['favorites']
10194 aliases = data['alias']
10195 trusted = data['lichsettings']['trusted_scripts']
10196 if favs.class == Hash
10197 begin
10198 Lich.db.execute('BEGIN')
10199 rescue SQLite3::BusyException
10200 sleep 0.1
10201 retry
10202 end
10203 favs.each { |scope,script_list|
10204 hash = { 'scripts' => Array.new }
10205 script_list.each { |name,args| hash['scripts'].push(:name => name, :args => args) }
10206 blob = SQLite3::Blob.new(Marshal.dump(hash))
10207 if scope == 'global'
10208 begin
10209 Lich.db.execute("INSERT OR REPLACE INTO script_auto_settings(script,scope,hash) VALUES('autostart',':',?);", blob)
10210 rescue SQLite3::BusyException
10211 sleep 0.1
10212 retry
10213 end
10214 else
10215 characters.find_all { |c| c =~ /^.+:#{scope}$/ }.each { |c|
10216 begin
10217 Lich.db.execute("INSERT OR REPLACE INTO script_auto_settings(script,scope,hash) VALUES('autostart',?,?);", c.encode('UTF-8'), blob)
10218 rescue SQLite3::BusyException
10219 sleep 0.1
10220 retry
10221 end
10222 }
10223 end
10224 }
10225 begin
10226 Lich.db.execute('END')
10227 rescue SQLite3::BusyException
10228 sleep 0.1
10229 retry
10230 end
10231 end
10232 favs = nil
10233
10234 db = SQLite3::Database.new("#{DATA_DIR}/alias.db3")
10235 begin
10236 db.execute("CREATE TABLE IF NOT EXISTS global (trigger TEXT NOT NULL, target TEXT NOT NULL, UNIQUE(trigger));")
10237 rescue SQLite3::BusyException
10238 sleep 0.1
10239 retry
10240 end
10241 begin
10242 db.execute('BEGIN')
10243 rescue SQLite3::BusyException
10244 sleep 0.1
10245 retry
10246 end
10247 if aliases.class == Hash
10248 aliases.each { |scope,alias_hash|
10249 if scope == 'global'
10250 tables = ['global']
10251 else
10252 tables = characters.find_all { |c| c =~ /^.+:#{scope}$/ }.collect { |t| t.downcase.sub(':', '_').gsub(/[^a-z_]/, '').encode('UTF-8') }
10253 end
10254 tables.each { |t|
10255 begin
10256 db.execute("CREATE TABLE IF NOT EXISTS #{t} (trigger TEXT NOT NULL, target TEXT NOT NULL, UNIQUE(trigger));")
10257 rescue SQLite3::BusyException
10258 sleep 0.1
10259 retry
10260 end
10261 }
10262 alias_hash.each { |trigger,target|
10263 tables.each { |t|
10264 begin
10265 db.execute("INSERT OR REPLACE INTO #{t} (trigger,target) VALUES(?,?);", trigger.gsub(/\\(.)/) { $1 }.encode('UTF-8'), target.encode('UTF-8'))
10266 rescue SQLite3::BusyException
10267 sleep 0.1
10268 retry
10269 end
10270 }
10271 }
10272 }
10273 end
10274 begin
10275 db.execute('END')
10276 rescue SQLite3::BusyException
10277 sleep 0.1
10278 retry
10279 end
10280
10281 begin
10282 Lich.db.execute('BEGIN')
10283 rescue SQLite3::BusyException
10284 sleep 0.1
10285 retry
10286 end
10287 trusted.each { |script_name|
10288 begin
10289 Lich.db.execute('INSERT OR REPLACE INTO trusted_scripts(name) values(?);', script_name.encode('UTF-8'))
10290 rescue SQLite3::BusyException
10291 sleep 0.1
10292 retry
10293 end
10294 }
10295 begin
10296 Lich.db.execute('END')
10297 rescue SQLite3::BusyException
10298 sleep 0.1
10299 retry
10300 end
10301 db.close rescue nil
10302 db = nil
10303 data = nil
10304 aliases = nil
10305 characters = nil
10306 trusted = nil
10307 File.rename("#{DATA_DIR}/lich.sav", "#{backup_dir}lich.sav")
10308 end
10309end
10310
10311if argv_options[:sal]
10312 unless File.exists?(argv_options[:sal])
10313 Lich.log "error: launch file does not exist: #{argv_options[:sal]}"
10314 Lich.msgbox "error: launch file does not exist: #{argv_options[:sal]}"
10315 exit
10316 end
10317 Lich.log "info: launch file: #{argv_options[:sal]}"
10318 if argv_options[:sal] =~ /SGE\.sal/i
10319 unless launcher_cmd = Lich.get_simu_launcher
10320 $stdout.puts 'error: failed to find the Simutronics launcher'
10321 Lich.log 'error: failed to find the Simutronics launcher'
10322 exit
10323 end
10324 launcher_cmd.sub!('%1', argv_options[:sal])
10325 Lich.log "info: launcher_cmd: #{launcher_cmd}"
10326 if defined?(Win32) and launcher_cmd =~ /^"(.*?)"\s*(.*)$/
10327 dir_file = $1
10328 param = $2
10329 dir = dir_file.slice(/^.*[\\\/]/)
10330 file = dir_file.sub(/^.*[\\\/]/, '')
10331 operation = (Win32.isXP? ? 'open' : 'runas')
10332 Win32.ShellExecute(:lpOperation => operation, :lpFile => file, :lpDirectory => dir, :lpParameters => param)
10333 if r < 33
10334 Lich.log "error: Win32.ShellExecute returned #{r}; Win32.GetLastError: #{Win32.GetLastError}"
10335 end
10336 elsif defined?(Wine)
10337 system("#{Wine::BIN} #{launcher_cmd}")
10338 else
10339 system(launcher_cmd)
10340 end
10341 exit
10342 end
10343end
10344
10345if arg = ARGV.find { |a| (a == '-g') or (a == '--game') }
10346 game_host, game_port = ARGV[ARGV.index(arg)+1].split(':')
10347 game_port = game_port.to_i
10348 if ARGV.any? { |arg| (arg == '-s') or (arg == '--stormfront') }
10349 $frontend = 'stormfront'
10350 elsif ARGV.any? { |arg| (arg == '-w') or (arg == '--wizard') }
10351 $frontend = 'wizard'
10352 elsif ARGV.any? { |arg| arg == '--avalon' }
10353 $frontend = 'avalon'
10354 else
10355 $frontend = 'unknown'
10356 end
10357elsif ARGV.include?('--gemstone')
10358 if ARGV.include?('--platinum')
10359 $platinum = true
10360 if ARGV.any? { |arg| (arg == '-s') or (arg == '--stormfront') }
10361 game_host = 'storm.gs4.game.play.net'
10362 game_port = 10124
10363 $frontend = 'stormfront'
10364 else
10365 game_host = 'gs-plat.simutronics.net'
10366 game_port = 10121
10367 if ARGV.any? { |arg| arg == '--avalon' }
10368 $frontend = 'avalon'
10369 else
10370 $frontend = 'wizard'
10371 end
10372 end
10373 else
10374 $platinum = false
10375 if ARGV.any? { |arg| (arg == '-s') or (arg == '--stormfront') }
10376 game_host = 'storm.gs4.game.play.net'
10377 game_port = 10024
10378 $frontend = 'stormfront'
10379 else
10380 game_host = 'gs3.simutronics.net'
10381 game_port = 4900
10382 if ARGV.any? { |arg| arg == '--avalon' }
10383 $frontend = 'avalon'
10384 else
10385 $frontend = 'wizard'
10386 end
10387 end
10388 end
10389elsif ARGV.include?('--shattered')
10390 $platinum = false
10391 if ARGV.any? { |arg| (arg == '-s') or (arg == '--stormfront') }
10392 game_host = 'storm.gs4.game.play.net'
10393 game_port = 10324
10394 $frontend = 'stormfront'
10395 else
10396 game_host = 'gs4.simutronics.net'
10397 game_port = 10321
10398 if ARGV.any? { |arg| arg == '--avalon' }
10399 $frontend = 'avalon'
10400 else
10401 $frontend = 'wizard'
10402 end
10403 end
10404elsif ARGV.include?('--dragonrealms')
10405 if ARGV.include?('--platinum')
10406 $platinum = true
10407 if ARGV.any? { |arg| (arg == '-s') or (arg == '--stormfront') }
10408 $stdout.puts "fixme"
10409 Lich.log "fixme"
10410 exit
10411 $frontend = 'stormfront'
10412 else
10413 $stdout.puts "fixme"
10414 Lich.log "fixme"
10415 exit
10416 $frontend = 'wizard'
10417 end
10418 else
10419 $platinum = false
10420 if ARGV.any? { |arg| (arg == '-s') or (arg == '--stormfront') }
10421 $frontend = 'stormfront'
10422 $stdout.puts "fixme"
10423 Lich.log "fixme"
10424 exit
10425 else
10426 game_host = 'dr.simutronics.net'
10427 game_port = 4901
10428 if ARGV.any? { |arg| arg == '--avalon' }
10429 $frontend = 'avalon'
10430 else
10431 $frontend = 'wizard'
10432 end
10433 end
10434 end
10435else
10436 game_host, game_port = nil, nil
10437 Lich.log "info: no force-mode info given"
10438end
10439
10440if defined?(Gtk)
10441 Gtk.queue { Gtk::Window.default_icon = Gdk::Pixbuf.new(Zlib::Inflate.inflate("eJyVl8tSE1EQhieTggULX8utr2CVOzc+gpXMhUDIBYhIAuQCAQEDKDEiF9eWZVneUDc+gk+gMtKN+doTJ1pSEP45/Z/uPn07k+u3bt/wvGsLfsbzPfn1vLvyl9y843n68Vw+cpdryYWLMoIS0H9JFf0QlAelSQNB3wVFgr6B/r6WJv2K5jnWXrDWFBQLmnWl6kFd0IygYop0SVARaSjoJagqqIxmXXuFlpKgiqAZ1l6juSCoBlLpG6IWC1p0fX7rSqvued9x3ozv+0kkj/P6ePmTBGxT8ntUKa8uKE+YRqQN1YL0gxkSzUrpyON5igdqLSdoWdAzKA3XRoAHyluhuDQ92V/WEvm/io5pdNjOOlZrSNdABfTqjlVBC4LmBFXwpMkOTXubdFbdZLddXpcoLwkK8WUdnu4dIC0SR9XXdXl9kPq3iVQL6pAS1Lw8cKWq9JADasC2OaBSmrihhXIqqEeYqiSsxwF1bSBoD+NqSP3ry6M/zNWugEWc11mjhdGisXSrhuBMHlWwTFiOCb2Ww6ygI9aa9O2AtTYBV80ajEPqc4MdBxjvEPWCoH0yqzW7ImgL6UO3olcI6bSgXfYG9JTa2CExOXnMjSVPTExc2ci5R9jGbgi558TaaCElWMflEHVbaNLeKSGYxSkrxk3IM+jTs2QmJyevJormc4MQzDIuzJd1wldEqm5khw3NAdokqER8uzjRogjKGFRnFzETUhhFynONvRGzYA6plVfEbCGqEVmJ2NChRmM2xAyUkH7rssOSGeNASDJtx4EgP5vNJjRUk361IakddcahTBoiPRHUGSc9cgdN7GrWgDipUU/84crOOLUt2nis9DRFWsPoyThpndYJ4ZnraVpGDnbPtLCmCTkep0qltiNkFAYpmhukP2BEBa7SNN6Aqhvh1SkO5fUpMbO7lMJ7DK+Q4p8Vm824OZenzWwXUhGTEZMkj7Tk+nLVKlK8EXMhT+dP45pdUlXXeEywI0ogYqbY5ZijpPKu8cANZ8ABQ7ohQ37N8dC1toz0qevfSKIaSJ/gi/EKKby+yws5W8AcUfTI7UfVZ++e9iqjd1h2amqKF6MafEusvcjsU9c5V7llsgV5D7QAr8xaB9QDzcOrsKYzWYvC7jy71RRV2FZlm+V5F9fK8Obdc2xC3nHrOsL7HIVnL0F2rcXw7BLNoM/SHhNi5Z2zprU+Q2JV+tFtFr217uOBrn2SR+2xq1fc30fuZzpHryaG7xfUrqHswmGMfBfzxbd/s4Z2U1jI7PteQgZGkVhL/txl3zV/AnftNz0=".unpack('m')[0]).unpack('c*'), false) }
10442end
10443
10444main_thread = Thread.new {
10445 test_mode = false
10446 $SEND_CHARACTER = '>'
10447 $cmd_prefix = '<c>'
10448 $clean_lich_char = ';' # fixme
10449 $lich_char = Regexp.escape($clean_lich_char)
10450
10451 launch_data = nil
10452
10453 if ARGV.include?('--login')
10454 if File.exists?("#{DATA_DIR}/entry.dat")
10455 entry_data = File.open("#{DATA_DIR}/entry.dat", 'r') { |file|
10456 begin
10457 Marshal.load(file.read.unpack('m').first)
10458 rescue
10459 Array.new
10460 end
10461 }
10462 else
10463 entry_data = Array.new
10464 end
10465 char_name = ARGV[ARGV.index('--login')+1].capitalize
10466 if ARGV.include?('--gemstone')
10467 if ARGV.include?('--platinum')
10468 data = entry_data.find { |d| (d[:char_name] == char_name) and (d[:game_code] == 'GSX') }
10469 elsif ARGV.include?('--shattered')
10470 data = entry_data.find { |d| (d[:char_name] == char_name) and (d[:game_code] == 'GSF') }
10471 else
10472 data = entry_data.find { |d| (d[:char_name] == char_name) and (d[:game_code] == 'GS3') }
10473 end
10474 elsif ARGV.include?('--shattered')
10475 data = entry_data.find { |d| (d[:char_name] == char_name) and (d[:game_code] == 'GSF') }
10476 else
10477 data = entry_data.find { |d| (d[:char_name] == char_name) }
10478 end
10479 if data
10480 Lich.log "info: using quick game entry settings for #{char_name}"
10481 msgbox = proc { |msg|
10482 if defined?(Gtk)
10483 done = false
10484 Gtk.queue {
10485 dialog = Gtk::MessageDialog.new(nil, Gtk::Dialog::DESTROY_WITH_PARENT, Gtk::MessageDialog::QUESTION, Gtk::MessageDialog::BUTTONS_CLOSE, msg)
10486 dialog.run
10487 dialog.destroy
10488 done = true
10489 }
10490 sleep 0.1 until done
10491 else
10492 $stdout.puts(msg)
10493 Lich.log(msg)
10494 end
10495 }
10496
10497 login_server = nil
10498 connect_thread = nil
10499 timeout_thread = Thread.new {
10500 sleep 30
10501 $stdout.puts "error: timed out connecting to eaccess.play.net:7900"
10502 Lich.log "error: timed out connecting to eaccess.play.net:7900"
10503 connect_thread.kill rescue nil
10504 login_server = nil
10505 }
10506 connect_thread = Thread.new {
10507 begin
10508 login_server = TCPSocket.new('eaccess.play.net', 7900)
10509 rescue
10510 login_server = nil
10511 $stdout.puts "error connecting to server: #{$!}"
10512 Lich.log "error connecting to server: #{$!}"
10513 end
10514 }
10515 connect_thread.join
10516 timeout_thread.kill rescue nil
10517
10518 if login_server
10519 login_server.puts "K\n"
10520 hashkey = login_server.gets
10521 if 'test'[0].class == String
10522 password = data[:password].split('').collect { |c| c.getbyte(0) }
10523 hashkey = hashkey.split('').collect { |c| c.getbyte(0) }
10524 else
10525 password = data[:password].split('').collect { |c| c[0] }
10526 hashkey = hashkey.split('').collect { |c| c[0] }
10527 end
10528 password.each_index { |i| password[i] = ((password[i]-32)^hashkey[i])+32 }
10529 password = password.collect { |c| c.chr }.join
10530 login_server.puts "A\t#{data[:user_id]}\t#{password}\n"
10531 password = nil
10532 response = login_server.gets
10533 login_key = /KEY\t([^\t]+)\t/.match(response).captures.first
10534 if login_key
10535 login_server.puts "M\n"
10536 response = login_server.gets
10537 if response =~ /^M\t/
10538 login_server.puts "F\t#{data[:game_code]}\n"
10539 response = login_server.gets
10540 if response =~ /NORMAL|PREMIUM|TRIAL|INTERNAL|FREE/
10541 login_server.puts "G\t#{data[:game_code]}\n"
10542 login_server.gets
10543 login_server.puts "P\t#{data[:game_code]}\n"
10544 login_server.gets
10545 login_server.puts "C\n"
10546 char_code = login_server.gets.sub(/^C\t[0-9]+\t[0-9]+\t[0-9]+\t[0-9]+[\t\n]/, '').scan(/[^\t]+\t[^\t^\n]+/).find { |c| c.split("\t")[1] == data[:char_name] }.split("\t")[0]
10547 login_server.puts "L\t#{char_code}\tSTORM\n"
10548 response = login_server.gets
10549 if response =~ /^L\t/
10550 login_server.close unless login_server.closed?
10551 launch_data = response.sub(/^L\tOK\t/, '').split("\t")
10552 if data[:frontend] == 'wizard'
10553 launch_data.collect! { |line| line.sub(/GAMEFILE=.+/, 'GAMEFILE=WIZARD.EXE').sub(/GAME=.+/, 'GAME=WIZ').sub(/FULLGAMENAME=.+/, 'FULLGAMENAME=Wizard Front End') }
10554 end
10555 if data[:custom_launch]
10556 launch_data.push "CUSTOMLAUNCH=#{data[:custom_launch]}"
10557 if data[:custom_launch_dir]
10558 launch_data.push "CUSTOMLAUNCHDIR=#{data[:custom_launch_dir]}"
10559 end
10560 end
10561 else
10562 login_server.close unless login_server.closed?
10563 $stdout.puts "error: unrecognized response from server. (#{response})"
10564 Lich.log "error: unrecognized response from server. (#{response})"
10565 end
10566 else
10567 login_server.close unless login_server.closed?
10568 $stdout.puts "error: unrecognized response from server. (#{response})"
10569 Lich.log "error: unrecognized response from server. (#{response})"
10570 end
10571 else
10572 login_server.close unless login_server.closed?
10573 $stdout.puts "error: unrecognized response from server. (#{response})"
10574 Lich.log "error: unrecognized response from server. (#{response})"
10575 end
10576 else
10577 login_server.close unless login_server.closed?
10578 $stdout.puts "Something went wrong... probably invalid user id and/or password.\nserver response: #{response}"
10579 Lich.log "Something went wrong... probably invalid user id and/or password.\nserver response: #{response}"
10580 reconnect_if_wanted.call
10581 end
10582 else
10583 $stdout.puts "error: failed to connect to server"
10584 Lich.log "error: failed to connect to server"
10585 reconnect_if_wanted.call
10586 Lich.log "info: exiting..."
10587 Gtk.queue { Gtk.main_quit } if defined?(Gtk)
10588 exit
10589 end
10590 else
10591 $stdout.puts "error: failed to find login data for #{char_name}"
10592 Lich.log "error: failed to find login data for #{char_name}"
10593 end
10594 elsif defined?(Gtk) and ARGV.empty?
10595 if File.exists?("#{DATA_DIR}/entry.dat")
10596 entry_data = File.open("#{DATA_DIR}/entry.dat", 'r') { |file|
10597 begin
10598 Marshal.load(file.read.unpack('m').first).sort { |a,b| [a[:user_id].downcase, a[:char_name]] <=> [b[:user_id].downcase, b[:char_name]] }
10599 rescue
10600 Array.new
10601 end
10602 }
10603 else
10604 entry_data = Array.new
10605 end
10606 save_entry_data = false
10607 done = false
10608 Gtk.queue {
10609
10610 login_server = nil
10611 window = nil
10612 install_tab_loaded = false
10613
10614 msgbox = proc { |msg|
10615 dialog = Gtk::MessageDialog.new(window, Gtk::Dialog::DESTROY_WITH_PARENT, Gtk::MessageDialog::QUESTION, Gtk::MessageDialog::BUTTONS_CLOSE, msg)
10616 dialog.run
10617 dialog.destroy
10618 }
10619
10620 #
10621 # quick game entry tab
10622 #
10623 if entry_data.empty?
10624 box = Gtk::HBox.new
10625 box.pack_start(Gtk::Label.new('You have no saved login info.'), true, true, 0)
10626 quick_game_entry_tab = Gtk::VBox.new
10627 quick_game_entry_tab.border_width = 5
10628 quick_game_entry_tab.pack_start(box, true, true, 0)
10629 else
10630 quick_box = Gtk::VBox.new
10631 last_user_id = nil
10632 entry_data.each { |login_info|
10633 if login_info[:user_id].downcase != last_user_id
10634 last_user_id = login_info[:user_id].downcase
10635 quick_box.pack_start(Gtk::Label.new("Account: " + last_user_id), false, false, 6)
10636 end
10637
10638 label = Gtk::Label.new("#{login_info[:char_name]} (#{login_info[:game_name]}, #{login_info[:frontend]})")
10639 play_button = Gtk::Button.new('Play')
10640 remove_button = Gtk::Button.new('X')
10641 char_box = Gtk::HBox.new
10642 char_box.pack_start(label, false, false, 6)
10643 char_box.pack_end(remove_button, false, false, 0)
10644 char_box.pack_end(play_button, false, false, 0)
10645 quick_box.pack_start(char_box, false, false, 0)
10646 play_button.signal_connect('clicked') {
10647 play_button.sensitive = false
10648 begin
10649 login_server = nil
10650 connect_thread = Thread.new {
10651 login_server = TCPSocket.new('eaccess.play.net', 7900)
10652 }
10653 300.times {
10654 sleep 0.1
10655 break unless connect_thread.status
10656 }
10657 if connect_thread.status
10658 connect_thread.kill rescue nil
10659 msgbox.call "error: timed out connecting to eaccess.play.net:7900"
10660 end
10661 rescue
10662 msgbox.call "error connecting to server: #{$!}"
10663 play_button.sensitive = true
10664 end
10665 if login_server
10666 login_server.puts "K\n"
10667 hashkey = login_server.gets
10668 if 'test'[0].class == String
10669 password = login_info[:password].split('').collect { |c| c.getbyte(0) }
10670 hashkey = hashkey.split('').collect { |c| c.getbyte(0) }
10671 else
10672 password = login_info[:password].split('').collect { |c| c[0] }
10673 hashkey = hashkey.split('').collect { |c| c[0] }
10674 end
10675 password.each_index { |i| password[i] = ((password[i]-32)^hashkey[i])+32 }
10676 password = password.collect { |c| c.chr }.join
10677 login_server.puts "A\t#{login_info[:user_id]}\t#{password}\n"
10678 password = nil
10679 response = login_server.gets
10680 login_key = /KEY\t([^\t]+)\t/.match(response).captures.first
10681 if login_key
10682 login_server.puts "M\n"
10683 response = login_server.gets
10684 if response =~ /^M\t/
10685 login_server.puts "F\t#{login_info[:game_code]}\n"
10686 response = login_server.gets
10687 if response =~ /NORMAL|PREMIUM|TRIAL|INTERNAL|FREE/
10688 login_server.puts "G\t#{login_info[:game_code]}\n"
10689 login_server.gets
10690 login_server.puts "P\t#{login_info[:game_code]}\n"
10691 login_server.gets
10692 login_server.puts "C\n"
10693 char_code = login_server.gets.sub(/^C\t[0-9]+\t[0-9]+\t[0-9]+\t[0-9]+[\t\n]/, '').scan(/[^\t]+\t[^\t^\n]+/).find { |c| c.split("\t")[1] == login_info[:char_name] }.split("\t")[0]
10694 login_server.puts "L\t#{char_code}\tSTORM\n"
10695 response = login_server.gets
10696 if response =~ /^L\t/
10697 login_server.close unless login_server.closed?
10698 launch_data = response.sub(/^L\tOK\t/, '').split("\t")
10699 if login_info[:frontend] == 'wizard'
10700 launch_data.collect! { |line| line.sub(/GAMEFILE=.+/, 'GAMEFILE=WIZARD.EXE').sub(/GAME=.+/, 'GAME=WIZ').sub(/FULLGAMENAME=.+/, 'FULLGAMENAME=Wizard Front End') }
10701 end
10702 if login_info[:custom_launch]
10703 launch_data.push "CUSTOMLAUNCH=#{login_info[:custom_launch]}"
10704 if login_info[:custom_launch_dir]
10705 launch_data.push "CUSTOMLAUNCHDIR=#{login_info[:custom_launch_dir]}"
10706 end
10707 end
10708 window.destroy
10709 done = true
10710 else
10711 login_server.close unless login_server.closed?
10712 msgbox.call("Unrecognized response from server. (#{response})")
10713 play_button.sensitive = true
10714 end
10715 else
10716 login_server.close unless login_server.closed?
10717 msgbox.call("Unrecognized response from server. (#{response})")
10718 play_button.sensitive = true
10719 end
10720 else
10721 login_server.close unless login_server.closed?
10722 msgbox.call("Unrecognized response from server. (#{response})")
10723 play_button.sensitive = true
10724 end
10725 else
10726 login_server.close unless login_server.closed?
10727 msgbox.call "Something went wrong... probably invalid user id and/or password.\nserver response: #{response}"
10728 play_button.sensitive = true
10729 end
10730 else
10731 msgbox.call "error: failed to connect to server"
10732 play_button.sensitive = true
10733 end
10734 }
10735 remove_button.signal_connect('clicked') {
10736 entry_data.delete(login_info)
10737 save_entry_data = true
10738 char_box.visible = false
10739 }
10740 }
10741
10742 adjustment = Gtk::Adjustment.new(0, 0, 1000, 5, 20, 500)
10743 quick_vp = Gtk::Viewport.new(adjustment, adjustment)
10744 quick_vp.add(quick_box)
10745
10746 quick_sw = Gtk::ScrolledWindow.new
10747 quick_sw.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_ALWAYS)
10748 quick_sw.add(quick_vp)
10749
10750 quick_game_entry_tab = Gtk::VBox.new
10751 quick_game_entry_tab.border_width = 5
10752 quick_game_entry_tab.pack_start(quick_sw, true, true, 5)
10753 end
10754
10755=begin
10756 #
10757 # game entry tab
10758 #
10759
10760 checked_frontends = false
10761 wizard_dir = nil
10762 stormfront_dir = nil
10763 found_profanity = false
10764
10765 account_name_label = Gtk::Label.new('Account Name:')
10766 account_name_entry = Gtk::Entry.new
10767 password_label = Gtk::Label.new('Password:')
10768 password_entry = Gtk::Entry.new
10769 password_entry.visibility = false
10770
10771 account_name_label_box = Gtk::HBox.new
10772 account_name_label_box.pack_end(account_name_label, false, false, 0)
10773
10774 password_label_box = Gtk::HBox.new
10775 password_label_box.pack_end(password_label, false, false, 0)
10776
10777 login_table = Gtk::Table.new(2, 2, false)
10778 login_table.attach(account_name_label_box, 0, 1, 0, 1, Gtk::FILL, Gtk::FILL, 5, 5)
10779 login_table.attach(account_name_entry, 1, 2, 0, 1, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 5, 5)
10780 login_table.attach(password_label_box, 0, 1, 1, 2, Gtk::FILL, Gtk::FILL, 5, 5)
10781 login_table.attach(password_entry, 1, 2, 1, 2, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 5, 5)
10782
10783 disconnect_button = Gtk::Button.new(' Disconnect ')
10784 disconnect_button.sensitive = false
10785
10786 connect_button = Gtk::Button.new(' Connect ')
10787
10788 login_button_box = Gtk::HBox.new
10789 login_button_box.pack_end(connect_button, false, false, 5)
10790 login_button_box.pack_end(disconnect_button, false, false, 5)
10791
10792 liststore = Gtk::ListStore.new(String, String, String, String)
10793 liststore.set_sort_column_id(1, Gtk::SORT_ASCENDING)
10794
10795 renderer = Gtk::CellRendererText.new
10796
10797 treeview = Gtk::TreeView.new(liststore)
10798 treeview.height_request = 160
10799
10800 col = Gtk::TreeViewColumn.new("Game", renderer, :text => 1)
10801 col.resizable = true
10802 treeview.append_column(col)
10803
10804 col = Gtk::TreeViewColumn.new("Character", renderer, :text => 3)
10805 col.resizable = true
10806 treeview.append_column(col)
10807
10808 sw = Gtk::ScrolledWindow.new
10809 sw.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_ALWAYS)
10810 sw.add(treeview)
10811
10812 wizard_option = Gtk::RadioButton.new('WizardFE')
10813 stormfront_option = Gtk::RadioButton.new(wizard_option, 'Stormfront')
10814 profanity_option = Gtk::RadioButton.new(wizard_option, 'ProfanityFE')
10815 other_fe_option = Gtk::RadioButton.new(wizard_option, '(other)')
10816
10817 frontend_label = Gtk::Label.new('Frontend: ')
10818
10819 frontend_option = Gtk::ComboBox.new(is_text_only = true)
10820 frontend_option.append_text('WizardFE')
10821 frontend_option.append_text('Stormfront')
10822 frontend_option.append_text('ProfanityFE')
10823 frontend_option.append_text('(other)')
10824
10825 frontend_box2 = Gtk::HBox.new(false, 10)
10826 frontend_box2.pack_start(frontend_label, false, false, 0)
10827 frontend_box2.pack_start(frontend_option, false, false, 0)
10828
10829 launch_label = Gtk::Label.new('Launch method: ')
10830
10831 launch_option = Gtk::ComboBox.new(is_text_only = true)
10832 launch_option.append_text('ShellExecute')
10833 launch_option.append_text('spawn')
10834 launch_option.append_text('system')
10835 launch_option.active = 0
10836
10837 launch_box = Gtk::HBox.new(false, 10)
10838 launch_box.pack_start(launch_label, false, false, 0)
10839 launch_box.pack_start(launch_option, false, false, 0)
10840
10841 frontend_box = Gtk::HBox.new(false, 10)
10842 frontend_box.pack_start(wizard_option, false, false, 0)
10843 frontend_box.pack_start(stormfront_option, false, false, 0)
10844 frontend_box.pack_start(profanity_option, false, false, 0)
10845 frontend_box.pack_start(other_fe_option, false, false, 0)
10846
10847 use_simu_launcher_option = Gtk::CheckButton.new('Use the Simutronics Launcher')
10848 use_simu_launcher_option.active = true
10849
10850 custom_launch_option = Gtk::CheckButton.new('Use a custom launch command')
10851 custom_launch_entry = Gtk::ComboBoxEntry.new()
10852 custom_launch_entry.child.text = "(enter custom launch command)"
10853 custom_launch_entry.append_text("Wizard.Exe /GGS /H127.0.0.1 /P%port% /K%key%")
10854 custom_launch_entry.append_text("Stormfront.exe /GGS /H127.0.0.1 /P%port% /K%key%")
10855 custom_launch_dir = Gtk::ComboBoxEntry.new()
10856 custom_launch_dir.child.text = "(enter working directory for command)"
10857 custom_launch_dir.append_text("../wizard")
10858 custom_launch_dir.append_text("../StormFront")
10859
10860 remember_use_simu_launcher_active = nil
10861 revert_custom_launch_active = nil
10862 frontend_option.signal_connect('changed') {
10863 if ((frontend_option.active == 0) and not wizard_dir) or ((frontend_option.active == 1) and not stormfront_dir) or (frontend_option.active == 2) or (frontend_option.active == 3)
10864# if (frontend_option.active != 0) and (frontend_option.active != 1) # Wizard or Stormfront
10865 if use_simu_launcher_option.sensitive?
10866 remember_use_simu_launcher_active = use_simu_launcher_option.active?
10867 use_simu_launcher_option.active = true
10868 use_simu_launcher_option.sensitive = false
10869 end
10870 elsif not use_simu_launcher_option.sensitive? and not custom_launch_option.active?
10871 use_simu_launcher_option.sensitive = true
10872 use_simu_launcher_option.active = remember_use_simu_launcher_active
10873 end
10874 if (frontend_option.active == 3) or ((frontend_option.active == 2) and not found_profanity)
10875 if custom_launch_option.sensitive?
10876 if not custom_launch_option.active?
10877 revert_custom_launch_active = true
10878 else
10879 revert_custom_launch_active = false
10880 end
10881 custom_launch_option.active = true
10882 custom_launch_option.sensitive = false
10883 end
10884 elsif not custom_launch_option.sensitive?
10885 custom_launch_option.sensitive = true
10886 if revert_custom_launch_active
10887 revert_custom_launch_active = false
10888 custom_launch_option.active = false
10889 end
10890 end
10891 }
10892 frontend_option.active = 0
10893
10894 make_quick_option = Gtk::CheckButton.new('Save this info for quick game entry')
10895
10896 play_button = Gtk::Button.new(' Play ')
10897 play_button.sensitive = false
10898
10899 play_button_box = Gtk::HBox.new
10900 play_button_box.pack_end(play_button, false, false, 5)
10901
10902 game_entry_tab = Gtk::VBox.new
10903 game_entry_tab.border_width = 5
10904 game_entry_tab.pack_start(login_table, false, false, 0)
10905 game_entry_tab.pack_start(login_button_box, false, false, 0)
10906 game_entry_tab.pack_start(sw, true, true, 3)
10907# game_entry_tab.pack_start(frontend_box, false, false, 3)
10908 game_entry_tab.pack_start(frontend_box2, false, false, 3)
10909 game_entry_tab.pack_start(launch_box, false, false, 3)
10910 game_entry_tab.pack_start(use_simu_launcher_option, false, false, 3)
10911 game_entry_tab.pack_start(custom_launch_option, false, false, 3)
10912 game_entry_tab.pack_start(custom_launch_entry, false, false, 3)
10913 game_entry_tab.pack_start(custom_launch_dir, false, false, 3)
10914 game_entry_tab.pack_start(make_quick_option, false, false, 3)
10915 game_entry_tab.pack_start(play_button_box, false, false, 3)
10916
10917 custom_launch_option.signal_connect('toggled') {
10918 custom_launch_entry.visible = custom_launch_option.active?
10919 custom_launch_dir.visible = custom_launch_option.active?
10920 if custom_launch_option.active?
10921 if use_simu_launcher_option.sensitive?
10922 remember_use_simu_launcher_active = use_simu_launcher_option.active?
10923 use_simu_launcher_option.active = false
10924 use_simu_launcher_option.sensitive = false
10925 end
10926 elsif not use_simu_launcher_option.sensitive? and ((frontend_option.active == 0) or (frontend_option.active == 1) or ((frontend_option.active == 2) and not found_profanity))
10927 use_simu_launcher_option.sensitive = true
10928 use_simu_launcher_option.active = remember_use_simu_launcher_active
10929 end
10930 }
10931
10932 connect_button.signal_connect('clicked') {
10933 connect_button.sensitive = false
10934 account_name_entry.sensitive = false
10935 password_entry.sensitive = false
10936 iter = liststore.append
10937 iter[1] = 'working...'
10938 Gtk.queue {
10939 begin
10940 login_server = nil
10941 connect_thread = Thread.new {
10942 login_server = TCPSocket.new('eaccess.play.net', 7900)
10943 }
10944 300.times {
10945 sleep 0.1
10946 break unless connect_thread.status
10947 }
10948 if connect_thread.status
10949 connect_thread.kill rescue nil
10950 msgbox.call "error: timed out connecting to eaccess.play.net:7900"
10951 end
10952 rescue
10953 msgbox.call "error connecting to server: #{$!}"
10954 connect_button.sensitive = true
10955 account_name_entry.sensitive = true
10956 password_entry.sensitive = true
10957 end
10958 disconnect_button.sensitive = true
10959 if login_server
10960 login_server.puts "K\n"
10961 hashkey = login_server.gets
10962 if 'test'[0].class == String
10963 password = password_entry.text.split('').collect { |c| c.getbyte(0) }
10964 hashkey = hashkey.split('').collect { |c| c.getbyte(0) }
10965 else
10966 password = password_entry.text.split('').collect { |c| c[0] }
10967 hashkey = hashkey.split('').collect { |c| c[0] }
10968 end
10969 # password_entry.text = String.new
10970 password.each_index { |i| password[i] = ((password[i]-32)^hashkey[i])+32 }
10971 password = password.collect { |c| c.chr }.join
10972 login_server.puts "A\t#{account_name_entry.text}\t#{password}\n"
10973 password = nil
10974 response = login_server.gets
10975 login_key = /KEY\t([^\t]+)\t/.match(response).captures.first
10976 if login_key
10977 login_server.puts "M\n"
10978 response = login_server.gets
10979 if response =~ /^M\t/
10980 liststore.clear
10981 for game in response.sub(/^M\t/, '').scan(/[^\t]+\t[^\t^\n]+/)
10982 game_code, game_name = game.split("\t")
10983 login_server.puts "N\t#{game_code}\n"
10984 if login_server.gets =~ /STORM/
10985 login_server.puts "F\t#{game_code}\n"
10986 if login_server.gets =~ /NORMAL|PREMIUM|TRIAL|INTERNAL|FREE/
10987 login_server.puts "G\t#{game_code}\n"
10988 login_server.gets
10989 login_server.puts "P\t#{game_code}\n"
10990 login_server.gets
10991 login_server.puts "C\n"
10992 for code_name in login_server.gets.sub(/^C\t[0-9]+\t[0-9]+\t[0-9]+\t[0-9]+[\t\n]/, '').scan(/[^\t]+\t[^\t^\n]+/)
10993 char_code, char_name = code_name.split("\t")
10994 iter = liststore.append
10995 iter[0] = game_code
10996 iter[1] = game_name
10997 iter[2] = char_code
10998 iter[3] = char_name
10999 end
11000 end
11001 end
11002 end
11003 disconnect_button.sensitive = true
11004 else
11005 login_server.close unless login_server.closed?
11006 msgbox.call "Unrecognized response from server (#{response})"
11007 end
11008 else
11009 login_server.close unless login_server.closed?
11010 disconnect_button.sensitive = false
11011 connect_button.sensitive = true
11012 account_name_entry.sensitive = true
11013 password_entry.sensitive = true
11014 msgbox.call "Something went wrong... probably invalid user id and/or password.\nserver response: #{response}"
11015 end
11016 end
11017 }
11018 }
11019 treeview.signal_connect('cursor-changed') {
11020 if login_server
11021 play_button.sensitive = true
11022 end
11023 }
11024 disconnect_button.signal_connect('clicked') {
11025 disconnect_button.sensitive = false
11026 play_button.sensitive = false
11027 liststore.clear
11028 login_server.close unless login_server.closed?
11029 connect_button.sensitive = true
11030 account_name_entry.sensitive = true
11031 password_entry.sensitive = true
11032 }
11033 play_button.signal_connect('clicked') {
11034 play_button.sensitive = false
11035 game_code = treeview.selection.selected[0]
11036 char_code = treeview.selection.selected[2]
11037 if login_server and not login_server.closed?
11038 login_server.puts "F\t#{game_code}\n"
11039 login_server.gets
11040 login_server.puts "G\t#{game_code}\n"
11041 login_server.gets
11042 login_server.puts "P\t#{game_code}\n"
11043 login_server.gets
11044 login_server.puts "C\n"
11045 login_server.gets
11046 login_server.puts "L\t#{char_code}\tSTORM\n"
11047 response = login_server.gets
11048 if response =~ /^L\t/
11049 login_server.close unless login_server.closed?
11050 port = /GAMEPORT=([0-9]+)/.match(response).captures.first
11051 host = /GAMEHOST=([^\t\n]+)/.match(response).captures.first
11052 key = /KEY=([^\t\n]+)/.match(response).captures.first
11053 launch_data = response.sub(/^L\tOK\t/, '').split("\t")
11054 login_server.close unless login_server.closed?
11055 if wizard_option.active?
11056 launch_data.collect! { |line| line.sub(/GAMEFILE=.+/, "GAMEFILE=WIZARD.EXE").sub(/GAME=.+/, "GAME=WIZ") }
11057 elsif suks_option.active?
11058 launch_data.collect! { |line| line.sub(/GAMEFILE=.+/, "GAMEFILE=WIZARD.EXE").sub(/GAME=.+/, "GAME=SUKS") }
11059 end
11060 if custom_launch_option.active?
11061 launch_data.push "CUSTOMLAUNCH=#{custom_launch_entry.child.text}"
11062 unless custom_launch_dir.child.text.empty? or custom_launch_dir.child.text == "(enter working directory for command)"
11063 launch_data.push "CUSTOMLAUNCHDIR=#{custom_launch_dir.child.text}"
11064 end
11065 end
11066 if make_quick_option.active?
11067 if wizard_option.active?
11068 frontend = 'wizard'
11069 else
11070 frontend = 'stormfront'
11071 end
11072 if custom_launch_option.active?
11073 custom_launch = custom_launch_entry.child.text
11074 if custom_launch_dir.child.text.empty? or custom_launch_dir.child.text == "(enter working directory for command)"
11075 custom_launch_dir = nil
11076 else
11077 custom_launch_dir = custom_launch_dir.child.text
11078 end
11079 else
11080 custom_launch = nil
11081 custom_launch_dir = nil
11082 end
11083 entry_data.push h={ :char_name => treeview.selection.selected[3], :game_code => treeview.selection.selected[0], :game_name => treeview.selection.selected[1], :user_id => account_name_entry.text, :password => password_entry.text, :frontend => frontend, :custom_launch => custom_launch, :custom_launch_dir => custom_launch_dir }
11084 save_entry_data = true
11085 end
11086 account_name_entry.text = String.new
11087 password_entry.text = String.new
11088 window.destroy
11089 done = true
11090 else
11091 login_server.close unless login_server.closed?
11092 disconnect_button.sensitive = false
11093 play_button.sensitive = false
11094 connect_button.sensitive = true
11095 account_name_entry.sensitive = true
11096 password_entry.sensitive = true
11097 end
11098 else
11099 disconnect_button.sensitive = false
11100 play_button.sensitive = false
11101 connect_button.sensitive = true
11102 account_name_entry.sensitive = true
11103 password_entry.sensitive = true
11104 end
11105 }
11106 account_name_entry.signal_connect('activate') {
11107 password_entry.grab_focus
11108 }
11109 password_entry.signal_connect('activate') {
11110 connect_button.clicked
11111 }
11112=end
11113
11114 #
11115 # old game entry tab
11116 #
11117
11118 user_id_entry = Gtk::Entry.new
11119
11120 pass_entry = Gtk::Entry.new
11121 pass_entry.visibility = false
11122
11123 login_table = Gtk::Table.new(2, 2, false)
11124 login_table.attach(Gtk::Label.new('User ID:'), 0, 1, 0, 1, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 5, 5)
11125 login_table.attach(user_id_entry, 1, 2, 0, 1, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 5, 5)
11126 login_table.attach(Gtk::Label.new('Password:'), 0, 1, 1, 2, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 5, 5)
11127 login_table.attach(pass_entry, 1, 2, 1, 2, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 5, 5)
11128
11129 disconnect_button = Gtk::Button.new(' Disconnect ')
11130 disconnect_button.sensitive = false
11131
11132 connect_button = Gtk::Button.new(' Connect ')
11133
11134 login_button_box = Gtk::HBox.new
11135 login_button_box.pack_end(connect_button, false, false, 5)
11136 login_button_box.pack_end(disconnect_button, false, false, 5)
11137
11138 liststore = Gtk::ListStore.new(String, String, String, String)
11139 liststore.set_sort_column_id(1, Gtk::SORT_ASCENDING)
11140
11141 renderer = Gtk::CellRendererText.new
11142# renderer.background = 'white'
11143
11144 treeview = Gtk::TreeView.new(liststore)
11145 treeview.height_request = 160
11146
11147 col = Gtk::TreeViewColumn.new("Game", renderer, :text => 1)
11148 col.resizable = true
11149 treeview.append_column(col)
11150
11151 col = Gtk::TreeViewColumn.new("Character", renderer, :text => 3)
11152 col.resizable = true
11153 treeview.append_column(col)
11154
11155 sw = Gtk::ScrolledWindow.new
11156 sw.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_ALWAYS)
11157 sw.add(treeview)
11158
11159 wizard_option = Gtk::RadioButton.new('Wizard')
11160 stormfront_option = Gtk::RadioButton.new(wizard_option, 'Stormfront')
11161 suks_option = Gtk::RadioButton.new(wizard_option, 'suks')
11162
11163 frontend_box = Gtk::HBox.new(false, 10)
11164 frontend_box.pack_start(wizard_option, false, false, 0)
11165 frontend_box.pack_start(stormfront_option, false, false, 0)
11166 #frontend_box.pack_start(suks_option, false, false, 0)
11167
11168 custom_launch_option = Gtk::CheckButton.new('Custom launch command')
11169 custom_launch_entry = Gtk::ComboBoxEntry.new()
11170 custom_launch_entry.child.text = "(enter custom launch command)"
11171 custom_launch_entry.append_text("Wizard.Exe /GGS /H127.0.0.1 /P%port% /K%key%")
11172 custom_launch_entry.append_text("Stormfront.exe /GGS/Hlocalhost/P%port%/K%key%")
11173 custom_launch_dir = Gtk::ComboBoxEntry.new()
11174 custom_launch_dir.child.text = "(enter working directory for command)"
11175 custom_launch_dir.append_text("../wizard")
11176 custom_launch_dir.append_text("../StormFront")
11177
11178 make_quick_option = Gtk::CheckButton.new('Save this info for quick game entry')
11179
11180 play_button = Gtk::Button.new(' Play ')
11181 play_button.sensitive = false
11182
11183 play_button_box = Gtk::HBox.new
11184 play_button_box.pack_end(play_button, false, false, 5)
11185
11186 game_entry_tab = Gtk::VBox.new
11187 game_entry_tab.border_width = 5
11188 game_entry_tab.pack_start(login_table, false, false, 0)
11189 game_entry_tab.pack_start(login_button_box, false, false, 0)
11190 game_entry_tab.pack_start(sw, true, true, 3)
11191 game_entry_tab.pack_start(frontend_box, false, false, 3)
11192 game_entry_tab.pack_start(custom_launch_option, false, false, 3)
11193 game_entry_tab.pack_start(custom_launch_entry, false, false, 3)
11194 game_entry_tab.pack_start(custom_launch_dir, false, false, 3)
11195 game_entry_tab.pack_start(make_quick_option, false, false, 3)
11196 game_entry_tab.pack_start(play_button_box, false, false, 3)
11197
11198 custom_launch_option.signal_connect('toggled') {
11199 custom_launch_entry.visible = custom_launch_option.active?
11200 custom_launch_dir.visible = custom_launch_option.active?
11201 }
11202
11203 connect_button.signal_connect('clicked') {
11204 connect_button.sensitive = false
11205 user_id_entry.sensitive = false
11206 pass_entry.sensitive = false
11207 iter = liststore.append
11208 iter[1] = 'working...'
11209 Gtk.queue {
11210 begin
11211 login_server = nil
11212 connect_thread = Thread.new {
11213 login_server = TCPSocket.new('eaccess.play.net', 7900)
11214 }
11215 300.times {
11216 sleep 0.1
11217 break unless connect_thread.status
11218 }
11219 if connect_thread.status
11220 connect_thread.kill rescue nil
11221 msgbox.call "error: timed out connecting to eaccess.play.net:7900"
11222 end
11223 rescue
11224 msgbox.call "error connecting to server: #{$!}"
11225 connect_button.sensitive = true
11226 user_id_entry.sensitive = true
11227 pass_entry.sensitive = true
11228 end
11229 disconnect_button.sensitive = true
11230 if login_server
11231 login_server.puts "K\n"
11232 hashkey = login_server.gets
11233 if 'test'[0].class == String
11234 password = pass_entry.text.split('').collect { |c| c.getbyte(0) }
11235 hashkey = hashkey.split('').collect { |c| c.getbyte(0) }
11236 else
11237 password = pass_entry.text.split('').collect { |c| c[0] }
11238 hashkey = hashkey.split('').collect { |c| c[0] }
11239 end
11240 # pass_entry.text = String.new
11241 password.each_index { |i| password[i] = ((password[i]-32)^hashkey[i])+32 }
11242 password = password.collect { |c| c.chr }.join
11243 login_server.puts "A\t#{user_id_entry.text}\t#{password}\n"
11244 password = nil
11245 response = login_server.gets
11246 login_key = /KEY\t([^\t]+)\t/.match(response).captures.first
11247 if login_key
11248 login_server.puts "M\n"
11249 response = login_server.gets
11250 if response =~ /^M\t/
11251 liststore.clear
11252 for game in response.sub(/^M\t/, '').scan(/[^\t]+\t[^\t^\n]+/)
11253 game_code, game_name = game.split("\t")
11254 login_server.puts "N\t#{game_code}\n"
11255 if login_server.gets =~ /STORM/
11256 login_server.puts "F\t#{game_code}\n"
11257 if login_server.gets =~ /NORMAL|PREMIUM|TRIAL|INTERNAL|FREE/
11258 login_server.puts "G\t#{game_code}\n"
11259 login_server.gets
11260 login_server.puts "P\t#{game_code}\n"
11261 login_server.gets
11262 login_server.puts "C\n"
11263 for code_name in login_server.gets.sub(/^C\t[0-9]+\t[0-9]+\t[0-9]+\t[0-9]+[\t\n]/, '').scan(/[^\t]+\t[^\t^\n]+/)
11264 char_code, char_name = code_name.split("\t")
11265 iter = liststore.append
11266 iter[0] = game_code
11267 iter[1] = game_name
11268 iter[2] = char_code
11269 iter[3] = char_name
11270 end
11271 end
11272 end
11273 end
11274 disconnect_button.sensitive = true
11275 else
11276 login_server.close unless login_server.closed?
11277 msgbox.call "Unrecognized response from server (#{response})"
11278 end
11279 else
11280 login_server.close unless login_server.closed?
11281 disconnect_button.sensitive = false
11282 connect_button.sensitive = true
11283 user_id_entry.sensitive = true
11284 pass_entry.sensitive = true
11285 msgbox.call "Something went wrong... probably invalid user id and/or password.\nserver response: #{response}"
11286 end
11287 end
11288 }
11289 }
11290 treeview.signal_connect('cursor-changed') {
11291 if login_server
11292 play_button.sensitive = true
11293 end
11294 }
11295 disconnect_button.signal_connect('clicked') {
11296 disconnect_button.sensitive = false
11297 play_button.sensitive = false
11298 liststore.clear
11299 login_server.close unless login_server.closed?
11300 connect_button.sensitive = true
11301 user_id_entry.sensitive = true
11302 pass_entry.sensitive = true
11303 }
11304 play_button.signal_connect('clicked') {
11305 play_button.sensitive = false
11306 game_code = treeview.selection.selected[0]
11307 char_code = treeview.selection.selected[2]
11308 if login_server and not login_server.closed?
11309 login_server.puts "F\t#{game_code}\n"
11310 login_server.gets
11311 login_server.puts "G\t#{game_code}\n"
11312 login_server.gets
11313 login_server.puts "P\t#{game_code}\n"
11314 login_server.gets
11315 login_server.puts "C\n"
11316 login_server.gets
11317 login_server.puts "L\t#{char_code}\tSTORM\n"
11318 response = login_server.gets
11319 if response =~ /^L\t/
11320 login_server.close unless login_server.closed?
11321 port = /GAMEPORT=([0-9]+)/.match(response).captures.first
11322 host = /GAMEHOST=([^\t\n]+)/.match(response).captures.first
11323 key = /KEY=([^\t\n]+)/.match(response).captures.first
11324 launch_data = response.sub(/^L\tOK\t/, '').split("\t")
11325 login_server.close unless login_server.closed?
11326 if wizard_option.active?
11327 launch_data.collect! { |line| line.sub(/GAMEFILE=.+/, "GAMEFILE=WIZARD.EXE").sub(/GAME=.+/, "GAME=WIZ") }
11328 elsif suks_option.active?
11329 launch_data.collect! { |line| line.sub(/GAMEFILE=.+/, "GAMEFILE=WIZARD.EXE").sub(/GAME=.+/, "GAME=SUKS") }
11330 end
11331 if custom_launch_option.active?
11332 launch_data.push "CUSTOMLAUNCH=#{custom_launch_entry.child.text}"
11333 unless custom_launch_dir.child.text.empty? or custom_launch_dir.child.text == "(enter working directory for command)"
11334 launch_data.push "CUSTOMLAUNCHDIR=#{custom_launch_dir.child.text}"
11335 end
11336 end
11337 if make_quick_option.active?
11338 if wizard_option.active?
11339 frontend = 'wizard'
11340 else
11341 frontend = 'stormfront'
11342 end
11343 if custom_launch_option.active?
11344 custom_launch = custom_launch_entry.child.text
11345 if custom_launch_dir.child.text.empty? or custom_launch_dir.child.text == "(enter working directory for command)"
11346 custom_launch_dir = nil
11347 else
11348 custom_launch_dir = custom_launch_dir.child.text
11349 end
11350 else
11351 custom_launch = nil
11352 custom_launch_dir = nil
11353 end
11354 entry_data.push h={ :char_name => treeview.selection.selected[3], :game_code => treeview.selection.selected[0], :game_name => treeview.selection.selected[1], :user_id => user_id_entry.text, :password => pass_entry.text, :frontend => frontend, :custom_launch => custom_launch, :custom_launch_dir => custom_launch_dir }
11355 save_entry_data = true
11356 end
11357 user_id_entry.text = String.new
11358 pass_entry.text = String.new
11359 window.destroy
11360 done = true
11361 else
11362 login_server.close unless login_server.closed?
11363 disconnect_button.sensitive = false
11364 play_button.sensitive = false
11365 connect_button.sensitive = true
11366 user_id_entry.sensitive = true
11367 pass_entry.sensitive = true
11368 end
11369 else
11370 disconnect_button.sensitive = false
11371 play_button.sensitive = false
11372 connect_button.sensitive = true
11373 user_id_entry.sensitive = true
11374 pass_entry.sensitive = true
11375 end
11376 }
11377 user_id_entry.signal_connect('activate') {
11378 pass_entry.grab_focus
11379 }
11380 pass_entry.signal_connect('activate') {
11381 connect_button.clicked
11382 }
11383
11384 #
11385 # link tab
11386 #
11387
11388 link_to_web_button = Gtk::Button.new('Link to Website')
11389 unlink_from_web_button = Gtk::Button.new('Unlink from Website')
11390 web_button_box = Gtk::HBox.new
11391 web_button_box.pack_start(link_to_web_button, true, true, 5)
11392 web_button_box.pack_start(unlink_from_web_button, true, true, 5)
11393
11394 web_order_label = Gtk::Label.new
11395 web_order_label.text = "Unknown"
11396
11397 web_box = Gtk::VBox.new
11398 web_box.pack_start(web_order_label, true, true, 5)
11399 web_box.pack_start(web_button_box, true, true, 5)
11400
11401 web_frame = Gtk::Frame.new('Website Launch Chain')
11402 web_frame.add(web_box)
11403
11404 link_to_sge_button = Gtk::Button.new('Link to SGE')
11405 unlink_from_sge_button = Gtk::Button.new('Unlink from SGE')
11406 sge_button_box = Gtk::HBox.new
11407 sge_button_box.pack_start(link_to_sge_button, true, true, 5)
11408 sge_button_box.pack_start(unlink_from_sge_button, true, true, 5)
11409
11410 sge_order_label = Gtk::Label.new
11411 sge_order_label.text = "Unknown"
11412
11413 sge_box = Gtk::VBox.new
11414 sge_box.pack_start(sge_order_label, true, true, 5)
11415 sge_box.pack_start(sge_button_box, true, true, 5)
11416
11417 sge_frame = Gtk::Frame.new('SGE Launch Chain')
11418 sge_frame.add(sge_box)
11419
11420
11421 refresh_button = Gtk::Button.new(' Refresh ')
11422
11423 refresh_box = Gtk::HBox.new
11424 refresh_box.pack_end(refresh_button, false, false, 5)
11425
11426 install_tab = Gtk::VBox.new
11427 install_tab.border_width = 5
11428 install_tab.pack_start(web_frame, false, false, 5)
11429 install_tab.pack_start(sge_frame, false, false, 5)
11430 install_tab.pack_start(refresh_box, false, false, 5)
11431
11432 refresh_button.signal_connect('clicked') {
11433 install_tab_loaded = true
11434 if defined?(Win32)
11435 begin
11436 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]
11437 web_launch_cmd = Win32.RegQueryValueEx(:hKey => key)[:lpData]
11438 real_web_launch_cmd = Win32.RegQueryValueEx(:hKey => key, :lpValueName => 'RealCommand')[:lpData]
11439 rescue
11440 web_launch_cmd = String.new
11441 real_web_launch_cmd = String.new
11442 ensure
11443 Win32.RegCloseKey(:hKey => key) rescue nil
11444 end
11445 begin
11446 key = Win32.RegOpenKeyEx(:hKey => Win32::HKEY_LOCAL_MACHINE, :lpSubKey => 'Software\\Simutronics\\Launcher', :samDesired => (Win32::KEY_ALL_ACCESS|Win32::KEY_WOW64_32KEY))[:phkResult]
11447 sge_launch_cmd = Win32.RegQueryValueEx(:hKey => key, :lpValueName => 'Directory')[:lpData]
11448 real_sge_launch_cmd = Win32.RegQueryValueEx(:hKey => key, :lpValueName => 'RealDirectory')[:lpData]
11449 rescue
11450 sge_launch_cmd = String.new
11451 real_launch_cmd = String.new
11452 ensure
11453 Win32.RegCloseKey(:hKey => key) rescue nil
11454 end
11455 elsif defined?(Wine)
11456 web_launch_cmd = Wine.registry_gets('HKEY_LOCAL_MACHINE\\Software\\Classes\\Simutronics.Autolaunch\\Shell\\Open\\command\\').to_s
11457 real_web_launch_cmd = Wine.registry_gets('HKEY_LOCAL_MACHINE\\Software\\Classes\\Simutronics.Autolaunch\\Shell\\Open\\command\\RealCommand').to_s
11458 sge_launch_cmd = Wine.registry_gets('HKEY_LOCAL_MACHINE\\Software\\Simutronics\\Launcher\\Directory').to_s
11459 real_sge_launch_cmd = Wine.registry_gets('HKEY_LOCAL_MACHINE\\Software\\Simutronics\\Launcher\\RealDirectory').to_s
11460 else
11461 web_launch_cmd = String.new
11462 sge_launch_cmd = String.new
11463 end
11464 if web_launch_cmd =~ /lich/i
11465 link_to_web_button.sensitive = false
11466 unlink_from_web_button.sensitive = true
11467 if real_web_launch_cmd =~ /launcher.exe/i
11468 web_order_label.text = "Website => Lich => Simu Launcher => Frontend"
11469 else
11470 web_order_label.text = "Website => Lich => Unknown"
11471 end
11472 elsif web_launch_cmd =~ /launcher.exe/i
11473 web_order_label.text = "Website => Simu Launcher => Frontend"
11474 link_to_web_button.sensitive = true
11475 unlink_from_web_button.sensitive = false
11476 else
11477 web_order_label.text = "Website => Unknown"
11478 link_to_web_button.sensitive = false
11479 unlink_from_web_button.sensitive = false
11480 end
11481 if sge_launch_cmd =~ /lich/i
11482 link_to_sge_button.sensitive = false
11483 unlink_from_sge_button.sensitive = true
11484 if real_sge_launch_cmd and (defined?(Wine) or File.exists?("#{real_sge_launch_cmd}\\launcher.exe"))
11485 sge_order_label.text = "SGE => Lich => Simu Launcher => Frontend"
11486 else
11487 sge_order_label.text = "SGE => Lich => Unknown"
11488 end
11489 elsif sge_launch_cmd and (defined?(Wine) or File.exists?("#{sge_launch_cmd}\\launcher.exe"))
11490 sge_order_label.text = "SGE => Simu Launcher => Frontend"
11491 link_to_sge_button.sensitive = true
11492 unlink_from_sge_button.sensitive = false
11493 else
11494 sge_order_label.text = "SGE => Unknown"
11495 link_to_sge_button.sensitive = false
11496 unlink_from_sge_button.sensitive = false
11497 end
11498 }
11499 link_to_web_button.signal_connect('clicked') {
11500 link_to_web_button.sensitive = false
11501 Lich.link_to_sal
11502 if defined?(Win32)
11503 refresh_button.clicked
11504 else
11505 Lich.msgbox(:message => 'WINE will take 5-30 seconds to update the registry. Wait a while and click the refresh button.')
11506 end
11507 }
11508 unlink_from_web_button.signal_connect('clicked') {
11509 unlink_from_web_button.sensitive = false
11510 Lich.unlink_from_sal
11511 if defined?(Win32)
11512 refresh_button.clicked
11513 else
11514 Lich.msgbox(:message => 'WINE will take 5-30 seconds to update the registry. Wait a while and click the refresh button.')
11515 end
11516 }
11517 link_to_sge_button.signal_connect('clicked') {
11518 link_to_sge_button.sensitive = false
11519 Lich.link_to_sge
11520 if defined?(Win32)
11521 refresh_button.clicked
11522 else
11523 Lich.msgbox(:message => 'WINE will take 5-30 seconds to update the registry. Wait a while and click the refresh button.')
11524 end
11525 }
11526 unlink_from_sge_button.signal_connect('clicked') {
11527 unlink_from_sge_button.sensitive = false
11528 Lich.unlink_from_sge
11529 if defined?(Win32)
11530 refresh_button.clicked
11531 else
11532 Lich.msgbox(:message => 'WINE will take 5-30 seconds to update the registry. Wait a while and click the refresh button.')
11533 end
11534 }
11535
11536=begin
11537 #
11538 # options tab
11539 #
11540
11541 lich_char_label = Gtk::Label.new('Lich char:')
11542 lich_char_label.xalign = 1
11543 lich_char_entry = Gtk::Entry.new
11544 lich_char_entry.text = ';' # fixme LichSettings['lich_char'].to_s
11545 lich_box = Gtk::HBox.new
11546 lich_box.pack_end(lich_char_entry, true, true, 5)
11547 lich_box.pack_end(lich_char_label, true, true, 5)
11548
11549 cache_serverbuffer_button = Gtk::CheckButton.new('Cache to disk')
11550 cache_serverbuffer_button.active = LichSettings['cache_serverbuffer']
11551
11552 serverbuffer_max_label = Gtk::Label.new('Maximum lines in memory:')
11553 serverbuffer_max_entry = Gtk::Entry.new
11554 serverbuffer_max_entry.text = LichSettings['serverbuffer_max_size'].to_s
11555 serverbuffer_min_label = Gtk::Label.new('Minumum lines in memory:')
11556 serverbuffer_min_entry = Gtk::Entry.new
11557 serverbuffer_min_entry.text = LichSettings['serverbuffer_min_size'].to_s
11558 serverbuffer_min_entry.sensitive = cache_serverbuffer_button.active?
11559
11560 serverbuffer_table = Gtk::Table.new(2, 2, false)
11561 serverbuffer_table.attach(serverbuffer_max_label, 0, 1, 0, 1, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 5, 5)
11562 serverbuffer_table.attach(serverbuffer_max_entry, 1, 2, 0, 1, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 5, 5)
11563 serverbuffer_table.attach(serverbuffer_min_label, 0, 1, 1, 2, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 5, 5)
11564 serverbuffer_table.attach(serverbuffer_min_entry, 1, 2, 1, 2, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 5, 5)
11565
11566 serverbuffer_box = Gtk::VBox.new
11567 serverbuffer_box.pack_start(cache_serverbuffer_button, false, false, 5)
11568 serverbuffer_box.pack_start(serverbuffer_table, false, false, 5)
11569
11570 serverbuffer_frame = Gtk::Frame.new('Server Buffer')
11571 serverbuffer_frame.add(serverbuffer_box)
11572
11573 cache_clientbuffer_button = Gtk::CheckButton.new('Cache to disk')
11574 cache_clientbuffer_button.active = LichSettings['cache_clientbuffer']
11575
11576 clientbuffer_max_label = Gtk::Label.new('Maximum lines in memory:')
11577 clientbuffer_max_entry = Gtk::Entry.new
11578 clientbuffer_max_entry.text = LichSettings['clientbuffer_max_size'].to_s
11579 clientbuffer_min_label = Gtk::Label.new('Minumum lines in memory:')
11580 clientbuffer_min_entry = Gtk::Entry.new
11581 clientbuffer_min_entry.text = LichSettings['clientbuffer_min_size'].to_s
11582 clientbuffer_min_entry.sensitive = cache_clientbuffer_button.active?
11583
11584 clientbuffer_table = Gtk::Table.new(2, 2, false)
11585 clientbuffer_table.attach(clientbuffer_max_label, 0, 1, 0, 1, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 5, 5)
11586 clientbuffer_table.attach(clientbuffer_max_entry, 1, 2, 0, 1, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 5, 5)
11587 clientbuffer_table.attach(clientbuffer_min_label, 0, 1, 1, 2, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 5, 5)
11588 clientbuffer_table.attach(clientbuffer_min_entry, 1, 2, 1, 2, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 5, 5)
11589
11590 clientbuffer_box = Gtk::VBox.new
11591 clientbuffer_box.pack_start(cache_clientbuffer_button, false, false, 5)
11592 clientbuffer_box.pack_start(clientbuffer_table, false, false, 5)
11593
11594 clientbuffer_frame = Gtk::Frame.new('Client Buffer')
11595 clientbuffer_frame.add(clientbuffer_box)
11596
11597 save_button = Gtk::Button.new(' Save ')
11598 save_button.sensitive = false
11599
11600 save_button_box = Gtk::HBox.new
11601 save_button_box.pack_end(save_button, false, false, 5)
11602
11603 options_tab = Gtk::VBox.new
11604 options_tab.border_width = 5
11605 options_tab.pack_start(lich_box, false, false, 5)
11606 options_tab.pack_start(serverbuffer_frame, false, false, 5)
11607 options_tab.pack_start(clientbuffer_frame, false, false, 5)
11608 options_tab.pack_start(save_button_box, false, false, 5)
11609
11610 check_changed = proc {
11611 Gtk.queue {
11612 if (LichSettings['lich_char'] == lich_char_entry.text) and (LichSettings['cache_serverbuffer'] == cache_serverbuffer_button.active?) and (LichSettings['serverbuffer_max_size'] == serverbuffer_max_entry.text.to_i) and (LichSettings['serverbuffer_min_size'] == serverbuffer_min_entry.text.to_i) and (LichSettings['cache_clientbuffer'] == cache_clientbuffer_button.active?) and (LichSettings['clientbuffer_max_size'] == clientbuffer_max_entry.text.to_i) and (LichSettings['clientbuffer_min_size'] == clientbuffer_min_entry.text.to_i)
11613 save_button.sensitive = false
11614 else
11615 save_button.sensitive = true
11616 end
11617 }
11618 }
11619
11620 lich_char_entry.signal_connect('key-press-event') {
11621 check_changed.call
11622 false
11623 }
11624 serverbuffer_max_entry.signal_connect('key-press-event') {
11625 check_changed.call
11626 false
11627 }
11628 serverbuffer_min_entry.signal_connect('key-press-event') {
11629 check_changed.call
11630 false
11631 }
11632 clientbuffer_max_entry.signal_connect('key-press-event') {
11633 check_changed.call
11634 false
11635 }
11636 clientbuffer_min_entry.signal_connect('key-press-event') {
11637 check_changed.call
11638 false
11639 }
11640 cache_serverbuffer_button.signal_connect('clicked') {
11641 serverbuffer_min_entry.sensitive = cache_serverbuffer_button.active?
11642 check_changed.call
11643 }
11644 cache_clientbuffer_button.signal_connect('clicked') {
11645 clientbuffer_min_entry.sensitive = cache_clientbuffer_button.active?
11646 check_changed.call
11647 }
11648 save_button.signal_connect('clicked') {
11649 LichSettings['lich_char'] = lich_char_entry.text
11650 LichSettings['cache_serverbuffer'] = cache_serverbuffer_button.active?
11651 LichSettings['serverbuffer_max_size'] = serverbuffer_max_entry.text.to_i
11652 LichSettings['serverbuffer_min_size'] = serverbuffer_min_entry.text.to_i
11653 LichSettings['cache_clientbuffer'] = cache_clientbuffer_button.active?
11654 LichSettings['clientbuffer_max_size'] = clientbuffer_max_entry.text.to_i
11655 LichSettings['clientbuffer_min_size'] = clientbuffer_min_entry.text.to_i
11656 LichSettings.save
11657 save_button.sensitive = false
11658 }
11659=end
11660
11661 #
11662 # put it together and show the window
11663 #
11664
11665 notebook = Gtk::Notebook.new
11666 notebook.append_page(quick_game_entry_tab, Gtk::Label.new('Quick Game Entry'))
11667 notebook.append_page(game_entry_tab, Gtk::Label.new('Game Entry'))
11668 notebook.append_page(install_tab, Gtk::Label.new('Link'))
11669# notebook.append_page(options_tab, Gtk::Label.new('Options'))
11670 notebook.signal_connect('switch-page') { |who,page,page_num|
11671 if (page_num == 2) and not install_tab_loaded
11672 refresh_button.clicked
11673=begin
11674 elsif (page_num == 1) and not checked_frontends
11675 checked_frontends = true
11676 found_profanity = File.exists?("#{LICH_DIR}/profanity.rb")
11677 if defined?(Win32)
11678 begin
11679 key = Win32.RegOpenKeyEx(:hKey => Win32::HKEY_LOCAL_MACHINE, :lpSubKey => 'Software\\Simutronics\\STORM32', :samDesired => (Win32::KEY_ALL_ACCESS|Win32::KEY_WOW64_32KEY))[:phkResult]
11680 stormfront_dir = Win32.RegQueryValueEx(:hKey => key, :lpValueName => 'Directory')[:lpData]
11681 rescue
11682 stormfront_dir = nil
11683 ensure
11684 Win32.RegCloseKey(:hKey => key) rescue nil
11685 end
11686 begin
11687 key = Win32.RegOpenKeyEx(:hKey => Win32::HKEY_LOCAL_MACHINE, :lpSubKey => 'Software\\Simutronics\\WIZ32', :samDesired => (Win32::KEY_ALL_ACCESS|Win32::KEY_WOW64_32KEY))[:phkResult]
11688 wizard_dir = Win32.RegQueryValueEx(:hKey => key, :lpValueName => 'Directory')[:lpData]
11689 rescue
11690 wizard_dir = nil
11691 ensure
11692 Win32.RegCloseKey(:hKey => key) rescue nil
11693 end
11694 elsif defined?(Wine)
11695 stormfront_dir = Wine.registry_gets('HKEY_LOCAL_MACHINE\\Software\\Simutronics\\STORM32\\Directory').gsub("\\", "/")
11696 wizard_dir = Wine.registry_gets('HKEY_LOCAL_MACHINE\\Software\\Simutronics\\WIZ32\\Directory').gsub("\\", "/")
11697 else
11698 stormfront_dir = nil
11699 wizard_dir = nil
11700 end
11701 Lich.log "wizard_dir: #{wizard_dir}"
11702 Lich.log "stormfront_dir: #{stormfront_dir}"
11703 unless File.exists?("#{stormfront_dir}\\Stormfront.exe")
11704 Lich.log "stormfront doesn't exist"
11705 stormfront_dir = nil
11706 end
11707 unless File.exists?("#{wizard_dir}\\Wizard.Exe")
11708 Lich.log "wizard doesn't exist"
11709 wizard_dir = nil
11710 end
11711=end
11712 end
11713 }
11714
11715 window = Gtk::Window.new
11716 window.title = "Lich v#{LICH_VERSION}"
11717 window.border_width = 5
11718 window.add(notebook)
11719 window.signal_connect('delete_event') { window.destroy; done = true }
11720 window.default_width = 400
11721
11722 window.show_all
11723
11724 custom_launch_entry.visible = false
11725 custom_launch_dir.visible = false
11726
11727 notebook.set_page(1) if entry_data.empty?
11728 }
11729
11730 wait_until { done }
11731
11732 if save_entry_data
11733 File.open("#{DATA_DIR}/entry.dat", 'w') { |file|
11734 file.write([Marshal.dump(entry_data)].pack('m'))
11735 }
11736 end
11737 entry_data = nil
11738
11739 unless launch_data
11740 Gtk.queue { Gtk.main_quit }
11741 Thread.kill
11742 end
11743 end
11744 $_SERVERBUFFER_ = LimitedArray.new
11745 $_SERVERBUFFER_.max_size = 400
11746 $_CLIENTBUFFER_ = LimitedArray.new
11747 $_CLIENTBUFFER_.max_size = 100
11748
11749 Socket.do_not_reverse_lookup = true
11750
11751 #
11752 # open the client and have it connect to us
11753 #
11754 if argv_options[:sal]
11755 begin
11756 launch_data = File.open(argv_options[:sal]) { |file| file.readlines }.collect { |line| line.chomp }
11757 rescue
11758 $stdout.puts "error: failed to read launch_file: #{$!}"
11759 Lich.log "info: launch_file: #{argv_options[:sal]}"
11760 Lich.log "error: failed to read launch_file: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
11761 exit
11762 end
11763 end
11764 if launch_data
11765 unless gamecode = launch_data.find { |line| line =~ /GAMECODE=/ }
11766 $stdout.puts "error: launch_data contains no GAMECODE info"
11767 Lich.log "error: launch_data contains no GAMECODE info"
11768 exit(1)
11769 end
11770 unless gameport = launch_data.find { |line| line =~ /GAMEPORT=/ }
11771 $stdout.puts "error: launch_data contains no GAMEPORT info"
11772 Lich.log "error: launch_data contains no GAMEPORT info"
11773 exit(1)
11774 end
11775 unless gamehost = launch_data.find { |opt| opt =~ /GAMEHOST=/ }
11776 $stdout.puts "error: launch_data contains no GAMEHOST info"
11777 Lich.log "error: launch_data contains no GAMEHOST info"
11778 exit(1)
11779 end
11780 unless game = launch_data.find { |opt| opt =~ /GAME=/ }
11781 $stdout.puts "error: launch_data contains no GAME info"
11782 Lich.log "error: launch_data contains no GAME info"
11783 exit(1)
11784 end
11785 if custom_launch = launch_data.find { |opt| opt =~ /CUSTOMLAUNCH=/ }
11786 custom_launch.sub!(/^.*?\=/, '')
11787 Lich.log "info: using custom launch command: #{custom_launch}"
11788 end
11789 if custom_launch_dir = launch_data.find { |opt| opt =~ /CUSTOMLAUNCHDIR=/ }
11790 custom_launch_dir.sub!(/^.*?\=/, '')
11791 Lich.log "info: using working directory for custom launch command: #{custom_launch_dir}"
11792 end
11793 if ARGV.include?('--without-frontend')
11794 $frontend = 'unknown'
11795 unless (game_key = launch_data.find { |opt| opt =~ /KEY=/ }) && (game_key = game_key.split('=').last.chomp)
11796 $stdout.puts "error: launch_data contains no KEY info"
11797 Lich.log "error: launch_data contains no KEY info"
11798 exit(1)
11799 end
11800 elsif game =~ /SUKS/i
11801 $frontend = 'suks'
11802 unless (game_key = launch_data.find { |opt| opt =~ /KEY=/ }) && (game_key = game_key.split('=').last.chomp)
11803 $stdout.puts "error: launch_data contains no KEY info"
11804 Lich.log "error: launch_data contains no KEY info"
11805 exit(1)
11806 end
11807 elsif custom_launch
11808 unless (game_key = launch_data.find { |opt| opt =~ /KEY=/ }) && (game_key = game_key.split('=').last.chomp)
11809 $stdout.puts "error: launch_data contains no KEY info"
11810 Lich.log "error: launch_data contains no KEY info"
11811 exit(1)
11812 end
11813 else
11814 unless launcher_cmd = Lich.get_simu_launcher
11815 $stdout.puts 'error: failed to find the Simutronics launcher'
11816 Lich.log 'error: failed to find the Simutronics launcher'
11817 exit(1)
11818 end
11819 end
11820 gamecode = gamecode.split('=').last
11821 gameport = gameport.split('=').last
11822 gamehost = gamehost.split('=').last
11823 game = game.split('=').last
11824
11825 if (gameport == '10121') or (gameport == '10124')
11826 $platinum = true
11827 else
11828 $platinum = false
11829 end
11830 Lich.log "info: gamehost: #{gamehost}"
11831 Lich.log "info: gameport: #{gameport}"
11832 Lich.log "info: game: #{game}"
11833 if ARGV.include?('--without-frontend')
11834 $_CLIENT_ = nil
11835 elsif $frontend == 'suks'
11836 nil
11837 else
11838 if game =~ /WIZ/i
11839 $frontend = 'wizard'
11840 elsif game =~ /STORM/i
11841 $frontend = 'stormfront'
11842 else
11843 $frontend = 'unknown'
11844 end
11845 begin
11846 listener = TCPServer.new('127.0.0.1', nil)
11847 rescue
11848 $stdout.puts "--- error: cannot bind listen socket to local port: #{$!}"
11849 Lich.log "error: cannot bind listen socket to local port: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
11850 exit(1)
11851 end
11852 accept_thread = Thread.new { $_CLIENT_ = SynchronizedSocket.new(listener.accept) }
11853 localport = listener.addr[1]
11854 if custom_launch
11855 sal_filename = nil
11856 launcher_cmd = custom_launch.sub(/\%port\%/, localport.to_s).sub(/\%key\%/, game_key.to_s)
11857 scrubbed_launcher_cmd = custom_launch.sub(/\%port\%/, localport.to_s).sub(/\%key\%/, '[scrubbed key]')
11858 Lich.log "info: launcher_cmd: #{scrubbed_launcher_cmd}"
11859 else
11860 launch_data.collect! { |line| line.sub(/GAMEPORT=.+/, "GAMEPORT=#{localport}").sub(/GAMEHOST=.+/, "GAMEHOST=localhost") }
11861 sal_filename = "#{TEMP_DIR}/lich#{rand(10000)}.sal"
11862 while File.exists?(sal_filename)
11863 sal_filename = "#{TEMP_DIR}/lich#{rand(10000)}.sal"
11864 end
11865 File.open(sal_filename, 'w') { |f| f.puts launch_data }
11866 launcher_cmd = launcher_cmd.sub('%1', sal_filename)
11867 launcher_cmd = launcher_cmd.tr('/', "\\") if (RUBY_PLATFORM =~ /mingw|win/i) and (RUBY_PLATFORM !~ /darwin/i)
11868 end
11869 begin
11870 if custom_launch_dir
11871 Dir.chdir(custom_launch_dir)
11872 end
11873 if defined?(Win32)
11874 launcher_cmd =~ /^"(.*?)"\s*(.*)$/
11875 dir_file = $1
11876 param = $2
11877 dir = dir_file.slice(/^.*[\\\/]/)
11878 file = dir_file.sub(/^.*[\\\/]/, '')
11879 if Lich.win32_launch_method and Lich.win32_launch_method =~ /^(\d+):(.+)$/
11880 method_num = $1.to_i
11881 if $2 == 'fail'
11882 method_num = (method_num + 1) % 6
11883 end
11884 else
11885 method_num = 5
11886 end
11887 if method_num == 5
11888 begin
11889 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]
11890 if Win32.RegQueryValueEx(:hKey => key)[:lpData] =~ /Launcher\.exe/i
11891 associated = true
11892 else
11893 associated = false
11894 end
11895 rescue
11896 associated = false
11897 ensure
11898 Win32.RegCloseKey(:hKey => key) rescue nil
11899 end
11900 unless associated
11901 Lich.log "warning: skipping launch method #{method_num + 1} because .sal files are not associated with the Simutronics Launcher"
11902 method_num = (method_num + 1) % 6
11903 end
11904 end
11905 Lich.win32_launch_method = "#{method_num}:fail"
11906 if method_num == 0
11907 Lich.log "info: launcher_cmd: #{launcher_cmd}"
11908 spawn launcher_cmd
11909 elsif method_num == 1
11910 Lich.log "info: launcher_cmd: Win32.ShellExecute(:lpOperation => \"open\", :lpFile => #{file.inspect}, :lpDirectory => #{dir.inspect}, :lpParameters => #{param.inspect})"
11911 Win32.ShellExecute(:lpOperation => 'open', :lpFile => file, :lpDirectory => dir, :lpParameters => param)
11912 elsif method_num == 2
11913 Lich.log "info: launcher_cmd: Win32.ShellExecuteEx(:lpOperation => \"runas\", :lpFile => #{file.inspect}, :lpDirectory => #{dir.inspect}, :lpParameters => #{param.inspect})"
11914 Win32.ShellExecuteEx(:lpOperation => 'runas', :lpFile => file, :lpDirectory => dir, :lpParameters => param)
11915 elsif method_num == 3
11916 Lich.log "info: launcher_cmd: Win32.AdminShellExecute(:op => \"open\", :file => #{file.inspect}, :dir => #{dir.inspect}, :params => #{param.inspect})"
11917 Win32.AdminShellExecute(:op => 'open', :file => file, :dir => dir, :params => param)
11918 elsif method_num == 4
11919 Lich.log "info: launcher_cmd: Win32.AdminShellExecute(:op => \"runas\", :file => #{file.inspect}, :dir => #{dir.inspect}, :params => #{param.inspect})"
11920 Win32.AdminShellExecute(:op => 'runas', :file => file, :dir => dir, :params => param)
11921 else # method_num == 5
11922 file = File.expand_path(sal_filename).tr('/', "\\")
11923 dir = File.expand_path(File.dirname(sal_filename)).tr('/', "\\")
11924 Lich.log "info: launcher_cmd: Win32.ShellExecute(:lpOperation => \"open\", :lpFile => #{file.inspect}, :lpDirectory => #{dir.inspect})"
11925 Win32.ShellExecute(:lpOperation => 'open', :lpFile => file, :lpDirectory => dir)
11926 end
11927 elsif defined?(Wine)
11928 Lich.log "info: launcher_cmd: #{Wine::BIN} #{launcher_cmd}"
11929 spawn "#{Wine::BIN} #{launcher_cmd}"
11930 else
11931 Lich.log "info: launcher_cmd: #{launcher_cmd}"
11932 spawn launcher_cmd
11933 end
11934 rescue
11935 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
11936 Lich.msgbox(:message => "error: #{$!}", :icon => :error)
11937 end
11938 Lich.log 'info: waiting for client to connect...'
11939 300.times { sleep 0.1; break unless accept_thread.status }
11940 accept_thread.kill if accept_thread.status
11941 Dir.chdir(LICH_DIR)
11942 unless $_CLIENT_
11943 Lich.log "error: timeout waiting for client to connect"
11944 if defined?(Win32)
11945 Lich.msgbox(:message => "error: launch method #{method_num + 1} timed out waiting for the client to connect\n\nTry again and another method will be used.", :icon => :error)
11946 else
11947 Lich.msgbox(:message => "error: timeout waiting for client to connect", :icon => :error)
11948 end
11949 if sal_filename
11950 File.delete(sal_filename) rescue()
11951 end
11952 listener.close rescue()
11953 $_CLIENT_.close rescue()
11954 reconnect_if_wanted.call
11955 Lich.log "info: exiting..."
11956 Gtk.queue { Gtk.main_quit } if defined?(Gtk)
11957 exit
11958 end
11959 if defined?(Win32)
11960 Lich.win32_launch_method = "#{method_num}:success"
11961 end
11962 Lich.log 'info: connected'
11963 listener.close rescue nil
11964 if sal_filename
11965 File.delete(sal_filename) rescue nil
11966 end
11967 end
11968 gamehost, gameport = Lich.fix_game_host_port(gamehost, gameport)
11969 Lich.log "info: connecting to game server (#{gamehost}:#{gameport})"
11970 begin
11971 connect_thread = Thread.new {
11972 Game.open(gamehost, gameport)
11973 }
11974 300.times {
11975 sleep 0.1
11976 break unless connect_thread.status
11977 }
11978 if connect_thread.status
11979 connect_thread.kill rescue nil
11980 raise "error: timed out connecting to #{gamehost}:#{gameport}"
11981 end
11982 rescue
11983 Lich.log "error: #{$!}"
11984 gamehost, gameport = Lich.break_game_host_port(gamehost, gameport)
11985 Lich.log "info: connecting to game server (#{gamehost}:#{gameport})"
11986 begin
11987 connect_thread = Thread.new {
11988 Game.open(gamehost, gameport)
11989 }
11990 300.times {
11991 sleep 0.1
11992 break unless connect_thread.status
11993 }
11994 if connect_thread.status
11995 connect_thread.kill rescue nil
11996 raise "error: timed out connecting to #{gamehost}:#{gameport}"
11997 end
11998 rescue
11999 Lich.log "error: #{$!}"
12000 $_CLIENT_.close rescue nil
12001 reconnect_if_wanted.call
12002 Lich.log "info: exiting..."
12003 Gtk.queue { Gtk.main_quit } if defined?(Gtk)
12004 exit
12005 end
12006 end
12007 Lich.log 'info: connected'
12008 elsif game_host and game_port
12009 unless Lich.hosts_file
12010 Lich.log "error: cannot find hosts file"
12011 $stdout.puts "error: cannot find hosts file"
12012 exit
12013 end
12014 game_quad_ip = IPSocket.getaddress(game_host)
12015 error_count = 0
12016 begin
12017 listener = TCPServer.new('127.0.0.1', game_port)
12018 begin
12019 listener.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR,1)
12020 rescue
12021 Lich.log "warning: setsockopt with SO_REUSEADDR failed: #{$!}"
12022 end
12023 rescue
12024 sleep 1
12025 if (error_count += 1) >= 30
12026 $stdout.puts 'error: failed to bind to the proper port'
12027 Lich.log 'error: failed to bind to the proper port'
12028 exit!
12029 else
12030 retry
12031 end
12032 end
12033 Lich.modify_hosts(game_host)
12034
12035 $stdout.puts "Pretending to be #{game_host}"
12036 $stdout.puts "Listening on port #{game_port}"
12037 $stdout.puts "Waiting for the client to connect..."
12038 Lich.log "info: pretending to be #{game_host}"
12039 Lich.log "info: listening on port #{game_port}"
12040 Lich.log "info: waiting for the client to connect..."
12041
12042 timeout_thread = Thread.new {
12043 sleep 120
12044 listener.close rescue nil
12045 $stdout.puts 'error: timed out waiting for client to connect'
12046 Lich.log 'error: timed out waiting for client to connect'
12047 Lich.restore_hosts
12048 exit
12049 }
12050# $_CLIENT_ = listener.accept
12051 $_CLIENT_ = SynchronizedSocket.new(listener.accept)
12052 listener.close rescue nil
12053 timeout_thread.kill
12054 $stdout.puts "Connection with the local game client is open."
12055 Lich.log "info: connection with the game client is open"
12056 Lich.restore_hosts
12057 if test_mode
12058 $_SERVER_ = $stdin # fixme
12059 $_CLIENT_.puts "Running in test mode: host socket set to stdin."
12060 else
12061 Lich.log 'info: connecting to the real game host...'
12062 game_host, game_port = Lich.fix_game_host_port(game_host, game_port)
12063 begin
12064 timeout_thread = Thread.new {
12065 sleep 30
12066 Lich.log "error: timed out connecting to #{game_host}:#{game_port}"
12067 $stdout.puts "error: timed out connecting to #{game_host}:#{game_port}"
12068 exit
12069 }
12070 begin
12071 Game.open(game_host, game_port)
12072 rescue
12073 Lich.log "error: #{$!}"
12074 $stdout.puts "error: #{$!}"
12075 exit
12076 end
12077 timeout_thread.kill rescue nil
12078 Lich.log 'info: connection with the game host is open'
12079 end
12080 end
12081 else
12082 # offline mode removed
12083 Lich.log "error: don't know what to do"
12084 exit
12085 end
12086
12087 listener = timeout_thr = nil
12088
12089 #
12090 # drop superuser privileges
12091 #
12092 unless (RUBY_PLATFORM =~ /mingw|win/i) and (RUBY_PLATFORM !~ /darwin/i)
12093 Lich.log "info: dropping superuser privileges..."
12094 begin
12095 Process.uid = `id -ru`.strip.to_i
12096 Process.gid = `id -rg`.strip.to_i
12097 Process.egid = `id -rg`.strip.to_i
12098 Process.euid = `id -ru`.strip.to_i
12099 rescue SecurityError
12100 Lich.log "error: failed to drop superuser privileges: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
12101 rescue SystemCallError
12102 Lich.log "error: failed to drop superuser privileges: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
12103 rescue
12104 Lich.log "error: failed to drop superuser privileges: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
12105 end
12106 end
12107
12108 # backward compatibility
12109 if $frontend =~ /^(?:wizard|avalon)$/
12110 $fake_stormfront = true
12111 else
12112 $fake_stormfront = false
12113 end
12114
12115 undef :exit!
12116
12117 if ARGV.include?('--without-frontend')
12118 Thread.new {
12119 client_thread = nil
12120 #
12121 # send the login key
12122 #
12123 Game._puts(game_key)
12124 game_key = nil
12125 #
12126 # send version string
12127 #
12128 client_string = "/FE:WIZARD /VERSION:1.0.1.22 /P:#{RUBY_PLATFORM} /XML"
12129 $_CLIENTBUFFER_.push(client_string.dup)
12130 Game._puts(client_string)
12131 #
12132 # tell the server we're ready
12133 #
12134 2.times {
12135 sleep 0.3
12136 $_CLIENTBUFFER_.push("<c>\r\n")
12137 Game._puts("<c>")
12138 }
12139 $login_time = Time.now
12140 }
12141 else
12142 #
12143 # shutdown listening socket
12144 #
12145 error_count = 0
12146 begin
12147 # Somehow... for some ridiculous reason... Windows doesn't let us close the socket if we shut it down first...
12148 # listener.shutdown
12149 listener.close unless listener.closed?
12150 rescue
12151 Lich.log "warning: failed to close listener socket: #{$!}"
12152 if (error_count += 1) > 20
12153 Lich.log 'warning: giving up...'
12154 else
12155 sleep 0.05
12156 retry
12157 end
12158 end
12159
12160 $stdout = $_CLIENT_
12161 $_CLIENT_.sync = true
12162
12163 client_thread = Thread.new {
12164 $login_time = Time.now
12165
12166 if $offline_mode
12167 nil
12168 elsif $frontend =~ /^(?:wizard|avalon)$/
12169 #
12170 # send the login key
12171 #
12172 client_string = $_CLIENT_.gets
12173 Game._puts(client_string)
12174 #
12175 # take the version string from the client, ignore it, and ask the server for xml
12176 #
12177 $_CLIENT_.gets
12178 client_string = "/FE:WIZARD /VERSION:1.0.1.22 /P:#{RUBY_PLATFORM} /XML"
12179 $_CLIENTBUFFER_.push(client_string.dup)
12180 Game._puts(client_string)
12181 #
12182 # tell the server we're ready
12183 #
12184 2.times {
12185 sleep 0.3
12186 $_CLIENTBUFFER_.push("#{$cmd_prefix}\r\n")
12187 Game._puts($cmd_prefix)
12188 }
12189 #
12190 # set up some stuff
12191 #
12192 for client_string in [ "#{$cmd_prefix}_injury 2", "#{$cmd_prefix}_flag Display Inventory Boxes 1", "#{$cmd_prefix}_flag Display Dialog Boxes 0" ]
12193 $_CLIENTBUFFER_.push(client_string)
12194 Game._puts(client_string)
12195 end
12196 #
12197 # client wants to send "GOOD", xml server won't recognize it
12198 #
12199 $_CLIENT_.gets
12200 else
12201 inv_off_proc = proc { |server_string|
12202 if server_string =~ /^<(?:container|clearContainer|exposeContainer)/
12203 server_string.gsub!(/<(?:container|clearContainer|exposeContainer)[^>]*>|<inv.+\/inv>/, '')
12204 if server_string.empty?
12205 nil
12206 else
12207 server_string
12208 end
12209 elsif server_string =~ /^<flag id="Display Inventory Boxes" status='on' desc="Display all inventory and container windows."\/>/
12210 server_string.sub("status='on'", "status='off'")
12211 elsif server_string =~ /^\s*<d cmd="flag Inventory off">Inventory<\/d>\s+ON/
12212 server_string.sub("flag Inventory off", "flag Inventory on").sub('ON', 'OFF')
12213 else
12214 server_string
12215 end
12216 }
12217 DownstreamHook.add('inventory_boxes_off', inv_off_proc)
12218 inv_toggle_proc = proc { |client_string|
12219 if client_string =~ /^(?:<c>)?_flag Display Inventory Boxes ([01])/
12220 if $1 == '1'
12221 DownstreamHook.remove('inventory_boxes_off')
12222 Lich.set_inventory_boxes(XMLData.player_id, true)
12223 else
12224 DownstreamHook.add('inventory_boxes_off', inv_off_proc)
12225 Lich.set_inventory_boxes(XMLData.player_id, false)
12226 end
12227 nil
12228 elsif client_string =~ /^(?:<c>)?\s*(?:set|flag)\s+inv(?:e|en|ent|ento|entor|entory)?\s+(on|off)/i
12229 if $1.downcase == 'on'
12230 DownstreamHook.remove('inventory_boxes_off')
12231 respond 'You have enabled viewing of inventory and container windows.'
12232 Lich.set_inventory_boxes(XMLData.player_id, true)
12233 else
12234 DownstreamHook.add('inventory_boxes_off', inv_off_proc)
12235 respond 'You have disabled viewing of inventory and container windows.'
12236 Lich.set_inventory_boxes(XMLData.player_id, false)
12237 end
12238 nil
12239 else
12240 client_string
12241 end
12242 }
12243 UpstreamHook.add('inventory_boxes_toggle', inv_toggle_proc)
12244
12245 unless $offline_mode
12246 client_string = $_CLIENT_.gets
12247 Game._puts(client_string)
12248 client_string = $_CLIENT_.gets
12249 $_CLIENTBUFFER_.push(client_string.dup)
12250 Game._puts(client_string)
12251 end
12252 end
12253
12254 begin
12255 while client_string = $_CLIENT_.gets
12256 client_string = "#{$cmd_prefix}#{client_string}" if $frontend =~ /^(?:wizard|avalon)$/
12257 begin
12258 $_IDLETIMESTAMP_ = Time.now
12259 do_client(client_string)
12260 rescue
12261 respond "--- Lich: error: client_thread: #{$!}"
12262 respond $!.backtrace.first
12263 Lich.log "error: client_thread: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
12264 end
12265 end
12266 rescue
12267 respond "--- Lich: error: client_thread: #{$!}"
12268 respond $!.backtrace.first
12269 Lich.log "error: client_thread: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
12270 sleep 0.2
12271 retry unless $_CLIENT_.closed? or Game.closed? or !Game.thread.alive? or ($!.to_s =~ /invalid argument|A connection attempt failed|An existing connection was forcibly closed/i)
12272 end
12273 Game.close
12274 }
12275 end
12276
12277 if detachable_client_port
12278 detachable_client_thread = Thread.new {
12279 loop {
12280 begin
12281 server = TCPServer.new('127.0.0.1', detachable_client_port)
12282 $_DETACHABLE_CLIENT_ = SynchronizedSocket.new(server.accept)
12283 $_DETACHABLE_CLIENT_.sync = true
12284 rescue
12285 Lich.log "#{$!}\n\t#{$!.backtrace.join("\n\t")}"
12286 server.close rescue nil
12287 $_DETACHABLE_CLIENT_.close rescue nil
12288 $_DETACHABLE_CLIENT_ = nil
12289 sleep 5
12290 next
12291 ensure
12292 server.close rescue nil
12293 end
12294 if $_DETACHABLE_CLIENT_
12295 begin
12296 $frontend = 'profanity'
12297 Thread.new {
12298 100.times { sleep 0.1; break if XMLData.indicator['IconJOINED'] }
12299 init_str = "<progressBar id='mana' value='0' text='mana #{XMLData.mana}/#{XMLData.max_mana}'/>"
12300 init_str.concat "<progressBar id='health' value='0' text='health #{XMLData.health}/#{XMLData.max_health}'/>"
12301 init_str.concat "<progressBar id='spirit' value='0' text='spirit #{XMLData.spirit}/#{XMLData.max_spirit}'/>"
12302 init_str.concat "<progressBar id='stamina' value='0' text='stamina #{XMLData.stamina}/#{XMLData.max_stamina}'/>"
12303 init_str.concat "<progressBar id='encumlevel' value='#{XMLData.encumbrance_value}' text='#{XMLData.encumbrance_text}'/>"
12304 init_str.concat "<progressBar id='pbarStance' value='#{XMLData.stance_value}'/>"
12305 init_str.concat "<progressBar id='mindState' value='#{XMLData.mind_value}' text='#{XMLData.mind_text}'/>"
12306 init_str.concat "<spell>#{XMLData.prepared_spell}</spell>"
12307 init_str.concat "<right>#{GameObj.right_hand.name}</right>"
12308 init_str.concat "<left>#{GameObj.left_hand.name}</left>"
12309 for indicator in [ 'IconBLEEDING', 'IconPOISONED', 'IconDISEASED', 'IconSTANDING', 'IconKNEELING', 'IconSITTING', 'IconPRONE' ]
12310 init_str.concat "<indicator id='#{indicator}' visible='#{XMLData.indicator[indicator]}'/>"
12311 end
12312 for area in [ 'back', 'leftHand', 'rightHand', 'head', 'rightArm', 'abdomen', 'leftEye', 'leftArm', 'chest', 'rightLeg', 'neck', 'leftLeg', 'nsys', 'rightEye' ]
12313 if Wounds.send(area) > 0
12314 init_str.concat "<image id=\"#{area}\" name=\"Injury#{Wounds.send(area)}\"/>"
12315 elsif Scars.send(area) > 0
12316 init_str.concat "<image id=\"#{area}\" name=\"Scar#{Scars.send(area)}\"/>"
12317 end
12318 end
12319 init_str.concat '<compass>'
12320 shorten_dir = { 'north' => 'n', 'northeast' => 'ne', 'east' => 'e', 'southeast' => 'se', 'south' => 's', 'southwest' => 'sw', 'west' => 'w', 'northwest' => 'nw', 'up' => 'up', 'down' => 'down', 'out' => 'out' }
12321 for dir in XMLData.room_exits
12322 if short_dir = shorten_dir[dir]
12323 init_str.concat "<dir value='#{short_dir}'/>"
12324 end
12325 end
12326 init_str.concat '</compass>'
12327 $_DETACHABLE_CLIENT_.puts init_str
12328 init_str = nil
12329 }
12330 while client_string = $_DETACHABLE_CLIENT_.gets
12331 client_string = "#{$cmd_prefix}#{client_string}" # if $frontend =~ /^(?:wizard|avalon)$/
12332 begin
12333 $_IDLETIMESTAMP_ = Time.now
12334 do_client(client_string)
12335 rescue
12336 respond "--- Lich: error: client_thread: #{$!}"
12337 respond $!.backtrace.first
12338 Lich.log "error: client_thread: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
12339 end
12340 end
12341 rescue
12342 respond "--- Lich: error: client_thread: #{$!}"
12343 respond $!.backtrace.first
12344 Lich.log "error: client_thread: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
12345 $_DETACHABLE_CLIENT_.close rescue nil
12346 $_DETACHABLE_CLIENT_ = nil
12347 ensure
12348 $_DETACHABLE_CLIENT_.close rescue nil
12349 $_DETACHABLE_CLIENT_ = nil
12350 end
12351 end
12352 sleep 0.1
12353 }
12354 }
12355 else
12356 detachable_client_thread = nil
12357 end
12358
12359 wait_while { $offline_mode }
12360
12361 if $frontend == 'wizard'
12362 $link_highlight_start = "\207"
12363 $link_highlight_end = "\240"
12364 $speech_highlight_start = "\212"
12365 $speech_highlight_end = "\240"
12366 end
12367
12368 client_thread.priority = 3
12369
12370 $_CLIENT_.puts "\n--- Lich v#{LICH_VERSION} is active. Type #{$clean_lich_char}help for usage info.\n\n"
12371
12372 Game.thread.join
12373 client_thread.kill rescue nil
12374 detachable_client_thread.kill rescue nil
12375
12376 Lich.log 'info: stopping scripts...'
12377 Script.running.each { |script| script.kill }
12378 Script.hidden.each { |script| script.kill }
12379 200.times { sleep 0.1; break if Script.running.empty? and Script.hidden.empty? }
12380 Lich.log 'info: saving script settings...'
12381 Settings.save
12382 Vars.save
12383 Lich.log 'info: closing connections...'
12384 Game.close
12385 $_CLIENT_.close rescue nil
12386# Lich.db.close rescue nil
12387 reconnect_if_wanted.call
12388 Lich.log "info: exiting..."
12389 Gtk.queue { Gtk.main_quit } if defined?(Gtk)
12390 exit
12391}
12392
12393if defined?(Gtk)
12394 Thread.current.priority = -10
12395 Gtk.main
12396else
12397 main_thread.join
12398end
12399exit