· 9 years ago · Jan 01, 2017, 10:30 PM
1LICH_VERSION = '4.6.36'
2TESTING = false
3
4if RUBY_VERSION !~ /^2/
5 if (RUBY_PLATFORM =~ /mingw|win/) and (RUBY_PLATFORM !~ /darwin/i)
6 if RUBY_VERSION =~ /^1\.9/
7 require 'fiddle'
8 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)
9 else
10 # fixme: This message never shows up on Ruby 1.8 because it errors out on negative lookbehind regex later in the file
11 require 'dl'
12 DL.dlopen('user32.dll')['MessageBox', 'LLPPL'].call(0, 'Upgrade Ruby to version 2.0', "Lich v#{LICH_VERSION}", 16)
13 end
14 else
15 puts "Upgrade Ruby to version 2.0"
16 end
17 exit
18end
19
20require 'time'
21require 'socket'
22require 'rexml/document'
23require 'rexml/streamlistener'
24require 'stringio'
25require 'zlib'
26require 'drb'
27require 'resolv'
28require 'digest/md5'
29
30begin
31 # stupid workaround for Windows
32 # seems to avoid a 10 second lag when starting lnet, without adding a 10 second lag at startup
33 require 'openssl'
34 OpenSSL::PKey::RSA.new(512)
35rescue LoadError
36 nil # not required for basic Lich; however, lnet and repository scripts will fail without openssl
37rescue
38 nil
39end
40if (RUBY_PLATFORM =~ /mingw|win/i) and (RUBY_PLATFORM !~ /darwin/i)
41 #
42 # Windows API made slightly less annoying
43 #
44 require 'fiddle'
45 require 'fiddle/import'
46 module Win32
47 SIZEOF_CHAR = Fiddle::SIZEOF_CHAR
48 SIZEOF_LONG = Fiddle::SIZEOF_LONG
49 SEE_MASK_NOCLOSEPROCESS = 0x00000040
50 MB_OK = 0x00000000
51 MB_OKCANCEL = 0x00000001
52 MB_YESNO = 0x00000004
53 MB_ICONERROR = 0x00000010
54 MB_ICONQUESTION = 0x00000020
55 MB_ICONWARNING = 0x00000030
56 IDIOK = 1
57 IDICANCEL = 2
58 IDIYES = 6
59 IDINO = 7
60 KEY_ALL_ACCESS = 0xF003F
61 KEY_CREATE_SUB_KEY = 0x0004
62 KEY_ENUMERATE_SUB_KEYS = 0x0008
63 KEY_EXECUTE = 0x20019
64 KEY_NOTIFY = 0x0010
65 KEY_QUERY_VALUE = 0x0001
66 KEY_READ = 0x20019
67 KEY_SET_VALUE = 0x0002
68 KEY_WOW64_32KEY = 0x0200
69 KEY_WOW64_64KEY = 0x0100
70 KEY_WRITE = 0x20006
71 TokenElevation = 20
72 TOKEN_QUERY = 8
73 STILL_ACTIVE = 259
74 SW_SHOWNORMAL = 1
75 SW_SHOW = 5
76 PROCESS_QUERY_INFORMATION = 1024
77 PROCESS_VM_READ = 16
78 HKEY_LOCAL_MACHINE = -2147483646
79 REG_NONE = 0
80 REG_SZ = 1
81 REG_EXPAND_SZ = 2
82 REG_BINARY = 3
83 REG_DWORD = 4
84 REG_DWORD_LITTLE_ENDIAN = 4
85 REG_DWORD_BIG_ENDIAN = 5
86 REG_LINK = 6
87 REG_MULTI_SZ = 7
88 REG_QWORD = 11
89 REG_QWORD_LITTLE_ENDIAN = 11
90
91 module Kernel32
92 extend Fiddle::Importer
93 dlload 'kernel32'
94 extern 'int GetCurrentProcess()'
95 extern 'int GetExitCodeProcess(int, int*)'
96 extern 'int GetModuleFileName(int, void*, int)'
97 extern 'int GetVersionEx(void*)'
98# extern 'int OpenProcess(int, int, int)' # fixme
99 extern 'int GetLastError()'
100 extern 'int CreateProcess(void*, void*, void*, void*, int, int, void*, void*, void*, void*)'
101 end
102 def Win32.GetLastError
103 return Kernel32.GetLastError()
104 end
105 def Win32.CreateProcess(args)
106 if args[:lpCommandLine]
107 lpCommandLine = args[:lpCommandLine].dup
108 else
109 lpCommandLine = nil
110 end
111 if args[:bInheritHandles] == false
112 bInheritHandles = 0
113 elsif args[:bInheritHandles] == true
114 bInheritHandles = 1
115 else
116 bInheritHandles = args[:bInheritHandles].to_i
117 end
118 if args[:lpEnvironment].class == Array
119 # fixme
120 end
121 lpStartupInfo = [ 68, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]
122 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 }
123 for sym in [ :lpDesktop, :lpTitle ]
124 if args[sym]
125 args[sym] = "#{args[sym]}\0" unless args[sym][-1,1] == "\0"
126 lpStartupInfo[lpStartupInfo_index[sym]] = Fiddle::Pointer.to_ptr(args[sym]).to_i
127 end
128 end
129 for sym in [ :dwX, :dwY, :dwXSize, :dwYSize, :dwXCountChars, :dwYCountChars, :dwFillAttribute, :dwFlags, :wShowWindow, :hStdInput, :hStdOutput, :hStdError ]
130 if args[sym]
131 lpStartupInfo[lpStartupInfo_index[sym]] = args[sym]
132 end
133 end
134 lpStartupInfo = lpStartupInfo.pack('LLLLLLLLLLLLSSLLLL')
135 lpProcessInformation = [ 0, 0, 0, 0, ].pack('LLLL')
136 r = Kernel32.CreateProcess(args[:lpApplicationName], lpCommandLine, args[:lpProcessAttributes], args[:lpThreadAttributes], bInheritHandles, args[:dwCreationFlags].to_i, args[:lpEnvironment], args[:lpCurrentDirectory], lpStartupInfo, lpProcessInformation)
137 lpProcessInformation = lpProcessInformation.unpack('LLLL')
138 return :return => (r > 0 ? true : false), :hProcess => lpProcessInformation[0], :hThread => lpProcessInformation[1], :dwProcessId => lpProcessInformation[2], :dwThreadId => lpProcessInformation[3]
139 end
140# Win32.CreateProcess(:lpApplicationName => 'Launcher.exe', :lpCommandLine => 'lich2323.sal', :lpCurrentDirectory => 'C:\\PROGRA~1\\SIMU')
141# def Win32.OpenProcess(args={})
142# return Kernel32.OpenProcess(args[:dwDesiredAccess].to_i, args[:bInheritHandle].to_i, args[:dwProcessId].to_i)
143# end
144 def Win32.GetCurrentProcess
145 return Kernel32.GetCurrentProcess
146 end
147 def Win32.GetExitCodeProcess(args)
148 lpExitCode = [ 0 ].pack('L')
149 r = Kernel32.GetExitCodeProcess(args[:hProcess].to_i, lpExitCode)
150 return :return => r, :lpExitCode => lpExitCode.unpack('L')[0]
151 end
152 def Win32.GetModuleFileName(args={})
153 args[:nSize] ||= 256
154 buffer = "\0" * args[:nSize].to_i
155 r = Kernel32.GetModuleFileName(args[:hModule].to_i, buffer, args[:nSize].to_i)
156 return :return => r, :lpFilename => buffer.gsub("\0", '')
157 end
158 def Win32.GetVersionEx
159 a = [ 156, 0, 0, 0, 0, ("\0" * 128), 0, 0, 0, 0, 0].pack('LLLLLa128SSSCC')
160 r = Kernel32.GetVersionEx(a)
161 a = a.unpack('LLLLLa128SSSCC')
162 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]
163 end
164
165 module User32
166 extend Fiddle::Importer
167 dlload 'user32'
168 extern 'int MessageBox(int, char*, char*, int)'
169 end
170 def Win32.MessageBox(args)
171 args[:lpCaption] ||= "Lich v#{LICH_VERSION}"
172 return User32.MessageBox(args[:hWnd].to_i, args[:lpText], args[:lpCaption], args[:uType].to_i)
173 end
174
175 module Advapi32
176 extend Fiddle::Importer
177 dlload 'advapi32'
178 extern 'int GetTokenInformation(int, int, void*, int, void*)'
179 extern 'int OpenProcessToken(int, int, void*)'
180 extern 'int RegOpenKeyEx(int, char*, int, int, void*)'
181 extern 'int RegQueryValueEx(int, char*, void*, void*, void*, void*)'
182 extern 'int RegSetValueEx(int, char*, int, int, char*, int)'
183 extern 'int RegDeleteValue(int, char*)'
184 extern 'int RegCloseKey(int)'
185 end
186 def Win32.GetTokenInformation(args)
187 if args[:TokenInformationClass] == TokenElevation
188 token_information_length = SIZEOF_LONG
189 token_information = [ 0 ].pack('L')
190 else
191 return nil
192 end
193 return_length = [ 0 ].pack('L')
194 r = Advapi32.GetTokenInformation(args[:TokenHandle].to_i, args[:TokenInformationClass], token_information, token_information_length, return_length)
195 if args[:TokenInformationClass] == TokenElevation
196 return :return => r, :TokenIsElevated => token_information.unpack('L')[0]
197 end
198 end
199 def Win32.OpenProcessToken(args)
200 token_handle = [ 0 ].pack('L')
201 r = Advapi32.OpenProcessToken(args[:ProcessHandle].to_i, args[:DesiredAccess].to_i, token_handle)
202 return :return => r, :TokenHandle => token_handle.unpack('L')[0]
203 end
204 def Win32.RegOpenKeyEx(args)
205 phkResult = [ 0 ].pack('L')
206 r = Advapi32.RegOpenKeyEx(args[:hKey].to_i, args[:lpSubKey].to_s, 0, args[:samDesired].to_i, phkResult)
207 return :return => r, :phkResult => phkResult.unpack('L')[0]
208 end
209 def Win32.RegQueryValueEx(args)
210 args[:lpValueName] ||= 0
211 lpcbData = [ 0 ].pack('L')
212 r = Advapi32.RegQueryValueEx(args[:hKey].to_i, args[:lpValueName], 0, 0, 0, lpcbData)
213 if r == 0
214 lpcbData = lpcbData.unpack('L')[0]
215 lpData = String.new.rjust(lpcbData, "\x00")
216 lpcbData = [ lpcbData ].pack('L')
217 lpType = [ 0 ].pack('L')
218 r = Advapi32.RegQueryValueEx(args[:hKey].to_i, args[:lpValueName], 0, lpType, lpData, lpcbData)
219 lpType = lpType.unpack('L')[0]
220 lpcbData = lpcbData.unpack('L')[0]
221 if [REG_EXPAND_SZ, REG_SZ, REG_LINK].include?(lpType)
222 lpData.gsub!("\x00", '')
223 elsif lpType == REG_MULTI_SZ
224 lpData = lpData.gsub("\x00\x00", '').split("\x00")
225 elsif lpType == REG_DWORD
226 lpData = lpData.unpack('L')[0]
227 elsif lpType == REG_QWORD
228 lpData = lpData.unpack('Q')[0]
229 elsif lpType == REG_BINARY
230 # fixme
231 elsif lpType == REG_DWORD_BIG_ENDIAN
232 # fixme
233 else
234 # fixme
235 end
236 return :return => r, :lpType => lpType, :lpcbData => lpcbData, :lpData => lpData
237 else
238 return :return => r
239 end
240 end
241 def Win32.RegSetValueEx(args)
242 if [REG_EXPAND_SZ, REG_SZ, REG_LINK].include?(args[:dwType]) and (args[:lpData].class == String)
243 lpData = args[:lpData].dup
244 lpData.concat("\x00")
245 cbData = lpData.length
246 elsif (args[:dwType] == REG_MULTI_SZ) and (args[:lpData].class == Array)
247 lpData = args[:lpData].join("\x00").concat("\x00\x00")
248 cbData = lpData.length
249 elsif (args[:dwType] == REG_DWORD) and (args[:lpData].class == Fixnum)
250 lpData = [args[:lpData]].pack('L')
251 cbData = 4
252 elsif (args[:dwType] == REG_QWORD) and (args[:lpData].class == Fixnum or args[:lpData].class == Bignum)
253 lpData = [args[:lpData]].pack('Q')
254 cbData = 8
255 elsif args[:dwType] == REG_BINARY
256 # fixme
257 return false
258 elsif args[:dwType] == REG_DWORD_BIG_ENDIAN
259 # fixme
260 return false
261 else
262 # fixme
263 return false
264 end
265 args[:lpValueName] ||= 0
266 return Advapi32.RegSetValueEx(args[:hKey].to_i, args[:lpValueName], 0, args[:dwType], lpData, cbData)
267 end
268 def Win32.RegDeleteValue(args)
269 args[:lpValueName] ||= 0
270 return Advapi32.RegDeleteValue(args[:hKey].to_i, args[:lpValueName])
271 end
272 def Win32.RegCloseKey(args)
273 return Advapi32.RegCloseKey(args[:hKey])
274 end
275
276 module Shell32
277 extend Fiddle::Importer
278 dlload 'shell32'
279 extern 'int ShellExecuteEx(void*)'
280 extern 'int ShellExecute(int, char*, char*, char*, char*, int)'
281 end
282 def Win32.ShellExecuteEx(args)
283# struct = [ (SIZEOF_LONG * 15), 0, 0, 0, 0, 0, 0, SW_SHOWNORMAL, 0, 0, 0, 0, 0, 0, 0 ]
284 struct = [ (SIZEOF_LONG * 15), 0, 0, 0, 0, 0, 0, SW_SHOW, 0, 0, 0, 0, 0, 0, 0 ]
285 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 }
286 for sym in [ :lpVerb, :lpFile, :lpParameters, :lpDirectory, :lpIDList, :lpClass ]
287 if args[sym]
288 args[sym] = "#{args[sym]}\0" unless args[sym][-1,1] == "\0"
289 struct[struct_index[sym]] = Fiddle::Pointer.to_ptr(args[sym]).to_i
290 end
291 end
292 for sym in [ :fMask, :hwnd, :nShow, :hkeyClass, :dwHotKey, :hIcon, :hMonitor, :hProcess ]
293 if args[sym]
294 struct[struct_index[sym]] = args[sym]
295 end
296 end
297 struct = struct.pack('LLLLLLLLLLLLLLL')
298 r = Shell32.ShellExecuteEx(struct)
299 struct = struct.unpack('LLLLLLLLLLLLLLL')
300 return :return => r, :hProcess => struct[struct_index[:hProcess]], :hInstApp => struct[struct_index[:hInstApp]]
301 end
302 def Win32.ShellExecute(args)
303 args[:lpOperation] ||= 0
304 args[:lpParameters] ||= 0
305 args[:lpDirectory] ||= 0
306 args[:nShowCmd] ||= 1
307 return Shell32.ShellExecute(args[:hwnd].to_i, args[:lpOperation], args[:lpFile], args[:lpParameters], args[:lpDirectory], args[:nShowCmd])
308 end
309
310 begin
311 module Kernel32
312 extern 'int EnumProcesses(void*, int, void*)'
313 end
314 def Win32.EnumProcesses(args={})
315 args[:cb] ||= 400
316 pProcessIds = Array.new((args[:cb]/SIZEOF_LONG), 0).pack(''.rjust((args[:cb]/SIZEOF_LONG), 'L'))
317 pBytesReturned = [ 0 ].pack('L')
318 r = Kernel32.EnumProcesses(pProcessIds, args[:cb], pBytesReturned)
319 pBytesReturned = pBytesReturned.unpack('L')[0]
320 return :return => r, :pProcessIds => pProcessIds.unpack(''.rjust((args[:cb]/SIZEOF_LONG), 'L'))[0...(pBytesReturned/SIZEOF_LONG)], :pBytesReturned => pBytesReturned
321 end
322 rescue
323 module Psapi
324 extend Fiddle::Importer
325 dlload 'psapi'
326 extern 'int EnumProcesses(void*, int, void*)'
327 end
328 def Win32.EnumProcesses(args={})
329 args[:cb] ||= 400
330 pProcessIds = Array.new((args[:cb]/SIZEOF_LONG), 0).pack(''.rjust((args[:cb]/SIZEOF_LONG), 'L'))
331 pBytesReturned = [ 0 ].pack('L')
332 r = Psapi.EnumProcesses(pProcessIds, args[:cb], pBytesReturned)
333 pBytesReturned = pBytesReturned.unpack('L')[0]
334 return :return => r, :pProcessIds => pProcessIds.unpack(''.rjust((args[:cb]/SIZEOF_LONG), 'L'))[0...(pBytesReturned/SIZEOF_LONG)], :pBytesReturned => pBytesReturned
335 end
336 end
337
338 def Win32.isXP?
339 return (Win32.GetVersionEx[:dwMajorVersion] < 6)
340 end
341# def Win32.isWin8?
342# r = Win32.GetVersionEx
343# return ((r[:dwMajorVersion] == 6) and (r[:dwMinorVersion] >= 2))
344# end
345 def Win32.admin?
346 if Win32.isXP?
347 return true
348 else
349 r = Win32.OpenProcessToken(:ProcessHandle => Win32.GetCurrentProcess, :DesiredAccess => TOKEN_QUERY)
350 token_handle = r[:TokenHandle]
351 r = Win32.GetTokenInformation(:TokenInformationClass => TokenElevation, :TokenHandle => token_handle)
352 return (r[:TokenIsElevated] != 0)
353 end
354 end
355 def Win32.AdminShellExecute(args)
356 # open ruby/lich as admin and tell it to open something else
357 if not caller.any? { |c| c =~ /eval|run/ }
358 r = Win32.GetModuleFileName
359 if r[:return] > 0
360 if File.exists?(r[:lpFilename])
361 Win32.ShellExecuteEx(:lpVerb => 'runas', :lpFile => r[:lpFilename], :lpParameters => "#{File.expand_path($PROGRAM_NAME)} shellexecute #{[Marshal.dump(args)].pack('m').gsub("\n",'')}")
362 end
363 end
364 end
365 end
366 end
367else
368 if arg = ARGV.find { |a| a =~ /^--wine=.+$/i }
369 $wine_bin = arg.sub(/^--wine=/, '')
370 else
371 begin
372 $wine_bin = `which wine`.strip
373 rescue
374 $wine_bin = nil
375 end
376 end
377 if arg = ARGV.find { |a| a =~ /^--wine-prefix=.+$/i }
378 $wine_prefix = arg.sub(/^--wine-prefix=/, '')
379 elsif ENV['WINEPREFIX']
380 $wine_prefix = ENV['WINEPREFIX']
381 elsif ENV['HOME']
382 $wine_prefix = ENV['HOME'] + '/.wine'
383 else
384 $wine_prefix = nil
385 end
386 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)
387 module Wine
388 BIN = $wine_bin
389 PREFIX = $wine_prefix
390 def Wine.registry_gets(key)
391 hkey, subkey, thingie = /(HKEY_LOCAL_MACHINE|HKEY_CURRENT_USER)\\(.+)\\([^\\]*)/.match(key).captures # fixme: stupid highlights ]/
392 if File.exists?(PREFIX + '/system.reg')
393 if hkey == 'HKEY_LOCAL_MACHINE'
394 subkey = "[#{subkey.gsub('\\', '\\\\\\')}]"
395 if thingie.nil? or thingie.empty?
396 thingie = '@'
397 else
398 thingie = "\"#{thingie}\""
399 end
400 lookin = result = false
401 File.open(PREFIX + '/system.reg') { |f| f.readlines }.each { |line|
402 if line[0...subkey.length] == subkey
403 lookin = true
404 elsif line =~ /^\[/
405 lookin = false
406 elsif lookin and line =~ /^#{thingie}="(.*)"$/i
407 result = $1.split('\\"').join('"').split('\\\\').join('\\').sub(/\\0$/, '')
408 break
409 end
410 }
411 return result
412 else
413 return false
414 end
415 else
416 return false
417 end
418 end
419 def Wine.registry_puts(key, value)
420 hkey, subkey, thingie = /(HKEY_LOCAL_MACHINE|HKEY_CURRENT_USER)\\(.+)\\([^\\]*)/.match(key).captures # fixme ]/
421 if File.exists?(PREFIX)
422 if thingie.nil? or thingie.empty?
423 thingie = '@'
424 else
425 thingie = "\"#{thingie}\""
426 end
427 # gsub sucks for this..
428 value = value.split('\\').join('\\\\')
429 value = value.split('"').join('\"')
430 begin
431 regedit_data = "REGEDIT4\n\n[#{hkey}\\#{subkey}]\n#{thingie}=\"#{value}\"\n\n"
432 filename = "#{TEMP_DIR}/wine-#{Time.now.to_i}.reg"
433 File.open(filename, 'w') { |f| f.write(regedit_data) }
434 system("#{BIN} regedit #{filename}")
435 sleep 0.2
436 File.delete(filename)
437 rescue
438 return false
439 end
440 return true
441 end
442 end
443 end
444 end
445 $wine_bin = nil
446 $wine_prefix = nil
447end
448
449if ARGV[0] == 'shellexecute'
450 args = Marshal.load(ARGV[1].unpack('m')[0])
451 Win32.ShellExecute(:lpOperation => args[:op], :lpFile => args[:file], :lpDirectory => args[:dir], :lpParameters => args[:params])
452 exit
453end
454
455begin
456 require 'sqlite3'
457rescue LoadError
458 if defined?(Win32)
459 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))
460 if r == Win32::IDIYES
461 r = Win32.GetModuleFileName
462 if r[:return] > 0
463 ruby_bin_dir = File.dirname(r[:lpFilename])
464 if File.exists?("#{ruby_bin_dir}\\gem.bat")
465 verb = (Win32.isXP? ? 'open' : 'runas')
466 # fixme: using --source http://rubygems.org to avoid https because it has been failing to validate the certificate on Windows
467 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')
468 if r[:return] > 0
469 pid = r[:hProcess]
470 sleep 1 while Win32.GetExitCodeProcess(:hProcess => pid)[:lpExitCode] == Win32::STILL_ACTIVE
471 r = Win32.MessageBox(:lpText => "Install finished. Lich will restart now.", :lpCaption => "Lich v#{LICH_VERSION}", :uType => Win32::MB_OKCANCEL)
472 else
473 # ShellExecuteEx failed: this seems to happen with an access denied error even while elevated on some random systems
474 r = Win32.ShellExecute(:lpOperation => verb, :lpFile => "#{ruby_bin_dir}\\gem.bat", :lpParameters => 'install sqlite3 --source http://rubygems.org --no-ri --no-rdoc')
475 if r <= 32
476 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))
477 exit
478 end
479 r = Win32.MessageBox(:lpText => "When the installer is finished, click OK to restart Lich.", :lpCaption => "Lich v#{LICH_VERSION}", :uType => Win32::MB_OKCANCEL)
480 end
481 if r == Win32::IDIOK
482 if File.exists?("#{ruby_bin_dir}\\rubyw.exe")
483 Win32.ShellExecute(:lpOperation => 'open', :lpFile => "#{ruby_bin_dir}\\rubyw.exe", :lpParameters => "\"#{File.expand_path($PROGRAM_NAME)}\"")
484 else
485 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))
486 end
487 else
488 # user doesn't want to restart Lich
489 end
490 else
491 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))
492 end
493 else
494 Win32.MessageBox(:lpText => "error: GetModuleFileName failed", :lpCaption => "Lich v#{LICH_VERSION}", :uType => (Win32::MB_OK | Win32::MB_ICONERROR))
495 end
496 else
497 # user doesn't want to install sqlite3 gem
498 end
499 else
500 # fixme: no sqlite3 on Linux/Mac
501 puts "The sqlite3 gem is not installed (or failed to load), you may need to: sudo gem install sqlite3"
502 end
503 exit
504end
505
506begin
507 require 'gtk2'
508 HAVE_GTK = true
509rescue LoadError
510 if ARGV.empty? or ARGV.any? { |arg| arg =~ /^--gui$/ } or not $stdout.isatty
511 if defined?(Win32)
512 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))
513 if r == Win32::IDIYES
514 r = Win32.GetModuleFileName
515 if r[:return] > 0
516 ruby_bin_dir = File.dirname(r[:lpFilename])
517 if File.exists?("#{ruby_bin_dir}\\gem.bat")
518 verb = (Win32.isXP? ? 'open' : 'runas')
519 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 --no-ri --no-rdoc')
520 if r[:return] > 0
521 pid = r[:hProcess]
522 sleep 1 while Win32.GetExitCodeProcess(:hProcess => pid)[:lpExitCode] == Win32::STILL_ACTIVE
523 r = Win32.MessageBox(:lpText => "Install finished. Lich will restart now.", :lpCaption => "Lich v#{LICH_VERSION}", :uType => Win32::MB_OKCANCEL)
524 else
525 # ShellExecuteEx failed: this seems to happen with an access denied error even while elevated on some random systems
526 r = Win32.ShellExecute(:lpOperation => verb, :lpFile => "#{ruby_bin_dir}\\gem.bat", :lpParameters => 'install cairo:1.14.3 gtk2:2.2.5 --no-ri --no-rdoc')
527 if r <= 32
528 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 --no-ri --no-rdoc\")\n\nerror code: #{Win32.GetLastError}", :lpCaption => "Lich v#{LICH_VERSION}", :uType => (Win32::MB_OK | Win32::MB_ICONERROR))
529 exit
530 end
531 r = Win32.MessageBox(:lpText => "When the installer is finished, click OK to restart Lich.", :lpCaption => "Lich v#{LICH_VERSION}", :uType => Win32::MB_OKCANCEL)
532 end
533 if r == Win32::IDIOK
534 if File.exists?("#{ruby_bin_dir}\\rubyw.exe")
535 Win32.ShellExecute(:lpOperation => 'open', :lpFile => "#{ruby_bin_dir}\\rubyw.exe", :lpParameters => "\"#{File.expand_path($PROGRAM_NAME)}\"")
536 else
537 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))
538 end
539 else
540 # user doesn't want to restart Lich
541 end
542 else
543 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))
544 end
545 else
546 Win32.MessageBox(:lpText => "error: GetModuleFileName failed", :lpCaption => "Lich v#{LICH_VERSION}", :uType => (Win32::MB_OK | Win32::MB_ICONERROR))
547 end
548 else
549 # user doesn't want to install gtk2 gem
550 end
551 else
552 # fixme: no gtk2 on Linux/Mac
553 puts "The gtk2 gem is not installed (or failed to load), you may need to: sudo gem install gtk2"
554 end
555 exit
556 else
557 # gtk is optional if command line arguments are given or started in a terminal
558 HAVE_GTK = false
559 early_gtk_error = "warning: failed to load GTK\n\t#{$!}\n\t#{$!.backtrace.join("\n\t")}"
560 end
561end
562
563if defined?(Gtk)
564 module Gtk
565 # Calling Gtk API in a thread other than the main thread may cause random segfaults
566 def Gtk.queue &block
567 GLib::Timeout.add(1) {
568 begin
569 block.call
570 rescue
571 respond "error in Gtk.queue: #{$!}"
572 Lich.log "error in Gtk.queue: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
573 rescue SyntaxError
574 respond "error in Gtk.queue: #{$!}"
575 Lich.log "error in Gtk.queue: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
576 rescue SystemExit
577 nil
578 rescue SecurityError
579 respond "error in Gtk.queue: #{$!}"
580 Lich.log "error in Gtk.queue: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
581 rescue ThreadError
582 respond "error in Gtk.queue: #{$!}"
583 Lich.log "error in Gtk.queue: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
584 rescue SystemStackError
585 respond "error in Gtk.queue: #{$!}"
586 Lich.log "error in Gtk.queue: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
587 rescue Exception
588 respond "error in Gtk.queue: #{$!}"
589 Lich.log "error in Gtk.queue: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
590 rescue ScriptError
591 respond "error in Gtk.queue: #{$!}"
592 Lich.log "error in Gtk.queue: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
593 rescue LoadError
594 respond "error in Gtk.queue: #{$!}"
595 Lich.log "error in Gtk.queue: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
596 rescue NoMemoryError
597 respond "error in Gtk.queue: #{$!}"
598 Lich.log "error in Gtk.queue: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
599 rescue
600 respond "error in Gtk.queue: #{$!}"
601 Lich.log "error in Gtk.queue: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
602 end
603 false # don't repeat timeout
604 }
605 end
606 end
607end
608
609module Lich
610 @@hosts_file = nil
611 @@lich_db = nil
612 def Lich.db
613 if $SAFE == 0
614 @@lich_db ||= SQLite3::Database.new("#{DATA_DIR}/lich.db3")
615 else
616 nil
617 end
618 end
619 def Lich.init_db
620 begin
621 Lich.db.execute("CREATE TABLE IF NOT EXISTS script_setting (script TEXT NOT NULL, name TEXT NOT NULL, value BLOB, PRIMARY KEY(script, name));")
622 Lich.db.execute("CREATE TABLE IF NOT EXISTS script_auto_settings (script TEXT NOT NULL, scope TEXT, hash BLOB, PRIMARY KEY(script, scope));")
623 Lich.db.execute("CREATE TABLE IF NOT EXISTS lich_settings (name TEXT NOT NULL, value TEXT, PRIMARY KEY(name));")
624 Lich.db.execute("CREATE TABLE IF NOT EXISTS uservars (scope TEXT NOT NULL, hash BLOB, PRIMARY KEY(scope));")
625 Lich.db.execute("CREATE TABLE IF NOT EXISTS trusted_scripts (name TEXT NOT NULL);")
626 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));")
627 Lich.db.execute("CREATE TABLE IF NOT EXISTS enable_inventory_boxes (player_id INTEGER NOT NULL, PRIMARY KEY(player_id));")
628 rescue SQLite3::BusyException
629 sleep 0.1
630 retry
631 end
632 end
633 def Lich.class_variable_get(*a); nil; end
634 def Lich.class_eval(*a); nil; end
635 def Lich.module_eval(*a); nil; end
636 def Lich.log(msg)
637 $stderr.puts "#{Time.now.strftime("%Y-%m-%d %H:%M:%S")}: #{msg}"
638 end
639 def Lich.msgbox(args)
640 if defined?(Win32)
641 if args[:buttons] == :ok_cancel
642 buttons = Win32::MB_OKCANCEL
643 elsif args[:buttons] == :yes_no
644 buttons = Win32::MB_YESNO
645 else
646 buttons = Win32::MB_OK
647 end
648 if args[:icon] == :error
649 icon = Win32::MB_ICONERROR
650 elsif args[:icon] == :question
651 icon = Win32::MB_ICONQUESTION
652 elsif args[:icon] == :warning
653 icon = Win32::MB_ICONWARNING
654 else
655 icon = 0
656 end
657 args[:title] ||= "Lich v#{LICH_VERSION}"
658 r = Win32.MessageBox(:lpText => args[:message], :lpCaption => args[:title], :uType => (buttons|icon))
659 if r == Win32::IDIOK
660 return :ok
661 elsif r == Win32::IDICANCEL
662 return :cancel
663 elsif r == Win32::IDIYES
664 return :yes
665 elsif r == Win32::IDINO
666 return :no
667 else
668 return nil
669 end
670 elsif defined?(Gtk)
671 if args[:buttons] == :ok_cancel
672 buttons = Gtk::MessageDialog::BUTTONS_OK_CANCEL
673 elsif args[:buttons] == :yes_no
674 buttons = Gtk::MessageDialog::BUTTONS_YES_NO
675 else
676 buttons = Gtk::MessageDialog::BUTTONS_OK
677 end
678 if args[:icon] == :error
679 type = Gtk::MessageDialog::ERROR
680 elsif args[:icon] == :question
681 type = Gtk::MessageDialog::QUESTION
682 elsif args[:icon] == :warning
683 type = Gtk::MessageDialog::WARNING
684 else
685 type = Gtk::MessageDialog::INFO
686 end
687 dialog = Gtk::MessageDialog.new(nil, Gtk::Dialog::MODAL, type, buttons, args[:message])
688 args[:title] ||= "Lich v#{LICH_VERSION}"
689 dialog.title = args[:title]
690 response = nil
691 dialog.run { |r|
692 response = r
693 dialog.destroy
694 }
695 if response == Gtk::Dialog::RESPONSE_OK
696 return :ok
697 elsif response == Gtk::Dialog::RESPONSE_CANCEL
698 return :cancel
699 elsif response == Gtk::Dialog::RESPONSE_YES
700 return :yes
701 elsif response == Gtk::Dialog::RESPONSE_NO
702 return :no
703 else
704 return nil
705 end
706 elsif $stdout.isatty
707 $stdout.puts(args[:message])
708 return nil
709 end
710 end
711 def Lich.get_simu_launcher
712 if defined?(Win32)
713 begin
714 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]
715 launcher_cmd = Win32.RegQueryValueEx(:hKey => launcher_key, :lpValueName => 'RealCommand')[:lpData]
716 if launcher_cmd.nil? or launcher_cmd.empty?
717 launcher_cmd = Win32.RegQueryValueEx(:hKey => launcher_key)[:lpData]
718 end
719 return launcher_cmd
720 ensure
721 Win32.RegCloseKey(:hKey => launcher_key) rescue nil
722 end
723 elsif defined?(Wine)
724 launcher_cmd = Wine.registry_gets('HKEY_LOCAL_MACHINE\\Software\\Classes\\Simutronics.Autolaunch\\Shell\\Open\\command\\RealCommand')
725 unless launcher_cmd and not launcher_cmd.empty?
726 launcher_cmd = Wine.registry_gets('HKEY_LOCAL_MACHINE\\Software\\Classes\\Simutronics.Autolaunch\\Shell\\Open\\command\\')
727 end
728 return launcher_cmd
729 else
730 return nil
731 end
732 end
733 def Lich.link_to_sge
734 if defined?(Win32)
735 if Win32.admin?
736 begin
737 launcher_key = Win32.RegOpenKeyEx(:hKey => Win32::HKEY_LOCAL_MACHINE, :lpSubKey => 'Software\\Simutronics\\Launcher', :samDesired => (Win32::KEY_ALL_ACCESS|Win32::KEY_WOW64_32KEY))[:phkResult]
738 r = Win32.RegQueryValueEx(:hKey => launcher_key, :lpValueName => 'RealDirectory')
739 if (r[:return] == 0) and not r[:lpData].empty?
740 # already linked
741 return true
742 end
743 r = Win32.GetModuleFileName
744 unless r[:return] > 0
745 # fixme
746 return false
747 end
748 new_launcher_dir = "\"#{r[:lpFilename]}\" \"#{File.expand_path($PROGRAM_NAME)}\" "
749 r = Win32.RegQueryValueEx(:hKey => launcher_key, :lpValueName => 'Directory')
750 launcher_dir = r[:lpData]
751 r = Win32.RegSetValueEx(:hKey => launcher_key, :lpValueName => 'RealDirectory', :dwType => Win32::REG_SZ, :lpData => launcher_dir)
752 return false unless (r == 0)
753 r = Win32.RegSetValueEx(:hKey => launcher_key, :lpValueName => 'Directory', :dwType => Win32::REG_SZ, :lpData => new_launcher_dir)
754 return (r == 0)
755 ensure
756 Win32.RegCloseKey(:hKey => launcher_key) rescue nil
757 end
758 else
759 begin
760 r = Win32.GetModuleFileName
761 file = ((r[:return] > 0) ? r[:lpFilename] : 'rubyw.exe')
762 params = "#{$PROGRAM_NAME.split(/\/|\\/).last} --link-to-sge"
763 r = Win32.ShellExecuteEx(:lpVerb => 'runas', :lpFile => file, :lpDirectory => LICH_DIR.tr("/", "\\"), :lpParameters => params, :fMask => Win32::SEE_MASK_NOCLOSEPROCESS)
764 if r[:return] > 0
765 process_id = r[:hProcess]
766 sleep 0.2 while Win32.GetExitCodeProcess(:hProcess => process_id)[:lpExitCode] == Win32::STILL_ACTIVE
767 sleep 3
768 else
769 Win32.ShellExecute(:lpOperation => 'runas', :lpFile => file, :lpDirectory => LICH_DIR.tr("/", "\\"), :lpParameters => params)
770 sleep 6
771 end
772 rescue
773 Lich.msgbox(:message => $!)
774 end
775 end
776 elsif defined?(Wine)
777 launch_dir = Wine.registry_gets('HKEY_LOCAL_MACHINE\\Software\\Simutronics\\Launcher\\Directory')
778 return false unless launch_dir
779 lich_launch_dir = "#{File.expand_path($PROGRAM_NAME)} --wine=#{Wine::BIN} --wine-prefix=#{Wine::PREFIX} "
780 result = true
781 if launch_dir
782 if launch_dir =~ /lich/i
783 $stdout.puts "--- warning: Lich appears to already be installed to the registry"
784 Lich.log "warning: Lich appears to already be installed to the registry"
785 Lich.log 'info: launch_dir: ' + launch_dir
786 else
787 result = result && Wine.registry_puts('HKEY_LOCAL_MACHINE\\Software\\Simutronics\\Launcher\\RealDirectory', launch_dir)
788 result = result && Wine.registry_puts('HKEY_LOCAL_MACHINE\\Software\\Simutronics\\Launcher\\Directory', lich_launch_dir)
789 end
790 end
791 return result
792 else
793 return false
794 end
795 end
796 def Lich.unlink_from_sge
797 if defined?(Win32)
798 if Win32.admin?
799 begin
800 launcher_key = Win32.RegOpenKeyEx(:hKey => Win32::HKEY_LOCAL_MACHINE, :lpSubKey => 'Software\\Simutronics\\Launcher', :samDesired => (Win32::KEY_ALL_ACCESS|Win32::KEY_WOW64_32KEY))[:phkResult]
801 real_directory = Win32.RegQueryValueEx(:hKey => launcher_key, :lpValueName => 'RealDirectory')[:lpData]
802 if real_directory.nil? or real_directory.empty?
803 # not linked
804 return true
805 end
806 r = Win32.RegSetValueEx(:hKey => launcher_key, :lpValueName => 'Directory', :dwType => Win32::REG_SZ, :lpData => real_directory)
807 return false unless (r == 0)
808 r = Win32.RegDeleteValue(:hKey => launcher_key, :lpValueName => 'RealDirectory')
809 return (r == 0)
810 ensure
811 Win32.RegCloseKey(:hKey => launcher_key) rescue nil
812 end
813 else
814 begin
815 r = Win32.GetModuleFileName
816 file = ((r[:return] > 0) ? r[:lpFilename] : 'rubyw.exe')
817 params = "#{$PROGRAM_NAME.split(/\/|\\/).last} --unlink-from-sge"
818 r = Win32.ShellExecuteEx(:lpVerb => 'runas', :lpFile => file, :lpDirectory => LICH_DIR.tr("/", "\\"), :lpParameters => params, :fMask => Win32::SEE_MASK_NOCLOSEPROCESS)
819 if r[:return] > 0
820 process_id = r[:hProcess]
821 sleep 0.2 while Win32.GetExitCodeProcess(:hProcess => process_id)[:lpExitCode] == Win32::STILL_ACTIVE
822 sleep 3
823 else
824 Win32.ShellExecute(:lpOperation => 'runas', :lpFile => file, :lpDirectory => LICH_DIR.tr("/", "\\"), :lpParameters => params)
825 sleep 6
826 end
827 rescue
828 Lich.msgbox(:message => $!)
829 end
830 end
831 elsif defined?(Wine)
832 real_launch_dir = Wine.registry_gets('HKEY_LOCAL_MACHINE\\Software\\Simutronics\\Launcher\\RealDirectory')
833 result = true
834 if real_launch_dir and not real_launch_dir.empty?
835 result = result && Wine.registry_puts('HKEY_LOCAL_MACHINE\\Software\\Simutronics\\Launcher\\Directory', real_launch_dir)
836 result = result && Wine.registry_puts('HKEY_LOCAL_MACHINE\\Software\\Simutronics\\Launcher\\RealDirectory', '')
837 end
838 return result
839 else
840 return false
841 end
842 end
843 def Lich.link_to_sal
844 if defined?(Win32)
845 if Win32.admin?
846 begin
847 # fixme: 64 bit browsers?
848 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]
849 r = Win32.RegQueryValueEx(:hKey => launcher_key, :lpValueName => 'RealCommand')
850 if (r[:return] == 0) and not r[:lpData].empty?
851 # already linked
852 return true
853 end
854 r = Win32.GetModuleFileName
855 unless r[:return] > 0
856 # fixme
857 return false
858 end
859 new_launcher_cmd = "\"#{r[:lpFilename]}\" \"#{File.expand_path($PROGRAM_NAME)}\" %1"
860 r = Win32.RegQueryValueEx(:hKey => launcher_key)
861 launcher_cmd = r[:lpData]
862 r = Win32.RegSetValueEx(:hKey => launcher_key, :lpValueName => 'RealCommand', :dwType => Win32::REG_SZ, :lpData => launcher_cmd)
863 return false unless (r == 0)
864 r = Win32.RegSetValueEx(:hKey => launcher_key, :dwType => Win32::REG_SZ, :lpData => new_launcher_cmd)
865 return (r == 0)
866 ensure
867 Win32.RegCloseKey(:hKey => launcher_key) rescue nil
868 end
869 else
870 begin
871 r = Win32.GetModuleFileName
872 file = ((r[:return] > 0) ? r[:lpFilename] : 'rubyw.exe')
873 params = "#{$PROGRAM_NAME.split(/\/|\\/).last} --link-to-sal"
874 r = Win32.ShellExecuteEx(:lpVerb => 'runas', :lpFile => file, :lpDirectory => LICH_DIR.tr("/", "\\"), :lpParameters => params, :fMask => Win32::SEE_MASK_NOCLOSEPROCESS)
875 if r[:return] > 0
876 process_id = r[:hProcess]
877 sleep 0.2 while Win32.GetExitCodeProcess(:hProcess => process_id)[:lpExitCode] == Win32::STILL_ACTIVE
878 sleep 3
879 else
880 Win32.ShellExecute(:lpOperation => 'runas', :lpFile => file, :lpDirectory => LICH_DIR.tr("/", "\\"), :lpParameters => params)
881 sleep 6
882 end
883 rescue
884 Lich.msgbox(:message => $!)
885 end
886 end
887 elsif defined?(Wine)
888 launch_cmd = Wine.registry_gets('HKEY_LOCAL_MACHINE\\Software\\Classes\\Simutronics.Autolaunch\\Shell\\Open\\command\\')
889 return false unless launch_cmd
890 new_launch_cmd = "#{File.expand_path($PROGRAM_NAME)} --wine=#{Wine::BIN} --wine-prefix=#{Wine::PREFIX} %1"
891 result = true
892 if launch_cmd
893 if launch_cmd =~ /lich/i
894 $stdout.puts "--- warning: Lich appears to already be installed to the registry"
895 Lich.log "warning: Lich appears to already be installed to the registry"
896 Lich.log 'info: launch_cmd: ' + launch_cmd
897 else
898 result = result && Wine.registry_puts('HKEY_LOCAL_MACHINE\\Software\\Classes\\Simutronics.Autolaunch\\Shell\\Open\\command\\RealCommand', launch_cmd)
899 result = result && Wine.registry_puts('HKEY_LOCAL_MACHINE\\Software\\Classes\\Simutronics.Autolaunch\\Shell\\Open\\command\\', new_launch_cmd)
900 end
901 end
902 return result
903 else
904 return false
905 end
906 end
907 def Lich.unlink_from_sal
908 if defined?(Win32)
909 if Win32.admin?
910 begin
911 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]
912 real_directory = Win32.RegQueryValueEx(:hKey => launcher_key, :lpValueName => 'RealCommand')[:lpData]
913 if real_directory.nil? or real_directory.empty?
914 # not linked
915 return true
916 end
917 r = Win32.RegSetValueEx(:hKey => launcher_key, :dwType => Win32::REG_SZ, :lpData => real_directory)
918 return false unless (r == 0)
919 r = Win32.RegDeleteValue(:hKey => launcher_key, :lpValueName => 'RealCommand')
920 return (r == 0)
921 ensure
922 Win32.RegCloseKey(:hKey => launcher_key) rescue nil
923 end
924 else
925 begin
926 r = Win32.GetModuleFileName
927 file = ((r[:return] > 0) ? r[:lpFilename] : 'rubyw.exe')
928 params = "#{$PROGRAM_NAME.split(/\/|\\/).last} --unlink-from-sal"
929 r = Win32.ShellExecuteEx(:lpVerb => 'runas', :lpFile => file, :lpDirectory => LICH_DIR.tr("/", "\\"), :lpParameters => params, :fMask => Win32::SEE_MASK_NOCLOSEPROCESS)
930 if r[:return] > 0
931 process_id = r[:hProcess]
932 sleep 0.2 while Win32.GetExitCodeProcess(:hProcess => process_id)[:lpExitCode] == Win32::STILL_ACTIVE
933 sleep 3
934 else
935 Win32.ShellExecute(:lpOperation => 'runas', :lpFile => file, :lpDirectory => LICH_DIR.tr("/", "\\"), :lpParameters => params)
936 sleep 6
937 end
938 rescue
939 Lich.msgbox(:message => $!)
940 end
941 end
942 elsif defined?(Wine)
943 real_launch_cmd = Wine.registry_gets('HKEY_LOCAL_MACHINE\\Software\\Classes\\Simutronics.Autolaunch\\Shell\\Open\\command\\RealCommand')
944 result = true
945 if real_launch_cmd and not real_launch_cmd.empty?
946 result = result && Wine.registry_puts('HKEY_LOCAL_MACHINE\\Software\\Classes\\Simutronics.Autolaunch\\Shell\\Open\\command\\', real_launch_cmd)
947 result = result && Wine.registry_puts('HKEY_LOCAL_MACHINE\\Software\\Classes\\Simutronics.Autolaunch\\Shell\\Open\\command\\RealCommand', '')
948 end
949 return result
950 else
951 return false
952 end
953 end
954 def Lich.hosts_file
955 Lich.find_hosts_file if @@hosts_file.nil?
956 return @@hosts_file
957 end
958 def Lich.find_hosts_file
959 if defined?(Win32)
960 begin
961 key = Win32.RegOpenKeyEx(:hKey => Win32::HKEY_LOCAL_MACHINE, :lpSubKey => 'System\\CurrentControlSet\\Services\\Tcpip\\Parameters', :samDesired => Win32::KEY_READ)[:phkResult]
962 hosts_path = Win32.RegQueryValueEx(:hKey => key, :lpValueName => 'DataBasePath')[:lpData]
963 ensure
964 Win32.RegCloseKey(:hKey => key) rescue nil
965 end
966 if hosts_path
967 windir = (ENV['windir'] || ENV['SYSTEMROOT'] || 'c:\windows')
968 hosts_path.gsub('%SystemRoot%', windir)
969 hosts_file = "#{hosts_path}\\hosts"
970 if File.exists?(hosts_file)
971 return (@@hosts_file = hosts_file)
972 end
973 end
974 if (windir = (ENV['windir'] || ENV['SYSTEMROOT'])) and File.exists?("#{windir}\\system32\\drivers\\etc\\hosts")
975 return (@@hosts_file = "#{windir}\\system32\\drivers\\etc\\hosts")
976 end
977 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']
978 for windir in ['winnt','windows']
979 if File.exists?("#{drive}:\\#{windir}\\system32\\drivers\\etc\\hosts")
980 return (@@hosts_file = "#{drive}:\\#{windir}\\system32\\drivers\\etc\\hosts")
981 end
982 end
983 end
984 else # Linux/Mac
985 if File.exists?('/etc/hosts')
986 return (@@hosts_file = '/etc/hosts')
987 elsif File.exists?('/private/etc/hosts')
988 return (@@hosts_file = '/private/etc/hosts')
989 end
990 end
991 return (@@hosts_file = false)
992 end
993 def Lich.modify_hosts(game_host)
994 if Lich.hosts_file and File.exists?(Lich.hosts_file)
995 at_exit { Lich.restore_hosts }
996 Lich.restore_hosts
997 if File.exists?("#{Lich.hosts_file}.bak")
998 return false
999 end
1000 begin
1001 # copy hosts to hosts.bak
1002 File.open("#{Lich.hosts_file}.bak", 'w') { |hb| File.open(Lich.hosts_file) { |h| hb.write(h.read) } }
1003 rescue
1004 File.unlink("#{Lich.hosts_file}.bak") if File.exists?("#{Lich.hosts_file}.bak")
1005 return false
1006 end
1007 File.open(Lich.hosts_file, 'a') { |f| f.write "\r\n127.0.0.1\t\t#{game_host}" }
1008 return true
1009 else
1010 return false
1011 end
1012 end
1013 def Lich.restore_hosts
1014 if Lich.hosts_file and File.exists?(Lich.hosts_file)
1015 begin
1016 # fixme: use rename instead? test rename on windows
1017 if File.exists?("#{Lich.hosts_file}.bak")
1018 File.open("#{Lich.hosts_file}.bak") { |infile|
1019 File.open(Lich.hosts_file, 'w') { |outfile|
1020 outfile.write(infile.read)
1021 }
1022 }
1023 File.unlink "#{Lich.hosts_file}.bak"
1024 end
1025 rescue
1026 $stdout.puts "--- error: restore_hosts: #{$!}"
1027 Lich.log "error: restore_hosts: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
1028 exit(1)
1029 end
1030 end
1031 end
1032 def Lich.inventory_boxes(player_id)
1033 begin
1034 v = Lich.db.get_first_value('SELECT player_id FROM enable_inventory_boxes WHERE player_id=?;', player_id.to_i)
1035 rescue SQLite3::BusyException
1036 sleep 0.1
1037 retry
1038 end
1039 if v
1040 true
1041 else
1042 false
1043 end
1044 end
1045 def Lich.set_inventory_boxes(player_id, enabled)
1046 if enabled
1047 begin
1048 Lich.db.execute('INSERT OR REPLACE INTO enable_inventory_boxes values(?);', player_id.to_i)
1049 rescue SQLite3::BusyException
1050 sleep 0.1
1051 retry
1052 end
1053 else
1054 begin
1055 Lich.db.execute('DELETE FROM enable_inventory_boxes where player_id=?;', player_id.to_i)
1056 rescue SQLite3::BusyException
1057 sleep 0.1
1058 retry
1059 end
1060 end
1061 nil
1062 end
1063 def Lich.win32_launch_method
1064 begin
1065 val = Lich.db.get_first_value("SELECT value FROM lich_settings WHERE name='win32_launch_method';")
1066 rescue SQLite3::BusyException
1067 sleep 0.1
1068 retry
1069 end
1070 val
1071 end
1072 def Lich.win32_launch_method=(val)
1073 begin
1074 Lich.db.execute("INSERT OR REPLACE INTO lich_settings(name,value) values('win32_launch_method',?);", val.to_s.encode('UTF-8'))
1075 rescue SQLite3::BusyException
1076 sleep 0.1
1077 retry
1078 end
1079 nil
1080 end
1081 def Lich.fix_game_host_port(gamehost,gameport)
1082 if (gamehost == 'gs-plat.simutronics.net') and (gameport.to_i == 10121)
1083 gamehost = 'storm.gs4.game.play.net'
1084 gameport = 10124
1085 elsif (gamehost == 'gs3.simutronics.net') and (gameport.to_i == 4900)
1086 gamehost = 'storm.gs4.game.play.net'
1087 gameport = 10024
1088 elsif (gamehost == 'gs4.simutronics.net') and (gameport.to_i == 10321)
1089 game_host = 'storm.gs4.game.play.net'
1090 game_port = 10324
1091 elsif (gamehost == 'prime.dr.game.play.net') and (gameport.to_i == 4901)
1092 gamehost = 'dr.simutronics.net'
1093 gameport = 11024
1094 end
1095 [ gamehost, gameport ]
1096 end
1097 def Lich.break_game_host_port(gamehost,gameport)
1098 if (gamehost == 'storm.gs4.game.play.net') and (gameport.to_i == 10324)
1099 gamehost = 'gs4.simutronics.net'
1100 gameport = 10321
1101 elsif (gamehost == 'storm.gs4.game.play.net') and (gameport.to_i == 10124)
1102 gamehost = 'gs-plat.simutronics.net'
1103 gameport = 10121
1104 elsif (gamehost == 'storm.gs4.game.play.net') and (gameport.to_i == 10024)
1105 gamehost = 'gs3.simutronics.net'
1106 gameport = 4900
1107 elsif (gamehost == 'storm.gs4.game.play.net') and (gameport.to_i == 10324)
1108 game_host = 'gs4.simutronics.net'
1109 game_port = 10321
1110 elsif (gamehost == 'dr.simutronics.net') and (gameport.to_i == 11024)
1111 gamehost = 'prime.dr.game.play.net'
1112 gameport = 4901
1113 end
1114 [ gamehost, gameport ]
1115 end
1116end
1117
1118class NilClass
1119 def dup
1120 nil
1121 end
1122 def method_missing(*args)
1123 nil
1124 end
1125 def split(*val)
1126 Array.new
1127 end
1128 def to_s
1129 ""
1130 end
1131 def strip
1132 ""
1133 end
1134 def +(val)
1135 val
1136 end
1137 def closed?
1138 true
1139 end
1140end
1141
1142class Numeric
1143 def as_time
1144 sprintf("%d:%02d:%02d", (self / 60).truncate, self.truncate % 60, ((self % 1) * 60).truncate)
1145 end
1146 def with_commas
1147 self.to_s.reverse.scan(/(?:\d*\.)?\d{1,3}-?/).join(',').reverse
1148 end
1149end
1150
1151class TrueClass
1152 def method_missing(*usersave)
1153 true
1154 end
1155end
1156
1157class FalseClass
1158 def method_missing(*usersave)
1159 nil
1160 end
1161end
1162
1163class String
1164 @@elevated_untaint = proc { |what| what.orig_untaint }
1165 alias :orig_untaint :untaint
1166 def untaint
1167 @@elevated_untaint.call(self)
1168 end
1169 def to_s
1170 self.dup
1171 end
1172 def stream
1173 @stream
1174 end
1175 def stream=(val)
1176 @stream ||= val
1177 end
1178end
1179
1180class StringProc
1181 def initialize(string)
1182 @string = string
1183 @string.untaint
1184 end
1185 def kind_of?(type)
1186 Proc.new {}.kind_of? type
1187 end
1188 def class
1189 Proc
1190 end
1191 def call(*a)
1192 if $SAFE < 3
1193 proc { $SAFE = 3; eval(@string) }.call
1194 else
1195 eval(@string)
1196 end
1197 end
1198 def _dump(d=nil)
1199 @string
1200 end
1201 def inspect
1202 "StringProc.new(#{@string.inspect})"
1203 end
1204end
1205
1206class SynchronizedSocket
1207 def initialize(o)
1208 @delegate = o
1209 @mutex = Mutex.new
1210 self
1211 end
1212 def puts(*args, &block)
1213 @mutex.synchronize {
1214 @delegate.puts *args, &block
1215 }
1216 end
1217 def write(*args, &block)
1218 @mutex.synchronize {
1219 @delegate.write *args, &block
1220 }
1221 end
1222 def method_missing(method, *args, &block)
1223 @delegate.__send__ method, *args, &block
1224 end
1225end
1226
1227class LimitedArray < Array
1228 attr_accessor :max_size
1229 def initialize(size=0, obj=nil)
1230 @max_size = 200
1231 super
1232 end
1233 def push(line)
1234 self.shift while self.length >= @max_size
1235 super
1236 end
1237 def shove(line)
1238 push(line)
1239 end
1240 def history
1241 Array.new
1242 end
1243end
1244
1245class XMLParser
1246 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
1247 attr_accessor :send_fake_tags
1248
1249 @@warned_deprecated_spellfront = 0
1250
1251 include REXML::StreamListener
1252
1253 def initialize
1254 @buffer = String.new
1255 @unescape = { 'lt' => '<', 'gt' => '>', 'quot' => '"', 'apos' => "'", 'amp' => '&' }
1256 @bold = false
1257 @active_tags = Array.new
1258 @active_ids = Array.new
1259 @last_tag = String.new
1260 @last_id = String.new
1261 @current_stream = String.new
1262 @current_style = String.new
1263 @stow_container_id = nil
1264 @obj_location = nil
1265 @obj_exist = nil
1266 @obj_noun = nil
1267 @obj_before_name = nil
1268 @obj_name = nil
1269 @obj_after_name = nil
1270 @pc = nil
1271 @last_obj = nil
1272 @in_stream = false
1273 @player_status = nil
1274 @fam_mode = String.new
1275 @room_window_disabled = false
1276 @wound_gsl = String.new
1277 @scar_gsl = String.new
1278 @send_fake_tags = false
1279 @prompt = String.new
1280 @nerve_tracker_num = 0
1281 @nerve_tracker_active = 'no'
1282 @server_time = Time.now.to_i
1283 @server_time_offset = 0
1284 @roundtime_end = 0
1285 @cast_roundtime_end = 0
1286 @last_pulse = Time.now.to_i
1287 @level = 0
1288 @next_level_value = 0
1289 @next_level_text = String.new
1290
1291 @room_count = 0
1292 @room_title = String.new
1293 @room_description = String.new
1294 @room_exits = Array.new
1295 @room_exits_string = String.new
1296
1297 @familiar_room_title = String.new
1298 @familiar_room_description = String.new
1299 @familiar_room_exits = Array.new
1300
1301 @bounty_task = String.new
1302 @society_task = String.new
1303
1304 @name = String.new
1305 @game = String.new
1306 @player_id = String.new
1307 @mana = 0
1308 @max_mana = 0
1309 @health = 0
1310 @max_health = 0
1311 @spirit = 0
1312 @max_spirit = 0
1313 @last_spirit = nil
1314 @stamina = 0
1315 @max_stamina = 0
1316 @stance_text = String.new
1317 @stance_value = 0
1318 @mind_text = String.new
1319 @mind_value = 0
1320 @prepared_spell = 'None'
1321 @encumbrance_text = String.new
1322 @encumbrance_full_text = String.new
1323 @encumbrance_value = 0
1324 @indicator = Hash.new
1325 @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}}
1326 @injury_mode = 0
1327
1328 @active_spells = Hash.new
1329
1330 end
1331
1332 def reset
1333 @active_tags = Array.new
1334 @active_ids = Array.new
1335 @current_stream = String.new
1336 @current_style = String.new
1337 end
1338
1339 def make_wound_gsl
1340 @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'])
1341 end
1342
1343 def make_scar_gsl
1344 @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'])
1345 end
1346
1347 def parse(line)
1348 @buffer.concat(line)
1349 loop {
1350 if str = @buffer.slice!(/^[^<]+/)
1351 text(str.gsub(/&(lt|gt|quot|apos|amp)/) { @unescape[$1] })
1352 elsif str = @buffer.slice!(/^<\/[^<]+>/)
1353 element = /^<\/([^\s>\/]+)/.match(str).captures.first
1354 tag_end(element)
1355 elsif str = @buffer.slice!(/^<[^<]+>/)
1356 element = /^<([^\s>\/]+)/.match(str).captures.first
1357 attributes = Hash.new
1358 str.scan(/([A-z][A-z0-9_\-]*)=(["'])(.*?)\2/).each { |attr| attributes[attr[0]] = attr[2] }
1359 tag_start(element, attributes)
1360 tag_end(element) if str =~ /\/>$/
1361 else
1362 break
1363 end
1364 }
1365 end
1366
1367 def tag_start(name, attributes)
1368 begin
1369 @active_tags.push(name)
1370 @active_ids.push(attributes['id'].to_s)
1371 if name =~ /^(?:a|right|left)$/
1372 @obj_exist = attributes['exist']
1373 @obj_noun = attributes['noun']
1374 elsif name == 'inv'
1375 if attributes['id'] == 'stow'
1376 @obj_location = @stow_container_id
1377 else
1378 @obj_location = attributes['id']
1379 end
1380 @obj_exist = nil
1381 @obj_noun = nil
1382 @obj_name = nil
1383 @obj_before_name = nil
1384 @obj_after_name = nil
1385 elsif name == 'dialogData' and attributes['id'] == 'ActiveSpells' and attributes['clear'] == 't'
1386 @active_spells.clear
1387 elsif name == 'resource' or name == 'nav'
1388 nil
1389 elsif name == 'pushStream'
1390 @in_stream = true
1391 @current_stream = attributes['id'].to_s
1392 GameObj.clear_inv if attributes['id'].to_s == 'inv'
1393 elsif name == 'popStream'
1394 if attributes['id'] == 'room'
1395 unless @room_window_disabled
1396 @room_count += 1
1397 $room_count += 1
1398 end
1399 end
1400 @in_stream = false
1401 if attributes['id'] == 'bounty'
1402 @bounty_task.strip!
1403 end
1404 @current_stream = String.new
1405 elsif name == 'pushBold'
1406 @bold = true
1407 elsif name == 'popBold'
1408 @bold = false
1409 elsif (name == 'streamWindow')
1410 if (attributes['id'] == 'main') and attributes['subtitle']
1411 @room_title = '[' + attributes['subtitle'][3..-1] + ']'
1412 end
1413 elsif name == 'style'
1414 @current_style = attributes['id']
1415 elsif name == 'prompt'
1416 @server_time = attributes['time'].to_i
1417 @server_time_offset = (Time.now.to_i - @server_time)
1418 $_CLIENT_.puts "\034GSq#{sprintf('%010d', @server_time)}\r\n" if @send_fake_tags
1419 elsif (name == 'compDef') or (name == 'component')
1420 if attributes['id'] == 'room objs'
1421 GameObj.clear_loot
1422 GameObj.clear_npcs
1423 elsif attributes['id'] == 'room players'
1424 GameObj.clear_pcs
1425 elsif attributes['id'] == 'room exits'
1426 @room_exits = Array.new
1427 @room_exits_string = String.new
1428 elsif attributes['id'] == 'room desc'
1429 @room_description = String.new
1430 GameObj.clear_room_desc
1431 elsif attributes['id'] == 'room extra' # DragonRealms
1432 @room_count += 1
1433 $room_count += 1
1434 # elsif attributes['id'] == 'sprite'
1435 end
1436 elsif name == 'clearContainer'
1437 if attributes['id'] == 'stow'
1438 GameObj.clear_container(@stow_container_id)
1439 else
1440 GameObj.clear_container(attributes['id'])
1441 end
1442 elsif name == 'deleteContainer'
1443 GameObj.delete_container(attributes['id'])
1444 elsif name == 'progressBar'
1445 if attributes['id'] == 'pbarStance'
1446 @stance_text = attributes['text'].split.first
1447 @stance_value = attributes['value'].to_i
1448 $_CLIENT_.puts "\034GSg#{sprintf('%010d', @stance_value)}\r\n" if @send_fake_tags
1449 elsif attributes['id'] == 'mana'
1450 last_mana = @mana
1451 @mana, @max_mana = attributes['text'].scan(/-?\d+/).collect { |num| num.to_i }
1452 difference = @mana - last_mana
1453 # fixme: enhancives screw this up
1454 if (difference == noded_pulse) or (difference == unnoded_pulse) or ( (@mana == @max_mana) and (last_mana + noded_pulse > @max_mana) )
1455 @last_pulse = Time.now.to_i
1456 if @send_fake_tags
1457 $_CLIENT_.puts "\034GSZ#{sprintf('%010d',(@mana+1))}\n"
1458 $_CLIENT_.puts "\034GSZ#{sprintf('%010d',@mana)}\n"
1459 end
1460 end
1461 if @send_fake_tags
1462 $_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"
1463 end
1464 elsif attributes['id'] == 'stamina'
1465 @stamina, @max_stamina = attributes['text'].scan(/-?\d+/).collect { |num| num.to_i }
1466 elsif attributes['id'] == 'mindState'
1467 @mind_text = attributes['text']
1468 @mind_value = attributes['value'].to_i
1469 $_CLIENT_.puts "\034GSr#{MINDMAP[@mind_text]}\r\n" if @send_fake_tags
1470 elsif attributes['id'] == 'health'
1471 @health, @max_health = attributes['text'].scan(/-?\d+/).collect { |num| num.to_i }
1472 $_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
1473 elsif attributes['id'] == 'spirit'
1474 @last_spirit = @spirit if @last_spirit
1475 @spirit, @max_spirit = attributes['text'].scan(/-?\d+/).collect { |num| num.to_i }
1476 @last_spirit = @spirit unless @last_spirit
1477 $_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
1478 elsif attributes['id'] == 'nextLvlPB'
1479 Gift.pulse unless @next_level_text == attributes['text']
1480 @next_level_value = attributes['value'].to_i
1481 @next_level_text = attributes['text']
1482 elsif attributes['id'] == 'encumlevel'
1483 @encumbrance_value = attributes['value'].to_i
1484 @encumbrance_text = attributes['text']
1485 end
1486 elsif name == 'roundTime'
1487 @roundtime_end = attributes['value'].to_i
1488 $_CLIENT_.puts "\034GSQ#{sprintf('%010d', @roundtime_end)}\r\n" if @send_fake_tags
1489 elsif name == 'castTime'
1490 @cast_roundtime_end = attributes['value'].to_i
1491 elsif name == 'dropDownBox'
1492 if attributes['id'] == 'dDBTarget'
1493 if attributes['content_value'] =~ /^\#(\-?\d+)(?:,|$)/
1494 @current_target_id = $1
1495 else
1496 @current_target_id = nil
1497 end
1498 end
1499 elsif name == 'indicator'
1500 @indicator[attributes['id']] = attributes['visible']
1501 if @send_fake_tags
1502 if attributes['id'] == 'IconPOISONED'
1503 if attributes['visible'] == 'y'
1504 $_CLIENT_.puts "\034GSJ0000000000000000000100000000001\r\n"
1505 else
1506 $_CLIENT_.puts "\034GSJ0000000000000000000000000000000\r\n"
1507 end
1508 elsif attributes['id'] == 'IconDISEASED'
1509 if attributes['visible'] == 'y'
1510 $_CLIENT_.puts "\034GSK0000000000000000000100000000001\r\n"
1511 else
1512 $_CLIENT_.puts "\034GSK0000000000000000000000000000000\r\n"
1513 end
1514 else
1515 gsl_prompt = String.new; ICONMAP.keys.each { |icon| gsl_prompt += ICONMAP[icon] if @indicator[icon] == 'y' }
1516 $_CLIENT_.puts "\034GSP#{sprintf('%-30s', gsl_prompt)}\r\n"
1517 end
1518 end
1519 elsif (name == 'image') and @active_ids.include?('injuries')
1520 if @injuries.keys.include?(attributes['id'])
1521 if attributes['name'] =~ /Injury/i
1522 @injuries[attributes['id']]['wound'] = attributes['name'].slice(/\d/).to_i
1523 elsif attributes['name'] =~ /Scar/i
1524 @injuries[attributes['id']]['wound'] = 0
1525 @injuries[attributes['id']]['scar'] = attributes['name'].slice(/\d/).to_i
1526 elsif attributes['name'] =~ /Nsys/i
1527 rank = attributes['name'].slice(/\d/).to_i
1528 if rank == 0
1529 @injuries['nsys']['wound'] = 0
1530 @injuries['nsys']['scar'] = 0
1531 else
1532 Thread.new {
1533 wait_while { dead? }
1534 action = proc { |server_string|
1535 if (@nerve_tracker_active == 'maybe')
1536 if @nerve_tracker_active == 'maybe'
1537 if server_string =~ /^You/
1538 @nerve_tracker_active = 'yes'
1539 @injuries['nsys']['wound'] = 0
1540 @injuries['nsys']['scar'] = 0
1541 else
1542 @nerve_tracker_active = 'no'
1543 end
1544 end
1545 end
1546 if @nerve_tracker_active == 'yes'
1547 if server_string =~ /<output class=['"]['"]\/>/
1548 @nerve_tracker_active = 'no'
1549 @nerve_tracker_num -= 1
1550 DownstreamHook.remove('nerve_tracker') if @nerve_tracker_num < 1
1551 $_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
1552 server_string
1553 elsif server_string =~ /a case of uncontrollable convulsions/
1554 @injuries['nsys']['wound'] = 3
1555 nil
1556 elsif server_string =~ /a case of sporadic convulsions/
1557 @injuries['nsys']['wound'] = 2
1558 nil
1559 elsif server_string =~ /a strange case of muscle twitching/
1560 @injuries['nsys']['wound'] = 1
1561 nil
1562 elsif server_string =~ /a very difficult time with muscle control/
1563 @injuries['nsys']['scar'] = 3
1564 nil
1565 elsif server_string =~ /constant muscle spasms/
1566 @injuries['nsys']['scar'] = 2
1567 nil
1568 elsif server_string =~ /developed slurred speech/
1569 @injuries['nsys']['scar'] = 1
1570 nil
1571 end
1572 else
1573 if server_string =~ /<output class=['"]mono['"]\/>/
1574 @nerve_tracker_active = 'maybe'
1575 end
1576 server_string
1577 end
1578 }
1579 @nerve_tracker_num += 1
1580 DownstreamHook.add('nerve_tracker', action)
1581 Game._puts "#{$cmd_prefix}health"
1582 }
1583 end
1584 else
1585 @injuries[attributes['id']]['wound'] = 0
1586 @injuries[attributes['id']]['scar'] = 0
1587 end
1588 end
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 elsif name == 'compass'
1591 if @current_stream == 'familiar'
1592 @fam_mode = String.new
1593 elsif @room_window_disabled
1594 @room_exits = Array.new
1595 end
1596 elsif @room_window_disabled and (name == 'dir') and @active_tags.include?('compass')
1597 @room_exits.push(LONGDIR[attributes['value']])
1598 elsif name == 'radio'
1599 if attributes['id'] == 'injrRad'
1600 @injury_mode = 0 if attributes['value'] == '1'
1601 elsif attributes['id'] == 'scarRad'
1602 @injury_mode = 1 if attributes['value'] == '1'
1603 elsif attributes['id'] == 'bothRad'
1604 @injury_mode = 2 if attributes['value'] == '1'
1605 end
1606 elsif name == 'label'
1607 if attributes['id'] == 'yourLvl'
1608 @level = Stats.level = attributes['value'].slice(/\d+/).to_i
1609 elsif attributes['id'] == 'encumblurb'
1610 @encumbrance_full_text = attributes['value']
1611 elsif @active_tags[-2] == 'dialogData' and @active_ids[-2] == 'ActiveSpells'
1612 if (name = /^lbl(.+)$/.match(attributes['id']).captures.first) and (value = /^\s*([0-9\:]+)\s*$/.match(attributes['value']).captures.first)
1613 hour, minute = value.split(':')
1614 @active_spells[name] = Time.now + (hour.to_i * 3600) + (minute.to_i * 60)
1615 end
1616 end
1617 elsif (name == 'container') and (attributes['id'] == 'stow')
1618 @stow_container_id = attributes['target'].sub('#', '')
1619 elsif (name == 'clearStream')
1620 if attributes['id'] == 'bounty'
1621 @bounty_task = String.new
1622 end
1623 elsif (name == 'playerID')
1624 @player_id = attributes['id']
1625 unless $frontend =~ /^(?:wizard|avalon)$/
1626 if Lich.inventory_boxes(@player_id)
1627 DownstreamHook.remove('inventory_boxes_off')
1628 end
1629 end
1630 elsif (name == 'app') and (@name = attributes['char'])
1631 @game = attributes['game']
1632 if @game.nil? or @game.empty?
1633 @game = 'unknown'
1634 end
1635 unless File.exists?("#{DATA_DIR}/#{@game}")
1636 Dir.mkdir("#{DATA_DIR}/#{@game}")
1637 end
1638 unless File.exists?("#{DATA_DIR}/#{@game}/#{@name}")
1639 Dir.mkdir("#{DATA_DIR}/#{@game}/#{@name}")
1640 end
1641 if $frontend =~ /^(?:wizard|avalon)$/
1642 Game._puts "#{$cmd_prefix}_flag Display Dialog Boxes 0"
1643 sleep 0.05
1644 Game._puts "#{$cmd_prefix}_injury 2"
1645 sleep 0.05
1646 # fixme: game name hardcoded as Gemstone IV; maybe doesn't make any difference to the client
1647 $_CLIENT_.puts "\034GSB0000000000#{attributes['char']}\r\n\034GSA#{Time.now.to_i.to_s}GemStone IV\034GSD\r\n"
1648 # 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
1649 @send_fake_tags = true
1650 # Send all the tags we missed out on
1651 $_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"
1652 $_CLIENT_.puts "\034GSg#{sprintf('%010d', @stance_value)}\r\n"
1653 $_CLIENT_.puts "\034GSr#{MINDMAP[@mind_text]}\r\n"
1654 gsl_prompt = String.new
1655 @indicator.keys.each { |icon| gsl_prompt += ICONMAP[icon] if @indicator[icon] == 'y' }
1656 $_CLIENT_.puts "\034GSP#{sprintf('%-30s', gsl_prompt)}\r\n"
1657 gsl_prompt = nil
1658 gsl_exits = String.new
1659 @room_exits.each { |exit| gsl_exits.concat(DIRMAP[SHORTDIR[exit]].to_s) }
1660 $_CLIENT_.puts "\034GSj#{sprintf('%-20s', gsl_exits)}\r\n"
1661 gsl_exits = nil
1662 $_CLIENT_.puts "\034GSn#{sprintf('%-14s', @prepared_spell)}\r\n"
1663 $_CLIENT_.puts "\034GSm#{sprintf('%-45s', GameObj.right_hand.name)}\r\n"
1664 $_CLIENT_.puts "\034GSl#{sprintf('%-45s', GameObj.left_hand.name)}\r\n"
1665 $_CLIENT_.puts "\034GSq#{sprintf('%010d', @server_time)}\r\n"
1666 $_CLIENT_.puts "\034GSQ#{sprintf('%010d', @roundtime_end)}\r\n" if @roundtime_end > 0
1667 end
1668 Game._puts("#{$cmd_prefix}_flag Display Inventory Boxes 1")
1669 Script.start('autostart') if Script.exists?('autostart')
1670 if arg = ARGV.find { |a| a=~ /^\-\-start\-scripts=/ }
1671 for script_name in arg.sub('--start-scripts=', '').split(',')
1672 Script.start(script_name)
1673 end
1674 end
1675 end
1676 rescue
1677 $stdout.puts "--- error: XMLParser.tag_start: #{$!}"
1678 Lich.log "error: XMLParser.tag_start: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
1679 sleep 0.1
1680 reset
1681 end
1682 end
1683 def text(text_string)
1684 begin
1685 # fixme: /<stream id="Spells">.*?<\/stream>/m
1686 # $_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)$/))
1687 if @active_tags.include?('inv')
1688 if @active_tags[-1] == 'a'
1689 @obj_name = text_string
1690 elsif @obj_name.nil?
1691 @obj_before_name = text_string.strip
1692 else
1693 @obj_after_name = text_string.strip
1694 end
1695 elsif @active_tags.last == 'prompt'
1696 @prompt = text_string
1697 elsif @active_tags.include?('right')
1698 GameObj.new_right_hand(@obj_exist, @obj_noun, text_string)
1699 $_CLIENT_.puts "\034GSm#{sprintf('%-45s', text_string)}\r\n" if @send_fake_tags
1700 elsif @active_tags.include?('left')
1701 GameObj.new_left_hand(@obj_exist, @obj_noun, text_string)
1702 $_CLIENT_.puts "\034GSl#{sprintf('%-45s', text_string)}\r\n" if @send_fake_tags
1703 elsif @active_tags.include?('spell')
1704 @prepared_spell = text_string
1705 $_CLIENT_.puts "\034GSn#{sprintf('%-14s', text_string)}\r\n" if @send_fake_tags
1706 elsif @active_tags.include?('compDef') or @active_tags.include?('component')
1707 if @active_ids.include?('room objs')
1708 if @active_tags.include?('a')
1709 if @bold
1710 GameObj.new_npc(@obj_exist, @obj_noun, text_string)
1711 else
1712 GameObj.new_loot(@obj_exist, @obj_noun, text_string)
1713 end
1714 elsif (text_string =~ /that (?:is|appears) ([\w\s]+)(?:,| and|\.)/) or (text_string =~ / \(([^\(]+)\)/)
1715 GameObj.npcs[-1].status = $1
1716 end
1717 elsif @active_ids.include?('room players')
1718 if @active_tags.include?('a')
1719 @pc = GameObj.new_pc(@obj_exist, @obj_noun, "#{@player_title}#{text_string}", @player_status)
1720 @player_status = nil
1721 else
1722 if @game =~ /^DR/
1723 GameObj.clear_pcs
1724 text_string.sub(/^Also here\: /, '').sub(/ and ([^,]+)\./) { ", #{$1}" }.split(', ').each { |player|
1725 if player =~ / who is (.+)/
1726 status = $1
1727 player.sub!(/ who is .+/, '')
1728 elsif player =~ / \((.+)\)/
1729 status = $1
1730 player.sub!(/ \(.+\)/, '')
1731 else
1732 status = nil
1733 end
1734 noun = player.slice(/\b[A-Z][a-z]+$/)
1735 if player =~ /the body of /
1736 player.sub!('the body of ', '')
1737 if status
1738 status.concat ' dead'
1739 else
1740 status = 'dead'
1741 end
1742 end
1743 if player =~ /a stunned /
1744 player.sub!('a stunned ', '')
1745 if status
1746 status.concat ' stunned'
1747 else
1748 status = 'stunned'
1749 end
1750 end
1751 GameObj.new_pc(nil, noun, player, status)
1752 }
1753 else
1754 if (text_string =~ /^ who (?:is|appears) ([\w\s]+)(?:,| and|\.|$)/) or (text_string =~ / \(([\w\s]+)\)(?: \(([\w\s]+)\))?/)
1755 if @pc.status
1756 @pc.status.concat " #{$1}"
1757 else
1758 @pc.status = $1
1759 end
1760 @pc.status.concat " #{$2}" if $2
1761 end
1762 if text_string =~ /(?:^Also here: |, )(?:a )?([a-z\s]+)?([\w\s\-!\?',]+)?$/
1763 @player_status = ($1.strip.gsub('the body of', 'dead')) if $1
1764 @player_title = $2
1765 end
1766 end
1767 end
1768 elsif @active_ids.include?('room desc')
1769 if text_string == '[Room window disabled at this location.]'
1770 @room_window_disabled = true
1771 else
1772 @room_window_disabled = false
1773 @room_description.concat(text_string)
1774 if @active_tags.include?('a')
1775 GameObj.new_room_desc(@obj_exist, @obj_noun, text_string)
1776 end
1777 end
1778 elsif @active_ids.include?('room exits')
1779 @room_exits_string.concat(text_string)
1780 @room_exits.push(text_string) if @active_tags.include?('d')
1781 end
1782 elsif @current_stream == 'bounty'
1783 @bounty_task += text_string
1784 elsif @current_stream == 'society'
1785 @society_task = text_string
1786 elsif (@current_stream == 'inv') and @active_tags.include?('a')
1787 GameObj.new_inv(@obj_exist, @obj_noun, text_string, nil)
1788 elsif @current_stream == 'familiar'
1789 # 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
1790 if @current_style == 'roomName'
1791 @familiar_room_title = text_string
1792 @familiar_room_description = String.new
1793 @familiar_room_exits = Array.new
1794 GameObj.clear_fam_room_desc
1795 GameObj.clear_fam_loot
1796 GameObj.clear_fam_npcs
1797 GameObj.clear_fam_pcs
1798 @fam_mode = String.new
1799 elsif @current_style == 'roomDesc'
1800 @familiar_room_description.concat(text_string)
1801 if @active_tags.include?('a')
1802 GameObj.new_fam_room_desc(@obj_exist, @obj_noun, text_string)
1803 end
1804 elsif text_string =~ /^You also see/
1805 @fam_mode = 'things'
1806 elsif text_string =~ /^Also here/
1807 @fam_mode = 'people'
1808 elsif text_string =~ /Obvious (?:paths|exits)/
1809 @fam_mode = 'paths'
1810 elsif @fam_mode == 'things'
1811 if @active_tags.include?('a')
1812 if @bold
1813 GameObj.new_fam_npc(@obj_exist, @obj_noun, text_string)
1814 else
1815 GameObj.new_fam_loot(@obj_exist, @obj_noun, text_string)
1816 end
1817 end
1818 # puts 'things: ' + text_string
1819 elsif @fam_mode == 'people' and @active_tags.include?('a')
1820 GameObj.new_fam_pc(@obj_exist, @obj_noun, text_string)
1821 # puts 'people: ' + text_string
1822 elsif (@fam_mode == 'paths') and @active_tags.include?('a')
1823 @familiar_room_exits.push(text_string)
1824 end
1825 elsif @room_window_disabled
1826 if @current_style == 'roomDesc'
1827 @room_description.concat(text_string)
1828 if @active_tags.include?('a')
1829 GameObj.new_room_desc(@obj_exist, @obj_noun, text_string)
1830 end
1831 elsif text_string =~ /^Obvious (?:paths|exits): (?:none)?$/
1832 @room_exits_string = text_string.strip
1833 end
1834 end
1835 rescue
1836 $stdout.puts "--- error: XMLParser.text: #{$!}"
1837 Lich.log "error: XMLParser.text: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
1838 sleep 0.1
1839 reset
1840 end
1841 end
1842 def tag_end(name)
1843 begin
1844 if name == 'inv'
1845 if @obj_exist == @obj_location
1846 if @obj_after_name == 'is closed.'
1847 GameObj.delete_container(@stow_container_id)
1848 end
1849 elsif @obj_exist
1850 GameObj.new_inv(@obj_exist, @obj_noun, @obj_name, @obj_location, @obj_before_name, @obj_after_name)
1851 end
1852 elsif @send_fake_tags and (@active_ids.last == 'room exits')
1853 gsl_exits = String.new
1854 @room_exits.each { |exit| gsl_exits.concat(DIRMAP[SHORTDIR[exit]].to_s) }
1855 $_CLIENT_.puts "\034GSj#{sprintf('%-20s', gsl_exits)}\r\n"
1856 gsl_exits = nil
1857 elsif @room_window_disabled and (name == 'compass')
1858# @room_window_disabled = false
1859 @room_description = @room_description.strip
1860 @room_exits_string.concat " #{@room_exits.join(', ')}" unless @room_exits.empty?
1861 gsl_exits = String.new
1862 @room_exits.each { |exit| gsl_exits.concat(DIRMAP[SHORTDIR[exit]].to_s) }
1863 $_CLIENT_.puts "\034GSj#{sprintf('%-20s', gsl_exits)}\r\n"
1864 gsl_exits = nil
1865 @room_count += 1
1866 $room_count += 1
1867 end
1868 @last_tag = @active_tags.pop
1869 @last_id = @active_ids.pop
1870 rescue
1871 $stdout.puts "--- error: XMLParser.tag_end: #{$!}"
1872 Lich.log "error: XMLParser.tag_end: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
1873 sleep 0.1
1874 reset
1875 end
1876 end
1877 # here for backwards compatibility, but spellfront xml isn't sent by the game anymore
1878 def spellfront
1879 if (Time.now.to_i - @@warned_deprecated_spellfront) > 300
1880 @@warned_deprecated_spellfront = Time.now.to_i
1881 unless script_name = Script.current.name
1882 script_name = 'unknown script'
1883 end
1884 respond "--- warning: #{script_name} is using deprecated method XMLData.spellfront; this method will be removed in a future version of Lich"
1885 end
1886 @active_spells.keys
1887 end
1888end
1889
1890class UpstreamHook
1891 @@upstream_hooks ||= Hash.new
1892 def UpstreamHook.add(name, action)
1893 unless action.class == Proc
1894 echo "UpstreamHook: not a Proc (#{action})"
1895 return false
1896 end
1897 @@upstream_hooks[name] = action
1898 end
1899 def UpstreamHook.run(client_string)
1900 for key in @@upstream_hooks.keys
1901 begin
1902 client_string = @@upstream_hooks[key].call(client_string)
1903 rescue
1904 @@upstream_hooks.delete(key)
1905 respond "--- Lich: UpstreamHook: #{$!}"
1906 respond $!.backtrace.first
1907 end
1908 return nil if client_string.nil?
1909 end
1910 return client_string
1911 end
1912 def UpstreamHook.remove(name)
1913 @@upstream_hooks.delete(name)
1914 end
1915 def UpstreamHook.list
1916 @@upstream_hooks.keys.dup
1917 end
1918end
1919
1920class DownstreamHook
1921 @@downstream_hooks ||= Hash.new
1922 def DownstreamHook.add(name, action)
1923 unless action.class == Proc
1924 echo "DownstreamHook: not a Proc (#{action})"
1925 return false
1926 end
1927 @@downstream_hooks[name] = action
1928 end
1929 def DownstreamHook.run(server_string)
1930 for key in @@downstream_hooks.keys
1931 begin
1932 server_string = @@downstream_hooks[key].call(server_string.dup)
1933 rescue
1934 @@downstream_hooks.delete(key)
1935 respond "--- Lich: DownstreamHook: #{$!}"
1936 respond $!.backtrace.first
1937 end
1938 return nil if server_string.nil?
1939 end
1940 return server_string
1941 end
1942 def DownstreamHook.remove(name)
1943 @@downstream_hooks.delete(name)
1944 end
1945 def DownstreamHook.list
1946 @@downstream_hooks.keys.dup
1947 end
1948end
1949
1950module Setting
1951 @@load = proc { |args|
1952 unless script = Script.current
1953 respond '--- error: Setting.load: calling script is unknown'
1954 respond $!.backtrace[0..2]
1955 next nil
1956 end
1957 if script.class == ExecScript
1958 respond "--- Lich: error: Setting.load: exec scripts can't have settings"
1959 respond $!.backtrace[0..2]
1960 exit
1961 end
1962 if args.empty?
1963 respond '--- error: Setting.load: no setting specified'
1964 respond $!.backtrace[0..2]
1965 exit
1966 end
1967 if args.any? { |a| a.class != String }
1968 respond "--- Lich: error: Setting.load: non-string given as setting name"
1969 respond $!.backtrace[0..2]
1970 exit
1971 end
1972 values = Array.new
1973 for setting in args
1974 begin
1975 v = Lich.db.get_first_value('SELECT value FROM script_setting WHERE script=? AND name=?;', script.name.encode('UTF-8'), setting.encode('UTF-8'))
1976 rescue SQLite3::BusyException
1977 sleep 0.1
1978 retry
1979 end
1980 if v.nil?
1981 values.push(v)
1982 else
1983 begin
1984 values.push(Marshal.load(v))
1985 rescue
1986 respond "--- Lich: error: Setting.load: #{$!}"
1987 respond $!.backtrace[0..2]
1988 exit
1989 end
1990 end
1991 end
1992 if args.length == 1
1993 next values[0]
1994 else
1995 next values
1996 end
1997 }
1998 @@save = proc { |hash|
1999 unless script = Script.current
2000 respond '--- error: Setting.save: calling script is unknown'
2001 respond $!.backtrace[0..2]
2002 next nil
2003 end
2004 if script.class == ExecScript
2005 respond "--- Lich: error: Setting.load: exec scripts can't have settings"
2006 respond $!.backtrace[0..2]
2007 exit
2008 end
2009 if hash.class != Hash
2010 respond "--- Lich: error: Setting.save: invalid arguments: use Setting.save('setting1' => 'value1', 'setting2' => 'value2')"
2011 respond $!.backtrace[0..2]
2012 exit
2013 end
2014 if hash.empty?
2015 next nil
2016 end
2017 if hash.keys.any? { |k| k.class != String }
2018 respond "--- Lich: error: Setting.save: non-string given as a setting name"
2019 respond $!.backtrace[0..2]
2020 exit
2021 end
2022 if hash.length > 1
2023 begin
2024 Lich.db.execute('BEGIN')
2025 rescue SQLite3::BusyException
2026 sleep 0.1
2027 retry
2028 end
2029 end
2030 hash.each { |setting,value|
2031 begin
2032 if value.nil?
2033 begin
2034 Lich.db.execute('DELETE FROM script_setting WHERE script=? AND name=?;', script.name.encode('UTF-8'), setting.encode('UTF-8'))
2035 rescue SQLite3::BusyException
2036 sleep 0.1
2037 retry
2038 end
2039 else
2040 v = SQLite3::Blob.new(Marshal.dump(value))
2041 begin
2042 Lich.db.execute('INSERT OR REPLACE INTO script_setting(script,name,value) VALUES(?,?,?);', script.name.encode('UTF-8'), setting.encode('UTF-8'), v)
2043 rescue SQLite3::BusyException
2044 sleep 0.1
2045 retry
2046 end
2047 end
2048 rescue SQLite3::BusyException
2049 sleep 0.1
2050 retry
2051 end
2052 }
2053 if hash.length > 1
2054 begin
2055 Lich.db.execute('END')
2056 rescue SQLite3::BusyException
2057 sleep 0.1
2058 retry
2059 end
2060 end
2061 true
2062 }
2063 @@list = proc {
2064 unless script = Script.current
2065 respond '--- error: Setting: unknown calling script'
2066 next nil
2067 end
2068 if script.class == ExecScript
2069 respond "--- Lich: error: Setting.load: exec scripts can't have settings"
2070 respond $!.backtrace[0..2]
2071 exit
2072 end
2073 begin
2074 rows = Lich.db.execute('SELECT name FROM script_setting WHERE script=?;', script.name.encode('UTF-8'))
2075 rescue SQLite3::BusyException
2076 sleep 0.1
2077 retry
2078 end
2079 if rows
2080 # fixme
2081 next rows.inspect
2082 else
2083 next nil
2084 end
2085 }
2086 def Setting.load(*args)
2087 @@load.call(args)
2088 end
2089 def Setting.save(hash)
2090 @@save.call(hash)
2091 end
2092 def Setting.list
2093 @@list.call
2094 end
2095end
2096
2097module GameSetting
2098 def GameSetting.load(*args)
2099 Setting.load(args.collect { |a| "#{XMLData.game}:#{a}" })
2100 end
2101 def GameSetting.save(hash)
2102 game_hash = Hash.new
2103 hash.each_pair { |k,v| game_hash["#{XMLData.game}:#{k}"] = v }
2104 Setting.save(game_hash)
2105 end
2106end
2107
2108module CharSetting
2109 def CharSetting.load(*args)
2110 Setting.load(args.collect { |a| "#{XMLData.game}:#{XMLData.name}:#{a}" })
2111 end
2112 def CharSetting.save(hash)
2113 game_hash = Hash.new
2114 hash.each_pair { |k,v| game_hash["#{XMLData.game}:#{XMLData.name}:#{k}"] = v }
2115 Setting.save(game_hash)
2116 end
2117end
2118
2119module Settings
2120 settings = Hash.new
2121 md5_at_load = Hash.new
2122 mutex = Mutex.new
2123 @@settings = proc { |scope|
2124 unless script = Script.current
2125 respond '--- error: Settings: unknown calling script'
2126 next nil
2127 end
2128 unless scope =~ /^#{XMLData.game}\:#{XMLData.name}$|^#{XMLData.game}$|^\:$/
2129 respond '--- error: Settings: invalid scope'
2130 next nil
2131 end
2132 mutex.synchronize {
2133 unless settings[script.name] and settings[script.name][scope]
2134 begin
2135 _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'))
2136 rescue SQLite3::BusyException
2137 sleep 0.1
2138 retry
2139 end
2140 settings[script.name] ||= Hash.new
2141 if _hash.nil?
2142 settings[script.name][scope] = Hash.new
2143 else
2144 begin
2145 hash = Marshal.load(_hash)
2146 rescue
2147 respond "--- Lich: error: #{$!}"
2148 respond $!.backtrace[0..1]
2149 exit
2150 end
2151 settings[script.name][scope] = hash
2152 end
2153 md5_at_load[script.name] ||= Hash.new
2154 md5_at_load[script.name][scope] = Digest::MD5.hexdigest(settings[script.name][scope].to_s)
2155 end
2156 }
2157 settings[script.name][scope]
2158 }
2159 @@save = proc {
2160 mutex.synchronize {
2161 sql_began = false
2162 settings.each_pair { |script_name,scopedata|
2163 scopedata.each_pair { |scope,data|
2164 if Digest::MD5.hexdigest(data.to_s) != md5_at_load[script_name][scope]
2165 unless sql_began
2166 begin
2167 Lich.db.execute('BEGIN')
2168 rescue SQLite3::BusyException
2169 sleep 0.1
2170 retry
2171 end
2172 sql_began = true
2173 end
2174 blob = SQLite3::Blob.new(Marshal.dump(data))
2175 begin
2176 Lich.db.execute('INSERT OR REPLACE INTO script_auto_settings(script,scope,hash) VALUES(?,?,?);', script_name.encode('UTF-8'), scope.encode('UTF-8'), blob)
2177 rescue SQLite3::BusyException
2178 sleep 0.1
2179 retry
2180 rescue
2181 respond "--- Lich: error: #{$!}"
2182 respond $!.backtrace[0..1]
2183 next
2184 end
2185 end
2186 }
2187 unless Script.running?(script_name)
2188 settings.delete(script_name)
2189 md5_at_load.delete(script_name)
2190 end
2191 }
2192 if sql_began
2193 begin
2194 Lich.db.execute('END')
2195 rescue SQLite3::BusyException
2196 sleep 0.1
2197 retry
2198 end
2199 end
2200 }
2201 }
2202 Thread.new {
2203 loop {
2204 sleep 300
2205 begin
2206 @@save.call
2207 rescue
2208 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
2209 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
2210 end
2211 }
2212 }
2213 def Settings.[](name)
2214 @@settings.call(':')[name]
2215 end
2216 def Settings.[]=(name, value)
2217 @@settings.call(':')[name] = value
2218 end
2219 def Settings.to_hash(scope=':')
2220 @@settings.call(scope)
2221 end
2222 def Settings.char
2223 @@settings.call("#{XMLData.game}:#{XMLData.name}")
2224 end
2225 def Settings.save
2226 @@save.call
2227 end
2228end
2229
2230module GameSettings
2231 def GameSettings.[](name)
2232 Settings.to_hash(XMLData.game)[name]
2233 end
2234 def GameSettings.[]=(name, value)
2235 Settings.to_hash(XMLData.game)[name] = value
2236 end
2237 def GameSettings.to_hash
2238 Settings.to_hash(XMLData.game)
2239 end
2240end
2241
2242module CharSettings
2243 def CharSettings.[](name)
2244 Settings.to_hash("#{XMLData.game}:#{XMLData.name}")[name]
2245 end
2246 def CharSettings.[]=(name, value)
2247 Settings.to_hash("#{XMLData.game}:#{XMLData.name}")[name] = value
2248 end
2249 def CharSettings.to_hash
2250 Settings.to_hash("#{XMLData.game}:#{XMLData.name}")
2251 end
2252end
2253
2254module Vars
2255 @@vars = Hash.new
2256 md5 = nil
2257 mutex = Mutex.new
2258 @@loaded = false
2259 @@load = proc {
2260 mutex.synchronize {
2261 unless @@loaded
2262 begin
2263 h = Lich.db.get_first_value('SELECT hash FROM uservars WHERE scope=?;', "#{XMLData.game}:#{XMLData.name}".encode('UTF-8'))
2264 rescue SQLite3::BusyException
2265 sleep 0.1
2266 retry
2267 end
2268 if h
2269 begin
2270 hash = Marshal.load(h)
2271 hash.each { |k,v| @@vars[k] = v }
2272 md5 = Digest::MD5.hexdigest(hash.to_s)
2273 rescue
2274 respond "--- Lich: error: #{$!}"
2275 respond $!.backtrace[0..2]
2276 end
2277 end
2278 @@loaded = true
2279 end
2280 }
2281 nil
2282 }
2283 @@save = proc {
2284 mutex.synchronize {
2285 if @@loaded
2286 if Digest::MD5.hexdigest(@@vars.to_s) != md5
2287 md5 = Digest::MD5.hexdigest(@@vars.to_s)
2288 blob = SQLite3::Blob.new(Marshal.dump(@@vars))
2289 begin
2290 Lich.db.execute('INSERT OR REPLACE INTO uservars(scope,hash) VALUES(?,?);', "#{XMLData.game}:#{XMLData.name}".encode('UTF-8'), blob)
2291 rescue SQLite3::BusyException
2292 sleep 0.1
2293 retry
2294 end
2295 end
2296 end
2297 }
2298 nil
2299 }
2300 Thread.new {
2301 loop {
2302 sleep 300
2303 begin
2304 @@save.call
2305 rescue
2306 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
2307 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
2308 end
2309 }
2310 }
2311 def Vars.[](name)
2312 @@load.call unless @@loaded
2313 @@vars[name]
2314 end
2315 def Vars.[]=(name, val)
2316 @@load.call unless @@loaded
2317 if val.nil?
2318 @@vars.delete(name)
2319 else
2320 @@vars[name] = val
2321 end
2322 end
2323 def Vars.list
2324 @@load.call unless @@loaded
2325 @@vars.dup
2326 end
2327 def Vars.save
2328 @@save.call
2329 end
2330 def Vars.method_missing(arg1, arg2='')
2331 @@load.call unless @@loaded
2332 if arg1[-1,1] == '='
2333 if arg2.nil?
2334 @@vars.delete(arg1.to_s.chop)
2335 else
2336 @@vars[arg1.to_s.chop] = arg2
2337 end
2338 else
2339 @@vars[arg1.to_s]
2340 end
2341 end
2342end
2343
2344#
2345# script bindings are convoluted, but don't change them without testing if:
2346# 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)
2347# local variables become shared between scripts
2348# local variable 'file' is shared between scripts, even though other local variables aren't
2349# defined methods are instantly inaccessible
2350# 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
2351#
2352class Scripting
2353 def script
2354 Proc.new {}.binding
2355 end
2356end
2357def _script
2358 Proc.new {}.binding
2359end
2360
2361TRUSTED_SCRIPT_BINDING = proc { _script }
2362
2363class Script
2364 @@elevated_script_start = proc { |args|
2365 if args.empty?
2366 # fixme: error
2367 next nil
2368 elsif args[0].class == String
2369 script_name = args[0]
2370 if args[1]
2371 if args[1].class == String
2372 script_args = args[1]
2373 if args[2]
2374 if args[2].class == Hash
2375 options = args[2]
2376 else
2377 # fixme: error
2378 next nil
2379 end
2380 end
2381 elsif args[1].class == Hash
2382 options = args[1]
2383 script_args = (options[:args] || String.new)
2384 else
2385 # fixme: error
2386 next nil
2387 end
2388 else
2389 options = Hash.new
2390 end
2391 elsif args[0].class == Hash
2392 options = args[0]
2393 if options[:name]
2394 script_name = options[:name]
2395 else
2396 # fixme: error
2397 next nil
2398 end
2399 script_args = (options[:args] || String.new)
2400 end
2401 # fixme: look in wizard script directory
2402 # fixme: allow subdirectories?
2403 file_list = Dir.entries(SCRIPT_DIR).delete_if { |fn| (fn == '.') or (fn == '..') }.sort
2404 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 })
2405 script_name = file_name.sub(/\..{1,3}$/, '')
2406 end
2407 file_list = nil
2408 if file_name.nil?
2409 respond "--- Lich: could not find script '#{script_name}' in directory #{SCRIPT_DIR}"
2410 next nil
2411 end
2412 if (options[:force] != true) and (Script.running + Script.hidden).find { |s| s.name =~ /^#{Regexp.escape(script_name)}$/i }
2413 respond "--- Lich: #{script_name} is already running (use #{$clean_lich_char}force [scriptname] if desired)."
2414 next nil
2415 end
2416 begin
2417 if file_name =~ /\.(?:cmd|wiz)(?:\.gz)?$/i
2418 trusted = false
2419 script_obj = WizardScript.new("#{SCRIPT_DIR}/#{file_name}", script_args)
2420 else
2421 begin
2422 trusted = Lich.db.get_first_value('SELECT name FROM trusted_scripts WHERE name=?;', script_name.encode('UTF-8'))
2423 rescue SQLite3::BusyException
2424 sleep 0.1
2425 retry
2426 end
2427 script_obj = Script.new(:file => "#{SCRIPT_DIR}/#{file_name}", :args => script_args, :quiet => options[:quiet])
2428 end
2429 if trusted and not script_obj.labels.length > 1
2430 script_binding = TRUSTED_SCRIPT_BINDING.call
2431 else
2432 script_binding = Scripting.new.script
2433 end
2434 rescue
2435 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
2436 next nil
2437 end
2438 unless script_obj
2439 respond "--- Lich: error: failed to start script (#{script_name})"
2440 next nil
2441 end
2442 script_obj.quiet = true if options[:quiet]
2443 new_thread = Thread.new {
2444 100.times { break if Script.current == script_obj; sleep 0.01 }
2445 if script = Script.current
2446 eval('script = Script.current', script_binding, script.name)
2447 Thread.current.priority = 1
2448 respond("--- Lich: #{script.name} active.") unless script.quiet
2449 if trusted
2450 begin
2451 eval(script.labels[script.current_label].to_s, script_binding, script.name)
2452 rescue SystemExit
2453 nil
2454 rescue SyntaxError
2455 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
2456 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
2457 rescue ScriptError
2458 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
2459 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
2460 rescue NoMemoryError
2461 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
2462 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
2463 rescue LoadError
2464 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
2465 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
2466 rescue SecurityError
2467 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
2468 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
2469 rescue ThreadError
2470 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
2471 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
2472 rescue SystemStackError
2473 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
2474 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
2475 rescue Exception
2476 if $! == JUMP
2477 retry if Script.current.get_next_label != JUMP_ERROR
2478 respond "--- label error: `#{Script.current.jump_label}' was not found, and no `LabelError' label was found!"
2479 respond $!.backtrace.first
2480 Lich.log "label error: `#{Script.current.jump_label}' was not found, and no `LabelError' label was found!\n\t#{$!.backtrace.join("\n\t")}"
2481 Script.current.kill
2482 else
2483 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
2484 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
2485 end
2486 rescue
2487 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
2488 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
2489 ensure
2490 Script.current.kill
2491 end
2492 else
2493 begin
2494 while (script = Script.current) and script.current_label
2495 proc { foo = script.labels[script.current_label]; foo.untaint; $SAFE = 3; eval(foo, script_binding, script.name, 1) }.call
2496 Script.current.get_next_label
2497 end
2498 rescue SystemExit
2499 nil
2500 rescue SyntaxError
2501 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
2502 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
2503 rescue ScriptError
2504 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
2505 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
2506 rescue NoMemoryError
2507 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
2508 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
2509 rescue LoadError
2510 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
2511 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
2512 rescue SecurityError
2513 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
2514 if name = Script.current.name
2515 respond "--- Lich: review this script (#{name}) to make sure it isn't malicious, and type #{$clean_lich_char}trust #{name}"
2516 end
2517 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
2518 rescue ThreadError
2519 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
2520 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
2521 rescue SystemStackError
2522 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
2523 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
2524 rescue Exception
2525 if $! == JUMP
2526 retry if Script.current.get_next_label != JUMP_ERROR
2527 respond "--- label error: `#{Script.current.jump_label}' was not found, and no `LabelError' label was found!"
2528 respond $!.backtrace.first
2529 Lich.log "label error: `#{Script.current.jump_label}' was not found, and no `LabelError' label was found!\n\t#{$!.backtrace.join("\n\t")}"
2530 Script.current.kill
2531 else
2532 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
2533 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
2534 end
2535 rescue
2536 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
2537 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
2538 ensure
2539 Script.current.kill
2540 end
2541 end
2542 else
2543 respond '--- error: out of cheese'
2544 end
2545 }
2546 script_obj.thread_group.add(new_thread)
2547 script_obj
2548 }
2549 @@elevated_exists = proc { |script_name|
2550 if script_name =~ /\\|\//
2551 nil
2552 elsif script_name =~ /\.(?:lic|lich|rb|cmd|wiz)(?:\.gz)?$/i
2553 File.exists?("#{SCRIPT_DIR}/#{script_name}")
2554 else
2555 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")
2556 end
2557 }
2558 @@elevated_log = proc { |data|
2559 if script = Script.current
2560 if script.name =~ /\\|\//
2561 nil
2562 else
2563 begin
2564 Dir.mkdir("#{LICH_DIR}/logs") unless File.exists?("#{LICH_DIR}/logs")
2565 File.open("#{LICH_DIR}/logs/#{script.name}.log", 'a') { |f| f.puts data }
2566 true
2567 rescue
2568 respond "--- Lich: error: Script.log: #{$!}"
2569 false
2570 end
2571 end
2572 else
2573 respond '--- error: Script.log: unable to identify calling script'
2574 false
2575 end
2576 }
2577 @@elevated_db = proc {
2578 if script = Script.current
2579 if script.name =~ /^lich$/i
2580 respond '--- error: Script.db cannot be used by a script named lich'
2581 nil
2582 elsif script.class == ExecScript
2583 respond '--- error: Script.db cannot be used by exec scripts'
2584 nil
2585 else
2586 SQLite3::Database.new("#{DATA_DIR}/#{script.name.gsub(/\/|\\/, '_')}.db3")
2587 end
2588 else
2589 respond '--- error: Script.db called by an unknown script'
2590 nil
2591 end
2592 }
2593 @@elevated_open_file = proc { |ext,mode,block|
2594 if script = Script.current
2595 if script.name =~ /^lich$/i
2596 respond '--- error: Script.open_file cannot be used by a script named lich'
2597 nil
2598 elsif script.name =~ /^entry$/i
2599 respond '--- error: Script.open_file cannot be used by a script named entry'
2600 nil
2601 elsif script.class == ExecScript
2602 respond '--- error: Script.open_file cannot be used by exec scripts'
2603 nil
2604 elsif ext.downcase == 'db3'
2605 SQLite3::Database.new("#{DATA_DIR}/#{script.name.gsub(/\/|\\/, '_')}.db3")
2606# fixme: block gets elevated... why?
2607# elsif block
2608# File.open("#{DATA_DIR}/#{script.name.gsub(/\/|\\/, '_')}.#{ext.gsub(/\/|\\/, '_')}", mode, &block)
2609 else
2610 File.open("#{DATA_DIR}/#{script.name.gsub(/\/|\\/, '_')}.#{ext.gsub(/\/|\\/, '_')}", mode)
2611 end
2612 else
2613 respond '--- error: Script.open_file called by an unknown script'
2614 nil
2615 end
2616 }
2617 @@running = Array.new
2618
2619 attr_reader :name, :vars, :safe, :file_name, :label_order, :at_exit_procs
2620 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
2621 def Script.list
2622 @@running.dup
2623 end
2624 def Script.current
2625 if script = @@running.find { |s| s.has_thread?(Thread.current) }
2626 sleep 0.2 while script.paused? and not script.ignore_pause
2627 script
2628 else
2629 nil
2630 end
2631 end
2632 def Script.start(*args)
2633 @@elevated_script_start.call(args)
2634 end
2635 def Script.run(*args)
2636 if s = @@elevated_script_start.call(args)
2637 sleep 0.1 while @@running.include?(s)
2638 end
2639 end
2640 def Script.running?(name)
2641 @@running.any? { |i| (i.name =~ /^#{name}$/i) }
2642 end
2643 def Script.pause(name=nil)
2644 if name.nil?
2645 Script.current.pause
2646 Script.current
2647 else
2648 if s = (@@running.find { |i| (i.name == name) and not i.paused? }) || (@@running.find { |i| (i.name =~ /^#{name}$/i) and not i.paused? })
2649 s.pause
2650 true
2651 else
2652 false
2653 end
2654 end
2655 end
2656 def Script.unpause(name)
2657 if s = (@@running.find { |i| (i.name == name) and i.paused? }) || (@@running.find { |i| (i.name =~ /^#{name}$/i) and i.paused? })
2658 s.unpause
2659 true
2660 else
2661 false
2662 end
2663 end
2664 def Script.kill(name)
2665 if s = (@@running.find { |i| i.name == name }) || (@@running.find { |i| i.name =~ /^#{name}$/i })
2666 s.kill
2667 true
2668 else
2669 false
2670 end
2671 end
2672 def Script.paused?(name)
2673 if s = (@@running.find { |i| i.name == name }) || (@@running.find { |i| i.name =~ /^#{name}$/i })
2674 s.paused?
2675 else
2676 nil
2677 end
2678 end
2679 def Script.exists?(script_name)
2680 @@elevated_exists.call(script_name)
2681 end
2682 def Script.new_downstream_xml(line)
2683 for script in @@running
2684 script.downstream_buffer.push(line.chomp) if script.want_downstream_xml
2685 end
2686 end
2687 def Script.new_upstream(line)
2688 for script in @@running
2689 script.upstream_buffer.push(line.chomp) if script.want_upstream
2690 end
2691 end
2692 def Script.new_downstream(line)
2693 @@running.each { |script|
2694 script.downstream_buffer.push(line.chomp) if script.want_downstream
2695 unless script.watchfor.empty?
2696 script.watchfor.each_pair { |trigger,action|
2697 if line =~ trigger
2698 new_thread = Thread.new {
2699 sleep 0.011 until Script.current
2700 begin
2701 action.call
2702 rescue
2703 echo "watchfor error: #{$!}"
2704 end
2705 }
2706 script.thread_group.add(new_thread)
2707 end
2708 }
2709 end
2710 }
2711 end
2712 def Script.new_script_output(line)
2713 for script in @@running
2714 script.downstream_buffer.push(line.chomp) if script.want_script_output
2715 end
2716 end
2717 def Script.log(data)
2718 @@elevated_log.call(data)
2719 end
2720 def Script.db
2721 @@elevated_db.call
2722 end
2723 def Script.open_file(ext, mode='r', &block)
2724 @@elevated_open_file.call(ext, mode, block)
2725 end
2726 def Script.at_exit(&block)
2727 if script = Script.current
2728 script.at_exit(&block)
2729 else
2730 respond "--- Lich: error: Script.at_exit: can't identify calling script"
2731 return false
2732 end
2733 end
2734 def Script.clear_exit_procs
2735 if script = Script.current
2736 script.clear_exit_procs
2737 else
2738 respond "--- Lich: error: Script.clear_exit_procs: can't identify calling script"
2739 return false
2740 end
2741 end
2742 def Script.exit!
2743 if script = Script.current
2744 script.exit!
2745 else
2746 respond "--- Lich: error: Script.exit!: can't identify calling script"
2747 return false
2748 end
2749 end
2750 def Script.trust(script_name)
2751 # fixme: case sensitive blah blah
2752 if ($SAFE == 0) and not caller.any? { |c| c =~ /eval|run/ }
2753 begin
2754 Lich.db.execute('INSERT OR REPLACE INTO trusted_scripts(name) values(?);', script_name.encode('UTF-8'))
2755 rescue SQLite3::BusyException
2756 sleep 0.1
2757 retry
2758 end
2759 true
2760 else
2761 respond '--- error: scripts may not trust scripts'
2762 false
2763 end
2764 end
2765 def Script.distrust(script_name)
2766 begin
2767 there = Lich.db.get_first_value('SELECT name FROM trusted_scripts WHERE name=?;', script_name.encode('UTF-8'))
2768 rescue SQLite3::BusyException
2769 sleep 0.1
2770 retry
2771 end
2772 if there
2773 begin
2774 Lich.db.execute('DELETE FROM trusted_scripts WHERE name=?;', script_name.encode('UTF-8'))
2775 rescue SQLite3::BusyException
2776 sleep 0.1
2777 retry
2778 end
2779 true
2780 else
2781 false
2782 end
2783 end
2784 def Script.list_trusted
2785 list = Array.new
2786 begin
2787 Lich.db.execute('SELECT name FROM trusted_scripts;').each { |name| list.push(name[0]) }
2788 rescue SQLite3::BusyException
2789 sleep 0.1
2790 retry
2791 end
2792 list
2793 end
2794 def initialize(args)
2795 @file_name = args[:file]
2796 @name = /.*[\/\\]+([^\.]+)\./.match(@file_name).captures.first
2797 if args[:args].class == String
2798 if args[:args].empty?
2799 @vars = Array.new
2800 else
2801 @vars = [ args[:args] ]
2802 @vars.concat args[:args].scan(/[^\s"]*(?<!\\)"(?:\\"|[^"])+(?<!\\)"[^\s]*|(?:\\"|[^"\s])+/).collect { |s| s.gsub(/(?<!\\)"/,'').gsub('\\"', '"') }
2803 end
2804 elsif args[:args].class == Array
2805 @vars = args[:args] # fixme: set @vars[0] ?
2806 else
2807 @vars = Array.new
2808 end
2809 @quiet = (args[:quiet] ? true : false)
2810 @downstream_buffer = LimitedArray.new
2811 @want_downstream = true
2812 @want_downstream_xml = false
2813 @want_script_output = false
2814 @upstream_buffer = LimitedArray.new
2815 @want_upstream = false
2816 @unique_buffer = LimitedArray.new
2817 @watchfor = Hash.new
2818 @at_exit_procs = Array.new
2819 @die_with = Array.new
2820 @paused = false
2821 @hidden = false
2822 @no_pause_all = false
2823 @no_kill_all = false
2824 @silent = false
2825 @safe = false
2826 @no_echo = false
2827 @match_stack_labels = Array.new
2828 @match_stack_strings = Array.new
2829 @label_order = Array.new
2830 @labels = Hash.new
2831 @killer_mutex = Mutex.new
2832 @ignore_pause = false
2833 data = nil
2834 if @file_name =~ /\.gz$/i
2835 begin
2836 Zlib::GzipReader.open(@file_name) { |f| data = f.readlines.collect { |line| line.chomp } }
2837 rescue
2838 respond "--- Lich: error reading script file (#{@file_name}): #{$!}"
2839 return nil
2840 end
2841 else
2842 begin
2843 File.open(@file_name) { |f| data = f.readlines.collect { |line| line.chomp } }
2844 rescue
2845 respond "--- Lich: error reading script file (#{@file_name}): #{$!}"
2846 return nil
2847 end
2848 end
2849 @quiet = true if data[0] =~ /^[\t\s]*#?[\t\s]*(?:quiet|hush)$/i
2850 @current_label = '~start'
2851 @labels[@current_label] = String.new
2852 @label_order.push(@current_label)
2853 for line in data
2854 if line =~ /^([\d_\w]+):$/
2855 @current_label = $1
2856 @label_order.push(@current_label)
2857 @labels[@current_label] = String.new
2858 else
2859 @labels[@current_label].concat "#{line}\n"
2860 end
2861 end
2862 data = nil
2863 @current_label = @label_order[0]
2864 @thread_group = ThreadGroup.new
2865 @@running.push(self)
2866 return self
2867 end
2868 def kill
2869 Thread.new {
2870 @killer_mutex.synchronize {
2871 if @@running.include?(self)
2872 begin
2873 @thread_group.list.dup.each { |t|
2874 unless t == Thread.current
2875 t.kill rescue nil
2876 end
2877 }
2878 @thread_group.add(Thread.current)
2879 @die_with.each { |script_name| Script.kill(script_name) }
2880 @paused = false
2881 @at_exit_procs.each { |p| report_errors { p.call } }
2882 @die_with = @at_exit_procs = @downstream_buffer = @upstream_buffer = @match_stack_labels = @match_stack_strings = nil
2883 @@running.delete(self)
2884 respond("--- Lich: #{@name} has exited.") unless @quiet
2885 GC.start
2886 rescue
2887 respond "--- Lich: error: #{$!}"
2888 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
2889 end
2890 end
2891 }
2892 }
2893 @name
2894 end
2895 def at_exit(&block)
2896 if block
2897 @at_exit_procs.push(block)
2898 return true
2899 else
2900 respond '--- warning: Script.at_exit called with no code block'
2901 return false
2902 end
2903 end
2904 def clear_exit_procs
2905 @at_exit_procs.clear
2906 true
2907 end
2908 def exit
2909 kill
2910 end
2911 def exit!
2912 @at_exit_procs.clear
2913 kill
2914 end
2915 def instance_variable_get(*a); nil; end
2916 def instance_eval(*a); nil; end
2917 def labels
2918 ($SAFE == 0) ? @labels : nil
2919 end
2920 def thread_group
2921 ($SAFE == 0) ? @thread_group : nil
2922 end
2923 def has_thread?(t)
2924 @thread_group.list.include?(t)
2925 end
2926 def pause
2927 respond "--- Lich: #{@name} paused."
2928 @paused = true
2929 end
2930 def unpause
2931 respond "--- Lich: #{@name} unpaused."
2932 @paused = false
2933 end
2934 def paused?
2935 @paused
2936 end
2937 def get_next_label
2938 if !@jump_label
2939 @current_label = @label_order[@label_order.index(@current_label)+1]
2940 else
2941 if label = @labels.keys.find { |val| val =~ /^#{@jump_label}$/ }
2942 @current_label = label
2943 elsif label = @labels.keys.find { |val| val =~ /^#{@jump_label}$/i }
2944 @current_label = label
2945 elsif label = @labels.keys.find { |val| val =~ /^labelerror$/i }
2946 @current_label = label
2947 else
2948 @current_label = nil
2949 return JUMP_ERROR
2950 end
2951 @jump_label = nil
2952 @current_label
2953 end
2954 end
2955 def clear
2956 to_return = @downstream_buffer.dup
2957 @downstream_buffer.clear
2958 to_return
2959 end
2960 def to_s
2961 @name
2962 end
2963 def gets
2964 # fixme: no xml gets
2965 if @want_downstream or @want_downstream_xml or @want_script_output
2966 sleep 0.05 while @downstream_buffer.empty?
2967 @downstream_buffer.shift
2968 else
2969 echo 'this script is set as unique but is waiting for game data...'
2970 sleep 2
2971 false
2972 end
2973 end
2974 def gets?
2975 if @want_downstream or @want_downstream_xml or @want_script_output
2976 if @downstream_buffer.empty?
2977 nil
2978 else
2979 @downstream_buffer.shift
2980 end
2981 else
2982 echo 'this script is set as unique but is waiting for game data...'
2983 sleep 2
2984 false
2985 end
2986 end
2987 def upstream_gets
2988 sleep 0.05 while @upstream_buffer.empty?
2989 @upstream_buffer.shift
2990 end
2991 def upstream_gets?
2992 if @upstream_buffer.empty?
2993 nil
2994 else
2995 @upstream_buffer.shift
2996 end
2997 end
2998 def unique_gets
2999 sleep 0.05 while @unique_buffer.empty?
3000 @unique_buffer.shift
3001 end
3002 def unique_gets?
3003 if @unique_buffer.empty?
3004 nil
3005 else
3006 @unique_buffer.shift
3007 end
3008 end
3009 def safe?
3010 @safe
3011 end
3012 def feedme_upstream
3013 @want_upstream = !@want_upstream
3014 end
3015 def match_stack_add(label,string)
3016 @match_stack_labels.push(label)
3017 @match_stack_strings.push(string)
3018 end
3019 def match_stack_clear
3020 @match_stack_labels.clear
3021 @match_stack_strings.clear
3022 end
3023end
3024
3025class ExecScript<Script
3026 @@name_exec_mutex = Mutex.new
3027 @@elevated_start = proc { |cmd_data, options|
3028 options[:trusted] = false
3029 unless new_script = ExecScript.new(cmd_data, options)
3030 respond '--- Lich: failed to start exec script'
3031 return false
3032 end
3033 new_thread = Thread.new {
3034 100.times { break if Script.current == new_script; sleep 0.01 }
3035 if script = Script.current
3036 Thread.current.priority = 1
3037 respond("--- Lich: #{script.name} active.") unless script.quiet
3038 begin
3039 script_binding = Scripting.new.script
3040 eval('script = Script.current', script_binding, script.name.to_s)
3041 proc { cmd_data.untaint; $SAFE = 3; eval(cmd_data, script_binding, script.name.to_s) }.call
3042 Script.current.kill
3043 rescue SystemExit
3044 Script.current.kill
3045 rescue SyntaxError
3046 respond "--- SyntaxError: #{$!}"
3047 respond $!.backtrace.first
3048 Lich.log "SyntaxError: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
3049 Script.current.kill
3050 rescue ScriptError
3051 respond "--- ScriptError: #{$!}"
3052 respond $!.backtrace.first
3053 Lich.log "ScriptError: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
3054 Script.current.kill
3055 rescue NoMemoryError
3056 respond "--- NoMemoryError: #{$!}"
3057 respond $!.backtrace.first
3058 Lich.log "NoMemoryError: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
3059 Script.current.kill
3060 rescue LoadError
3061 respond("--- LoadError: #{$!}")
3062 respond "--- LoadError: #{$!}"
3063 respond $!.backtrace.first
3064 Lich.log "LoadError: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
3065 Script.current.kill
3066 rescue SecurityError
3067 respond "--- SecurityError: #{$!}"
3068 respond $!.backtrace[0..1]
3069 Lich.log "SecurityError: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
3070 Script.current.kill
3071 rescue ThreadError
3072 respond "--- ThreadError: #{$!}"
3073 respond $!.backtrace.first
3074 Lich.log "ThreadError: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
3075 Script.current.kill
3076 rescue SystemStackError
3077 respond "--- SystemStackError: #{$!}"
3078 respond $!.backtrace.first
3079 Lich.log "SystemStackError: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
3080 Script.current.kill
3081 rescue Exception
3082 respond "--- Exception: #{$!}"
3083 respond $!.backtrace.first
3084 Lich.log "Exception: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
3085 Script.current.kill
3086 rescue
3087 respond "--- Lich: error: #{$!}"
3088 respond $!.backtrace.first
3089 Lich.log "Error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
3090 Script.current.kill
3091 end
3092 else
3093 respond '--- Lich: error: ExecScript.start: out of cheese'
3094 end
3095 }
3096 new_script.thread_group.add(new_thread)
3097 new_script
3098 }
3099 attr_reader :cmd_data
3100 def ExecScript.start(cmd_data, options={})
3101 options = { :quiet => true } if options == true
3102 if $SAFE > 0
3103 @@elevated_start.call(cmd_data, options)
3104 else
3105 unless new_script = ExecScript.new(cmd_data, options)
3106 respond '--- Lich: failed to start exec script'
3107 return false
3108 end
3109 new_thread = Thread.new {
3110 100.times { break if Script.current == new_script; sleep 0.01 }
3111 if script = Script.current
3112 Thread.current.priority = 1
3113 respond("--- Lich: #{script.name} active.") unless script.quiet
3114 begin
3115 if options[:trusted]
3116 script_binding = TRUSTED_SCRIPT_BINDING.call
3117 eval('script = Script.current', script_binding, script.name.to_s)
3118 eval(cmd_data, script_binding, script.name.to_s)
3119 else
3120 script_binding = Scripting.new.script
3121 eval('script = Script.current', script_binding, script.name.to_s)
3122 proc { cmd_data.untaint; $SAFE = 3; eval(cmd_data, script_binding, script.name.to_s) }.call
3123 end
3124 Script.current.kill
3125 rescue SystemExit
3126 Script.current.kill
3127 rescue SyntaxError
3128 respond "--- SyntaxError: #{$!}"
3129 respond $!.backtrace.first
3130 Lich.log "SyntaxError: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
3131 Script.current.kill
3132 rescue ScriptError
3133 respond "--- ScriptError: #{$!}"
3134 respond $!.backtrace.first
3135 Lich.log "ScriptError: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
3136 Script.current.kill
3137 rescue NoMemoryError
3138 respond "--- NoMemoryError: #{$!}"
3139 respond $!.backtrace.first
3140 Lich.log "NoMemoryError: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
3141 Script.current.kill
3142 rescue LoadError
3143 respond("--- LoadError: #{$!}")
3144 respond "--- LoadError: #{$!}"
3145 respond $!.backtrace.first
3146 Lich.log "LoadError: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
3147 Script.current.kill
3148 rescue SecurityError
3149 respond "--- SecurityError: #{$!}"
3150 respond $!.backtrace[0..1]
3151 Lich.log "SecurityError: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
3152 Script.current.kill
3153 rescue ThreadError
3154 respond "--- ThreadError: #{$!}"
3155 respond $!.backtrace.first
3156 Lich.log "ThreadError: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
3157 Script.current.kill
3158 rescue SystemStackError
3159 respond "--- SystemStackError: #{$!}"
3160 respond $!.backtrace.first
3161 Lich.log "SystemStackError: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
3162 Script.current.kill
3163 rescue Exception
3164 respond "--- Exception: #{$!}"
3165 respond $!.backtrace.first
3166 Lich.log "Exception: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
3167 Script.current.kill
3168 rescue
3169 respond "--- Lich: error: #{$!}"
3170 respond $!.backtrace.first
3171 Lich.log "Error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
3172 Script.current.kill
3173 end
3174 else
3175 respond 'start_exec_script screwed up...'
3176 end
3177 }
3178 new_script.thread_group.add(new_thread)
3179 new_script
3180 end
3181 end
3182 def initialize(cmd_data, flags=Hash.new)
3183 @cmd_data = cmd_data
3184 @vars = Array.new
3185 @downstream_buffer = LimitedArray.new
3186 @killer_mutex = Mutex.new
3187 @want_downstream = true
3188 @want_downstream_xml = false
3189 @upstream_buffer = LimitedArray.new
3190 @want_upstream = false
3191 @at_exit_procs = Array.new
3192 @watchfor = Hash.new
3193 @hidden = false
3194 @paused = false
3195 @silent = false
3196 if flags[:quiet].nil?
3197 @quiet = false
3198 else
3199 @quiet = flags[:quiet]
3200 end
3201 @safe = false
3202 @no_echo = false
3203 @thread_group = ThreadGroup.new
3204 @unique_buffer = LimitedArray.new
3205 @die_with = Array.new
3206 @no_pause_all = false
3207 @no_kill_all = false
3208 @match_stack_labels = Array.new
3209 @match_stack_strings = Array.new
3210 num = '1'; num.succ! while @@running.any? { |s| s.name == "exec#{num}" }
3211 @name = "exec#{num}"
3212 @@running.push(self)
3213 self
3214 end
3215 def get_next_label
3216 echo 'goto labels are not available in exec scripts.'
3217 nil
3218 end
3219end
3220
3221class WizardScript<Script
3222 def initialize(file_name, cli_vars=[])
3223 @name = /.*[\/\\]+([^\.]+)\./.match(file_name).captures.first
3224 @file_name = file_name
3225 @vars = Array.new
3226 @killer_mutex = Mutex.new
3227 unless cli_vars.empty?
3228 if cli_vars.is_a?(String)
3229 cli_vars = cli_vars.split(' ')
3230 end
3231 cli_vars.each_index { |idx| @vars[idx+1] = cli_vars[idx] }
3232 @vars[0] = @vars[1..-1].join(' ')
3233 cli_vars = nil
3234 end
3235 if @vars.first =~ /^quiet$/i
3236 @quiet = true
3237 @vars.shift
3238 else
3239 @quiet = false
3240 end
3241 @downstream_buffer = LimitedArray.new
3242 @want_downstream = true
3243 @want_downstream_xml = false
3244 @upstream_buffer = LimitedArray.new
3245 @want_upstream = false
3246 @unique_buffer = LimitedArray.new
3247 @at_exit_procs = Array.new
3248 @patchfor = Hash.new
3249 @die_with = Array.new
3250 @paused = false
3251 @hidden = false
3252 @no_pause_all = false
3253 @no_kill_all = false
3254 @silent = false
3255 @safe = false
3256 @no_echo = false
3257 @match_stack_labels = Array.new
3258 @match_stack_strings = Array.new
3259 @label_order = Array.new
3260 @labels = Hash.new
3261 data = nil
3262 begin
3263 Zlib::GzipReader.open(file_name) { |f| data = f.readlines.collect { |line| line.chomp } }
3264 rescue
3265 begin
3266 File.open(file_name) { |f| data = f.readlines.collect { |line| line.chomp } }
3267 rescue
3268 respond "--- Lich: error reading script file (#{file_name}): #{$!}"
3269 return nil
3270 end
3271 end
3272 @quiet = true if data[0] =~ /^[\t\s]*#?[\t\s]*(?:quiet|hush)$/i
3273
3274 counter_action = {
3275 'add' => '+',
3276 'sub' => '-',
3277 'subtract' => '-',
3278 'multiply' => '*',
3279 'divide' => '/',
3280 'set' => ''
3281 }
3282
3283 setvars = Array.new
3284 data.each { |line| setvars.push($1) if line =~ /[\s\t]*setvariable\s+([^\s\t]+)[\s\t]/i and not setvars.include?($1) }
3285 has_counter = data.find { |line| line =~ /%c/i }
3286 has_save = data.find { |line| line =~ /%s/i }
3287 has_nextroom = data.find { |line| line =~ /nextroom/i }
3288
3289 fixstring = proc { |str|
3290 while not setvars.empty? and str =~ /%(#{setvars.join('|')})%/io
3291 str.gsub!('%' + $1 + '%', '#{' + $1.downcase + '}')
3292 end
3293 str.gsub!(/%c(?:%)?/i, '#{c}')
3294 str.gsub!(/%s(?:%)?/i, '#{sav}')
3295 while str =~ /%([0-9])(?:%)?/
3296 str.gsub!(/%#{$1}(?:%)?/, '#{script.vars[' + $1 + ']}')
3297 end
3298 str
3299 }
3300
3301 fixline = proc { |line|
3302 if line =~ /^[\s\t]*[A-Za-z0-9_\-']+:/i
3303 line = line.downcase.strip
3304 elsif line =~ /^([\s\t]*)counter\s+(add|sub|subtract|divide|multiply|set)\s+([0-9]+)/i
3305 line = "#{$1}c #{counter_action[$2]}= #{$3}"
3306 elsif line =~ /^([\s\t]*)counter\s+(add|sub|subtract|divide|multiply|set)\s+(.*)/i
3307 indent, action, arg = $1, $2, $3
3308 line = "#{indent}c #{counter_action[action]}= #{fixstring.call(arg.inspect)}.to_i"
3309 elsif line =~ /^([\s\t]*)save[\s\t]+"?(.*?)"?[\s\t]*$/i
3310 indent, arg = $1, $2
3311 line = "#{indent}sav = #{fixstring.call(arg.inspect)}"
3312 elsif line =~ /^([\s\t]*)echo[\s\t]+(.+)/i
3313 indent, arg = $1, $2
3314 line = "#{indent}echo #{fixstring.call(arg.inspect)}"
3315 elsif line =~ /^([\s\t]*)waitfor[\s\t]+(.+)/i
3316 indent, arg = $1, $2
3317 line = "#{indent}waitfor #{fixstring.call(Regexp.escape(arg).inspect.gsub("\\\\ ", ' '))}"
3318 elsif line =~ /^([\s\t]*)put[\s\t]+\.(.+)$/i
3319 indent, arg = $1, $2
3320 if arg.include?(' ')
3321 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"
3322 else
3323 line = "#{indent}start_script(#{Regexp.escape(fixstring.call(arg.inspect))})\n#{indent}exit"
3324 end
3325 elsif line =~ /^([\s\t]*)put[\s\t]+;(.+)$/i
3326 indent, arg = $1, $2
3327 if arg.include?(' ')
3328 line = "#{indent}start_script(#{Regexp.escape(fixstring.call(arg.split[0].inspect))}, #{fixstring.call(arg.split[1..-1].join(' ').scan(/"[^"]+"|[^"\s]+/).inspect)})"
3329 else
3330 line = "#{indent}start_script(#{Regexp.escape(fixstring.call(arg.inspect))})"
3331 end
3332 elsif line =~ /^([\s\t]*)(put|move)[\s\t]+(.+)/i
3333 indent, cmd, arg = $1, $2, $3
3334 line = "#{indent}waitrt?\n#{indent}clear\n#{indent}#{cmd.downcase} #{fixstring.call(arg.inspect)}"
3335 elsif line =~ /^([\s\t]*)goto[\s\t]+(.+)/i
3336 indent, arg = $1, $2
3337 line = "#{indent}goto #{fixstring.call(arg.inspect).downcase}"
3338 elsif line =~ /^([\s\t]*)waitforre[\s\t]+(.+)/i
3339 indent, arg = $1, $2
3340 line = "#{indent}waitforre #{arg}"
3341 elsif line =~ /^([\s\t]*)pause[\s\t]*(.*)/i
3342 indent, arg = $1, $2
3343 arg = '1' if arg.empty?
3344 arg = '0'+arg.strip if arg.strip =~ /^\.[0-9]+$/
3345 line = "#{indent}pause #{arg}"
3346 elsif line =~ /^([\s\t]*)match[\s\t]+([^\s\t]+)[\s\t]+(.+)/i
3347 indent, label, arg = $1, $2, $3
3348 line = "#{indent}match #{fixstring.call(label.inspect).downcase}, #{fixstring.call(Regexp.escape(arg).inspect.gsub("\\\\ ", ' '))}"
3349 elsif line =~ /^([\s\t]*)matchre[\s\t]+([^\s\t]+)[\s\t]+(.+)/i
3350 indent, label, regex = $1, $2, $3
3351 line = "#{indent}matchre #{fixstring.call(label.inspect).downcase}, #{regex}"
3352 elsif line =~ /^([\s\t]*)setvariable[\s\t]+([^\s\t]+)[\s\t]+(.+)/i
3353 indent, var, arg = $1, $2, $3
3354 line = "#{indent}#{var.downcase} = #{fixstring.call(arg.inspect)}"
3355 elsif line =~ /^([\s\t]*)deletevariable[\s\t]+(.+)/i
3356 line = "#{$1}#{$2.downcase} = nil"
3357 elsif line =~ /^([\s\t]*)(wait|nextroom|exit|echo)\b/i
3358 line = "#{$1}#{$2.downcase}"
3359 elsif line =~ /^([\s\t]*)matchwait\b/i
3360 line = "#{$1}matchwait"
3361 elsif line =~ /^([\s\t]*)if_([0-9])[\s\t]+(.*)/i
3362 indent, num, stuff = $1, $2, $3
3363 line = "#{indent}if script.vars[#{num}]\n#{indent}\t#{fixline.call($3)}\n#{indent}end"
3364 elsif line =~ /^([\s\t]*)shift\b/i
3365 line = "#{$1}script.vars.shift"
3366 else
3367 respond "--- Lich: unknown line: #{line}"
3368 line = '#' + line
3369 end
3370 }
3371
3372 lich_block = false
3373
3374 data.each_index { |idx|
3375 if lich_block
3376 if data[idx] =~ /\}[\s\t]*LICH[\s\t]*$/
3377 data[idx] = data[idx].sub(/\}[\s\t]*LICH[\s\t]*$/, '')
3378 lich_block = false
3379 else
3380 next
3381 end
3382 elsif data[idx] =~ /^[\s\t]*#|^[\s\t]*$/
3383 next
3384 elsif data[idx] =~ /^[\s\t]*LICH[\s\t]*\{/
3385 data[idx] = data[idx].sub(/LICH[\s\t]*\{/, '')
3386 if data[idx] =~ /\}[\s\t]*LICH[\s\t]*$/
3387 data[idx] = data[idx].sub(/\}[\s\t]*LICH[\s\t]*$/, '')
3388 else
3389 lich_block = true
3390 end
3391 else
3392 data[idx] = fixline.call(data[idx])
3393 end
3394 }
3395
3396 if has_counter or has_save or has_nextroom
3397 data.each_index { |idx|
3398 next if data[idx] =~ /^[\s\t]*#/
3399 data.insert(idx, '')
3400 data.insert(idx, 'c = 0') if has_counter
3401 data.insert(idx, "sav = Settings['sav'] || String.new\nbefore_dying { Settings['sav'] = sav }") if has_save
3402 data.insert(idx, "def nextroom\n\troom_count = XMLData.room_count\n\twait_while { room_count == XMLData.room_count }\nend") if has_nextroom
3403 data.insert(idx, '')
3404 break
3405 }
3406 end
3407
3408 @current_label = '~start'
3409 @labels[@current_label] = String.new
3410 @label_order.push(@current_label)
3411 for line in data
3412 if line =~ /^([\d_\w]+):$/
3413 @current_label = $1
3414 @label_order.push(@current_label)
3415 @labels[@current_label] = String.new
3416 else
3417 @labels[@current_label] += "#{line}\n"
3418 end
3419 end
3420 data = nil
3421 @current_label = @label_order[0]
3422 @thread_group = ThreadGroup.new
3423 @@running.push(self)
3424 return self
3425 end
3426end
3427
3428class Watchfor
3429 def initialize(line, theproc=nil, &block)
3430 return nil unless script = Script.current
3431 if line.class == String
3432 line = Regexp.new(Regexp.escape(line))
3433 elsif line.class != Regexp
3434 echo 'watchfor: no string or regexp given'
3435 return nil
3436 end
3437 if block.nil?
3438 if theproc.respond_to? :call
3439 block = theproc
3440 else
3441 echo 'watchfor: no block or proc given'
3442 return nil
3443 end
3444 end
3445 script.watchfor[line] = block
3446 end
3447 def Watchfor.clear
3448 script.watchfor = Hash.new
3449 end
3450end
3451
3452class Map
3453 @@loaded = false
3454 @@load_mutex = Mutex.new
3455 @@list ||= Array.new
3456 @@tags ||= Array.new
3457 @@current_room_mutex = Mutex.new
3458 @@current_room_id ||= 0
3459 @@current_room_count ||= -1
3460 @@fuzzy_room_mutex = Mutex.new
3461 @@fuzzy_room_id ||= 0
3462 @@fuzzy_room_count ||= -1
3463 @@current_location ||= nil
3464 @@current_location_count ||= -1
3465 @@elevated_load = proc { Map.load }
3466 @@elevated_load_dat = proc { Map.load_dat }
3467 @@elevated_load_xml = proc { Map.load_xml }
3468 @@elevated_save = proc { Map.save }
3469 @@elevated_save_xml = proc { Map.save_xml }
3470 attr_reader :id
3471 attr_accessor :title, :description, :paths, :location, :climate, :terrain, :wayto, :timeto, :image, :image_coords, :tags, :check_location, :unique_loot
3472 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)
3473 @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
3474 @@list[@id] = self
3475 end
3476 def outside?
3477 @paths.first =~ /Obvious paths:/
3478 end
3479 def to_i
3480 @id
3481 end
3482 def to_s
3483 "##{@id}:\n#{@title[-1]}\n#{@description[-1]}\n#{@paths[-1]}"
3484 end
3485 def inspect
3486 self.instance_variables.collect { |var| var.to_s + "=" + self.instance_variable_get(var).inspect }.join("\n")
3487 end
3488 def Map.get_free_id
3489 Map.load unless @@loaded
3490 free_id = 0
3491 until @@list[free_id].nil?
3492 free_id += 1
3493 end
3494 free_id
3495 end
3496 def Map.list
3497 Map.load unless @@loaded
3498 @@list
3499 end
3500 def Map.[](val)
3501 Map.load unless @@loaded
3502 if (val.class == Fixnum) or (val.class == Bignum) or val =~ /^[0-9]+$/
3503 @@list[val.to_i]
3504 else
3505 chkre = /#{val.strip.sub(/\.$/, '').gsub(/\.(?:\.\.)?/, '|')}/i
3506 chk = /#{Regexp.escape(val.strip)}/i
3507 @@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 } }
3508 end
3509 end
3510 def Map.get_location
3511 unless XMLData.room_count == @@current_location_count
3512 if script = Script.current
3513 save_want_downstream = script.want_downstream
3514 script.want_downstream = true
3515 waitrt?
3516 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\.$/
3517 script.want_downstream = save_want_downstream
3518 @@current_location_count = XMLData.room_count
3519 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\.$/
3520 @@current_location = false
3521 else
3522 @@current_location = /^You carefully survey your surroundings and guess that your current location is (.*?) or somewhere close to it\.$/.match(location_result).captures.first
3523 end
3524 else
3525 nil
3526 end
3527 end
3528 @@current_location
3529 end
3530 def Map.current
3531 Map.load unless @@loaded
3532 if script = Script.current
3533 @@current_room_mutex.synchronize {
3534 if XMLData.room_count == @@current_room_count
3535 if @@current_room_id.nil?
3536 return nil
3537 else
3538 return @@list[@@current_room_id]
3539 end
3540 else
3541 peer_history = Hash.new
3542 need_set_desc_off = false
3543 check_peer_tag = proc { |r|
3544 begin
3545 script.ignore_pause = true
3546 peer_room_count = XMLData.room_count
3547 if peer_tag = r.tags.find { |tag| tag =~ /^(set desc on; )?peer [a-z]+ =~ \/.+\/$/ }
3548 good = false
3549 need_desc, peer_direction, peer_requirement = /^(set desc on; )?peer ([a-z]+) =~ \/(.+)\/$/.match(peer_tag).captures
3550 need_desc = need_desc ? true : false
3551 if peer_history[peer_room_count][peer_direction][need_desc].nil?
3552 if need_desc
3553 unless last_roomdesc = $_SERVERBUFFER_.reverse.find { |line| line =~ /<style id="roomDesc"\/>/ } and (last_roomdesc =~ /<style id="roomDesc"\/>[^<]/)
3554 put 'set description on'
3555 need_set_desc_off = true
3556 end
3557 end
3558 save_want_downstream = script.want_downstream
3559 script.want_downstream = true
3560 squelch_started = false
3561 squelch_proc = proc { |server_string|
3562 if squelch_started
3563 if server_string =~ /<prompt/
3564 DownstreamHook.remove('squelch-peer')
3565 end
3566 nil
3567 elsif server_string =~ /^You peer/
3568 squelch_started = true
3569 nil
3570 else
3571 server_string
3572 end
3573 }
3574 DownstreamHook.add('squelch-peer', squelch_proc)
3575 result = dothistimeout "peer #{peer_direction}", 3, /^You peer|^\[Usage: PEER/
3576 if result =~ /^You peer/
3577 peer_results = Array.new
3578 5.times {
3579 if line = get?
3580 peer_results.push line
3581 break if line =~ /^Obvious/
3582 end
3583 }
3584 if XMLData.room_count == peer_room_count
3585 peer_history[peer_room_count] ||= Hash.new
3586 peer_history[peer_room_count][peer_direction] ||= Hash.new
3587 if need_desc
3588 peer_history[peer_room_count][peer_direction][true] = peer_results
3589 peer_history[peer_room_count][peer_direction][false] = peer_results
3590 else
3591 peer_history[peer_room_count][peer_direction][false] = peer_results
3592 end
3593 end
3594 end
3595 script.want_downstream = save_want_downstream
3596 end
3597 if peer_history[peer_room_count][peer_direction][need_desc].any? { |line| line =~ /#{peer_requirement}/ }
3598 good = true
3599 else
3600 good = false
3601 end
3602 else
3603 good = true
3604 end
3605 ensure
3606 script.ignore_pause = false
3607 end
3608 good
3609 }
3610 begin
3611 1.times {
3612 @@current_room_count = XMLData.room_count
3613 foggy_exits = (XMLData.room_exits_string =~ /^Obvious (?:exits|paths): obscured by a thick fog$/)
3614 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) }
3615 redo unless @@current_room_count == XMLData.room_count
3616 @@current_room_id = room.id
3617 return room
3618 else
3619 redo unless @@current_room_count == XMLData.room_count
3620 desc_regex = /#{Regexp.escape(XMLData.room_description.strip.sub(/\.+$/, '')).gsub(/\\\.(?:\\\.\\\.)?/, '|')}/
3621 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) }
3622 redo unless @@current_room_count == XMLData.room_count
3623 @@current_room_id = room.id
3624 return room
3625 else
3626 redo unless @@current_room_count == XMLData.room_count
3627 @@current_room_id = nil
3628 return nil
3629 end
3630 end
3631 }
3632 ensure
3633 put 'set description off' if need_set_desc_off
3634 end
3635 end
3636 }
3637 else
3638 @@fuzzy_room_mutex.synchronize {
3639 if XMLData.room_count == @@current_room_count
3640 if @@current_room_id.nil?
3641 return nil
3642 else
3643 return @@list[@@current_room_id]
3644 end
3645 elsif XMLData.room_count == @@fuzzy_room_count
3646 if @@fuzzy_room_id.nil?
3647 return nil
3648 else
3649 return @@list[@@fuzzy_room_id]
3650 end
3651 else
3652 1.times {
3653 @@fuzzy_room_count = XMLData.room_count
3654 foggy_exits = (XMLData.room_exits_string =~ /^Obvious (?:exits|paths): obscured by a thick fog$/)
3655 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) })
3656 redo unless @@fuzzy_room_count == XMLData.room_count
3657 if room.tags.any? { |tag| tag =~ /^(set desc on; )?peer [a-z]+ =~ \/.+\/$/ }
3658 @@fuzzy_room_id = nil
3659 return nil
3660 else
3661 @@fuzzy_room_id = room.id
3662 return room
3663 end
3664 else
3665 redo unless @@fuzzy_room_count == XMLData.room_count
3666 desc_regex = /#{Regexp.escape(XMLData.room_description.strip.sub(/\.+$/, '')).gsub(/\\\.(?:\\\.\\\.)?/, '|')}/
3667 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) }
3668 redo unless @@fuzzy_room_count == XMLData.room_count
3669 if room.tags.any? { |tag| tag =~ /^(set desc on; )?peer [a-z]+ =~ \/.+\/$/ }
3670 @@fuzzy_room_id = nil
3671 return nil
3672 else
3673 @@fuzzy_room_id = room.id
3674 return room
3675 end
3676 else
3677 redo unless @@fuzzy_room_count == XMLData.room_count
3678 @@fuzzy_room_id = nil
3679 return nil
3680 end
3681 end
3682 }
3683 end
3684 }
3685 end
3686 end
3687 def Map.current_or_new
3688 return nil unless Script.current
3689 if XMLData.game =~ /DR/
3690 @@current_room_count = -1
3691 @@fuzzy_room_count = -1
3692 Map.current || Map.new(Map.get_free_id, [ XMLData.room_title ], [ XMLData.room_description.strip ], [ XMLData.room_exits_string.strip ])
3693 else
3694 check_peer_tag = proc { |r|
3695 if peer_tag = r.tags.find { |tag| tag =~ /^(set desc on; )?peer [a-z]+ =~ \/.+\/$/ }
3696 good = false
3697 need_desc, peer_direction, peer_requirement = /^(set desc on; )?peer ([a-z]+) =~ \/(.+)\/$/.match(peer_tag).captures
3698 if need_desc
3699 unless last_roomdesc = $_SERVERBUFFER_.reverse.find { |line| line =~ /<style id="roomDesc"\/>/ } and (last_roomdesc =~ /<style id="roomDesc"\/>[^<]/)
3700 put 'set description on'
3701 end
3702 end
3703 script = Script.current
3704 save_want_downstream = script.want_downstream
3705 script.want_downstream = true
3706 squelch_started = false
3707 squelch_proc = proc { |server_string|
3708 if squelch_started
3709 if server_string =~ /<prompt/
3710 DownstreamHook.remove('squelch-peer')
3711 end
3712 nil
3713 elsif server_string =~ /^You peer/
3714 squelch_started = true
3715 nil
3716 else
3717 server_string
3718 end
3719 }
3720 DownstreamHook.add('squelch-peer', squelch_proc)
3721 result = dothistimeout "peer #{peer_direction}", 3, /^You peer|^\[Usage: PEER/
3722 if result =~ /^You peer/
3723 peer_results = Array.new
3724 5.times {
3725 if line = get?
3726 peer_results.push line
3727 break if line =~ /^Obvious/
3728 end
3729 }
3730 if peer_results.any? { |line| line =~ /#{peer_requirement}/ }
3731 good = true
3732 end
3733 end
3734 script.want_downstream = save_want_downstream
3735 else
3736 good = true
3737 end
3738 good
3739 }
3740 current_location = Map.get_location
3741 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) }
3742 return room
3743 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) }
3744 room.location = current_location
3745 return room
3746 else
3747 title = [ XMLData.room_title ]
3748 description = [ XMLData.room_description.strip ]
3749 paths = [ XMLData.room_exits_string.strip ]
3750 room = Map.new(Map.get_free_id, title, description, paths, current_location)
3751 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')) }
3752 if identical_rooms.length > 0
3753 room.check_location = true
3754 identical_rooms.each { |r| r.check_location = true }
3755 end
3756 return room
3757 end
3758 end
3759 end
3760 def Map.tags
3761 Map.load unless @@loaded
3762 if @@tags.empty?
3763 @@list.each { |r| r.tags.each { |t| @@tags.push(t) unless @@tags.include?(t) } }
3764 end
3765 @@tags.dup
3766 end
3767 def Map.clear
3768 @@load_mutex.synchronize {
3769 @@list.clear
3770 @@tags.clear
3771 @@loaded = false
3772 GC.start
3773 }
3774 true
3775 end
3776 def Map.reload
3777 Map.clear
3778 Map.load
3779 end
3780 def Map.load(filename=nil)
3781 if $SAFE == 0
3782 if filename.nil?
3783 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
3784 else
3785 file_list = [ filename ]
3786 end
3787 if file_list.empty?
3788 respond "--- Lich: error: no map database found"
3789 return false
3790 end
3791 while filename = file_list.shift
3792 if filename =~ /\.xml$/
3793 if Map.load_xml(filename)
3794 return true
3795 end
3796 else
3797 if Map.load_dat(filename)
3798 return true
3799 end
3800 end
3801 end
3802 return false
3803 else
3804 @@elevated_load.call
3805 end
3806 end
3807 def Map.load_dat(filename=nil)
3808 if $SAFE == 0
3809 @@load_mutex.synchronize {
3810 if @@loaded
3811 return true
3812 else
3813 if filename.nil?
3814 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
3815 else
3816 file_list = [ filename ]
3817 end
3818 if file_list.empty?
3819 respond "--- Lich: error: no map database found"
3820 return false
3821 end
3822 error = false
3823 while filename = file_list.shift
3824 begin
3825 @@list = File.open(filename, 'rb') { |f| Marshal.load(f.read) }
3826 respond "--- loaded #{filename}" if error
3827 @@loaded = true
3828 return true
3829 rescue
3830 error = true
3831 if file_list.empty?
3832 respond "--- Lich: error: failed to load #{filename}: #{$!}"
3833 else
3834 respond "--- warning: failed to load #{filename}: #{$!}"
3835 end
3836 end
3837 end
3838 return false
3839 end
3840 }
3841 else
3842 @@elevated_load_dat.call
3843 end
3844 end
3845 def Map.load_xml(filename="#{DATA_DIR}/#{XMLData.game}/map.xml")
3846 if $SAFE == 0
3847 @@load_mutex.synchronize {
3848 if @@loaded
3849 return true
3850 else
3851 unless File.exists?(filename)
3852 raise Exception.exception("MapDatabaseError"), "Fatal error: file `#{filename}' does not exist!"
3853 end
3854 missing_end = false
3855 current_tag = nil
3856 current_attributes = nil
3857 room = nil
3858 buffer = String.new
3859 unescape = { 'lt' => '<', 'gt' => '>', 'quot' => '"', 'apos' => "'", 'amp' => '&' }
3860 tag_start = proc { |element,attributes|
3861 current_tag = element
3862 current_attributes = attributes
3863 if element == 'room'
3864 room = Hash.new
3865 room['id'] = attributes['id'].to_i
3866 room['location'] = attributes['location']
3867 room['climate'] = attributes['climate']
3868 room['terrain'] = attributes['terrain']
3869 room['wayto'] = Hash.new
3870 room['timeto'] = Hash.new
3871 room['title'] = Array.new
3872 room['description'] = Array.new
3873 room['paths'] = Array.new
3874 room['tags'] = Array.new
3875 room['unique_loot'] = Array.new
3876 elsif element =~ /^(?:image|tsoran)$/ and attributes['name'] and attributes['x'] and attributes['y'] and attributes['size']
3877 room['image'] = attributes['name']
3878 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) ]
3879 elsif (element == 'image') and attributes['name'] and attributes['coords'] and (attributes['coords'] =~ /[0-9]+,[0-9]+,[0-9]+,[0-9]+/)
3880 room['image'] = attributes['name']
3881 room['image_coords'] = attributes['coords'].split(',').collect { |num| num.to_i }
3882 elsif element == 'map'
3883 missing_end = true
3884 end
3885 }
3886 text = proc { |text_string|
3887 if current_tag == 'tag'
3888 room['tags'].push(text_string)
3889 elsif current_tag =~ /^(?:title|description|paths|tag|unique_loot)$/
3890 room[current_tag].push(text_string)
3891 elsif current_tag == 'exit' and current_attributes['target']
3892 if current_attributes['type'].downcase == 'string'
3893 room['wayto'][current_attributes['target']] = text_string
3894 elsif
3895 room['wayto'][current_attributes['target']] = StringProc.new(text_string)
3896 end
3897 if current_attributes['cost'] =~ /^[0-9\.]+$/
3898 room['timeto'][current_attributes['target']] = current_attributes['cost'].to_f
3899 elsif current_attributes['cost'].length > 0
3900 room['timeto'][current_attributes['target']] = StringProc.new(current_attributes['cost'])
3901 else
3902 room['timeto'][current_attributes['target']] = 0.2
3903 end
3904 end
3905 }
3906 tag_end = proc { |element|
3907 if element == 'room'
3908 room['unique_loot'] = nil if room['unique_loot'].empty?
3909 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'])
3910 elsif element == 'map'
3911 missing_end = false
3912 end
3913 current_tag = nil
3914 }
3915 begin
3916 File.open(filename) { |file|
3917 while line = file.gets
3918 buffer.concat(line)
3919 # fixme: remove (?=<) ?
3920 while str = buffer.slice!(/^<([^>]+)><\/\1>|^[^<]+(?=<)|^<[^<]+>/)
3921 if str[0,1] == '<'
3922 if str[1,1] == '/'
3923 element = /^<\/([^\s>\/]+)/.match(str).captures.first
3924 tag_end.call(element)
3925 else
3926 if str =~ /^<([^>]+)><\/\1>/
3927 element = $1
3928 tag_start.call(element)
3929 text.call('')
3930 tag_end.call(element)
3931 else
3932 element = /^<([^\s>\/]+)/.match(str).captures.first
3933 attributes = Hash.new
3934 str.scan(/([A-z][A-z0-9_\-]*)=(["'])(.*?)\2/).each { |attr| attributes[attr[0]] = attr[2].gsub(/&(#{unescape.keys.join('|')});/) { unescape[$1] } }
3935 tag_start.call(element, attributes)
3936 tag_end.call(element) if str[-2,1] == '/'
3937 end
3938 end
3939 else
3940 text.call(str.gsub(/&(#{unescape.keys.join('|')});/) { unescape[$1] })
3941 end
3942 end
3943 end
3944 }
3945 if missing_end
3946 respond "--- Lich: error: failed to load #{filename}: unexpected end of file"
3947 return false
3948 end
3949 @@tags.clear
3950 @@loaded = true
3951 return true
3952 rescue
3953 respond "--- Lich: error: failed to load #{filename}: #{$!}"
3954 return false
3955 end
3956 end
3957 }
3958 else
3959 @@elevated_load_xml.call
3960 end
3961 end
3962 def Map.save(filename="#{DATA_DIR}/#{XMLData.game}/map-#{Time.now.to_i}.dat")
3963 if $SAFE == 0
3964 if File.exists?(filename)
3965 respond "--- Backing up map database"
3966 begin
3967 # fixme: does this work on all platforms? File.rename(filename, "#{filename}.bak")
3968 File.open(filename, 'rb') { |infile|
3969 File.open("#{filename}.bak", 'wb') { |outfile|
3970 outfile.write(infile.read)
3971 }
3972 }
3973 rescue
3974 respond "--- Lich: error: #{$!}"
3975 end
3976 end
3977 begin
3978 File.open(filename, 'wb') { |f| f.write(Marshal.dump(@@list)) }
3979 @@tags.clear
3980 respond "--- Map database saved"
3981 rescue
3982 respond "--- Lich: error: #{$!}"
3983 end
3984 else
3985 @@elevated_save.call
3986 end
3987 end
3988 def Map.save_xml(filename="#{DATA_DIR}/#{XMLData.game}/map-#{Time.now.to_i}.xml")
3989 if $SAFE == 0
3990 if File.exists?(filename)
3991 respond "File exists! Backing it up before proceeding..."
3992 begin
3993 File.open(filename, 'rb') { |infile|
3994 File.open("#{filename}.bak", "wb") { |outfile|
3995 outfile.write(infile.read)
3996 }
3997 }
3998 rescue
3999 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
4000 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
4001 end
4002 end
4003 begin
4004 escape = { '<' => '<', '>' => '>', '"' => '"', "'" => "'", '&' => '&' }
4005 File.open(filename, 'w') { |file|
4006 file.write "<map>\n"
4007 @@list.each { |room|
4008 next if room == nil
4009 if room.location
4010 location = " location=#{(room.location.gsub(/(<|>|"|'|&)/) { escape[$1] }).inspect}"
4011 else
4012 location = ''
4013 end
4014 if room.climate
4015 climate = " climate=#{(room.climate.gsub(/(<|>|"|'|&)/) { escape[$1] }).inspect}"
4016 else
4017 climate = ''
4018 end
4019 if room.terrain
4020 terrain = " terrain=#{(room.terrain.gsub(/(<|>|"|'|&)/) { escape[$1] }).inspect}"
4021 else
4022 terrain = ''
4023 end
4024 file.write " <room id=\"#{room.id}\"#{location}#{climate}#{terrain}>\n"
4025 room.title.each { |title| file.write " <title>#{title.gsub(/(<|>|"|'|&)/) { escape[$1] }}</title>\n" }
4026 room.description.each { |desc| file.write " <description>#{desc.gsub(/(<|>|"|'|&)/) { escape[$1] }}</description>\n" }
4027 room.paths.each { |paths| file.write " <paths>#{paths.gsub(/(<|>|"|'|&)/) { escape[$1] }}</paths>\n" }
4028 room.tags.each { |tag| file.write " <tag>#{tag.gsub(/(<|>|"|'|&)/) { escape[$1] }}</tag>\n" }
4029 room.unique_loot.to_a.each { |loot| file.write " <unique_loot>#{loot.gsub(/(<|>|"|'|&)/) { escape[$1] }}</unique_loot>\n" }
4030 file.write " <image name=\"#{room.image.gsub(/(<|>|"|'|&)/) { escape[$1] }}\" coords=\"#{room.image_coords.join(',')}\" />\n" if room.image and room.image_coords
4031 room.wayto.keys.each { |target|
4032 if room.timeto[target].class == Proc
4033 cost = " cost=\"#{room.timeto[target]._dump.gsub(/(<|>|"|'|&)/) { escape[$1] }}\""
4034 elsif room.timeto[target]
4035 cost = " cost=\"#{room.timeto[target]}\""
4036 else
4037 cost = ''
4038 end
4039 if room.wayto[target].class == Proc
4040 file.write " <exit target=\"#{target}\" type=\"Proc\"#{cost}>#{room.wayto[target]._dump.gsub(/(<|>|"|'|&)/) { escape[$1] }}</exit>\n"
4041 else
4042 file.write " <exit target=\"#{target}\" type=\"#{room.wayto[target].class}\"#{cost}>#{room.wayto[target].gsub(/(<|>|"|'|&)/) { escape[$1] }}</exit>\n"
4043 end
4044 }
4045 file.write " </room>\n"
4046 }
4047 file.write "</map>\n"
4048 }
4049 @@tags.clear
4050 respond "--- map database saved to: #{filename}"
4051 rescue
4052 respond $!
4053 end
4054 GC.start
4055 else
4056 @@elevated_save_xml.call
4057 end
4058 end
4059 def Map.estimate_time(array)
4060 Map.load unless @@loaded
4061 unless array.class == Array
4062 raise Exception.exception("MapError"), "Map.estimate_time was given something not an array!"
4063 end
4064 time = 0.to_f
4065 until array.length < 2
4066 room = array.shift
4067 if t = Map[room].timeto[array.first.to_s]
4068 if t.class == Proc
4069 time += t.call.to_f
4070 else
4071 time += t.to_f
4072 end
4073 else
4074 time += "0.2".to_f
4075 end
4076 end
4077 time
4078 end
4079 def Map.dijkstra(source, destination=nil)
4080 if source.class == Map
4081 source.dijkstra(destination)
4082 elsif room = Map[source]
4083 room.dijkstra(destination)
4084 else
4085 echo "Map.dijkstra: error: invalid source room"
4086 nil
4087 end
4088 end
4089 def dijkstra(destination=nil)
4090 begin
4091 Map.load unless @@loaded
4092 source = @id
4093 visited = Array.new
4094 shortest_distances = Array.new
4095 previous = Array.new
4096 pq = [ source ]
4097 pq_push = proc { |val|
4098 for i in 0...pq.size
4099 if shortest_distances[val] <= shortest_distances[pq[i]]
4100 pq.insert(i, val)
4101 break
4102 end
4103 end
4104 pq.push(val) if i.nil? or (i == pq.size-1)
4105 }
4106 visited[source] = true
4107 shortest_distances[source] = 0
4108 if destination.nil?
4109 until pq.size == 0
4110 v = pq.shift
4111 visited[v] = true
4112 @@list[v].wayto.keys.each { |adj_room|
4113 adj_room_i = adj_room.to_i
4114 unless visited[adj_room_i]
4115 if @@list[v].timeto[adj_room].class == Proc
4116 nd = @@list[v].timeto[adj_room].call
4117 else
4118 nd = @@list[v].timeto[adj_room]
4119 end
4120 if nd
4121 nd += shortest_distances[v]
4122 if shortest_distances[adj_room_i].nil? or (shortest_distances[adj_room_i] > nd)
4123 shortest_distances[adj_room_i] = nd
4124 previous[adj_room_i] = v
4125 pq_push.call(adj_room_i)
4126 end
4127 end
4128 end
4129 }
4130 end
4131 elsif destination.class == Fixnum
4132 until pq.size == 0
4133 v = pq.shift
4134 break if v == destination
4135 visited[v] = true
4136 @@list[v].wayto.keys.each { |adj_room|
4137 adj_room_i = adj_room.to_i
4138 unless visited[adj_room_i]
4139 if @@list[v].timeto[adj_room].class == Proc
4140 nd = @@list[v].timeto[adj_room].call
4141 else
4142 nd = @@list[v].timeto[adj_room]
4143 end
4144 if nd
4145 nd += shortest_distances[v]
4146 if shortest_distances[adj_room_i].nil? or (shortest_distances[adj_room_i] > nd)
4147 shortest_distances[adj_room_i] = nd
4148 previous[adj_room_i] = v
4149 pq_push.call(adj_room_i)
4150 end
4151 end
4152 end
4153 }
4154 end
4155 elsif destination.class == Array
4156 dest_list = destination.collect { |dest| dest.to_i }
4157 until pq.size == 0
4158 v = pq.shift
4159 break if dest_list.include?(v) and (shortest_distances[v] < 20)
4160 visited[v] = true
4161 @@list[v].wayto.keys.each { |adj_room|
4162 adj_room_i = adj_room.to_i
4163 unless visited[adj_room_i]
4164 if @@list[v].timeto[adj_room].class == Proc
4165 nd = @@list[v].timeto[adj_room].call
4166 else
4167 nd = @@list[v].timeto[adj_room]
4168 end
4169 if nd
4170 nd += shortest_distances[v]
4171 if shortest_distances[adj_room_i].nil? or (shortest_distances[adj_room_i] > nd)
4172 shortest_distances[adj_room_i] = nd
4173 previous[adj_room_i] = v
4174 pq_push.call(adj_room_i)
4175 end
4176 end
4177 end
4178 }
4179 end
4180 end
4181 return previous, shortest_distances
4182 rescue
4183 echo "Map.dijkstra: error: #{$!}"
4184 respond $!.backtrace
4185 nil
4186 end
4187 end
4188 def Map.findpath(source, destination)
4189 if source.class == Map
4190 source.path_to(destination)
4191 elsif room = Map[source]
4192 room.path_to(destination)
4193 else
4194 echo "Map.findpath: error: invalid source room"
4195 nil
4196 end
4197 end
4198 def path_to(destination)
4199 Map.load unless @@loaded
4200 destination = destination.to_i
4201 previous, shortest_distances = dijkstra(destination)
4202 return nil unless previous[destination]
4203 path = [ destination ]
4204 path.push(previous[path[-1]]) until previous[path[-1]] == @id
4205 path.reverse!
4206 path.pop
4207 return path
4208 end
4209 def find_nearest_by_tag(tag_name)
4210 target_list = Array.new
4211 @@list.each { |room| target_list.push(room.id) if room.tags.include?(tag_name) }
4212 previous, shortest_distances = Map.dijkstra(@id, target_list)
4213 if target_list.include?(@id)
4214 @id
4215 else
4216 target_list.delete_if { |room_num| shortest_distances[room_num].nil? }
4217 target_list.sort { |a,b| shortest_distances[a] <=> shortest_distances[b] }.first
4218 end
4219 end
4220 def find_all_nearest_by_tag(tag_name)
4221 target_list = Array.new
4222 @@list.each { |room| target_list.push(room.id) if room.tags.include?(tag_name) }
4223 previous, shortest_distances = Map.dijkstra(@id)
4224 target_list.delete_if { |room_num| shortest_distances[room_num].nil? }
4225 target_list.sort { |a,b| shortest_distances[a] <=> shortest_distances[b] }
4226 end
4227 def find_nearest(target_list)
4228 target_list = target_list.collect { |num| num.to_i }
4229 if target_list.include?(@id)
4230 @id
4231 else
4232 previous, shortest_distances = Map.dijkstra(@id, target_list)
4233 target_list.delete_if { |room_num| shortest_distances[room_num].nil? }
4234 target_list.sort { |a,b| shortest_distances[a] <=> shortest_distances[b] }.first
4235 end
4236 end
4237end
4238
4239class Room < Map
4240# private_class_method :new
4241 def Room.method_missing(*args)
4242 super(*args)
4243 end
4244end
4245
4246def hide_me
4247 Script.current.hidden = !Script.current.hidden
4248end
4249
4250def no_kill_all
4251 script = Script.current
4252 script.no_kill_all = !script.no_kill_all
4253end
4254
4255def no_pause_all
4256 script = Script.current
4257 script.no_pause_all = !script.no_pause_all
4258end
4259
4260def toggle_upstream
4261 unless script = Script.current then echo 'toggle_upstream: cannot identify calling script.'; return nil; end
4262 script.want_upstream = !script.want_upstream
4263end
4264
4265def silence_me
4266 unless script = Script.current then echo 'silence_me: cannot identify calling script.'; return nil; end
4267 if script.safe? then echo("WARNING: 'safe' script attempted to silence itself. Ignoring the request.")
4268 sleep 1
4269 return true
4270 end
4271 script.silent = !script.silent
4272end
4273
4274def toggle_echo
4275 unless script = Script.current then respond('--- toggle_echo: Unable to identify calling script.'); return nil; end
4276 script.no_echo = !script.no_echo
4277end
4278
4279def echo_on
4280 unless script = Script.current then respond('--- echo_on: Unable to identify calling script.'); return nil; end
4281 script.no_echo = false
4282end
4283
4284def echo_off
4285 unless script = Script.current then respond('--- echo_off: Unable to identify calling script.'); return nil; end
4286 script.no_echo = true
4287end
4288
4289def upstream_get
4290 unless script = Script.current then echo 'upstream_get: cannot identify calling script.'; return nil; end
4291 unless script.want_upstream
4292 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)")
4293 sleep 0.3
4294 return false
4295 end
4296 script.upstream_gets
4297end
4298
4299def upstream_get?
4300 unless script = Script.current then echo 'upstream_get: cannot identify calling script.'; return nil; end
4301 unless script.want_upstream
4302 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)")
4303 return false
4304 end
4305 script.upstream_gets?
4306end
4307
4308def echo(*messages)
4309 respond if messages.empty?
4310 if script = Script.current
4311 unless script.no_echo
4312 messages.each { |message| respond("[#{script.name}: #{message.to_s.chomp}]") }
4313 end
4314 else
4315 messages.each { |message| respond("[(unknown script): #{message.to_s.chomp}]") }
4316 end
4317 nil
4318end
4319
4320def _echo(*messages)
4321 _respond if messages.empty?
4322 if script = Script.current
4323 unless script.no_echo
4324 messages.each { |message| _respond("[#{script.name}: #{message.to_s.chomp}]") }
4325 end
4326 else
4327 messages.each { |message| _respond("[(unknown script): #{message.to_s.chomp}]") }
4328 end
4329 nil
4330end
4331
4332def goto(label)
4333 Script.current.jump_label = label.to_s
4334 raise JUMP
4335end
4336
4337def pause_script(*names)
4338 names.flatten!
4339 if names.empty?
4340 Script.current.pause
4341 Script.current
4342 else
4343 names.each { |scr|
4344 fnd = Script.list.find { |nm| nm.name =~ /^#{scr}/i }
4345 fnd.pause unless (fnd.paused || fnd.nil?)
4346 }
4347 end
4348end
4349
4350def unpause_script(*names)
4351 names.flatten!
4352 names.each { |scr|
4353 fnd = Script.list.find { |nm| nm.name =~ /^#{scr}/i }
4354 fnd.unpause if (fnd.paused and not fnd.nil?)
4355 }
4356end
4357
4358def fix_injury_mode
4359 unless XMLData.injury_mode == 2
4360 Game._puts '_injury 2'
4361 150.times { sleep 0.05; break if XMLData.injury_mode == 2 }
4362 end
4363end
4364
4365def hide_script(*args)
4366 args.flatten!
4367 args.each { |name|
4368 if script = Script.running.find { |scr| scr.name == name }
4369 script.hidden = !script.hidden
4370 end
4371 }
4372end
4373
4374def parse_list(string)
4375 string.split_as_list
4376end
4377
4378def waitrt
4379 wait_until { (XMLData.roundtime_end.to_f - Time.now.to_f + XMLData.server_time_offset.to_f) > 0 }
4380 sleep((XMLData.roundtime_end.to_f - Time.now.to_f + XMLData.server_time_offset.to_f + "0.6".to_f).abs)
4381end
4382
4383def waitrt?
4384 rt = XMLData.roundtime_end.to_f - Time.now.to_f + XMLData.server_time_offset.to_f + "0.6".to_f
4385 if rt > 0
4386 sleep rt
4387 end
4388end
4389
4390def waitcastrt
4391 wait_until { (XMLData.cast_roundtime_end.to_f - Time.now.to_f + XMLData.server_time_offset.to_f) > 0 }
4392 sleep((XMLData.cast_roundtime_end.to_f - Time.now.to_f + XMLData.server_time_offset.to_f + "0.6".to_f).abs)
4393end
4394
4395def waitcastrt?
4396 rt = XMLData.cast_roundtime_end.to_f - Time.now.to_f + XMLData.server_time_offset.to_f + "0.6".to_f
4397 if rt > 0
4398 sleep rt
4399 end
4400end
4401
4402def checkrt
4403 [XMLData.roundtime_end.to_f - Time.now.to_f + XMLData.server_time_offset.to_f + "0.6".to_f, 0].max
4404end
4405
4406def checkcastrt
4407 [XMLData.cast_roundtime_end.to_f - Time.now.to_f + XMLData.server_time_offset.to_f + "0.6".to_f, 0].max
4408end
4409
4410def checkpoison
4411 XMLData.indicator['IconPOISONED'] == 'y'
4412end
4413
4414def checkdisease
4415 XMLData.indicator['IconDISEASED'] == 'y'
4416end
4417
4418def checksitting
4419 XMLData.indicator['IconSITTING'] == 'y'
4420end
4421
4422def checkkneeling
4423 XMLData.indicator['IconKNEELING'] == 'y'
4424end
4425
4426def checkstunned
4427 XMLData.indicator['IconSTUNNED'] == 'y'
4428end
4429
4430def checkbleeding
4431 XMLData.indicator['IconBLEEDING'] == 'y'
4432end
4433
4434def checkgrouped
4435 XMLData.indicator['IconJOINED'] == 'y'
4436end
4437
4438def checkdead
4439 XMLData.indicator['IconDEAD'] == 'y'
4440end
4441
4442def checkreallybleeding
4443 checkbleeding and !(Spell[9909].active? or Spell[9905].active?)
4444end
4445
4446def muckled?
4447 muckled = checkwebbed or checkdead or checkstunned
4448 if defined?(checksleeping)
4449 muckled = muckled or checksleeping
4450 end
4451 if defined?(checkbound)
4452 muckled = muckled or checkbound
4453 end
4454 return muckled
4455end
4456
4457def checkhidden
4458 XMLData.indicator['IconHIDDEN'] == 'y'
4459end
4460
4461def checkinvisible
4462 XMLData.indicator['IconINVISIBLE'] == 'y'
4463end
4464
4465def checkwebbed
4466 XMLData.indicator['IconWEBBED'] == 'y'
4467end
4468
4469def checkprone
4470 XMLData.indicator['IconPRONE'] == 'y'
4471end
4472
4473def checknotstanding
4474 XMLData.indicator['IconSTANDING'] == 'n'
4475end
4476
4477def checkstanding
4478 XMLData.indicator['IconSTANDING'] == 'y'
4479end
4480
4481def checkname(*strings)
4482 strings.flatten!
4483 if strings.empty?
4484 XMLData.name
4485 else
4486 XMLData.name =~ /^(?:#{strings.join('|')})/i
4487 end
4488end
4489
4490def checkloot
4491 GameObj.loot.collect { |item| item.noun }
4492end
4493
4494def i_stand_alone
4495 unless script = Script.current then echo 'i_stand_alone: cannot identify calling script.'; return nil; end
4496 script.want_downstream = !script.want_downstream
4497 return !script.want_downstream
4498end
4499
4500def debug(*args)
4501 if $LICH_DEBUG
4502 if block_given?
4503 yield(*args)
4504 else
4505 echo(*args)
4506 end
4507 end
4508end
4509
4510def timetest(*contestants)
4511 contestants.collect { |code| start = Time.now; 5000.times { code.call }; Time.now - start }
4512end
4513
4514def dec2bin(n)
4515 "0" + [n].pack("N").unpack("B32")[0].sub(/^0+(?=\d)/, '')
4516end
4517
4518def bin2dec(n)
4519 [("0"*32+n.to_s)[-32..-1]].pack("B32").unpack("N")[0]
4520end
4521
4522def idle?(time = 60)
4523 Time.now - $_IDLETIMESTAMP_ >= time
4524end
4525
4526def selectput(string, success, failure, timeout = nil)
4527 timeout = timeout.to_f if timeout and !timeout.kind_of?(Numeric)
4528 success = [ success ] if success.kind_of? String
4529 failure = [ failure ] if failure.kind_of? String
4530 if !string.kind_of?(String) or !success.kind_of?(Array) or !failure.kind_of?(Array) or timeout && !timeout.kind_of?(Numeric)
4531 raise ArgumentError, "usage is: selectput(game_command,success_array,failure_array[,timeout_in_secs])"
4532 end
4533 success.flatten!
4534 failure.flatten!
4535 regex = /#{(success + failure).join('|')}/i
4536 successre = /#{success.join('|')}/i
4537 failurere = /#{failure.join('|')}/i
4538 thr = Thread.current
4539
4540 timethr = Thread.new {
4541 timeout -= sleep("0.1".to_f) until timeout <= 0
4542 thr.raise(StandardError)
4543 } if timeout
4544
4545 begin
4546 loop {
4547 fput(string)
4548 response = waitforre(regex)
4549 if successre.match(response.to_s)
4550 timethr.kill if timethr.alive?
4551 break(response.string)
4552 end
4553 yield(response.string) if block_given?
4554 }
4555 rescue
4556 nil
4557 end
4558end
4559
4560def toggle_unique
4561 unless script = Script.current then echo 'toggle_unique: cannot identify calling script.'; return nil; end
4562 script.want_downstream = !script.want_downstream
4563end
4564
4565def die_with_me(*vals)
4566 unless script = Script.current then echo 'die_with_me: cannot identify calling script.'; return nil; end
4567 script.die_with.push vals
4568 script.die_with.flatten!
4569 echo("The following script(s) will now die when I do: #{script.die_with.join(', ')}") unless script.die_with.empty?
4570end
4571
4572def upstream_waitfor(*strings)
4573 strings.flatten!
4574 script = Script.current
4575 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
4576 regexpstr = strings.join('|')
4577 while line = script.upstream_gets
4578 if line =~ /#{regexpstr}/i
4579 return line
4580 end
4581 end
4582end
4583
4584def send_to_script(*values)
4585 values.flatten!
4586 if script = Script.list.find { |val| val.name =~ /^#{values.first}/i }
4587 if script.want_downstream
4588 values[1..-1].each { |val| script.downstream_buffer.push(val) }
4589 else
4590 values[1..-1].each { |val| script.unique_buffer.push(val) }
4591 end
4592 echo("Sent to #{script.name} -- '#{values[1..-1].join(' ; ')}'")
4593 return true
4594 else
4595 echo("'#{values.first}' does not match any active scripts!")
4596 return false
4597 end
4598end
4599
4600def unique_send_to_script(*values)
4601 values.flatten!
4602 if script = Script.list.find { |val| val.name =~ /^#{values.first}/i }
4603 values[1..-1].each { |val| script.unique_buffer.push(val) }
4604 echo("sent to #{script}: #{values[1..-1].join(' ; ')}")
4605 return true
4606 else
4607 echo("'#{values.first}' does not match any active scripts!")
4608 return false
4609 end
4610end
4611
4612def unique_waitfor(*strings)
4613 unless script = Script.current then echo 'unique_waitfor: cannot identify calling script.'; return nil; end
4614 strings.flatten!
4615 regexp = /#{strings.join('|')}/
4616 while true
4617 str = script.unique_gets
4618 if str =~ regexp
4619 return str
4620 end
4621 end
4622end
4623
4624def unique_get
4625 unless script = Script.current then echo 'unique_get: cannot identify calling script.'; return nil; end
4626 script.unique_gets
4627end
4628
4629def unique_get?
4630 unless script = Script.current then echo 'unique_get: cannot identify calling script.'; return nil; end
4631 script.unique_gets?
4632end
4633
4634def multimove(*dirs)
4635 dirs.flatten.each { |dir| move(dir) }
4636end
4637
4638def n; 'north'; end
4639def ne; 'northeast'; end
4640def e; 'east'; end
4641def se; 'southeast'; end
4642def s; 'south'; end
4643def sw; 'southwest'; end
4644def w; 'west'; end
4645def nw; 'northwest'; end
4646def u; 'up'; end
4647def up; 'up'; end
4648def down; 'down'; end
4649def d; 'down'; end
4650def o; 'out'; end
4651def out; 'out'; end
4652
4653def move(dir='none', giveup_seconds=30, giveup_lines=30)
4654 #[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)
4655 #[LNet]-[Private]-Casis: "You smack into the water with a splash and sink far below the surface." (20:35:50)
4656 # 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."
4657 if dir == 'none'
4658 echo 'move: no direction given'
4659 return false
4660 end
4661
4662 need_full_hands = false
4663 tried_open = false
4664 tried_fix_drag = false
4665 line_count = 0
4666 room_count = XMLData.room_count
4667 giveup_time = Time.now.to_i + giveup_seconds.to_i
4668 save_stream = Array.new
4669
4670 put_dir = proc {
4671 if XMLData.room_count > room_count
4672 fill_hands if need_full_hands
4673 Script.current.downstream_buffer.unshift(save_stream)
4674 Script.current.downstream_buffer.flatten!
4675 return true
4676 end
4677 waitrt?
4678 wait_while { stunned? }
4679 giveup_time = Time.now.to_i + giveup_seconds.to_i
4680 line_count = 0
4681 save_stream.push(clear)
4682 put dir
4683 }
4684
4685 put_dir.call
4686
4687 loop {
4688 line = get?
4689 unless line.nil?
4690 save_stream.push(line)
4691 line_count += 1
4692 end
4693 if line.nil?
4694 sleep 0.1
4695 elsif line =~ /^You can't do that while engaged!|^You are engaged to /
4696 # DragonRealms
4697 fput 'retreat'
4698 fput 'retreat'
4699 put_dir.call
4700 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\.$/
4701 fput 'unhide'
4702 put_dir.call
4703 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/)
4704 which = [ 'first', 'second', 'third', 'fourth', 'fifth', 'sixth', 'seventh', 'eight', 'ninth', 'tenth', 'eleventh', 'twelfth' ]
4705 if dir =~ /\b#{which.join('|')}\b/
4706 dir.sub!(/\b(#{which.join('|')})\b/) { "#{which[which.index($1)+1]}" }
4707 else
4708 dir.sub!('door', 'second door')
4709 end
4710 put_dir.call
4711 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\.$/
4712 echo 'move: failed'
4713 fill_hands if need_full_hands
4714 Script.current.downstream_buffer.unshift(save_stream)
4715 Script.current.downstream_buffer.flatten!
4716 return false
4717 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!/
4718 echo 'move: failed'
4719 fill_hands if need_full_hands
4720 Script.current.downstream_buffer.unshift(save_stream)
4721 Script.current.downstream_buffer.flatten!
4722 # return nil instead of false to show the direction shouldn't be removed from the map database
4723 return nil
4724 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\.$/
4725 sleep 1
4726 waitrt?
4727 put_dir.call
4728 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\./
4729 sleep 1
4730 waitrt?
4731 fput 'stand' unless standing?
4732 waitrt?
4733 put_dir.call
4734 elsif line =~ /^You begin to climb up the silvery thread.* you tumble to the ground/
4735 sleep 0.5
4736 waitrt?
4737 fput 'stand' unless standing?
4738 waitrt?
4739 if checkleft or checkright
4740 need_full_hands = true
4741 empty_hands
4742 end
4743 put_dir.call
4744 elsif line == 'You are too injured to be doing any climbing!'
4745 if (resolve = Spell[9704]) and resolve.known?
4746 wait_until { resolve.affordable? }
4747 resove.cast
4748 put_dir.call
4749 else
4750 return nil
4751 end
4752 elsif line =~ /^You(?:'re going to| will) have to climb that\./
4753 dir.gsub!('go', 'climb')
4754 put_dir.call
4755 elsif line =~ /^You can't climb that\./
4756 dir.gsub!('climb', 'go')
4757 put_dir.call
4758 elsif line =~ /^You can't drag/
4759 if tried_fix_drag
4760 fill_hands if need_full_hands
4761 Script.current.downstream_buffer.unshift(save_stream)
4762 Script.current.downstream_buffer.flatten!
4763 return false
4764 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/ })
4765 tried_fix_drag = true
4766 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)
4767 target = /^(?:go|climb) (.+)$/.match(dir).captures.first
4768 fput "drag #{name}"
4769 dir = "drag #{name} #{target}"
4770 put_dir.call
4771 else
4772 tried_fix_drag = true
4773 dir.sub!(/^climb /, 'go ')
4774 put_dir.call
4775 end
4776 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\./
4777 need_full_hands = true
4778 empty_hands
4779 put_dir.call
4780 elsif line =~ /(?:appears|seems) to be closed\.$|^You cannot quite manage to squeeze between the stone doors\.$/
4781 if tried_open
4782 fill_hands if need_full_hands
4783 Script.current.downstream_buffer.unshift(save_stream)
4784 Script.current.downstream_buffer.flatten!
4785 return false
4786 else
4787 tried_open = true
4788 fput dir.sub(/go|climb/, 'open')
4789 put_dir.call
4790 end
4791 elsif line =~ /^(\.\.\.w|W)ait ([0-9]+) sec(onds)?\.$/
4792 if $2.to_i > 1
4793 sleep ($2.to_i - "0.2".to_f)
4794 else
4795 sleep 0.3
4796 end
4797 put_dir.call
4798 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/
4799 fput 'stand'
4800 waitrt?
4801 put_dir.call
4802 elsif line =~ /^Sorry, you may only type ahead/
4803 sleep 1
4804 put_dir.call
4805 elsif line == 'You are still stunned.'
4806 wait_while { stunned? }
4807 put_dir.call
4808 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!$/
4809 waitrt?
4810 fput 'stand' unless standing?
4811 waitrt?
4812 put_dir.call
4813 elsif line =~ /^You flick your hand (?:up|down)wards and focus your aura on your disk, but your disk only wobbles briefly\.$/
4814 put_dir.call
4815 elsif line =~ /^You dive into the fast-moving river, but the current catches you and whips you back to shore, wet and battered\.$/
4816 waitrt?
4817 put_dir.call
4818 elsif line == "You don't seem to be able to move to do that."
4819 30.times {
4820 break if clear.include?('You regain control of your senses!')
4821 sleep 0.1
4822 }
4823 put_dir.call
4824 end
4825 if XMLData.room_count > room_count
4826 fill_hands if need_full_hands
4827 Script.current.downstream_buffer.unshift(save_stream)
4828 Script.current.downstream_buffer.flatten!
4829 return true
4830 end
4831 if Time.now.to_i >= giveup_time
4832 echo "move: no recognized response in #{giveup_seconds} seconds. giving up."
4833 fill_hands if need_full_hands
4834 Script.current.downstream_buffer.unshift(save_stream)
4835 Script.current.downstream_buffer.flatten!
4836 return nil
4837 end
4838 if line_count >= giveup_lines
4839 echo "move: no recognized response after #{line_count} lines. giving up."
4840 fill_hands if need_full_hands
4841 Script.current.downstream_buffer.unshift(save_stream)
4842 Script.current.downstream_buffer.flatten!
4843 return nil
4844 end
4845 }
4846end
4847
4848def watchhealth(value, theproc=nil, &block)
4849 value = value.to_i
4850 if block.nil?
4851 if !theproc.respond_to? :call
4852 respond "`watchhealth' was not given a block or a proc to execute!"
4853 return nil
4854 else
4855 block = theproc
4856 end
4857 end
4858 Thread.new {
4859 wait_while { health(value) }
4860 block.call
4861 }
4862end
4863
4864def wait_until(announce=nil)
4865 priosave = Thread.current.priority
4866 Thread.current.priority = 0
4867 unless announce.nil? or yield
4868 respond(announce)
4869 end
4870 until yield
4871 sleep 0.25
4872 end
4873 Thread.current.priority = priosave
4874end
4875
4876def wait_while(announce=nil)
4877 priosave = Thread.current.priority
4878 Thread.current.priority = 0
4879 unless announce.nil? or !yield
4880 respond(announce)
4881 end
4882 while yield
4883 sleep 0.25
4884 end
4885 Thread.current.priority = priosave
4886end
4887
4888def checkpaths(dir="none")
4889 if dir == "none"
4890 if XMLData.room_exits.empty?
4891 return false
4892 else
4893 return XMLData.room_exits.collect { |dir| dir = SHORTDIR[dir] }
4894 end
4895 else
4896 XMLData.room_exits.include?(dir) || XMLData.room_exits.include?(SHORTDIR[dir])
4897 end
4898end
4899
4900def reverse_direction(dir)
4901 if dir == "n" then 's'
4902 elsif dir == "ne" then 'sw'
4903 elsif dir == "e" then 'w'
4904 elsif dir == "se" then 'nw'
4905 elsif dir == "s" then 'n'
4906 elsif dir == "sw" then 'ne'
4907 elsif dir == "w" then 'e'
4908 elsif dir == "nw" then 'se'
4909 elsif dir == "up" then 'down'
4910 elsif dir == "down" then 'up'
4911 elsif dir == "out" then 'out'
4912 elsif dir == 'o' then out
4913 elsif dir == 'u' then 'down'
4914 elsif dir == 'd' then up
4915 elsif dir == n then s
4916 elsif dir == ne then sw
4917 elsif dir == e then w
4918 elsif dir == se then nw
4919 elsif dir == s then n
4920 elsif dir == sw then ne
4921 elsif dir == w then e
4922 elsif dir == nw then se
4923 elsif dir == u then d
4924 elsif dir == d then u
4925 else echo("Cannot recognize direction to properly reverse it!"); false
4926 end
4927end
4928
4929def walk(*boundaries, &block)
4930 boundaries.flatten!
4931 unless block.nil?
4932 until val = yield
4933 walk(*boundaries)
4934 end
4935 return val
4936 end
4937 if $last_dir and !boundaries.empty? and checkroomdescrip =~ /#{boundaries.join('|')}/i
4938 move($last_dir)
4939 $last_dir = reverse_direction($last_dir)
4940 return checknpcs
4941 end
4942 dirs = checkpaths
4943 dirs.delete($last_dir) unless dirs.length < 2
4944 this_time = rand(dirs.length)
4945 $last_dir = reverse_direction(dirs[this_time])
4946 move(dirs[this_time])
4947 checknpcs
4948end
4949
4950def run
4951 loop { break unless walk }
4952end
4953
4954def check_mind(string=nil)
4955 if string.nil?
4956 return XMLData.mind_text
4957 elsif (string.class == String) and (string.to_i == 0)
4958 if string =~ /#{XMLData.mind_text}/i
4959 return true
4960 else
4961 return false
4962 end
4963 elsif string.to_i.between?(0,100)
4964 return string.to_i <= XMLData.mind_value.to_i
4965 else
4966 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
4967 return false
4968 end
4969end
4970
4971def checkmind(string=nil)
4972 if string.nil?
4973 return XMLData.mind_text
4974 elsif string.class == String and string.to_i == 0
4975 if string =~ /#{XMLData.mind_text}/i
4976 return true
4977 else
4978 return false
4979 end
4980 elsif string.to_i.between?(1,8)
4981 mind_state = ['clear as a bell','fresh and clear','clear','muddled','becoming numbed','numbed','must rest','saturated']
4982 if mind_state.index(XMLData.mind_text)
4983 mind = mind_state.index(XMLData.mind_text) + 1
4984 return string.to_i <= mind
4985 else
4986 echo "Bad string in checkmind: mind_state"
4987 nil
4988 end
4989 else
4990 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
4991 return false
4992 end
4993end
4994
4995def percentmind(num=nil)
4996 if num.nil?
4997 XMLData.mind_value
4998 else
4999 XMLData.mind_value >= num.to_i
5000 end
5001end
5002
5003def checkfried
5004 if XMLData.mind_text =~ /must rest|saturated/
5005 true
5006 else
5007 false
5008 end
5009end
5010
5011def checksaturated
5012 if XMLData.mind_text =~ /saturated/
5013 true
5014 else
5015 false
5016 end
5017end
5018
5019def checkmana(num=nil)
5020 if num.nil?
5021 XMLData.mana
5022 else
5023 XMLData.mana >= num.to_i
5024 end
5025end
5026
5027def maxmana
5028 XMLData.max_mana
5029end
5030
5031def percentmana(num=nil)
5032 if XMLData.max_mana == 0
5033 percent = 100
5034 else
5035 percent = ((XMLData.mana.to_f / XMLData.max_mana.to_f) * 100).to_i
5036 end
5037 if num.nil?
5038 percent
5039 else
5040 percent >= num.to_i
5041 end
5042end
5043
5044def checkhealth(num=nil)
5045 if num.nil?
5046 XMLData.health
5047 else
5048 XMLData.health >= num.to_i
5049 end
5050end
5051
5052def maxhealth
5053 XMLData.max_health
5054end
5055
5056def percenthealth(num=nil)
5057 if num.nil?
5058 ((XMLData.health.to_f / XMLData.max_health.to_f) * 100).to_i
5059 else
5060 ((XMLData.health.to_f / XMLData.max_health.to_f) * 100).to_i >= num.to_i
5061 end
5062end
5063
5064def checkspirit(num=nil)
5065 if num.nil?
5066 XMLData.spirit
5067 else
5068 XMLData.spirit >= num.to_i
5069 end
5070end
5071
5072def maxspirit
5073 XMLData.max_spirit
5074end
5075
5076def percentspirit(num=nil)
5077 if num.nil?
5078 ((XMLData.spirit.to_f / XMLData.max_spirit.to_f) * 100).to_i
5079 else
5080 ((XMLData.spirit.to_f / XMLData.max_spirit.to_f) * 100).to_i >= num.to_i
5081 end
5082end
5083
5084def checkstamina(num=nil)
5085 if num.nil?
5086 XMLData.stamina
5087 else
5088 XMLData.stamina >= num.to_i
5089 end
5090end
5091
5092def maxstamina()
5093 XMLData.max_stamina
5094end
5095
5096def percentstamina(num=nil)
5097 if XMLData.max_stamina == 0
5098 percent = 100
5099 else
5100 percent = ((XMLData.stamina.to_f / XMLData.max_stamina.to_f) * 100).to_i
5101 end
5102 if num.nil?
5103 percent
5104 else
5105 percent >= num.to_i
5106 end
5107end
5108
5109def checkstance(num=nil)
5110 if num.nil?
5111 XMLData.stance_text
5112 elsif (num.class == String) and (num.to_i == 0)
5113 if num =~ /off/i
5114 XMLData.stance_value == 0
5115 elsif num =~ /adv/i
5116 XMLData.stance_value.between?(01, 20)
5117 elsif num =~ /for/i
5118 XMLData.stance_value.between?(21, 40)
5119 elsif num =~ /neu/i
5120 XMLData.stance_value.between?(41, 60)
5121 elsif num =~ /gua/i
5122 XMLData.stance_value.between?(61, 80)
5123 elsif num =~ /def/i
5124 XMLData.stance_value == 100
5125 else
5126 echo "checkstance: invalid argument (#{num}). Must be off/adv/for/neu/gua/def or 0-100"
5127 nil
5128 end
5129 elsif (num.class == Fixnum) or (num =~ /^[0-9]+$/ and num = num.to_i)
5130 XMLData.stance_value == num.to_i
5131 else
5132 echo "checkstance: invalid argument (#{num}). Must be off/adv/for/neu/gua/def or 0-100"
5133 nil
5134 end
5135end
5136
5137def percentstance(num=nil)
5138 if num.nil?
5139 XMLData.stance_value
5140 else
5141 XMLData.stance_value >= num.to_i
5142 end
5143end
5144
5145def checkencumbrance(string=nil)
5146 if string.nil?
5147 XMLData.encumbrance_text
5148 elsif (string.class == Fixnum) or (string =~ /^[0-9]+$/ and string = string.to_i)
5149 string <= XMLData.encumbrance_value
5150 else
5151 # fixme
5152 if string =~ /#{XMLData.encumbrance_text}/i
5153 true
5154 else
5155 false
5156 end
5157 end
5158end
5159
5160def percentencumbrance(num=nil)
5161 if num.nil?
5162 XMLData.encumbrance_value
5163 else
5164 num.to_i <= XMLData.encumbrance_value
5165 end
5166end
5167
5168def checkarea(*strings)
5169 strings.flatten!
5170 if strings.empty?
5171 XMLData.room_title.split(',').first.sub('[','')
5172 else
5173 XMLData.room_title.split(',').first =~ /#{strings.join('|')}/i
5174 end
5175end
5176
5177def checkroom(*strings)
5178 strings.flatten!
5179 if strings.empty?
5180 XMLData.room_title.chomp
5181 else
5182 XMLData.room_title =~ /#{strings.join('|')}/i
5183 end
5184end
5185
5186def outside?
5187 if XMLData.room_exits_string =~ /Obvious paths:/
5188 true
5189 else
5190 false
5191 end
5192end
5193
5194def checkfamarea(*strings)
5195 strings.flatten!
5196 if strings.empty? then return XMLData.familiar_room_title.split(',').first.sub('[','') end
5197 XMLData.familiar_room_title.split(',').first =~ /#{strings.join('|')}/i
5198end
5199
5200def checkfampaths(dir="none")
5201 if dir == "none"
5202 if XMLData.familiar_room_exits.empty?
5203 return false
5204 else
5205 return XMLData.familiar_room_exits
5206 end
5207 else
5208 XMLData.familiar_room_exits.include?(dir)
5209 end
5210end
5211
5212def checkfamroom(*strings)
5213 strings.flatten! ; if strings.empty? then return XMLData.familiar_room_title.chomp end
5214 XMLData.familiar_room_title =~ /#{strings.join('|')}/i
5215end
5216
5217def checkfamnpcs(*strings)
5218 parsed = Array.new
5219 XMLData.familiar_npcs.each { |val| parsed.push(val.split.last) }
5220 if strings.empty?
5221 if parsed.empty?
5222 return false
5223 else
5224 return parsed
5225 end
5226 else
5227 if mtch = strings.find { |lookfor| parsed.find { |critter| critter =~ /#{lookfor}/ } }
5228 return mtch
5229 else
5230 return false
5231 end
5232 end
5233end
5234
5235def checkfampcs(*strings)
5236 familiar_pcs = Array.new
5237 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]+/)) }
5238 if familiar_pcs.empty?
5239 return false
5240 elsif strings.empty?
5241 return familiar_pcs
5242 else
5243 regexpstr = strings.join('|\b')
5244 peeps = familiar_pcs.find_all { |val| val =~ /\b#{regexpstr}/i }
5245 if peeps.empty?
5246 return false
5247 else
5248 return peeps
5249 end
5250 end
5251end
5252
5253def checkpcs(*strings)
5254 pcs = GameObj.pcs.collect { |pc| pc.noun }
5255 if pcs.empty?
5256 if strings.empty? then return nil else return false end
5257 end
5258 strings.flatten!
5259 if strings.empty?
5260 pcs
5261 else
5262 regexpstr = strings.join(' ')
5263 pcs.find { |pc| regexpstr =~ /\b#{pc}/i }
5264 end
5265end
5266
5267def checknpcs(*strings)
5268 npcs = GameObj.npcs.collect { |npc| npc.noun }
5269 if npcs.empty?
5270 if strings.empty? then return nil else return false end
5271 end
5272 strings.flatten!
5273 if strings.empty?
5274 npcs
5275 else
5276 regexpstr = strings.join(' ')
5277 npcs.find { |npc| regexpstr =~ /\b#{npc}/i }
5278 end
5279end
5280
5281def count_npcs
5282 checknpcs.length
5283end
5284
5285def checkright(*hand)
5286 if GameObj.right_hand.nil? then return nil end
5287 hand.flatten!
5288 if GameObj.right_hand.name == "Empty" or GameObj.right_hand.name.empty?
5289 nil
5290 elsif hand.empty?
5291 GameObj.right_hand.noun
5292 else
5293 hand.find { |instance| GameObj.right_hand.name =~ /#{instance}/i }
5294 end
5295end
5296
5297def checkleft(*hand)
5298 if GameObj.left_hand.nil? then return nil end
5299 hand.flatten!
5300 if GameObj.left_hand.name == "Empty" or GameObj.left_hand.name.empty?
5301 nil
5302 elsif hand.empty?
5303 GameObj.left_hand.noun
5304 else
5305 hand.find { |instance| GameObj.left_hand.name =~ /#{instance}/i }
5306 end
5307end
5308
5309def checkroomdescrip(*val)
5310 val.flatten!
5311 if val.empty?
5312 return XMLData.room_description
5313 else
5314 return XMLData.room_description =~ /#{val.join('|')}/i
5315 end
5316end
5317
5318def checkfamroomdescrip(*val)
5319 val.flatten!
5320 if val.empty?
5321 return XMLData.familiar_room_description
5322 else
5323 return XMLData.familiar_room_description =~ /#{val.join('|')}/i
5324 end
5325end
5326
5327def checkspell(*spells)
5328 spells.flatten!
5329 return false if Spell.active.empty?
5330 spells.each { |spell| return false unless Spell[spell].active? }
5331 true
5332end
5333
5334def checkprep(spell=nil)
5335 if spell.nil?
5336 XMLData.prepared_spell
5337 elsif spell.class != String
5338 echo("Checkprep error, spell # not implemented! You must use the spell name")
5339 false
5340 else
5341 XMLData.prepared_spell =~ /^#{spell}/i
5342 end
5343end
5344
5345def setpriority(val=nil)
5346 if val.nil? then return Thread.current.priority end
5347 if val.to_i > 3
5348 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")
5349 return Thread.current.priority
5350 else
5351 Thread.current.group.list.each { |thr| thr.priority = val.to_i }
5352 return Thread.current.priority
5353 end
5354end
5355
5356def checkbounty
5357 if XMLData.bounty_task
5358 return XMLData.bounty_task
5359 else
5360 return nil
5361 end
5362end
5363
5364def checksleeping
5365 return $infomon_sleeping
5366end
5367def sleeping?
5368 return $infomon_sleeping
5369end
5370def checkbound
5371 return $infomon_bound
5372end
5373def bound?
5374 return $infomon_bound
5375end
5376def checksilenced
5377 $infomon_silenced
5378end
5379def silenced?
5380 $infomon_silenced
5381end
5382def checkcalmed
5383 $infomon_calmed
5384end
5385def calmed?
5386 $infomon_calmed
5387end
5388def checkcutthroat
5389 $infomon_cutthroat
5390end
5391def cutthroat?
5392 $infomon_cutthroat
5393end
5394
5395def variable
5396 unless script = Script.current then echo 'variable: cannot identify calling script.'; return nil; end
5397 script.vars
5398end
5399
5400def pause(num=1)
5401 if num =~ /m/
5402 sleep((num.sub(/m/, '').to_f * 60))
5403 elsif num =~ /h/
5404 sleep((num.sub(/h/, '').to_f * 3600))
5405 elsif num =~ /d/
5406 sleep((num.sub(/d/, '').to_f * 86400))
5407 else
5408 sleep(num.to_f)
5409 end
5410end
5411
5412def cast(spell, target=nil, results_of_interest=nil)
5413 if spell.class == Spell
5414 spell.cast(target, results_of_interest)
5415 elsif ( (spell.class == Fixnum) or (spell.to_s =~ /^[0-9]+$/) ) and (find_spell = Spell[spell.to_i])
5416 find_spell.cast(target, results_of_interest)
5417 elsif (spell.class == String) and (find_spell = Spell[spell])
5418 find_spell.cast(target, results_of_interest)
5419 else
5420 echo "cast: invalid spell (#{spell})"
5421 false
5422 end
5423end
5424
5425def clear(opt=0)
5426 unless script = Script.current then respond('--- clear: Unable to identify calling script.'); return false; end
5427 to_return = script.downstream_buffer.dup
5428 script.downstream_buffer.clear
5429 to_return
5430end
5431
5432def match(label, string)
5433 strings = [ label, string ]
5434 strings.flatten!
5435 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
5436 if strings.empty? then echo("Error! 'match' was given no strings to look for!") ; sleep 1 ; return false end
5437 unless strings.length == 2
5438 while line_in = script.gets
5439 strings.each { |string|
5440 if line_in =~ /#{string}/ then return $~.to_s end
5441 }
5442 end
5443 else
5444 if script.respond_to?(:match_stack_add)
5445 script.match_stack_add(strings.first.to_s, strings.last)
5446 else
5447 script.match_stack_labels.push(strings[0].to_s)
5448 script.match_stack_strings.push(strings[1])
5449 end
5450 end
5451end
5452
5453def matchtimeout(secs, *strings)
5454 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
5455 unless (secs.class == Float || secs.class == Fixnum)
5456 echo('matchtimeout error! You appear to have given it a string, not a #! Syntax: matchtimeout(30, "You stand up")')
5457 return false
5458 end
5459 strings.flatten!
5460 if strings.empty?
5461 echo("matchtimeout without any strings to wait for!")
5462 sleep 1
5463 return false
5464 end
5465 regexpstr = strings.join('|')
5466 end_time = Time.now.to_f + secs
5467 loop {
5468 line = get?
5469 if line.nil?
5470 sleep 0.1
5471 elsif line =~ /#{regexpstr}/i
5472 return line
5473 end
5474 if (Time.now.to_f > end_time)
5475 return false
5476 end
5477 }
5478end
5479
5480def matchbefore(*strings)
5481 strings.flatten!
5482 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
5483 if strings.empty? then echo("matchbefore without any strings to wait for!") ; return false end
5484 regexpstr = strings.join('|')
5485 loop { if (line_in = script.gets) =~ /#{regexpstr}/ then return $`.to_s end }
5486end
5487
5488def matchafter(*strings)
5489 strings.flatten!
5490 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
5491 if strings.empty? then echo("matchafter without any strings to wait for!") ; return end
5492 regexpstr = strings.join('|')
5493 loop { if (line_in = script.gets) =~ /#{regexpstr}/ then return $'.to_s end }
5494end
5495
5496def matchboth(*strings)
5497 strings.flatten!
5498 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
5499 if strings.empty? then echo("matchboth without any strings to wait for!") ; return end
5500 regexpstr = strings.join('|')
5501 loop { if (line_in = script.gets) =~ /#{regexpstr}/ then break end }
5502 return [ $`.to_s, $'.to_s ]
5503end
5504
5505def matchwait(*strings)
5506 unless script = Script.current then respond('--- matchwait: Unable to identify calling script.'); return false; end
5507 strings.flatten!
5508 unless strings.empty?
5509 regexpstr = strings.collect { |str| str.kind_of?(Regexp) ? str.source : str }.join('|')
5510 regexobj = /#{regexpstr}/
5511 while line_in = script.gets
5512 return line_in if line_in =~ regexobj
5513 end
5514 else
5515 strings = script.match_stack_strings
5516 labels = script.match_stack_labels
5517 regexpstr = /#{strings.join('|')}/i
5518 while line_in = script.gets
5519 if mdata = regexpstr.match(line_in)
5520 jmp = labels[strings.index(mdata.to_s) || strings.index(strings.find { |str| line_in =~ /#{str}/i })]
5521 script.match_stack_clear
5522 goto jmp
5523 end
5524 end
5525 end
5526end
5527
5528def waitforre(regexp)
5529 unless script = Script.current then respond('--- waitforre: Unable to identify calling script.'); return false; end
5530 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
5531 regobj = regexp.match(script.gets) until regobj
5532end
5533
5534def waitfor(*strings)
5535 unless script = Script.current then respond('--- waitfor: Unable to identify calling script.'); return false; end
5536 strings.flatten!
5537 if (script.class == WizardScript) and (strings.length == 1) and (strings.first.strip == '>')
5538 return script.gets
5539 end
5540 if strings.empty?
5541 echo 'waitfor: no string to wait for'
5542 return false
5543 end
5544 regexpstr = strings.join('|')
5545 while true
5546 line_in = script.gets
5547 if (line_in =~ /#{regexpstr}/i) then return line_in end
5548 end
5549end
5550
5551def wait
5552 unless script = Script.current then respond('--- wait: unable to identify calling script.'); return false; end
5553 script.clear
5554 return script.gets
5555end
5556
5557def get
5558 Script.current.gets
5559end
5560
5561def get?
5562 Script.current.gets?
5563end
5564
5565def reget(*lines)
5566 unless script = Script.current then respond('--- reget: Unable to identify calling script.'); return false; end
5567 lines.flatten!
5568 if caller.find { |c| c =~ /regetall/ }
5569 history = ($_SERVERBUFFER_.history + $_SERVERBUFFER_).join("\n")
5570 else
5571 history = $_SERVERBUFFER_.dup.join("\n")
5572 end
5573 unless script.want_downstream_xml
5574 history.gsub!(/<pushStream id=["'](?:spellfront|inv|bounty|society)["'][^>]*\/>.*?<popStream[^>]*>/m, '')
5575 history.gsub!(/<stream id="Spells">.*?<\/stream>/m, '')
5576 history.gsub!(/<(compDef|inv|component|right|left|spell|prompt)[^>]*>.*?<\/\1>/m, '')
5577 history.gsub!(/<[^>]+>/, '')
5578 history.gsub!('>', '>')
5579 history.gsub!('<', '<')
5580 end
5581 history = history.split("\n").delete_if { |line| line.nil? or line.empty? or line =~ /^[\r\n\s\t]*$/ }
5582 if lines.first.kind_of?(Numeric) or lines.first.to_i.nonzero?
5583 history = history[-([lines.shift.to_i,history.length].min)..-1]
5584 end
5585 unless lines.empty? or lines.nil?
5586 regex = /#{lines.join('|')}/i
5587 history = history.find_all { |line| line =~ regex }
5588 end
5589 if history.empty?
5590 nil
5591 else
5592 history
5593 end
5594end
5595
5596def regetall(*lines)
5597 reget(*lines)
5598end
5599
5600def multifput(*cmds)
5601 cmds.flatten.compact.each { |cmd| fput(cmd) }
5602end
5603
5604def fput(message, *waitingfor)
5605 unless script = Script.current then respond('--- waitfor: Unable to identify calling script.'); return false; end
5606 waitingfor.flatten!
5607 clear
5608 put(message)
5609
5610 while string = get
5611 if string =~ /(?:\.\.\.wait |Wait )[0-9]+/
5612 hold_up = string.slice(/[0-9]+/).to_i
5613 sleep(hold_up) unless hold_up.nil?
5614 clear
5615 put(message)
5616 next
5617 elsif string =~ /^You.+struggle.+stand/
5618 clear
5619 fput 'stand'
5620 next
5621 elsif string =~ /stunned|can't do that while|cannot seem|^(?!You rummage).*can't seem|don't seem|Sorry, you may only type ahead/
5622 if dead?
5623 echo "You're dead...! You can't do that!"
5624 sleep 1
5625 script.downstream_buffer.unshift(string)
5626 return false
5627 elsif checkstunned
5628 while checkstunned
5629 sleep("0.25".to_f)
5630 end
5631 elsif checkwebbed
5632 while checkwebbed
5633 sleep("0.25".to_f)
5634 end
5635 elsif string =~ /Sorry, you may only type ahead/
5636 sleep 1
5637 else
5638 sleep 0.1
5639 script.downstream_buffer.unshift(string)
5640 return false
5641 end
5642 clear
5643 put(message)
5644 next
5645 else
5646 if waitingfor.empty?
5647 script.downstream_buffer.unshift(string)
5648 return string
5649 else
5650 if foundit = waitingfor.find { |val| string =~ /#{val}/i }
5651 script.downstream_buffer.unshift(string)
5652 return foundit
5653 end
5654 sleep 1
5655 clear
5656 put(message)
5657 next
5658 end
5659 end
5660 end
5661end
5662
5663def put(*messages)
5664 messages.each { |message| Game.puts(message) }
5665end
5666
5667def quiet_exit
5668 script = Script.current
5669 script.quiet = !(script.quiet)
5670end
5671
5672def matchfindexact(*strings)
5673 strings.flatten!
5674 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
5675 if strings.empty? then echo("error! 'matchfind' with no strings to look for!") ; sleep 1 ; return false end
5676 looking = Array.new
5677 strings.each { |str| looking.push(str.gsub('?', '(\b.+\b)')) }
5678 if looking.empty? then echo("matchfind without any strings to wait for!") ; return false end
5679 regexpstr = looking.join('|')
5680 while line_in = script.gets
5681 if gotit = line_in.slice(/#{regexpstr}/)
5682 matches = Array.new
5683 looking.each_with_index { |str,idx|
5684 if gotit =~ /#{str}/i
5685 strings[idx].count('?').times { |n| matches.push(eval("$#{n+1}")) }
5686 end
5687 }
5688 break
5689 end
5690 end
5691 if matches.length == 1
5692 return matches.first
5693 else
5694 return matches.compact
5695 end
5696end
5697
5698def matchfind(*strings)
5699 regex = /#{strings.flatten.join('|').gsub('?', '(.+)')}/i
5700 unless script = Script.current
5701 respond "Unknown script is asking to use matchfind! Cannot process request without identifying the calling script; killing this thread."
5702 Thread.current.kill
5703 end
5704 while true
5705 if reobj = regex.match(script.gets)
5706 ret = reobj.captures.compact
5707 if ret.length < 2
5708 return ret.first
5709 else
5710 return ret
5711 end
5712 end
5713 end
5714end
5715
5716def matchfindword(*strings)
5717 regex = /#{strings.flatten.join('|').gsub('?', '([\w\d]+)')}/i
5718 unless script = Script.current
5719 respond "Unknown script is asking to use matchfindword! Cannot process request without identifying the calling script; killing this thread."
5720 Thread.current.kill
5721 end
5722 while true
5723 if reobj = regex.match(script.gets)
5724 ret = reobj.captures.compact
5725 if ret.length < 2
5726 return ret.first
5727 else
5728 return ret
5729 end
5730 end
5731 end
5732end
5733
5734def send_scripts(*messages)
5735 messages.flatten!
5736 messages.each { |message|
5737 Script.new_downstream(message)
5738 }
5739 true
5740end
5741
5742def status_tags(onoff="none")
5743 script = Script.current
5744 if onoff == "on"
5745 script.want_downstream = false
5746 script.want_downstream_xml = true
5747 echo("Status tags will be sent to this script.")
5748 elsif onoff == "off"
5749 script.want_downstream = true
5750 script.want_downstream_xml = false
5751 echo("Status tags will no longer be sent to this script.")
5752 elsif script.want_downstream_xml
5753 script.want_downstream = true
5754 script.want_downstream_xml = false
5755 else
5756 script.want_downstream = false
5757 script.want_downstream_xml = true
5758 end
5759end
5760
5761def respond(first = "", *messages)
5762 str = ''
5763 begin
5764 if first.class == Array
5765 first.flatten.each { |ln| str += sprintf("%s\r\n", ln.to_s.chomp) }
5766 else
5767 str += sprintf("%s\r\n", first.to_s.chomp)
5768 end
5769 messages.flatten.each { |message| str += sprintf("%s\r\n", message.to_s.chomp) }
5770 str.split(/\r?\n/).each { |line| Script.new_script_output(line); Buffer.update(line, Buffer::SCRIPT_OUTPUT) }
5771 if $frontend == 'stormfront'
5772 str = "<output class=\"mono\"/>\r\n#{str.gsub('&', '&').gsub('<', '<').gsub('>', '>')}<output class=\"\"/>\r\n"
5773 elsif $frontend == 'profanity'
5774 str = str.gsub('&', '&').gsub('<', '<').gsub('>', '>')
5775 end
5776 wait_while { XMLData.in_stream }
5777 $_CLIENT_.puts(str)
5778 if $_DETACHABLE_CLIENT_
5779 $_DETACHABLE_CLIENT_.puts(str) rescue nil
5780 end
5781 rescue
5782 puts $!
5783 puts $!.backtrace.first
5784 end
5785end
5786
5787def _respond(first = "", *messages)
5788 str = ''
5789 begin
5790 if first.class == Array
5791 first.flatten.each { |ln| str += sprintf("%s\r\n", ln.to_s.chomp) }
5792 else
5793 str += sprintf("%s\r\n", first.to_s.chomp)
5794 end
5795 messages.flatten.each { |message| str += sprintf("%s\r\n", message.to_s.chomp) }
5796 str.split(/\r?\n/).each { |line| Script.new_script_output(line); Buffer.update(line, Buffer::SCRIPT_OUTPUT) } # fixme: strip/separate script output?
5797 wait_while { XMLData.in_stream }
5798 $_CLIENT_.puts(str)
5799 if $_DETACHABLE_CLIENT_
5800 $_DETACHABLE_CLIENT_.puts(str) rescue nil
5801 end
5802 rescue
5803 puts $!
5804 puts $!.backtrace.first
5805 end
5806end
5807
5808def noded_pulse
5809 if Stats.prof =~ /warrior|rogue|sorcerer/i
5810 stats = [ Skills.smc.to_i, Skills.emc.to_i ]
5811 elsif Stats.prof =~ /empath|bard/i
5812 stats = [ Skills.smc.to_i, Skills.mmc.to_i ]
5813 elsif Stats.prof =~ /wizard/i
5814 stats = [ Skills.emc.to_i, 0 ]
5815 elsif Stats.prof =~ /paladin|cleric|ranger/i
5816 stats = [ Skills.smc.to_i, 0 ]
5817 else
5818 stats = [ 0, 0 ]
5819 end
5820 return (maxmana * 25 / 100) + (stats.max/10) + (stats.min/20)
5821end
5822
5823def unnoded_pulse
5824 if Stats.prof =~ /warrior|rogue|sorcerer/i
5825 stats = [ Skills.smc.to_i, Skills.emc.to_i ]
5826 elsif Stats.prof =~ /empath|bard/i
5827 stats = [ Skills.smc.to_i, Skills.mmc.to_i ]
5828 elsif Stats.prof =~ /wizard/i
5829 stats = [ Skills.emc.to_i, 0 ]
5830 elsif Stats.prof =~ /paladin|cleric|ranger/i
5831 stats = [ Skills.smc.to_i, 0 ]
5832 else
5833 stats = [ 0, 0 ]
5834 end
5835 return (maxmana * 15 / 100) + (stats.max/10) + (stats.min/20)
5836end
5837
5838def empty_hands
5839 $fill_hands_actions ||= Array.new
5840 actions = Array.new
5841 right_hand = GameObj.right_hand
5842 left_hand = GameObj.left_hand
5843 if UserVars.lootsack.nil? or UserVars.lootsack.empty?
5844 lootsack = nil
5845 else
5846 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 }
5847 end
5848 other_containers_var = nil
5849 other_containers = proc {
5850 if other_containers_var.nil?
5851 Script.current.want_downstream = false
5852 Script.current.want_downstream_xml = true
5853 result = dothistimeout 'inventory containers', 5, /^You are wearing/
5854 Script.current.want_downstream_xml = false
5855 Script.current.want_downstream = true
5856 other_containers_ids = result.scan(/exist="(.*?)"/).flatten - [ lootsack.id ]
5857 other_containers_var = GameObj.inv.find_all { |obj| other_containers_ids.include?(obj.id) }
5858 end
5859 other_containers_var
5860 }
5861 if left_hand.id
5862 waitrt?
5863 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\.$/)
5864 actions.unshift proc {
5865 dothistimeout "remove ##{left_hand.id}", 3, /^You|^Remove what\?/
5866 20.times { break if GameObj.left_hand.id == left_hand.id or GameObj.right_hand.id == left_hand.id; sleep 0.1 }
5867 if GameObj.right_hand.id == left_hand.id
5868 dothistimeout 'swap', 3, /^You don't have anything to swap!|^You swap/
5869 end
5870 }
5871 else
5872 actions.unshift proc {
5873 dothistimeout "get ##{left_hand.id}", 3, /^You (?:shield the opening of .*? from view as you |discreetly |carefully )?(?:remove|draw|grab|reach|slip|tuck|retrieve|already have)|^Get what\?$|^Why don't you leave some for others\?$|^You need a free hand/
5874 20.times { break if (GameObj.left_hand.id == left_hand.id) or (GameObj.right_hand.id == left_hand.id); sleep 0.1 }
5875 if GameObj.right_hand.id == left_hand.id
5876 dothistimeout 'swap', 3, /^You don't have anything to swap!|^You swap/
5877 end
5878 }
5879 if lootsack
5880 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)|^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!$/
5881 if result =~ /^You can't .+ It's closed!$/
5882 actions.push proc { fput "close ##{lootsack.id}" }
5883 dothistimeout "open ##{lootsack.id}", 3, /^You open|^That is already open\./
5884 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)|^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!$/
5885 end
5886 else
5887 result = nil
5888 end
5889 if result.nil? or result =~ /^Your .*? won't fit in .*?\.$/
5890 for container in other_containers.call
5891 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)|^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!$/
5892 if result =~ /^You can't .+ It's closed!$/
5893 actions.push proc { fput "close ##{container.id}" }
5894 dothistimeout "open ##{container.id}", 3, /^You open|^That is already open\./
5895 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)|^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!$/
5896 end
5897 break if result =~ /^You (?:put|absent-mindedly drop|slip)/
5898 end
5899 end
5900 end
5901 end
5902 if right_hand.id
5903 waitrt?
5904 if XMLData.active_spells.keys.include?('Sonic Weapon Song') or XMLData.active_spells.keys.include?('1012')
5905 type = right_hand.noun
5906 if (type == 'sword') and right_hand.name =~ /short/
5907 type = 'short'
5908 elsif (type.downcase == 'hammer') and right_hand.name =~ /Hammer of Kai/
5909 type = 'hammer of kai'
5910 end
5911 actions.unshift proc {
5912 if (sonic_weapon_song = Spell[1012]) and sonic_weapon_song.known? and sonic_weapon_song.affordable?
5913 sonic_weapon_song.cast(type)
5914 end
5915 }
5916 fput 'stop 1012'
5917 else
5918 actions.unshift proc {
5919 dothistimeout "get ##{right_hand.id}", 3, /^You (?:shield the opening of .*? from view as you |discreetly |carefully )?(?:remove|draw|grab|reach|slip|tuck|retrieve|already have)|^Get what\?$|^Why don't you leave some for others\?$|^You need a free hand/
5920 20.times { break if GameObj.left_hand.id == right_hand.id or GameObj.right_hand.id == right_hand.id; sleep 0.1 }
5921 if GameObj.left_hand.id == right_hand.id
5922 dothistimeout 'swap', 3, /^You don't have anything to swap!|^You swap/
5923 end
5924 }
5925 if lootsack
5926 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)|^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!$/
5927 if result =~ /^You can't .+ It's closed!$/
5928 actions.push proc { fput "close ##{lootsack.id}" }
5929 dothistimeout "open ##{lootsack.id}", 3, /^You open|^That is already open\./
5930 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)|^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!$/
5931 end
5932 else
5933 result = nil
5934 end
5935 if result.nil? or result =~ /^Your .*? won't fit in .*?\.$/
5936 for container in other_containers.call
5937 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)|^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!$/
5938 if result =~ /^You can't .+ It's closed!$/
5939 actions.push proc { fput "close ##{container.id}" }
5940 dothistimeout "open ##{container.id}", 3, /^You open|^That is already open\./
5941 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)|^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!$/
5942 end
5943 break if result =~ /^You (?:put|absent-mindedly drop|slip)/
5944 end
5945 end
5946 end
5947 end
5948 $fill_hands_actions.push(actions)
5949end
5950
5951def fill_hands
5952 $fill_hands_actions ||= Array.new
5953 for action in $fill_hands_actions.pop
5954 action.call
5955 end
5956end
5957
5958def empty_hand
5959 $fill_hand_actions ||= Array.new
5960 actions = Array.new
5961 right_hand = GameObj.right_hand
5962 left_hand = GameObj.left_hand
5963 if UserVars.lootsack.nil? or UserVars.lootsack.empty?
5964 lootsack = nil
5965 else
5966 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 }
5967 end
5968 other_containers_var = nil
5969 other_containers = proc {
5970 if other_containers_var.nil?
5971 Script.current.want_downstream = false
5972 Script.current.want_downstream_xml = true
5973 result = dothistimeout 'inventory containers', 5, /^You are wearing/
5974 Script.current.want_downstream_xml = false
5975 Script.current.want_downstream = true
5976 other_containers_ids = result.scan(/exist="(.*?)"/).flatten - [ lootsack.id ]
5977 other_containers_var = GameObj.inv.find_all { |obj| other_containers_ids.include?(obj.id) }
5978 end
5979 other_containers_var
5980 }
5981 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))
5982 if right_hand.id and ((!XMLData.active_spells.keys.include?('Sonic Weapon Song') and !XMLData.active_spells.keys.include?('1012')) or ([ Wounds.leftArm, Wounds.leftHand, Scars.leftArm, Scars.leftHand ].max == 3)) and ([ Wounds.rightArm, Wounds.rightHand, Scars.rightArm, Scars.rightHand ].max < 3 or [ Wounds.leftArm, Wounds.leftHand, Scars.leftArm, Scars.leftHand ].max = 3)
5983 waitrt?
5984 if XMLData.active_spells.keys.include?('Sonic Weapon Song') or XMLData.active_spells.keys.include?('1012')
5985 type = right_hand.noun
5986 if (type == 'sword') and right_hand.name =~ /short/
5987 type = 'short'
5988 elsif (type.downcase == 'hammer') and right_hand.name =~ /Hammer of Kai/
5989 type = 'hammer of kai'
5990 end
5991 actions.unshift proc {
5992 if (sonic_weapon_song = Spell[1012]) and sonic_weapon_song.known? and sonic_weapon_song.affordable?
5993 sonic_weapon_song.cast(type)
5994 end
5995 }
5996 fput 'stop 1012'
5997 else
5998 actions.unshift proc {
5999 dothistimeout "get ##{right_hand.id}", 3, /^You (?:shield the opening of .*? from view as you |discreetly |carefully )?(?:remove|draw|grab|reach|slip|tuck|retrieve|already have)|^Get what\?$|^Why don't you leave some for others\?$|^You need a free hand/
6000 20.times { break if GameObj.left_hand.id == right_hand.id or GameObj.right_hand.id == right_hand.id; sleep 0.1 }
6001 if GameObj.left_hand.id == right_hand.id
6002 dothistimeout 'swap', 3, /^You don't have anything to swap!|^You swap/
6003 end
6004 }
6005 if lootsack
6006 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)|^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!$/
6007 if result =~ /^You can't .+ It's closed!$/
6008 actions.push proc { fput "close ##{lootsack.id}" }
6009 dothistimeout "open ##{lootsack.id}", 3, /^You open|^That is already open\./
6010 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)|^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!$/
6011 end
6012 else
6013 result = nil
6014 end
6015 if result.nil? or result =~ /^Your .*? won't fit in .*?\.$/
6016 for container in other_containers.call
6017 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)|^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!$/
6018 if result =~ /^You can't .+ It's closed!$/
6019 actions.push proc { fput "close ##{container.id}" }
6020 dothistimeout "open ##{container.id}", 3, /^You open|^That is already open\./
6021 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)|^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!$/
6022 end
6023 break if result =~ /^You (?:put|absent-mindedly drop|slip)/
6024 end
6025 end
6026 end
6027 else
6028 waitrt?
6029 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\.$/)
6030 actions.unshift proc {
6031 dothistimeout "remove ##{left_hand.id}", 3, /^You|^Remove what\?/
6032 20.times { break if GameObj.left_hand.id == left_hand.id or GameObj.right_hand.id == left_hand.id; sleep 0.1 }
6033 if GameObj.right_hand.id == left_hand.id
6034 dothistimeout 'swap', 3, /^You don't have anything to swap!|^You swap/
6035 end
6036 }
6037 else
6038 actions.unshift proc {
6039 dothistimeout "get ##{left_hand.id}", 3, /^You (?:shield the opening of .*? from view as you |discreetly |carefully )?(?:remove|draw|grab|reach|slip|tuck|retrieve|already have)|^Get what\?$|^Why don't you leave some for others\?$|^You need a free hand/
6040 20.times { break if GameObj.left_hand.id == left_hand.id or GameObj.right_hand.id == left_hand.id; sleep 0.1 }
6041 if GameObj.right_hand.id == left_hand.id
6042 dothistimeout 'swap', 3, /^You don't have anything to swap!|^You swap/
6043 end
6044 }
6045 if lootsack
6046 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)|^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 ##{lootsack.id}" }
6049 dothistimeout "open ##{lootsack.id}", 3, /^You open|^That is already open\./
6050 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)|^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 else
6053 result = nil
6054 end
6055 if result.nil? or result =~ /^Your .*? won't fit in .*?\.$/
6056 for container in other_containers.call
6057 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)|^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!$/
6058 if result =~ /^You can't .+ It's closed!$/
6059 actions.push proc { fput "close ##{container.id}" }
6060 dothistimeout "open ##{container.id}", 3, /^You open|^That is already open\./
6061 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)|^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!$/
6062 end
6063 break if result =~ /^You (?:put|absent-mindedly drop|slip)/
6064 end
6065 end
6066 end
6067 end
6068 end
6069 $fill_hand_actions.push(actions)
6070end
6071
6072def fill_hand
6073 $fill_hand_actions ||= Array.new
6074 for action in $fill_hand_actions.pop
6075 action.call
6076 end
6077end
6078
6079def empty_right_hand
6080 $fill_right_hand_actions ||= Array.new
6081 actions = Array.new
6082 right_hand = GameObj.right_hand
6083 if UserVars.lootsack.nil? or UserVars.lootsack.empty?
6084 lootsack = nil
6085 else
6086 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 }
6087 end
6088 other_containers_var = nil
6089 other_containers = proc {
6090 if other_containers_var.nil?
6091 Script.current.want_downstream = false
6092 Script.current.want_downstream_xml = true
6093 result = dothistimeout 'inventory containers', 5, /^You are wearing/
6094 Script.current.want_downstream_xml = false
6095 Script.current.want_downstream = true
6096 other_containers_ids = result.scan(/exist="(.*?)"/).flatten - [ lootsack.id ]
6097 other_containers_var = GameObj.inv.find_all { |obj| other_containers_ids.include?(obj.id) }
6098 end
6099 other_containers_var
6100 }
6101 if right_hand.id
6102 waitrt?
6103 if XMLData.active_spells.keys.include?('Sonic Weapon Song') or XMLData.active_spells.keys.include?('1012')
6104 type = right_hand.noun
6105 if (type == 'sword') and right_hand.name =~ /short/
6106 type = 'short'
6107 elsif (type.downcase == 'hammer') and right_hand.name =~ /Hammer of Kai/
6108 type = 'hammer of kai'
6109 end
6110 actions.unshift proc {
6111 if (sonic_weapon_song = Spell[1012]) and sonic_weapon_song.known? and sonic_weapon_song.affordable?
6112 sonic_weapon_song.cast(type)
6113 end
6114 }
6115 fput 'stop 1012'
6116 else
6117 actions.unshift proc {
6118 dothistimeout "get ##{right_hand.id}", 3, /^You (?:shield the opening of .*? from view as you |discreetly |carefully )?(?:remove|draw|grab|reach|slip|tuck|retrieve|already have)|^Get what\?$|^Why don't you leave some for others\?$|^You need a free hand/
6119 20.times { break if GameObj.left_hand.id == right_hand.id or GameObj.right_hand.id == right_hand.id; sleep 0.1 }
6120 if GameObj.left_hand.id == right_hand.id
6121 dothistimeout 'swap', 3, /^You don't have anything to swap!|^You swap/
6122 end
6123 }
6124 if lootsack
6125 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)|^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!$/
6126 if result =~ /^You can't .+ It's closed!$/
6127 actions.push proc { fput "close ##{lootsack.id}" }
6128 dothistimeout "open ##{lootsack.id}", 3, /^You open|^That is already open\./
6129 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)|^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!$/
6130 end
6131 else
6132 result = nil
6133 end
6134 if result.nil? or result =~ /^Your .*? won't fit in .*?\.$/
6135 for container in other_containers.call
6136 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)|^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!$/
6137 if result =~ /^You can't .+ It's closed!$/
6138 actions.push proc { fput "close ##{container.id}" }
6139 dothistimeout "open ##{container.id}", 3, /^You open|^That is already open\./
6140 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)|^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!$/
6141 end
6142 break if result =~ /^You (?:put|absent-mindedly drop|slip)/
6143 end
6144 end
6145 end
6146 end
6147 $fill_right_hand_actions.push(actions)
6148end
6149
6150def fill_right_hand
6151 $fill_right_hand_actions ||= Array.new
6152 for action in $fill_right_hand_actions.pop
6153 action.call
6154 end
6155end
6156
6157def empty_left_hand
6158 $fill_left_hand_actions ||= Array.new
6159 actions = Array.new
6160 left_hand = GameObj.left_hand
6161 if UserVars.lootsack.nil? or UserVars.lootsack.empty?
6162 lootsack = nil
6163 else
6164 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 }
6165 end
6166 other_containers_var = nil
6167 other_containers = proc {
6168 if other_containers_var.nil?
6169 Script.current.want_downstream = false
6170 Script.current.want_downstream_xml = true
6171 result = dothistimeout 'inventory containers', 5, /^You are wearing/
6172 Script.current.want_downstream_xml = false
6173 Script.current.want_downstream = true
6174 other_containers_ids = result.scan(/exist="(.*?)"/).flatten - [ lootsack.id ]
6175 other_containers_var = GameObj.inv.find_all { |obj| other_containers_ids.include?(obj.id) }
6176 end
6177 other_containers_var
6178 }
6179 if left_hand.id
6180 waitrt?
6181 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\.$/)
6182 actions.unshift proc {
6183 dothistimeout "remove ##{left_hand.id}", 3, /^You|^Remove what\?/
6184 20.times { break if GameObj.left_hand.id == left_hand.id or GameObj.right_hand.id == left_hand.id; sleep 0.1 }
6185 if GameObj.right_hand.id == left_hand.id
6186 dothistimeout 'swap', 3, /^You don't have anything to swap!|^You swap/
6187 end
6188 }
6189 else
6190 actions.unshift proc {
6191 dothistimeout "get ##{left_hand.id}", 3, /^You (?:shield the opening of .*? from view as you |discreetly |carefully )?(?:remove|draw|grab|reach|slip|tuck|retrieve|already have)|^Get what\?$|^Why don't you leave some for others\?$|^You need a free hand/
6192 20.times { break if GameObj.left_hand.id == left_hand.id or GameObj.right_hand.id == left_hand.id; sleep 0.1 }
6193 if GameObj.right_hand.id == left_hand.id
6194 dothistimeout 'swap', 3, /^You don't have anything to swap!|^You swap/
6195 end
6196 }
6197 if lootsack
6198 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)|^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!$/
6199 if result =~ /^You can't .+ It's closed!$/
6200 actions.push proc { fput "close ##{lootsack.id}" }
6201 dothistimeout "open ##{lootsack.id}", 3, /^You open|^That is already open\./
6202 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)|^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!$/
6203 end
6204 else
6205 result = nil
6206 end
6207 if result.nil? or result =~ /^Your .*? won't fit in .*?\.$/
6208 for container in other_containers.call
6209 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)|^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!$/
6210 if result =~ /^You can't .+ It's closed!$/
6211 actions.push proc { fput "close ##{container.id}" }
6212 dothistimeout "open ##{container.id}", 3, /^You open|^That is already open\./
6213 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)|^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!$/
6214 end
6215 break if result =~ /^You (?:put|absent-mindedly drop|slip)/
6216 end
6217 end
6218 end
6219 end
6220 $fill_left_hand_actions.push(actions)
6221end
6222
6223def fill_left_hand
6224 $fill_left_hand_actions ||= Array.new
6225 for action in $fill_left_hand_actions.pop
6226 action.call
6227 end
6228end
6229
6230def dothis (action, success_line)
6231 loop {
6232 Script.current.clear
6233 put action
6234 loop {
6235 line = get
6236 if line =~ success_line
6237 return line
6238 elsif line =~ /^(\.\.\.w|W)ait ([0-9]+) sec(onds)?\.$/
6239 if $2.to_i > 1
6240 sleep ($2.to_i - "0.5".to_f)
6241 else
6242 sleep 0.3
6243 end
6244 break
6245 elsif line == 'Sorry, you may only type ahead 1 command.'
6246 sleep 1
6247 break
6248 elsif line == 'You are still stunned.'
6249 wait_while { stunned? }
6250 break
6251 elsif line == 'That is impossible to do while unconscious!'
6252 100.times {
6253 unless line = get?
6254 sleep 0.1
6255 else
6256 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\.$/
6257 end
6258 }
6259 break
6260 elsif line == "You don't seem to be able to move to do that."
6261 100.times {
6262 unless line = get?
6263 sleep 0.1
6264 else
6265 break if line == 'The restricting force that envelops you dissolves away.'
6266 end
6267 }
6268 break
6269 elsif line == "You can't do that while entangled in a web."
6270 wait_while { checkwebbed }
6271 break
6272 elsif line == 'You find that impossible under the effects of the lullabye.'
6273 100.times {
6274 unless line = get?
6275 sleep 0.1
6276 else
6277 # fixme
6278 break if line == 'You shake off the effects of the lullabye.'
6279 end
6280 }
6281 break
6282 end
6283 }
6284 }
6285end
6286
6287def dothistimeout (action, timeout, success_line)
6288 end_time = Time.now.to_f + timeout
6289 line = nil
6290 loop {
6291 Script.current.clear
6292 put action unless action.nil?
6293 loop {
6294 line = get?
6295 if line.nil?
6296 sleep 0.1
6297 elsif line =~ success_line
6298 return line
6299 elsif line =~ /^(\.\.\.w|W)ait ([0-9]+) sec(onds)?\.$/
6300 if $2.to_i > 1
6301 sleep ($2.to_i - "0.5".to_f)
6302 else
6303 sleep 0.3
6304 end
6305 end_time = Time.now.to_f + timeout
6306 break
6307 elsif line == 'Sorry, you may only type ahead 1 command.'
6308 sleep 1
6309 end_time = Time.now.to_f + timeout
6310 break
6311 elsif line == 'You are still stunned.'
6312 wait_while { stunned? }
6313 end_time = Time.now.to_f + timeout
6314 break
6315 elsif line == 'That is impossible to do while unconscious!'
6316 100.times {
6317 unless line = get?
6318 sleep 0.1
6319 else
6320 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\.$/
6321 end
6322 }
6323 break
6324 elsif line == "You don't seem to be able to move to do that."
6325 100.times {
6326 unless line = get?
6327 sleep 0.1
6328 else
6329 break if line == 'The restricting force that envelops you dissolves away.'
6330 end
6331 }
6332 break
6333 elsif line == "You can't do that while entangled in a web."
6334 wait_while { checkwebbed }
6335 break
6336 elsif line == 'You find that impossible under the effects of the lullabye.'
6337 100.times {
6338 unless line = get?
6339 sleep 0.1
6340 else
6341 # fixme
6342 break if line == 'You shake off the effects of the lullabye.'
6343 end
6344 }
6345 break
6346 end
6347 if Time.now.to_f >= end_time
6348 return nil
6349 end
6350 }
6351 }
6352end
6353
6354$link_highlight_start = ''
6355$link_highlight_end = ''
6356$speech_highlight_start = ''
6357$speech_highlight_end = ''
6358
6359def sf_to_wiz(line)
6360 begin
6361 return line if line == "\r\n"
6362
6363 if $sftowiz_multiline
6364 $sftowiz_multiline = $sftowiz_multiline + line
6365 line = $sftowiz_multiline
6366 end
6367 if (line.scan(/<pushStream[^>]*\/>/).length > line.scan(/<popStream[^>]*\/>/).length)
6368 $sftowiz_multiline = line
6369 return nil
6370 end
6371 if (line.scan(/<style id="\w+"[^>]*\/>/).length > line.scan(/<style id=""[^>]*\/>/).length)
6372 $sftowiz_multiline = line
6373 return nil
6374 end
6375 $sftowiz_multiline = nil
6376 if line =~ /<LaunchURL src="(.*?)" \/>/
6377 $_CLIENT_.puts "\034GSw00005\r\nhttps://www.play.net#{$1}\r\n"
6378 end
6379 if line =~ /<preset id='speech'>(.*?)<\/preset>/m
6380 line = line.sub(/<preset id='speech'>.*?<\/preset>/m, "#{$speech_highlight_start}#{$1}#{$speech_highlight_end}")
6381 end
6382 if line =~ /<pushStream id="thoughts"[^>]*>(?:<a[^>]*>)?([A-Z][a-z]+)(?:<\/a>)?\s*([\s\[\]\(\)A-z]+)?:(.*?)<popStream\/>/m
6383 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}")
6384 end
6385 if line =~ /<pushStream id="voln"[^>]*>\[Voln \- (?:<a[^>]*>)?([A-Z][a-z]+)(?:<\/a>)?\]\s*(".*")[\r\n]*<popStream\/>/m
6386 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")
6387 end
6388 if line =~ /<stream id="thoughts"[^>]*>([^:]+): (.*?)<\/stream>/m
6389 line = line.sub(/<stream id="thoughts"[^>]*>.*?<\/stream>/m, "You hear the faint thoughts of #{$1} echo in your mind:\r\n#{$2}")
6390 end
6391 if line =~ /<pushStream id="familiar"[^>]*>(.*)<popStream\/>/m
6392 line = line.sub(/<pushStream id="familiar"[^>]*>.*<popStream\/>/m, "\034GSe\r\n#{$1}\034GSf\r\n")
6393 end
6394 if line =~ /<pushStream id="death"\/>(.*?)<popStream\/>/m
6395 line = line.sub(/<pushStream id="death"\/>.*?<popStream\/>/m, "\034GSw00003\r\n#{$1}\034GSw00004\r\n")
6396 end
6397 if line =~ /<style id="roomName" \/>(.*?)<style id=""\/>/m
6398 line = line.sub(/<style id="roomName" \/>.*?<style id=""\/>/m, "\034GSo\r\n#{$1}\034GSp\r\n")
6399 end
6400 line.gsub!(/<style id="roomDesc"\/><style id=""\/>\r?\n/, '')
6401 if line =~ /<style id="roomDesc"\/>(.*?)<style id=""\/>/m
6402 desc = $1.gsub(/<a[^>]*>/, $link_highlight_start).gsub("</a>", $link_highlight_end)
6403 line = line.sub(/<style id="roomDesc"\/>.*?<style id=""\/>/m, "\034GSH\r\n#{desc}\034GSI\r\n")
6404 end
6405 line = line.gsub("</prompt>\r\n", "</prompt>")
6406 line = line.gsub("<pushBold/>", "\034GSL\r\n")
6407 line = line.gsub("<popBold/>", "\034GSM\r\n")
6408 line = line.gsub(/<pushStream id=["'](?:spellfront|inv|bounty|society|speech|talk)["'][^>]*\/>.*?<popStream[^>]*>/m, '')
6409 line = line.gsub(/<stream id="Spells">.*?<\/stream>/m, '')
6410 line = line.gsub(/<(compDef|inv|component|right|left|spell|prompt)[^>]*>.*?<\/\1>/m, '')
6411 line = line.gsub(/<[^>]+>/, '')
6412 line = line.gsub('>', '>')
6413 line = line.gsub('<', '<')
6414 return nil if line.gsub("\r\n", '').length < 1
6415 return line
6416 rescue
6417 $_CLIENT_.puts "--- Error: sf_to_wiz: #{$!}"
6418 $_CLIENT_.puts '$_SERVERSTRING_: ' + $_SERVERSTRING_.to_s
6419 end
6420end
6421
6422def strip_xml(line)
6423 return line if line == "\r\n"
6424
6425 if $strip_xml_multiline
6426 $strip_xml_multiline = $strip_xml_multiline + line
6427 line = $strip_xml_multiline
6428 end
6429 if (line.scan(/<pushStream[^>]*\/>/).length > line.scan(/<popStream[^>]*\/>/).length)
6430 $strip_xml_multiline = line
6431 return nil
6432 end
6433 $strip_xml_multiline = nil
6434
6435 line = line.gsub(/<pushStream id=["'](?:spellfront|inv|bounty|society|speech|talk)["'][^>]*\/>.*?<popStream[^>]*>/m, '')
6436 line = line.gsub(/<stream id="Spells">.*?<\/stream>/m, '')
6437 line = line.gsub(/<(compDef|inv|component|right|left|spell|prompt)[^>]*>.*?<\/\1>/m, '')
6438 line = line.gsub(/<[^>]+>/, '')
6439 line = line.gsub('>', '>')
6440 line = line.gsub('<', '<')
6441
6442 return nil if line.gsub("\n", '').gsub("\r", '').gsub(' ', '').length < 1
6443 return line
6444end
6445
6446def monsterbold_start
6447 if $frontend =~ /^(?:wizard|avalon)$/
6448 "\034GSL\r\n"
6449 elsif $frontend == 'stormfront'
6450 '<pushBold/>'
6451 elsif $frontend == 'profanity'
6452 '<b>'
6453 else
6454 ''
6455 end
6456end
6457
6458def monsterbold_end
6459 if $frontend =~ /^(?:wizard|avalon)$/
6460 "\034GSM\r\n"
6461 elsif $frontend == 'stormfront'
6462 '<popBold/>'
6463 elsif $frontend == 'profanity'
6464 '</b>'
6465 else
6466 ''
6467 end
6468end
6469
6470def do_client(client_string)
6471 client_string.strip!
6472# Buffer.update(client_string, Buffer::UPSTREAM)
6473 client_string = UpstreamHook.run(client_string)
6474# Buffer.update(client_string, Buffer::UPSTREAM_MOD)
6475 return nil if client_string.nil?
6476 if client_string =~ /^(?:<c>)?#{$lich_char}(.+)$/
6477 cmd = $1
6478 if cmd =~ /^k$|^kill$|^stop$/
6479 if Script.running.empty?
6480 respond '--- Lich: no scripts to kill'
6481 else
6482 Script.running.last.kill
6483 end
6484 elsif cmd =~ /^p$|^pause$/
6485 if s = Script.running.reverse.find { |s| not s.paused? }
6486 s.pause
6487 else
6488 respond '--- Lich: no scripts to pause'
6489 end
6490 s = nil
6491 elsif cmd =~ /^u$|^unpause$/
6492 if s = Script.running.reverse.find { |s| s.paused? }
6493 s.unpause
6494 else
6495 respond '--- Lich: no scripts to unpause'
6496 end
6497 s = nil
6498 elsif cmd =~ /^ka$|^kill\s?all$|^stop\s?all$/
6499 did_something = false
6500 Script.running.find_all { |s| not s.no_kill_all }.each { |s| s.kill; did_something = true }
6501 respond('--- Lich: no scripts to kill') unless did_something
6502 elsif cmd =~ /^pa$|^pause\s?all$/
6503 did_something = false
6504 Script.running.find_all { |s| not s.paused? and not s.no_pause_all }.each { |s| s.pause; did_something = true }
6505 respond('--- Lich: no scripts to pause') unless did_something
6506 elsif cmd =~ /^ua$|^unpause\s?all$/
6507 did_something = false
6508 Script.running.find_all { |s| s.paused? and not s.no_pause_all }.each { |s| s.unpause; did_something = true }
6509 respond('--- Lich: no scripts to unpause') unless did_something
6510 elsif cmd =~ /^(k|kill|stop|p|pause|u|unpause)\s(.+)/
6511 action = $1
6512 target = $2
6513 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 }
6514 if script.nil?
6515 respond "--- Lich: #{target} does not appear to be running! Use ';list' or ';listall' to see what's active."
6516 elsif action =~ /^(?:k|kill|stop)$/
6517 script.kill
6518 elsif action =~/^(?:p|pause)$/
6519 script.pause
6520 elsif action =~/^(?:u|unpause)$/
6521 script.unpause
6522 end
6523 action = target = script = nil
6524 elsif cmd =~ /^list\s?(?:all)?$|^l(?:a)?$/i
6525 if cmd =~ /a(?:ll)?/i
6526 list = Script.running + Script.hidden
6527 else
6528 list = Script.running
6529 end
6530 if list.empty?
6531 respond '--- Lich: no active scripts'
6532 else
6533 respond "--- Lich: #{list.collect { |s| s.paused? ? "#{s.name} (paused)" : s.name }.join(", ")}"
6534 end
6535 list = nil
6536 elsif cmd =~ /^force\s+[^\s]+/
6537 if cmd =~ /^force\s+([^\s]+)\s+(.+)$/
6538 Script.start($1, $2, :force => true)
6539 elsif cmd =~ /^force\s+([^\s]+)/
6540 Script.start($1, :force => true)
6541 end
6542 elsif cmd =~ /^send |^s /
6543 if cmd.split[1] == "to"
6544 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 }
6545 if script
6546 msg = cmd.split[3..-1].join(' ').chomp
6547 if script.want_downstream
6548 script.downstream_buffer.push(msg)
6549 else
6550 script.unique_buffer.push(msg)
6551 end
6552 respond "--- sent to '#{script.name}': #{msg}"
6553 else
6554 respond "--- Lich: '#{cmd.split[2].chomp.strip}' does not match any active script!"
6555 end
6556 script = nil
6557 else
6558 if Script.running.empty? and Script.hidden.empty?
6559 respond('--- Lich: no active scripts to send to.')
6560 else
6561 msg = cmd.split[1..-1].join(' ').chomp
6562 respond("--- sent: #{msg}")
6563 Script.new_downstream(msg)
6564 end
6565 end
6566 elsif cmd =~ /^(?:exec|e)(q)? (.+)$/
6567 cmd_data = $2
6568 if $1.nil?
6569 ExecScript.start(cmd_data, flags={ :quiet => false, :trusted => true })
6570 else
6571 ExecScript.start(cmd_data, flags={ :quiet => true, :trusted => true })
6572 end
6573 elsif cmd =~ /^trust\s+(.*)/i
6574 script_name = $1
6575 if File.exists?("#{SCRIPT_DIR}/#{script_name}.lic")
6576 if Script.trust(script_name)
6577 respond "--- Lich: '#{script_name}' is now a trusted script."
6578 end
6579 else
6580 respond "--- Lich: could not find script: #{script_name}"
6581 end
6582 elsif cmd =~ /^(?:dis|un)trust\s+(.*)/i
6583 script_name = $1
6584 if Script.distrust(script_name)
6585 respond "--- Lich: '#{script_name}' is no longer a trusted script."
6586 else
6587 respond "--- Lich: '#{script_name}' was not found in the trusted script list."
6588 end
6589 elsif cmd =~ /^list\s?(?:un)?trust(?:ed)?$|^lt$/i
6590 list = Script.list_trusted
6591 if list.empty?
6592 respond "--- Lich: no scripts are trusted"
6593 else
6594 respond "--- Lich: trusted scripts: #{list.join(', ')}"
6595 end
6596 list = nil
6597 elsif cmd =~ /^help$/i
6598 respond
6599 respond "Lich v#{LICH_VERSION}"
6600 respond
6601 respond 'built-in commands:'
6602 respond " #{$clean_lich_char}<script name> start a script"
6603 respond " #{$clean_lich_char}force <script name> start a script even if it's already running"
6604 respond " #{$clean_lich_char}pause <script name> pause a script"
6605 respond " #{$clean_lich_char}p <script name> ''"
6606 respond " #{$clean_lich_char}unpause <script name> unpause a script"
6607 respond " #{$clean_lich_char}u <script name> ''"
6608 respond " #{$clean_lich_char}kill <script name> kill a script"
6609 respond " #{$clean_lich_char}k <script name> ''"
6610 respond " #{$clean_lich_char}pause pause the most recently started script that isn't aready paused"
6611 respond " #{$clean_lich_char}p ''"
6612 respond " #{$clean_lich_char}unpause unpause the most recently started script that is paused"
6613 respond " #{$clean_lich_char}u ''"
6614 respond " #{$clean_lich_char}kill kill the most recently started script"
6615 respond " #{$clean_lich_char}k ''"
6616 respond " #{$clean_lich_char}list show running scripts (except hidden ones)"
6617 respond " #{$clean_lich_char}l ''"
6618 respond " #{$clean_lich_char}pause all pause all scripts"
6619 respond " #{$clean_lich_char}pa ''"
6620 respond " #{$clean_lich_char}unpause all unpause all scripts"
6621 respond " #{$clean_lich_char}ua ''"
6622 respond " #{$clean_lich_char}kill all kill all scripts"
6623 respond " #{$clean_lich_char}ka ''"
6624 respond " #{$clean_lich_char}list all show all running scripts"
6625 respond " #{$clean_lich_char}la ''"
6626 respond
6627 respond " #{$clean_lich_char}exec <code> executes the code as if it was in a script"
6628 respond " #{$clean_lich_char}e <code> ''"
6629 respond " #{$clean_lich_char}execq <code> same as #{$clean_lich_char}exec but without the script active and exited messages"
6630 respond " #{$clean_lich_char}eq <code> ''"
6631 respond
6632 respond " #{$clean_lich_char}trust <script name> let the script do whatever it wants"
6633 respond " #{$clean_lich_char}distrust <script name> restrict the script from doing things that might harm your computer"
6634 respond " #{$clean_lich_char}list trusted show what scripts are trusted"
6635 respond " #{$clean_lich_char}lt ''"
6636 respond
6637 respond " #{$clean_lich_char}send <line> send a line to all scripts as if it came from the game"
6638 respond " #{$clean_lich_char}send to <script> <line> send a line to a specific script"
6639 respond
6640 respond 'If you liked this help message, you might also enjoy:'
6641 respond " #{$clean_lich_char}lnet help"
6642 respond " #{$clean_lich_char}magic help (infomon must be running)"
6643 respond " #{$clean_lich_char}go2 help"
6644 respond " #{$clean_lich_char}repository help"
6645 respond " #{$clean_lich_char}alias help"
6646 respond " #{$clean_lich_char}vars help"
6647 respond " #{$clean_lich_char}autostart help"
6648 respond
6649 else
6650 if cmd =~ /^([^\s]+)\s+(.+)/
6651 Script.start($1, $2)
6652 else
6653 Script.start(cmd)
6654 end
6655 end
6656 else
6657 if $offline_mode
6658 respond "--- Lich: offline mode: ignoring #{client_string}"
6659 else
6660 client_string = "#{$cmd_prefix}bbs" if ($frontend =~ /^(?:wizard|avalon)$/) and (client_string == "#{$cmd_prefix}\egbbk\n") # launch forum
6661 Game._puts client_string
6662 end
6663 $_CLIENTBUFFER_.push client_string
6664 end
6665 Script.new_upstream(client_string)
6666end
6667
6668def report_errors(&block)
6669 begin
6670 block.call
6671 rescue
6672 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
6673 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
6674 rescue SyntaxError
6675 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
6676 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
6677 rescue SystemExit
6678 nil
6679 rescue SecurityError
6680 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
6681 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
6682 rescue ThreadError
6683 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
6684 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
6685 rescue SystemStackError
6686 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
6687 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
6688 rescue Exception
6689 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
6690 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
6691 rescue ScriptError
6692 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
6693 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
6694 rescue LoadError
6695 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
6696 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
6697 rescue NoMemoryError
6698 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
6699 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
6700 rescue
6701 respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
6702 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
6703 end
6704end
6705
6706module Buffer
6707 DOWNSTREAM_STRIPPED = 1
6708 DOWNSTREAM_RAW = 2
6709 DOWNSTREAM_MOD = 4
6710 UPSTREAM = 8
6711 UPSTREAM_MOD = 16
6712 SCRIPT_OUTPUT = 32
6713 @@index = Hash.new
6714 @@streams = Hash.new
6715 @@mutex = Mutex.new
6716 @@offset = 0
6717 @@buffer = Array.new
6718 @@max_size = 3000
6719 def Buffer.gets
6720 thread_id = Thread.current.object_id
6721 if @@index[thread_id].nil?
6722 @@mutex.synchronize {
6723 @@index[thread_id] = (@@offset + @@buffer.length)
6724 @@streams[thread_id] ||= DOWNSTREAM_STRIPPED
6725 }
6726 end
6727 line = nil
6728 loop {
6729 if (@@index[thread_id] - @@offset) >= @@buffer.length
6730 sleep 0.05 while ((@@index[thread_id] - @@offset) >= @@buffer.length)
6731 end
6732 @@mutex.synchronize {
6733 if @@index[thread_id] < @@offset
6734 @@index[thread_id] = @@offset
6735 end
6736 line = @@buffer[@@index[thread_id] - @@offset]
6737 }
6738 @@index[thread_id] += 1
6739 break if ((line.stream & @@streams[thread_id]) != 0)
6740 }
6741 return line
6742 end
6743 def Buffer.gets?
6744 thread_id = Thread.current.object_id
6745 if @@index[thread_id].nil?
6746 @@mutex.synchronize {
6747 @@index[thread_id] = (@@offset + @@buffer.length)
6748 @@streams[thread_id] ||= DOWNSTREAM_STRIPPED
6749 }
6750 end
6751 line = nil
6752 loop {
6753 if (@@index[thread_id] - @@offset) >= @@buffer.length
6754 return nil
6755 end
6756 @@mutex.synchronize {
6757 if @@index[thread_id] < @@offset
6758 @@index[thread_id] = @@offset
6759 end
6760 line = @@buffer[@@index[thread_id] - @@offset]
6761 }
6762 @@index[thread_id] += 1
6763 break if ((line.stream & @@streams[thread_id]) != 0)
6764 }
6765 return line
6766 end
6767 def Buffer.rewind
6768 thread_id = Thread.current.object_id
6769 @@index[thread_id] = @@offset
6770 @@streams[thread_id] ||= DOWNSTREAM_STRIPPED
6771 return self
6772 end
6773 def Buffer.clear
6774 thread_id = Thread.current.object_id
6775 if @@index[thread_id].nil?
6776 @@mutex.synchronize {
6777 @@index[thread_id] = (@@offset + @@buffer.length)
6778 @@streams[thread_id] ||= DOWNSTREAM_STRIPPED
6779 }
6780 end
6781 lines = Array.new
6782 loop {
6783 if (@@index[thread_id] - @@offset) >= @@buffer.length
6784 return lines
6785 end
6786 line = nil
6787 @@mutex.synchronize {
6788 if @@index[thread_id] < @@offset
6789 @@index[thread_id] = @@offset
6790 end
6791 line = @@buffer[@@index[thread_id] - @@offset]
6792 }
6793 @@index[thread_id] += 1
6794 lines.push(line) if ((line.stream & @@streams[thread_id]) != 0)
6795 }
6796 return lines
6797 end
6798 def Buffer.update(line, stream=nil)
6799 @@mutex.synchronize {
6800 frozen_line = line.dup
6801 unless stream.nil?
6802 frozen_line.stream = stream
6803 end
6804 frozen_line.freeze
6805 @@buffer.push(frozen_line)
6806 while (@@buffer.length > @@max_size)
6807 @@buffer.shift
6808 @@offset += 1
6809 end
6810 }
6811 return self
6812 end
6813 def Buffer.streams
6814 @@streams[Thread.current.object_id]
6815 end
6816 def Buffer.streams=(val)
6817 if (val.class != Fixnum) or ((val & 63) == 0)
6818 respond "--- Lich: error: invalid streams value\n\t#{$!.caller[0..2].join("\n\t")}"
6819 return nil
6820 end
6821 @@streams[Thread.current.object_id] = val
6822 end
6823 def Buffer.cleanup
6824 @@index.delete_if { |k,v| not Thread.list.any? { |t| t.object_id == k } }
6825 @@streams.delete_if { |k,v| not Thread.list.any? { |t| t.object_id == k } }
6826 return self
6827 end
6828end
6829
6830class SharedBuffer
6831 attr_accessor :max_size
6832 def initialize(args={})
6833 @buffer = Array.new
6834 @buffer_offset = 0
6835 @buffer_index = Hash.new
6836 @buffer_mutex = Mutex.new
6837 @max_size = args[:max_size] || 500
6838 return self
6839 end
6840 def gets
6841 thread_id = Thread.current.object_id
6842 if @buffer_index[thread_id].nil?
6843 @buffer_mutex.synchronize { @buffer_index[thread_id] = (@buffer_offset + @buffer.length) }
6844 end
6845 if (@buffer_index[thread_id] - @buffer_offset) >= @buffer.length
6846 sleep 0.05 while ((@buffer_index[thread_id] - @buffer_offset) >= @buffer.length)
6847 end
6848 line = nil
6849 @buffer_mutex.synchronize {
6850 if @buffer_index[thread_id] < @buffer_offset
6851 @buffer_index[thread_id] = @buffer_offset
6852 end
6853 line = @buffer[@buffer_index[thread_id] - @buffer_offset]
6854 }
6855 @buffer_index[thread_id] += 1
6856 return line
6857 end
6858 def gets?
6859 thread_id = Thread.current.object_id
6860 if @buffer_index[thread_id].nil?
6861 @buffer_mutex.synchronize { @buffer_index[thread_id] = (@buffer_offset + @buffer.length) }
6862 end
6863 if (@buffer_index[thread_id] - @buffer_offset) >= @buffer.length
6864 return nil
6865 end
6866 line = nil
6867 @buffer_mutex.synchronize {
6868 if @buffer_index[thread_id] < @buffer_offset
6869 @buffer_index[thread_id] = @buffer_offset
6870 end
6871 line = @buffer[@buffer_index[thread_id] - @buffer_offset]
6872 }
6873 @buffer_index[thread_id] += 1
6874 return line
6875 end
6876 def clear
6877 thread_id = Thread.current.object_id
6878 if @buffer_index[thread_id].nil?
6879 @buffer_mutex.synchronize { @buffer_index[thread_id] = (@buffer_offset + @buffer.length) }
6880 return Array.new
6881 end
6882 if (@buffer_index[thread_id] - @buffer_offset) >= @buffer.length
6883 return Array.new
6884 end
6885 lines = Array.new
6886 @buffer_mutex.synchronize {
6887 if @buffer_index[thread_id] < @buffer_offset
6888 @buffer_index[thread_id] = @buffer_offset
6889 end
6890 lines = @buffer[(@buffer_index[thread_id] - @buffer_offset)..-1]
6891 @buffer_index[thread_id] = (@buffer_offset + @buffer.length)
6892 }
6893 return lines
6894 end
6895 def rewind
6896 @buffer_index[Thread.current.object_id] = @buffer_offset
6897 return self
6898 end
6899 def update(line)
6900 @buffer_mutex.synchronize {
6901 fline = line.dup
6902 fline.freeze
6903 @buffer.push(fline)
6904 while (@buffer.length > @max_size)
6905 @buffer.shift
6906 @buffer_offset += 1
6907 end
6908 }
6909 return self
6910 end
6911 def cleanup_threads
6912 @buffer_index.delete_if { |k,v| not Thread.list.any? { |t| t.object_id == k } }
6913 return self
6914 end
6915end
6916
6917class SpellRanks
6918 @@list ||= Array.new
6919 @@timestamp ||= 0
6920 @@loaded ||= false
6921 @@elevated_load = proc { SpellRanks.load }
6922 @@elevated_save = proc { SpellRanks.save }
6923 attr_reader :name
6924 attr_accessor :minorspiritual, :majorspiritual, :cleric, :minorelemental, :majorelemental, :minormental, :ranger, :sorcerer, :wizard, :bard, :empath, :paladin, :arcanesymbols, :magicitemuse, :monk
6925 def SpellRanks.load
6926 if $SAFE == 0
6927 if File.exists?("#{DATA_DIR}/#{XMLData.game}/spell-ranks.dat")
6928 begin
6929 File.open("#{DATA_DIR}/#{XMLData.game}/spell-ranks.dat", 'rb') { |f|
6930 @@timestamp, @@list = Marshal.load(f.read)
6931 }
6932 # minor mental circle added 2012-07-18; old data files will have @minormental as nil
6933 @@list.each { |rank_info| rank_info.minormental ||= 0 }
6934 # monk circle added 2013-01-15; old data files will have @minormental as nil
6935 @@list.each { |rank_info| rank_info.monk ||= 0 }
6936 @@loaded = true
6937 rescue
6938 respond "--- Lich: error: SpellRanks.load: #{$!}"
6939 Lich.log "error: SpellRanks.load: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
6940 @@list = Array.new
6941 @@timestamp = 0
6942 @@loaded = true
6943 end
6944 else
6945 @@loaded = true
6946 end
6947 else
6948 @@elevated_load.call
6949 end
6950 end
6951 def SpellRanks.save
6952 if $SAFE == 0
6953 begin
6954 File.open("#{DATA_DIR}/#{XMLData.game}/spell-ranks.dat", 'wb') { |f|
6955 f.write(Marshal.dump([@@timestamp, @@list]))
6956 }
6957 rescue
6958 respond "--- Lich: error: SpellRanks.save: #{$!}"
6959 Lich.log "error: SpellRanks.save: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
6960 end
6961 else
6962 @@elevated_save.call
6963 end
6964 end
6965 def SpellRanks.timestamp
6966 SpellRanks.load unless @@loaded
6967 @@timestamp
6968 end
6969 def SpellRanks.timestamp=(val)
6970 SpellRanks.load unless @@loaded
6971 @@timestamp = val
6972 end
6973 def SpellRanks.[](name)
6974 SpellRanks.load unless @@loaded
6975 @@list.find { |n| n.name == name }
6976 end
6977 def SpellRanks.list
6978 SpellRanks.load unless @@loaded
6979 @@list
6980 end
6981 def SpellRanks.method_missing(arg=nil)
6982 echo "error: unknown method #{arg} for class SpellRanks"
6983 respond caller[0..1]
6984 end
6985 def initialize(name)
6986 SpellRanks.load unless @@loaded
6987 @name = name
6988 @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
6989 @@list.push(self)
6990 end
6991end
6992
6993
6994module Games
6995 module Unknown
6996 module Game
6997 end
6998 end
6999 module Gemstone
7000 module Game
7001 @@socket = nil
7002 @@mutex = Mutex.new
7003 @@last_recv = nil
7004 @@thread = nil
7005 @@buffer = SharedBuffer.new
7006 @@_buffer = SharedBuffer.new
7007 @@_buffer.max_size = 1000
7008 def Game.open(host, port)
7009 @@socket = TCPSocket.open(host, port)
7010 begin
7011 @@socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true)
7012 rescue
7013 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
7014 rescue Exception
7015 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
7016 end
7017 @@socket.sync = true
7018
7019 Thread.new {
7020 @@last_recv = Time.now
7021 loop {
7022 if (@@last_recv + 300) < Time.now
7023 Lich.log "#{Time.now}: error: nothing recieved from game server in 5 minutes"
7024 @@thread.kill rescue nil
7025 break
7026 end
7027 sleep (300 - (Time.now - @@last_recv))
7028 sleep 1
7029 }
7030 }
7031
7032 @@thread = Thread.new {
7033 begin
7034 atmospherics = false
7035 while $_SERVERSTRING_ = @@socket.gets
7036 @@last_recv = Time.now
7037 @@_buffer.update($_SERVERSTRING_) if TESTING
7038 begin
7039 $cmd_prefix = String.new if $_SERVERSTRING_ =~ /^\034GSw/
7040 # The Rift, Scatter is broken...
7041 if $_SERVERSTRING_ =~ /<compDef id='room text'><\/compDef>/
7042 $_SERVERSTRING_.sub!(/(.*)\s\s<compDef id='room text'><\/compDef>/) { "<compDef id='room desc'>#{$1}</compDef>" }
7043 end
7044 if atmospherics
7045 atmospherics = false
7046 $_SERVERSTRING.prepend('<popStream id="atmospherics" \/>') unless $_SERVERSTRING =~ /<popStream id="atmospherics" \/>/
7047 end
7048 if $_SERVERSTRING_ =~ /<pushStream id="familiar" \/><prompt time="[0-9]+">><\/prompt>/ # Cry For Help spell is broken...
7049 $_SERVERSTRING_.sub!('<pushStream id="familiar" />', '')
7050 elsif $_SERVERSTRING_ =~ /<pushStream id="atmospherics" \/><prompt time="[0-9]+">><\/prompt>/ # pet pigs in DragonRealms are broken...
7051 $_SERVERSTRING_.sub!('<pushStream id="atmospherics" />', '')
7052 elsif ($_SERVERSTRING_ =~ /<pushStream id="atmospherics" \/>/)
7053 atmospherics = true
7054 end
7055# while $_SERVERSTRING_.scan('<pushStream').length > $_SERVERSTRING_.scan('<popStream').length
7056# $_SERVERSTRING_.concat(@@socket.gets)
7057# end
7058 $_SERVERBUFFER_.push($_SERVERSTRING_)
7059 if alt_string = DownstreamHook.run($_SERVERSTRING_)
7060# Buffer.update(alt_string, Buffer::DOWNSTREAM_MOD)
7061 if $_DETACHABLE_CLIENT_
7062 begin
7063 $_DETACHABLE_CLIENT_.write(alt_string)
7064 rescue
7065 $_DETACHABLE_CLIENT_.close rescue nil
7066 $_DETACHABLE_CLIENT_ = nil
7067 respond "--- Lich: error: client_thread: #{$!}"
7068 respond $!.backtrace.first
7069 Lich.log "error: client_thread: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
7070 end
7071 end
7072 if $frontend =~ /^(?:wizard|avalon)$/
7073 alt_string = sf_to_wiz(alt_string)
7074 end
7075 $_CLIENT_.write(alt_string)
7076 end
7077 unless $_SERVERSTRING_ =~ /^<setting/
7078 begin
7079 REXML::Document.parse_stream($_SERVERSTRING_, XMLData)
7080 # XMLData.parse($_SERVERSTRING_)
7081 rescue
7082 unless $!.to_s =~ /invalid byte sequence/
7083 if $_SERVERSTRING_ =~ /<[^>]+='[^=>'\\]+'[^=>']+'[\s>]/
7084 # Simu has a nasty habbit of bad quotes in XML. <tag attr='this's that'>
7085 $_SERVERSTRING_.gsub!(/(<[^>]+=)'([^=>'\\]+'[^=>']+)'([\s>])/) { "#{$1}\"#{$2}\"#{$3}" }
7086 retry
7087 end
7088 $stdout.puts "--- error: server_thread: #{$!}"
7089 Lich.log "error: server_thread: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
7090 end
7091 XMLData.reset
7092 end
7093 Script.new_downstream_xml($_SERVERSTRING_)
7094 stripped_server = strip_xml($_SERVERSTRING_)
7095 stripped_server.split("\r\n").each { |line|
7096 @@buffer.update(line) if TESTING
7097 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*$/
7098 Script.new_downstream(line) unless line.empty?
7099 end
7100 }
7101 end
7102 rescue
7103 $stdout.puts "--- error: server_thread: #{$!}"
7104 Lich.log "error: server_thread: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
7105 end
7106 end
7107 rescue Exception
7108 Lich.log "error: server_thread: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
7109 $stdout.puts "--- error: server_thread: #{$!}"
7110 sleep 0.2
7111 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)
7112 rescue
7113 Lich.log "error: server_thread: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
7114 $stdout.puts "--- error: server_thread: #{$!}"
7115 sleep 0.2
7116 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)
7117 end
7118 }
7119 @@thread.priority = 4
7120 $_SERVER_ = @@socket # deprecated
7121 end
7122 def Game.thread
7123 @@thread
7124 end
7125 def Game.closed?
7126 if @@socket.nil?
7127 true
7128 else
7129 @@socket.closed?
7130 end
7131 end
7132 def Game.close
7133 if @@socket
7134 @@socket.close rescue nil
7135 @@thread.kill rescue nil
7136 end
7137 end
7138 def Game._puts(str)
7139 @@mutex.synchronize {
7140 @@socket.puts(str)
7141 }
7142 end
7143 def Game.puts(str)
7144 $_SCRIPTIDLETIMESTAMP_ = Time.now
7145 if script = Script.current
7146 script_name = script.name
7147 else
7148 script_name = '(unknown script)'
7149 end
7150 $_CLIENTBUFFER_.push "[#{script_name}]#{$SEND_CHARACTER}#{$cmd_prefix}#{str}\r\n"
7151 if script.nil? or not script.silent
7152 respond "[#{script_name}]#{$SEND_CHARACTER}#{str}\r\n"
7153 end
7154 Game._puts "#{$cmd_prefix}#{str}"
7155 $_LASTUPSTREAM_ = "[#{script_name}]#{$SEND_CHARACTER}#{str}"
7156 end
7157 def Game.gets
7158 @@buffer.gets
7159 end
7160 def Game.buffer
7161 @@buffer
7162 end
7163 def Game._gets
7164 @@_buffer.gets
7165 end
7166 def Game._buffer
7167 @@_buffer
7168 end
7169 end
7170 class Char
7171 @@name ||= nil
7172 @@citizenship ||= nil
7173 private_class_method :new
7174 def Char.init(blah)
7175 echo 'Char.init is no longer used. Update or fix your script.'
7176 end
7177 def Char.name
7178 XMLData.name
7179 end
7180 def Char.name=(name)
7181 nil
7182 end
7183 def Char.health(*args)
7184 health(*args)
7185 end
7186 def Char.mana(*args)
7187 checkmana(*args)
7188 end
7189 def Char.spirit(*args)
7190 checkspirit(*args)
7191 end
7192 def Char.maxhealth
7193 Object.module_eval { maxhealth }
7194 end
7195 def Char.maxmana
7196 Object.module_eval { maxmana }
7197 end
7198 def Char.maxspirit
7199 Object.module_eval { maxspirit }
7200 end
7201 def Char.stamina(*args)
7202 checkstamina(*args)
7203 end
7204 def Char.maxstamina
7205 Object.module_eval { maxstamina }
7206 end
7207 def Char.cha(val=nil)
7208 nil
7209 end
7210 def Char.dump_info
7211 Marshal.dump([
7212 Spell.detailed?,
7213 Spell.serialize,
7214 Spellsong.serialize,
7215 Stats.serialize,
7216 Skills.serialize,
7217 Spells.serialize,
7218 Gift.serialize,
7219 Society.serialize,
7220 ])
7221 end
7222 def Char.load_info(string)
7223 save = Char.dump_info
7224 begin
7225 Spell.load_detailed,
7226 Spell.load_active,
7227 Spellsong.load_serialized,
7228 Stats.load_serialized,
7229 Skills.load_serialized,
7230 Spells.load_serialized,
7231 Gift.load_serialized,
7232 Society.load_serialized = Marshal.load(string)
7233 rescue
7234 raise $! if string == save
7235 string = save
7236 retry
7237 end
7238 end
7239 def Char.method_missing(meth, *args)
7240 [ Stats, Skills, Spellsong, Society ].each { |klass|
7241 begin
7242 result = klass.__send__(meth, *args)
7243 return result
7244 rescue
7245 end
7246 }
7247 respond 'missing method: ' + meth
7248 raise NoMethodError
7249 end
7250 def Char.info
7251 ary = []
7252 ary.push sprintf("Name: %s Race: %s Profession: %s", XMLData.name, Stats.race, Stats.prof)
7253 ary.push sprintf("Gender: %s Age: %d Expr: %d Level: %d", Stats.gender, Stats.age, Stats.exp, Stats.level)
7254 ary.push sprintf("%017.17s Normal (Bonus) ... Enhanced (Bonus)", "")
7255 %w[ Strength Constitution Dexterity Agility Discipline Aura Logic Intuition Wisdom Influence ].each { |stat|
7256 val, bon = Stats.send(stat[0..2].downcase)
7257 spc = " " * (4 - bon.to_s.length)
7258 ary.push sprintf("%012s (%s): %05s (%d) %s ... %05s (%d)", stat, stat[0..2].upcase, val, bon, spc, val, bon)
7259 }
7260 ary.push sprintf("Mana: %04s", mana)
7261 ary
7262 end
7263 def Char.skills
7264 ary = []
7265 ary.push sprintf("%s (at level %d), your current skill bonuses and ranks (including all modifiers) are:", XMLData.name, Stats.level)
7266 ary.push sprintf(" %-035s| Current Current", 'Skill Name')
7267 ary.push sprintf(" %-035s|%08s%08s", '', 'Bonus', 'Ranks')
7268 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' ] ]
7269 0.upto(fmt.first.length - 1) { |n|
7270 dots = '.' * (35 - fmt[0][n].length)
7271 rnk = Skills.send(fmt[1][n])
7272 ary.push sprintf(" %s%s|%08s%08s", fmt[0][n], dots, Skills.to_bonus(rnk), rnk) unless rnk.zero?
7273 }
7274 %[Minor Elemental,Major Elemental,Minor Spirit,Major Spirit,Minor Mental,Bard,Cleric,Empath,Paladin,Ranger,Sorcerer,Wizard].split(',').each { |circ|
7275 rnk = Spells.send(circ.gsub(" ", '').downcase)
7276 if rnk.nonzero?
7277 ary.push ''
7278 ary.push "Spell Lists"
7279 dots = '.' * (35 - circ.length)
7280 ary.push sprintf(" %s%s|%016s", circ, dots, rnk)
7281 end
7282 }
7283 ary
7284 end
7285 def Char.citizenship
7286 @@citizenship
7287 end
7288 def Char.citizenship=(val)
7289 @@citizenship = val.to_s
7290 end
7291 end
7292
7293 class Society
7294 @@status ||= String.new
7295 @@rank ||= 0
7296 def Society.serialize
7297 [@@status,@@rank]
7298 end
7299 def Society.load_serialized=(val)
7300 @@status,@@rank = val
7301 end
7302 def Society.status=(val)
7303 @@status = val
7304 end
7305 def Society.status
7306 @@status.dup
7307 end
7308 def Society.rank=(val)
7309 if val =~ /Master/
7310 if @@status =~ /Voln/
7311 @@rank = 26
7312 elsif @@status =~ /Council of Light|Guardians of Sunfist/
7313 @@rank = 20
7314 else
7315 @@rank = val.to_i
7316 end
7317 else
7318 @@rank = val.slice(/[0-9]+/).to_i
7319 end
7320 end
7321 def Society.step
7322 @@rank
7323 end
7324 def Society.member
7325 @@status.dup
7326 end
7327 def Society.rank
7328 @@rank
7329 end
7330 def Society.task
7331 XMLData.society_task
7332 end
7333 end
7334
7335 class Spellsong
7336 @@renewed ||= Time.at(Time.now.to_i - 1200)
7337 def Spellsong.renewed
7338 @@renewed = Time.now
7339 end
7340 def Spellsong.renewed=(val)
7341 @@renewed = val
7342 end
7343 def Spellsong.renewed_at
7344 @@renewed
7345 end
7346 def Spellsong.timeleft
7347 (Spellsong.duration - ((Time.now - @@renewed) % Spellsong.duration)) / 60.to_f
7348 end
7349 def Spellsong.serialize
7350 Spellsong.timeleft
7351 end
7352 def Spellsong.load_serialized=(old)
7353 Thread.new {
7354 n = 0
7355 while Stats.level == 0
7356 sleep 0.25
7357 n += 1
7358 break if n >= 4
7359 end
7360 unless n >= 4
7361 @@renewed = Time.at(Time.now.to_f - (Spellsong.duration - old * 60.to_f))
7362 else
7363 @@renewed = Time.now
7364 end
7365 }
7366 nil
7367 end
7368 def Spellsong.duration
7369 total = 120
7370 1.upto(Stats.level.to_i) { |n|
7371 if n < 26
7372 total += 4
7373 elsif n < 51
7374 total += 3
7375 elsif n < 76
7376 total += 2
7377 else
7378 total += 1
7379 end
7380 }
7381 total + Stats.log[1].to_i + (Stats.inf[1].to_i * 3) + (Skills.mltelepathy.to_i * 2)
7382 end
7383 def Spellsong.renew_cost
7384 # fixme: multi-spell penalty?
7385 total = num_active = 0
7386 [ 1003, 1006, 1009, 1010, 1012, 1014, 1018, 1019, 1025 ].each { |song_num|
7387 if song = Spell[song_num]
7388 if song.active?
7389 total += song.renew_cost
7390 num_active += 1
7391 end
7392 else
7393 echo "Spellsong.renew_cost: warning: can't find song number #{song_num}"
7394 end
7395 }
7396 return total
7397 end
7398 def Spellsong.sonicarmordurability
7399 210 + (Stats.level / 2).round + Skills.to_bonus(Skills.elair)
7400 end
7401 def Spellsong.sonicbladedurability
7402 160 + (Stats.level / 2).round + Skills.to_bonus(Skills.elair)
7403 end
7404 def Spellsong.sonicweapondurability
7405 Spellsong.sonicbladedurability
7406 end
7407 def Spellsong.sonicshielddurability
7408 125 + (Stats.level / 2).round + Skills.to_bonus(Skills.elair)
7409 end
7410 def Spellsong.tonishastebonus
7411 bonus = -1
7412 thresholds = [30,75]
7413 thresholds.each { |val| if Skills.elair >= val then bonus -= 1 end }
7414 bonus
7415 end
7416 def Spellsong.depressionpushdown
7417 20 + Skills.mltelepathy
7418 end
7419 def Spellsong.depressionslow
7420 thresholds = [10,25,45,70,100]
7421 bonus = -2
7422 thresholds.each { |val| if Skills.mltelepathy >= val then bonus -= 1 end }
7423 bonus
7424 end
7425 def Spellsong.holdingtargets
7426 1 + ((Spells.bard - 1) / 7).truncate
7427 end
7428 end
7429
7430 class Skills
7431 @@twoweaponcombat ||= 0
7432 @@armoruse ||= 0
7433 @@shielduse ||= 0
7434 @@combatmaneuvers ||= 0
7435 @@edgedweapons ||= 0
7436 @@bluntweapons ||= 0
7437 @@twohandedweapons ||= 0
7438 @@rangedweapons ||= 0
7439 @@thrownweapons ||= 0
7440 @@polearmweapons ||= 0
7441 @@brawling ||= 0
7442 @@ambush ||= 0
7443 @@multiopponentcombat ||= 0
7444 @@combatleadership ||= 0
7445 @@physicalfitness ||= 0
7446 @@dodging ||= 0
7447 @@arcanesymbols ||= 0
7448 @@magicitemuse ||= 0
7449 @@spellaiming ||= 0
7450 @@harnesspower ||= 0
7451 @@emc ||= 0
7452 @@mmc ||= 0
7453 @@smc ||= 0
7454 @@elair ||= 0
7455 @@elearth ||= 0
7456 @@elfire ||= 0
7457 @@elwater ||= 0
7458 @@slblessings ||= 0
7459 @@slreligion ||= 0
7460 @@slsummoning ||= 0
7461 @@sldemonology ||= 0
7462 @@slnecromancy ||= 0
7463 @@mldivination ||= 0
7464 @@mlmanipulation ||= 0
7465 @@mltelepathy ||= 0
7466 @@mltransference ||= 0
7467 @@mltransformation ||= 0
7468 @@survival ||= 0
7469 @@disarmingtraps ||= 0
7470 @@pickinglocks ||= 0
7471 @@stalkingandhiding ||= 0
7472 @@perception ||= 0
7473 @@climbing ||= 0
7474 @@swimming ||= 0
7475 @@firstaid ||= 0
7476 @@trading ||= 0
7477 @@pickpocketing ||= 0
7478
7479 def Skills.twoweaponcombat; @@twoweaponcombat; end
7480 def Skills.twoweaponcombat=(val); @@twoweaponcombat=val; end
7481 def Skills.armoruse; @@armoruse; end
7482 def Skills.armoruse=(val); @@armoruse=val; end
7483 def Skills.shielduse; @@shielduse; end
7484 def Skills.shielduse=(val); @@shielduse=val; end
7485 def Skills.combatmaneuvers; @@combatmaneuvers; end
7486 def Skills.combatmaneuvers=(val); @@combatmaneuvers=val; end
7487 def Skills.edgedweapons; @@edgedweapons; end
7488 def Skills.edgedweapons=(val); @@edgedweapons=val; end
7489 def Skills.bluntweapons; @@bluntweapons; end
7490 def Skills.bluntweapons=(val); @@bluntweapons=val; end
7491 def Skills.twohandedweapons; @@twohandedweapons; end
7492 def Skills.twohandedweapons=(val); @@twohandedweapons=val; end
7493 def Skills.rangedweapons; @@rangedweapons; end
7494 def Skills.rangedweapons=(val); @@rangedweapons=val; end
7495 def Skills.thrownweapons; @@thrownweapons; end
7496 def Skills.thrownweapons=(val); @@thrownweapons=val; end
7497 def Skills.polearmweapons; @@polearmweapons; end
7498 def Skills.polearmweapons=(val); @@polearmweapons=val; end
7499 def Skills.brawling; @@brawling; end
7500 def Skills.brawling=(val); @@brawling=val; end
7501 def Skills.ambush; @@ambush; end
7502 def Skills.ambush=(val); @@ambush=val; end
7503 def Skills.multiopponentcombat; @@multiopponentcombat; end
7504 def Skills.multiopponentcombat=(val); @@multiopponentcombat=val; end
7505 def Skills.combatleadership; @@combatleadership; end
7506 def Skills.combatleadership=(val); @@combatleadership=val; end
7507 def Skills.physicalfitness; @@physicalfitness; end
7508 def Skills.physicalfitness=(val); @@physicalfitness=val; end
7509 def Skills.dodging; @@dodging; end
7510 def Skills.dodging=(val); @@dodging=val; end
7511 def Skills.arcanesymbols; @@arcanesymbols; end
7512 def Skills.arcanesymbols=(val); @@arcanesymbols=val; end
7513 def Skills.magicitemuse; @@magicitemuse; end
7514 def Skills.magicitemuse=(val); @@magicitemuse=val; end
7515 def Skills.spellaiming; @@spellaiming; end
7516 def Skills.spellaiming=(val); @@spellaiming=val; end
7517 def Skills.harnesspower; @@harnesspower; end
7518 def Skills.harnesspower=(val); @@harnesspower=val; end
7519 def Skills.emc; @@emc; end
7520 def Skills.emc=(val); @@emc=val; end
7521 def Skills.mmc; @@mmc; end
7522 def Skills.mmc=(val); @@mmc=val; end
7523 def Skills.smc; @@smc; end
7524 def Skills.smc=(val); @@smc=val; end
7525 def Skills.elair; @@elair; end
7526 def Skills.elair=(val); @@elair=val; end
7527 def Skills.elearth; @@elearth; end
7528 def Skills.elearth=(val); @@elearth=val; end
7529 def Skills.elfire; @@elfire; end
7530 def Skills.elfire=(val); @@elfire=val; end
7531 def Skills.elwater; @@elwater; end
7532 def Skills.elwater=(val); @@elwater=val; end
7533 def Skills.slblessings; @@slblessings; end
7534 def Skills.slblessings=(val); @@slblessings=val; end
7535 def Skills.slreligion; @@slreligion; end
7536 def Skills.slreligion=(val); @@slreligion=val; end
7537 def Skills.slsummoning; @@slsummoning; end
7538 def Skills.slsummoning=(val); @@slsummoning=val; end
7539 def Skills.sldemonology; @@sldemonology; end
7540 def Skills.sldemonology=(val); @@sldemonology=val; end
7541 def Skills.slnecromancy; @@slnecromancy; end
7542 def Skills.slnecromancy=(val); @@slnecromancy=val; end
7543 def Skills.mldivination; @@mldivination; end
7544 def Skills.mldivination=(val); @@mldivination=val; end
7545 def Skills.mlmanipulation; @@mlmanipulation; end
7546 def Skills.mlmanipulation=(val); @@mlmanipulation=val; end
7547 def Skills.mltelepathy; @@mltelepathy; end
7548 def Skills.mltelepathy=(val); @@mltelepathy=val; end
7549 def Skills.mltransference; @@mltransference; end
7550 def Skills.mltransference=(val); @@mltransference=val; end
7551 def Skills.mltransformation; @@mltransformation; end
7552 def Skills.mltransformation=(val); @@mltransformation=val; end
7553 def Skills.survival; @@survival; end
7554 def Skills.survival=(val); @@survival=val; end
7555 def Skills.disarmingtraps; @@disarmingtraps; end
7556 def Skills.disarmingtraps=(val); @@disarmingtraps=val; end
7557 def Skills.pickinglocks; @@pickinglocks; end
7558 def Skills.pickinglocks=(val); @@pickinglocks=val; end
7559 def Skills.stalkingandhiding; @@stalkingandhiding; end
7560 def Skills.stalkingandhiding=(val); @@stalkingandhiding=val; end
7561 def Skills.perception; @@perception; end
7562 def Skills.perception=(val); @@perception=val; end
7563 def Skills.climbing; @@climbing; end
7564 def Skills.climbing=(val); @@climbing=val; end
7565 def Skills.swimming; @@swimming; end
7566 def Skills.swimming=(val); @@swimming=val; end
7567 def Skills.firstaid; @@firstaid; end
7568 def Skills.firstaid=(val); @@firstaid=val; end
7569 def Skills.trading; @@trading; end
7570 def Skills.trading=(val); @@trading=val; end
7571 def Skills.pickpocketing; @@pickpocketing; end
7572 def Skills.pickpocketing=(val); @@pickpocketing=val; end
7573
7574 def Skills.serialize
7575 [@@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]
7576 end
7577 def Skills.load_serialized=(array)
7578 @@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
7579 end
7580 def Skills.to_bonus(ranks)
7581 bonus = 0
7582 while ranks > 0
7583 if ranks > 40
7584 bonus += (ranks - 40)
7585 ranks = 40
7586 elsif ranks > 30
7587 bonus += (ranks - 30) * 2
7588 ranks = 30
7589 elsif ranks > 20
7590 bonus += (ranks - 20) * 3
7591 ranks = 20
7592 elsif ranks > 10
7593 bonus += (ranks - 10) * 4
7594 ranks = 10
7595 else
7596 bonus += (ranks * 5)
7597 ranks = 0
7598 end
7599 end
7600 bonus
7601 end
7602 end
7603
7604 class Spells
7605 @@minorelemental ||= 0
7606 @@minormental ||= 0
7607 @@majorelemental ||= 0
7608 @@minorspiritual ||= 0
7609 @@majorspiritual ||= 0
7610 @@wizard ||= 0
7611 @@sorcerer ||= 0
7612 @@ranger ||= 0
7613 @@paladin ||= 0
7614 @@empath ||= 0
7615 @@cleric ||= 0
7616 @@bard ||= 0
7617 def Spells.minorelemental=(val); @@minorelemental = val; end
7618 def Spells.minorelemental; @@minorelemental; end
7619 def Spells.minormental=(val); @@minormental = val; end
7620 def Spells.minormental; @@minormental; end
7621 def Spells.majorelemental=(val); @@majorelemental = val; end
7622 def Spells.majorelemental; @@majorelemental; end
7623 def Spells.minorspiritual=(val); @@minorspiritual = val; end
7624 def Spells.minorspiritual; @@minorspiritual; end
7625 def Spells.minorspirit=(val); @@minorspiritual = val; end
7626 def Spells.minorspirit; @@minorspiritual; end
7627 def Spells.majorspiritual=(val); @@majorspiritual = val; end
7628 def Spells.majorspiritual; @@majorspiritual; end
7629 def Spells.majorspirit=(val); @@majorspiritual = val; end
7630 def Spells.majorspirit; @@majorspiritual; end
7631 def Spells.wizard=(val); @@wizard = val; end
7632 def Spells.wizard; @@wizard; end
7633 def Spells.sorcerer=(val); @@sorcerer = val; end
7634 def Spells.sorcerer; @@sorcerer; end
7635 def Spells.ranger=(val); @@ranger = val; end
7636 def Spells.ranger; @@ranger; end
7637 def Spells.paladin=(val); @@paladin = val; end
7638 def Spells.paladin; @@paladin; end
7639 def Spells.empath=(val); @@empath = val; end
7640 def Spells.empath; @@empath; end
7641 def Spells.cleric=(val); @@cleric = val; end
7642 def Spells.cleric; @@cleric; end
7643 def Spells.bard=(val); @@bard = val; end
7644 def Spells.bard; @@bard; end
7645 def Spells.get_circle_name(num)
7646 val = num.to_s
7647 if val == '1'
7648 'Minor Spirit'
7649 elsif val == '2'
7650 'Major Spirit'
7651 elsif val == '3'
7652 'Cleric'
7653 elsif val == '4'
7654 'Minor Elemental'
7655 elsif val == '5'
7656 'Major Elemental'
7657 elsif val == '6'
7658 'Ranger'
7659 elsif val == '7'
7660 'Sorcerer'
7661 elsif val == '9'
7662 'Wizard'
7663 elsif val == '10'
7664 'Bard'
7665 elsif val == '11'
7666 'Empath'
7667 elsif val == '12'
7668 'Minor Mental'
7669 elsif val == '16'
7670 'Paladin'
7671 elsif val == '17'
7672 'Arcane'
7673 elsif val == '66'
7674 'Death'
7675 elsif val == '65'
7676 'Imbedded Enchantment'
7677 elsif val == '90'
7678 'Miscellaneous'
7679 elsif val == '95'
7680 'Armor Specialization'
7681 elsif val == '96'
7682 'Combat Maneuvers'
7683 elsif val == '97'
7684 'Guardians of Sunfist'
7685 elsif val == '98'
7686 'Order of Voln'
7687 elsif val == '99'
7688 'Council of Light'
7689 else
7690 'Unknown Circle'
7691 end
7692 end
7693 def Spells.active
7694 Spell.active
7695 end
7696 def Spells.known
7697 known_spells = Array.new
7698 Spell.list.each { |spell| known_spells.push(spell) if spell.known? }
7699 return known_spells
7700 end
7701 def Spells.serialize
7702 [@@minorelemental,@@majorelemental,@@minorspiritual,@@majorspiritual,@@wizard,@@sorcerer,@@ranger,@@paladin,@@empath,@@cleric,@@bard,@@minormental]
7703 end
7704 def Spells.load_serialized=(val)
7705 @@minorelemental,@@majorelemental,@@minorspiritual,@@majorspiritual,@@wizard,@@sorcerer,@@ranger,@@paladin,@@empath,@@cleric,@@bard,@@minormental = val
7706 # new spell circle added 2012-07-18; old data files will make @@minormental nil
7707 @@minormental ||= 0
7708 end
7709 end
7710
7711 class Spell
7712 @@list ||= Array.new
7713 @@loaded ||= false
7714 @@cast_lock ||= Array.new
7715 @@bonus_list ||= Array.new
7716 @@cost_list ||= Array.new
7717 @@load_mutex = Mutex.new
7718 @@elevated_load = proc { Spell.load }
7719 @@after_stance = nil
7720 attr_reader :num, :name, :timestamp, :msgup, :msgdn, :circle, :active, :type, :cast_proc, :real_time, :persist_on_death, :availability, :no_incant
7721 attr_accessor :stance, :channel
7722 def initialize(xml_spell)
7723 @num = xml_spell.attributes['number'].to_i
7724 @name = xml_spell.attributes['name']
7725 @type = xml_spell.attributes['type']
7726 @no_incant = ((xml_spell.attributes['incant'] == 'no') ? true : false)
7727 if xml_spell.attributes['availability'] == 'all'
7728 @availability = 'all'
7729 elsif xml_spell.attributes['availability'] == 'group'
7730 @availability = 'group'
7731 else
7732 @availability = 'self-cast'
7733 end
7734 @bonus = Hash.new
7735 xml_spell.elements.find_all { |e| e.name == 'bonus' }.each { |e|
7736 @bonus[e.attributes['type']] = e.text
7737 @bonus[e.attributes['type']].untaint
7738 }
7739 @msgup = xml_spell.elements.find_all { |e| (e.name == 'message') and (e.attributes['type'].downcase == 'start') }.collect { |e| e.text }.join('$|^')
7740 @msgup = nil if @msgup.empty?
7741 @msgdn = xml_spell.elements.find_all { |e| (e.name == 'message') and (e.attributes['type'].downcase == 'end') }.collect { |e| e.text }.join('$|^')
7742 @msgdn = nil if @msgdn.empty?
7743 @stance = ((xml_spell.attributes['stance'] =~ /^(yes|true)$/i) ? true : false)
7744 @channel = ((xml_spell.attributes['channel'] =~ /^(yes|true)$/i) ? true : false)
7745 @cost = Hash.new
7746 xml_spell.elements.find_all { |e| e.name == 'cost' }.each { |xml_cost|
7747 @cost[xml_cost.attributes['type'].downcase] ||= Hash.new
7748 if xml_cost.attributes['cast-type'].downcase == 'target'
7749 @cost[xml_cost.attributes['type'].downcase]['target'] = xml_cost.text
7750 else
7751 @cost[xml_cost.attributes['type'].downcase]['self'] = xml_cost.text
7752 end
7753 }
7754 @duration = Hash.new
7755 xml_spell.elements.find_all { |e| e.name == 'duration' }.each { |xml_duration|
7756 if xml_duration.attributes['cast-type'].downcase == 'target'
7757 cast_type = 'target'
7758 else
7759 cast_type = 'self'
7760 if xml_duration.attributes['real-time'] =~ /^(yes|true)$/i
7761 @real_time = true
7762 else
7763 @real_time = false
7764 end
7765 end
7766 @duration[cast_type] = Hash.new
7767 @duration[cast_type][:duration] = xml_duration.text
7768 @duration[cast_type][:stackable] = (xml_duration.attributes['span'].downcase == 'stackable')
7769 @duration[cast_type][:refreshable] = (xml_duration.attributes['span'].downcase == 'refreshable')
7770 if xml_duration.attributes['multicastable'] =~ /^(yes|true)$/i
7771 @duration[cast_type][:multicastable] = true
7772 else
7773 @duration[cast_type][:multicastable] = false
7774 end
7775 if xml_duration.attributes['persist-on-death'] =~ /^(yes|true)$/i
7776 @persist_on_death = true
7777 else
7778 @persist_on_death = false
7779 end
7780 if xml_duration.attributes['max']
7781 @duration[cast_type][:max_duration] = xml_duration.attributes['max'].to_f
7782 else
7783 @duration[cast_type][:max_duration] = 250.0
7784 end
7785 }
7786 @cast_proc = xml_spell.elements['cast-proc'].text
7787 @cast_proc.untaint
7788 @timestamp = Time.now
7789 @timeleft = 0
7790 @active = false
7791 @circle = (num.to_s.length == 3 ? num.to_s[0..0] : num.to_s[0..1])
7792 @@list.push(self) unless @@list.find { |spell| spell.num == @num }
7793 self
7794 end
7795 def Spell.after_stance=(val)
7796 @@after_stance = val
7797 end
7798 def Spell.load(filename=nil)
7799 if $SAFE == 0
7800 if filename.nil?
7801 if File.exists?("#{DATA_DIR}/spell-list.xml")
7802 filename = "#{DATA_DIR}/spell-list.xml"
7803 elsif File.exists?("#{SCRIPT_DIR}/spell-list.xml") # deprecated
7804 filename = "#{SCRIPT_DIR}/spell-list.xml"
7805 else
7806 filename = "#{DATA_DIR}/spell-list.xml"
7807 end
7808 end
7809 script = Script.current
7810 @@load_mutex.synchronize {
7811 return true if @loaded
7812 begin
7813 spell_times = Hash.new
7814 # reloading spell data should not reset spell tracking...
7815 unless @@list.empty?
7816 @@list.each { |spell| spell_times[spell.num] = spell.timeleft if spell.active? }
7817 @@list.clear
7818 end
7819 File.open(filename) { |file|
7820 xml_doc = REXML::Document.new(file)
7821 xml_root = xml_doc.root
7822 xml_root.elements.each { |xml_spell| Spell.new(xml_spell) }
7823 }
7824 @@list.each { |spell|
7825 if spell_times[spell.num]
7826 spell.timeleft = spell_times[spell.num]
7827 spell.active = true
7828 end
7829 }
7830 @@bonus_list = @@list.collect { |spell| spell._bonus.keys }.flatten
7831 @@bonus_list = @@bonus_list | @@bonus_list
7832 @@cost_list = @@list.collect { |spell| spell._cost.keys }.flatten
7833 @@cost_list = @@cost_list | @@cost_list
7834 @@loaded = true
7835 return true
7836 rescue
7837 respond "--- Lich: error: Spell.load: #{$!}"
7838 Lich.log "error: Spell.load: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
7839 @@loaded = false
7840 return false
7841 end
7842 }
7843 else
7844 @@elevated_load.call
7845 end
7846 end
7847 def Spell.[](val)
7848 Spell.load unless @@loaded
7849 if val.class == Spell
7850 val
7851 elsif (val.class == Fixnum) or (val.class == String and val =~ /^[0-9]+$/)
7852 @@list.find { |spell| spell.num == val.to_i }
7853 else
7854 (@@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 })
7855 end
7856 end
7857 def Spell.active
7858 Spell.load unless @@loaded
7859 active = Array.new
7860 @@list.each { |spell| active.push(spell) if spell.active? }
7861 active
7862 end
7863 def Spell.active?(val)
7864 Spell.load unless @@loaded
7865 Spell[val].active?
7866 end
7867 def Spell.list
7868 Spell.load unless @@loaded
7869 @@list
7870 end
7871 def Spell.upmsgs
7872 Spell.load unless @@loaded
7873 @@list.collect { |spell| spell.msgup }.compact
7874 end
7875 def Spell.dnmsgs
7876 Spell.load unless @@loaded
7877 @@list.collect { |spell| spell.msgdn }.compact
7878 end
7879 def time_per_formula(options={})
7880 activator_modifier = { 'tap' => 0.5, 'rub' => 1, 'wave' => 1, 'raise' => 1.33, 'drink' => 0, 'bite' => 0, 'eat' => 0, 'gobble' => 0 }
7881 can_haz_spell_ranks = /Spells\.(?:minorelemental|majorelemental|minorspiritual|majorspiritual|wizard|sorcerer|ranger|paladin|empath|cleric|bard|minormental)/
7882 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' ]
7883 if options[:caster] and (options[:caster] !~ /^(?:self|#{XMLData.name})$/i)
7884 if options[:target] and (options[:target].downcase == options[:caster].downcase)
7885 formula = @duration['self'][:duration].to_s.dup
7886 else
7887 formula = @duration['target'][:duration].dup || @duration['self'][:duration].to_s.dup
7888 end
7889 if options[:activator] =~ /^(#{activator_modifier.keys.join('|')})$/i
7890 if formula =~ can_haz_spell_ranks
7891 skills.each { |skill_name| formula.gsub!(skill_name, "(SpellRanks['#{options[:caster]}'].magicitemuse * #{activator_modifier[options[:activator]]}).to_i") }
7892 formula = "(#{formula})/2.0"
7893 elsif formula =~ /Skills\.(?:magicitemuse|arancesymbols)/
7894 skills.each { |skill_name| formula.gsub!(skill_name, "(SpellRanks['#{options[:caster]}'].magicitemuse * #{activator_modifier[options[:activator]]}).to_i") }
7895 end
7896 elsif options[:activator] =~ /^(invoke|scroll)$/i
7897 if formula =~ can_haz_spell_ranks
7898 skills.each { |skill_name| formula.gsub!(skill_name, "SpellRanks['#{options[:caster]}'].arcanesymbols.to_i") }
7899 formula = "(#{formula})/2.0"
7900 elsif formula =~ /Skills\.(?:magicitemuse|arancesymbols)/
7901 skills.each { |skill_name| formula.gsub!(skill_name, "SpellRanks['#{options[:caster]}'].arcanesymbols.to_i") }
7902 end
7903 else
7904 skills.each { |skill_name| formula.gsub!(skill_name, "SpellRanks[#{options[:caster].to_s.inspect}].#{skill_name.sub(/^(?:Spells|Skills)\./, '')}.to_i") }
7905 end
7906 else
7907 if options[:target] and (options[:target] !~ /^(?:self|#{XMLData.name})$/i)
7908 formula = @duration['target'][:duration].dup || @duration['self'][:duration].to_s.dup
7909 else
7910 formula = @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, "(Skills.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, "(Skills.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, "Skills.arcanesymbols.to_i") }
7922 formula = "(#{formula})/2.0"
7923 elsif formula =~ /Skills\.(?:magicitemuse|arancesymbols)/
7924 skills.each { |skill_name| formula.gsub!(skill_name, "Skills.arcanesymbols.to_i") }
7925 end
7926 end
7927 end
7928 formula.untaint
7929 formula
7930 end
7931 def time_per(options={})
7932 formula = self.time_per_formula(options)
7933 if options[:line]
7934 line = options[:line]
7935 end
7936 if $SAFE < 3
7937 proc { $SAFE = 3; eval(formula) }.call.to_f
7938 else
7939 eval(formula).to_f
7940 end
7941 end
7942 def timeleft=(val)
7943 @timeleft = val
7944 @timestamp = Time.now
7945 end
7946 def timeleft
7947 if self.time_per_formula.to_s == 'Spellsong.timeleft'
7948 @timeleft = Spellsong.timeleft
7949 else
7950 @timeleft = @timeleft - ((Time.now - @timestamp) / 60.to_f)
7951 if @timeleft <= 0
7952 self.putdown
7953 return 0.to_f
7954 end
7955 end
7956 @timestamp = Time.now
7957 @timeleft
7958 end
7959 def minsleft
7960 self.timeleft
7961 end
7962 def secsleft
7963 self.timeleft * 60
7964 end
7965 def active=(val)
7966 @active = val
7967 end
7968 def active?
7969 (self.timeleft > 0) and @active
7970 end
7971 def stackable?(options={})
7972 if options[:caster] and (options[:caster] !~ /^(?:self|#{XMLData.name})$/i)
7973 if options[:target] and (options[:target].downcase == options[:caster].downcase)
7974 @duration['self'][:stackable]
7975 else
7976 if @duration['target'][:stackable].nil?
7977 @duration['self'][:stackable]
7978 else
7979 @duration['target'][:stackable]
7980 end
7981 end
7982 else
7983 if options[:target] and (options[:target] !~ /^(?:self|#{XMLData.name})$/i)
7984 if @duration['target'][:stackable].nil?
7985 @duration['self'][:stackable]
7986 else
7987 @duration['target'][:stackable]
7988 end
7989 else
7990 @duration['self'][:stackable]
7991 end
7992 end
7993 end
7994 def refreshable?(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'][:refreshable]
7998 else
7999 if @duration['target'][:refreshable].nil?
8000 @duration['self'][:refreshable]
8001 else
8002 @duration['target'][:refreshable]
8003 end
8004 end
8005 else
8006 if options[:target] and (options[:target] !~ /^(?:self|#{XMLData.name})$/i)
8007 if @duration['target'][:refreshable].nil?
8008 @duration['self'][:refreshable]
8009 else
8010 @duration['target'][:refreshable]
8011 end
8012 else
8013 @duration['self'][:refreshable]
8014 end
8015 end
8016 end
8017 def multicastable?(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'][:multicastable]
8021 else
8022 if @duration['target'][:multicastable].nil?
8023 @duration['self'][:multicastable]
8024 else
8025 @duration['target'][:multicastable]
8026 end
8027 end
8028 else
8029 if options[:target] and (options[:target] !~ /^(?:self|#{XMLData.name})$/i)
8030 if @duration['target'][:multicastable].nil?
8031 @duration['self'][:multicastable]
8032 else
8033 @duration['target'][:multicastable]
8034 end
8035 else
8036 @duration['self'][:multicastable]
8037 end
8038 end
8039 end
8040 def known?
8041 if @num.to_s.length == 3
8042 circle_num = @num.to_s[0..0].to_i
8043 elsif @num.to_s.length == 4
8044 circle_num = @num.to_s[0..1].to_i
8045 else
8046 return false
8047 end
8048 if circle_num == 1
8049 ranks = [ Spells.minorspiritual, XMLData.level ].min
8050 elsif circle_num == 2
8051 ranks = [ Spells.majorspiritual, XMLData.level ].min
8052 elsif circle_num == 3
8053 ranks = [ Spells.cleric, XMLData.level ].min
8054 elsif circle_num == 4
8055 ranks = [ Spells.minorelemental, XMLData.level ].min
8056 elsif circle_num == 5
8057 ranks = [ Spells.majorelemental, XMLData.level ].min
8058 elsif circle_num == 6
8059 ranks = [ Spells.ranger, XMLData.level ].min
8060 elsif circle_num == 7
8061 ranks = [ Spells.sorcerer, XMLData.level ].min
8062 elsif circle_num == 9
8063 ranks = [ Spells.wizard, XMLData.level ].min
8064 elsif circle_num == 10
8065 ranks = [ Spells.bard, XMLData.level ].min
8066 elsif circle_num == 11
8067 ranks = [ Spells.empath, XMLData.level ].min
8068 elsif circle_num == 12
8069 ranks = [ Spells.minormental, XMLData.level ].min
8070 elsif circle_num == 16
8071 ranks = [ Spells.paladin, XMLData.level ].min
8072 elsif circle_num == 17
8073 if (@num == 1700) and (Char.prof =~ /^(?:Wizard|Cleric|Empath|Sorcerer|Savant)$/)
8074 return true
8075 else
8076 return false
8077 end
8078 elsif (circle_num == 97) and (Society.status == 'Guardians of Sunfist')
8079 ranks = Society.rank
8080 elsif (circle_num == 98) and (Society.status == 'Order of Voln')
8081 ranks = Society.rank
8082 elsif (circle_num == 99) and (Society.status == 'Council of Light')
8083 ranks = Society.rank
8084 elsif (circle_num == 96)
8085 if CMan[@name].to_i > 0
8086 return true
8087 else
8088 return false
8089 end
8090 else
8091 return false
8092 end
8093 if (@num % 100) <= ranks
8094 return true
8095 else
8096 return false
8097 end
8098 end
8099 def available?(options={})
8100 if self.known?
8101 if options[:caster] and (options[:caster] !~ /^(?:self|#{XMLData.name})$/i)
8102 if options[:target] and (options[:target].downcase == options[:caster].downcase)
8103 true
8104 else
8105 @availability == 'all'
8106 end
8107 else
8108 if options[:target] and (options[:target] !~ /^(?:self|#{XMLData.name})$/i)
8109 @availability == 'all'
8110 else
8111 true
8112 end
8113 end
8114 else
8115 false
8116 end
8117 end
8118 def to_s
8119 @name.to_s
8120 end
8121 def max_duration(options={})
8122 if options[:caster] and (options[:caster] !~ /^(?:self|#{XMLData.name})$/i)
8123 if options[:target] and (options[:target].downcase == options[:caster].downcase)
8124 @duration['self'][:max_duration]
8125 else
8126 @duration['target'][:max_duration] || @duration['self'][:max_duration]
8127 end
8128 else
8129 if options[:target] and (options[:target] !~ /^(?:self|#{XMLData.name})$/i)
8130 @duration['target'][:max_duration] || @duration['self'][:max_duration]
8131 else
8132 @duration['self'][:max_duration]
8133 end
8134 end
8135 end
8136 def putup(options={})
8137 if stackable?(options)
8138 self.timeleft = [ self.timeleft + self.time_per(options), self.max_duration(options) ].min
8139 else
8140 self.timeleft = [ self.time_per(options), self.max_duration(options) ].min
8141 end
8142 @active = true
8143 end
8144 def putdown
8145 self.timeleft = 0
8146 @active = false
8147 end
8148 def remaining
8149 self.timeleft.as_time
8150 end
8151 def affordable?(options={})
8152 # fixme: deal with them dirty bards!
8153 release_options = options.dup
8154 release_options[:multicast] = nil
8155 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)) )
8156 false
8157 elsif (self.stamina_cost(options) > 0) and (Spell[9699].active? or not checkstamina(self.stamina_cost(options)))
8158 false
8159 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)
8160 false
8161 else
8162 true
8163 end
8164 end
8165 def Spell.lock_cast
8166 script = Script.current
8167 @@cast_lock.push(script)
8168 until (@@cast_lock.first == script) or @@cast_lock.empty?
8169 sleep 0.1
8170 Script.current # allows this loop to be paused
8171 @@cast_lock.delete_if { |s| s.paused or not Script.list.include?(s) }
8172 end
8173 end
8174 def Spell.unlock_cast
8175 @@cast_lock.delete(Script.current)
8176 end
8177 def cast(target=nil, results_of_interest=nil)
8178 # fixme: find multicast in target and check mana for it
8179 script = Script.current
8180 if @type.nil?
8181 echo "cast: spell missing type (#{@name})"
8182 sleep 0.1
8183 return false
8184 end
8185 unless (self.mana_cost <= 0) or checkmana(self.mana_cost)
8186 echo 'cast: not enough mana'
8187 sleep 0.1
8188 return false
8189 end
8190 unless (self.spirit_cost > 0) or checkspirit(self.spirit_cost + 1 + [ 9912, 9913, 9914, 9916, 9916, 9916 ].delete_if { |num| !Spell[num].active? }.length)
8191 echo 'cast: not enough spirit'
8192 sleep 0.1
8193 return false
8194 end
8195 unless (self.stamina_cost <= 0) or checkstamina(self.stamina_cost)
8196 echo 'cast: not enough stamina'
8197 sleep 0.1
8198 return false
8199 end
8200 begin
8201 save_want_downstream = script.want_downstream
8202 save_want_downstream_xml = script.want_downstream_xml
8203 script.want_downstream = true
8204 script.want_downstream_xml = false
8205 @@cast_lock.push(script)
8206 until (@@cast_lock.first == script) or @@cast_lock.empty?
8207 sleep 0.1
8208 Script.current # allows this loop to be paused
8209 @@cast_lock.delete_if { |s| s.paused or not Script.list.include?(s) }
8210 end
8211 unless (self.mana_cost <= 0) or checkmana(self.mana_cost)
8212 echo 'cast: not enough mana'
8213 sleep 0.1
8214 return false
8215 end
8216 unless (self.spirit_cost > 0) or checkspirit(self.spirit_cost + 1 + [ 9912, 9913, 9914, 9916, 9916, 9916 ].delete_if { |num| !Spell[num].active? }.length)
8217 echo 'cast: not enough spirit'
8218 sleep 0.1
8219 return false
8220 end
8221 unless (self.stamina_cost <= 0) or checkstamina(self.stamina_cost)
8222 echo 'cast: not enough stamina'
8223 sleep 0.1
8224 return false
8225 end
8226 if @cast_proc
8227 waitrt?
8228 waitcastrt?
8229 unless (self.mana_cost <= 0) or checkmana(self.mana_cost)
8230 echo 'cast: not enough mana'
8231 sleep 0.1
8232 return false
8233 end
8234 unless (self.spirit_cost > 0) or checkspirit(self.spirit_cost + 1 + [ 9912, 9913, 9914, 9916, 9916, 9916 ].delete_if { |num| !Spell[num].active? }.length)
8235 echo 'cast: not enough spirit'
8236 sleep 0.1
8237 return false
8238 end
8239 unless (self.stamina_cost <= 0) or checkstamina(self.stamina_cost)
8240 echo 'cast: not enough stamina'
8241 sleep 0.1
8242 return false
8243 end
8244 begin
8245 if $SAFE < 3
8246 proc { $SAFE = 3; eval(@cast_proc) }.call
8247 else
8248 eval(@cast_proc)
8249 end
8250 rescue
8251 echo "cast: error: #{$!}"
8252 respond $!.backtrace[0..2]
8253 return false
8254 end
8255 else
8256 if @channel
8257 cast_cmd = 'channel'
8258 else
8259 cast_cmd = 'cast'
8260 end
8261 if (target.nil? or target.to_s.empty?) and not @no_incant
8262 cast_cmd = "incant #{@num}"
8263 elsif (target.nil? or target.to_s.empty?) and (@type =~ /attack/i) and not [410,435,525,912,909,609].include?(@num)
8264 cast_cmd += ' target'
8265 elsif target.class == GameObj
8266 cast_cmd += " ##{target.id}"
8267 elsif target.class == Fixnum
8268 cast_cmd += " ##{target}"
8269 else
8270 cast_cmd += " #{target}"
8271 end
8272 cast_result = nil
8273 loop {
8274 waitrt?
8275 if cast_cmd =~ /^incant/
8276 if (checkprep != @name) and (checkprep != 'None')
8277 dothistimeout 'release', 5, /^You feel the magic of your spell rush away from you\.$|^You don't have a prepared spell to release!$/
8278 end
8279 else
8280 unless checkprep == @name
8281 unless checkprep == 'None'
8282 dothistimeout 'release', 5, /^You feel the magic of your spell rush away from you\.$|^You don't have a prepared spell to release!$/
8283 unless (self.mana_cost <= 0) or checkmana(self.mana_cost)
8284 echo 'cast: not enough mana'
8285 sleep 0.1
8286 return false
8287 end
8288 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))
8289 echo 'cast: not enough spirit'
8290 sleep 0.1
8291 return false
8292 end
8293 unless (self.stamina_cost <= 0) or checkstamina(self.stamina_cost)
8294 echo 'cast: not enough stamina'
8295 sleep 0.1
8296 return false
8297 end
8298 end
8299 loop {
8300 waitrt?
8301 waitcastrt?
8302 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\.$/
8303 if prepare_result =~ /^Your spell(?:song)? is ready\./
8304 break
8305 elsif prepare_result == 'You already have a spell readied! You must RELEASE it if you wish to prepare another!'
8306 dothistimeout 'release', 5, /^You feel the magic of your spell rush away from you\.$|^You don't have a prepared spell to release!$/
8307 unless (self.mana_cost <= 0) or checkmana(self.mana_cost)
8308 echo 'cast: not enough mana'
8309 sleep 0.1
8310 return false
8311 end
8312 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\.$/
8313 sleep 0.1
8314 return prepare_result
8315 end
8316 }
8317 end
8318 end
8319 waitcastrt? unless Spell[515].active?#and (checkprep != 'None')
8320 if @stance and checkstance != 'offensive'
8321 put 'stance offensive'
8322 # dothistimeout 'stance offensive', 5, /^You (?:are now in|move into) an? offensive stance|^You are unable to change your stance\.$/
8323 end
8324 if results_of_interest.class == Regexp
8325 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\.$|#{results_of_interest.to_s}/
8326 else
8327 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\.$/
8328 end
8329 cast_result = dothistimeout cast_cmd, 1, results_regex
8330 if cast_result == "You don't seem to be able to move to do that."
8331 100.times { break if clear.any? { |line| line =~ /^You regain control of your senses!$/ }; sleep 0.1 }
8332 cast_result = dothistimeout cast_cmd, 5, results_regex
8333 end
8334 if @stance
8335 unless XMLData.active_spells.keys.include?('Rapid Fire')
8336 if @@after_stance
8337 if checkstance !~ /#{@@after_stance}/
8338 waitrt?
8339 dothistimeout "stance #{@@after_stance}", 3, /^You (?:are now in|move into) an? \w+ stance|^You are unable to change your stance\.$/
8340 end
8341 elsif checkstance !~ /^guarded$|^defensive$/
8342 waitrt?
8343 if checkcastrt > 0
8344 dothistimeout 'stance guarded', 3, /^You (?:are now in|move into) an? \w+ stance|^You are unable to change your stance\.$/
8345 else
8346 dothistimeout 'stance defensive', 3, /^You (?:are now in|move into) an? \w+ stance|^You are unable to change your stance\.$/
8347 end
8348 end
8349 end
8350 end
8351 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\.$/
8352 dothistimeout 'release', 5, /^You feel the magic of your spell rush away from you\.$|^You don't have a prepared spell to release!$/
8353 end
8354 break unless (@circle.to_i == 10) and (cast_result =~ /^\[Spell Hindrance for/)
8355 }
8356 cast_result
8357 end
8358 ensure
8359 script.want_downstream = save_want_downstream
8360 script.want_downstream_xml = save_want_downstream_xml
8361 @@cast_lock.delete(script)
8362 end
8363 end
8364 def _bonus
8365 @bonus.dup
8366 end
8367 def _cost
8368 @cost.dup
8369 end
8370 def method_missing(*args)
8371 if @@bonus_list.include?(args[0].to_s.gsub('_', '-'))
8372 if @bonus[args[0].to_s.gsub('_', '-')]
8373 if $SAFE < 3
8374 proc { $SAFE = 3; eval(@bonus[args[0].to_s.gsub('_', '-')]) }.call.to_i
8375 else
8376 eval(@bonus[args[0].to_s.gsub('_', '-')]).to_i
8377 end
8378 else
8379 0
8380 end
8381 elsif @@bonus_list.include?(args[0].to_s.sub(/_formula$/, '').gsub('_', '-'))
8382 @bonus[args[0].to_s.sub(/_formula$/, '').gsub('_', '-')].dup
8383 elsif (args[0].to_s =~ /_cost(?:_formula)?$/) and @@cost_list.include?(args[0].to_s.sub(/_formula$/, '').sub(/_cost$/, ''))
8384 options = args[1].to_hash
8385 if options[:caster] and (options[:caster] !~ /^(?:self|#{XMLData.name})$/i)
8386 if options[:target] and (options[:target].downcase == options[:caster].downcase)
8387 formula = @cost[args[0].to_s.sub(/_formula$/, '').sub(/_cost$/, '')]['self'].dup
8388 else
8389 formula = @cost[args[0].to_s.sub(/_formula$/, '').sub(/_cost$/, '')]['target'].dup || @cost[args[0].to_s.gsub('_', '-')]['self'].dup
8390 end
8391 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' }
8392 skills.each_pair { |a, b| formula.gsub!(a, b) }
8393 else
8394 if options[:target] and (options[:target] !~ /^(?:self|#{XMLData.name})$/i)
8395 formula = @cost[args[0].to_s.sub(/_formula$/, '').sub(/_cost$/, '')]['target'].dup || @cost[args[0].to_s.gsub('_', '-')]['self'].dup
8396 else
8397 formula = @cost[args[0].to_s.sub(/_formula$/, '').sub(/_cost$/, '')]['self'].dup
8398 end
8399 end
8400 if options[:multicast].to_i > 1
8401 formula = "(#{formula})*#{options[:multicast].to_i}"
8402 end
8403 if args[0].to_s =~ /_formula$/
8404 formula.dup
8405 else
8406 if formula
8407 formula.untaint if formula.tainted?
8408 if $SAFE < 3
8409 proc { $SAFE = 3; eval(formula) }.call.to_i
8410 else
8411 eval(formula).to_i
8412 end
8413 else
8414 0
8415 end
8416 end
8417 else
8418 respond 'missing method: ' + args.inspect.to_s
8419 raise NoMethodError
8420 end
8421 end
8422 def circle_name
8423 Spells.get_circle_name(@circle)
8424 end
8425 def clear_on_death
8426 !@persist_on_death
8427 end
8428 # for backwards compatiblity
8429 def duration; self.time_per_formula; end
8430 def cost; self.mana_cost_formula || '0'; end
8431 def manaCost; self.mana_cost_formula || '0'; end
8432 def spiritCost; self.spirit_cost_formula || '0'; end
8433 def staminaCost; self.stamina_cost_formula || '0'; end
8434 def boltAS; self.bolt_as_formula; end
8435 def physicalAS; self.physical_as_formula; end
8436 def boltDS; self.bolt_ds_formula; end
8437 def physicalDS; self.physical_ds_formula; end
8438 def elementalCS; self.elemental_cs_formula; end
8439 def mentalCS; self.mental_cs_formula; end
8440 def spiritCS; self.spirit_cs_formula; end
8441 def sorcererCS; self.sorcerer_cs_formula; end
8442 def elementalTD; self.elemental_td_formula; end
8443 def mentalTD; self.mental_td_formula; end
8444 def spiritTD; self.spirit_td_formula; end
8445 def sorcererTD; self.sorcerer_td_formula; end
8446 def castProc; @cast_proc; end
8447 def stacks; self.stackable? end
8448 def command; nil; end
8449 def circlename; self.circle_name; end
8450 def selfonly; @availability != 'all'; end
8451 end
8452
8453 class CMan
8454 @@bearhug ||= 0
8455 @@berserk ||= 0
8456 @@block_mastery ||= 0
8457 @@bull_rush ||= 0
8458 @@charge ||= 0
8459 @@cheapshots ||= 0
8460 @@combat_focus ||= 0
8461 @@combat_mastery ||= 0
8462 @@combat_mobility ||= 0
8463 @@combat_movement ||= 0
8464 @@combat_toughness ||= 0
8465 @@coup_de_grace ||= 0
8466 @@crowd_press ||= 0
8467 @@cunning_defense ||= 0
8468 @@cutthroat ||= 0
8469 @@dirtkick ||= 0
8470 @@disarm_weapon ||= 0
8471 @@divert ||= 0
8472 @@dust_shroud ||= 0
8473 @@evade_mastery ||= 0
8474 @@feint ||= 0
8475 @@garrote ||= 0
8476 @@groin_kick ||= 0
8477 @@hamstring ||= 0
8478 @@haymaker ||= 0
8479 @@headbutt ||= 0
8480 @@mighty_blow ||= 0
8481 @@multi_fire ||= 0
8482 @@parry_mastery ||= 0
8483 @@precision ||= 0
8484 @@quickstrike ||= 0
8485 @@shadow_mastery ||= 0
8486 @@shield_bash ||= 0
8487 @@shield_charge ||= 0
8488 @@side_by_side ||= 0
8489 @@silent_strike ||= 0
8490 @@specialization_i ||= 0
8491 @@specialization_ii ||= 0
8492 @@specialization_iii ||= 0
8493 @@spin_attack ||= 0
8494 @@staggering_blow ||= 0
8495 @@stun_maneuvers ||= 0
8496 @@subdual_strike ||= 0
8497 @@subdue ||= 0
8498 @@sucker_punch ||= 0
8499 @@sunder_shield ||= 0
8500 @@surge_of_strength ||= 0
8501 @@sweep ||= 0
8502 @@tackle ||= 0
8503 @@trip ||= 0
8504 @@truehand ||= 0
8505 @@twin_hammerfists ||= 0
8506 @@weapon_bonding ||= 0
8507 @@vanish ||= 0
8508 @@duck_and_weave ||= 0
8509 @@slippery_mind ||= 0
8510 @@predators_eye ||= 0
8511 @@burst_of_swiftness ||= 0
8512 @@rolling_krynch_stance ||= 0
8513 @@stance_of_the_mongoose ||= 0
8514 @@slippery_mind ||= 0
8515 @@flurry_of_blows ||= 0
8516 @@inner_harmony ||= 0
8517
8518 def CMan.bearhug; @@bearhug; end
8519 def CMan.berserk; @@berserk; end
8520 def CMan.block_mastery; @@block_mastery; end
8521 def CMan.bull_rush; @@bull_rush; end
8522 def CMan.burst_of_swiftness; @@burst_of_swiftness; end
8523 def CMan.charge; @@charge; end
8524 def CMan.cheapshots; @@cheapshots; end
8525 def CMan.combat_focus; @@combat_focus; end
8526 def CMan.combat_mastery; @@combat_mastery; end
8527 def CMan.combat_mobility; @@combat_mobility; end
8528 def CMan.combat_movement; @@combat_movement; end
8529 def CMan.combat_toughness; @@combat_toughness; end
8530 def CMan.coup_de_grace; @@coup_de_grace; end
8531 def CMan.crowd_press; @@crowd_press; end
8532 def CMan.cunning_defense; @@cunning_defense; end
8533 def CMan.cutthroat; @@cutthroat; end
8534 def CMan.dirtkick; @@dirtkick; end
8535 def CMan.disarm_weapon; @@disarm_weapon; end
8536 def CMan.divert; @@divert; end
8537 def CMan.dust_shroud; @@dust_shroud; end
8538 def CMan.evade_mastery; @@evade_mastery; end
8539 def CMan.feint; @@feint; end
8540 def CMan.garrote; @@garrote; end
8541 def CMan.groin_kick; @@groin_kick; end
8542 def CMan.hamstring; @@hamstring; end
8543 def CMan.haymaker; @@haymaker; end
8544 def CMan.headbutt; @@headbutt; end
8545 def CMan.mighty_blow; @@mighty_blow; end
8546 def CMan.multi_fire; @@multi_fire; end
8547 def CMan.parry_mastery; @@parry_mastery; end
8548 def CMan.precision; @@precision; end
8549 def CMan.quickstrike; @@quickstrike; end
8550 def CMan.shadow_mastery; @@shadow_mastery; end
8551 def CMan.shield_bash; @@shield_bash; end
8552 def CMan.shield_charge; @@shield_charge; end
8553 def CMan.side_by_side; @@side_by_side; end
8554 def CMan.silent_strike; @@silent_strike; end
8555 def CMan.specialization_i; @@specialization_i; end
8556 def CMan.specialization_ii; @@specialization_ii; end
8557 def CMan.specialization_iii; @@specialization_iii; end
8558 def CMan.spin_attack; @@spin_attack; end
8559 def CMan.staggering_blow; @@staggering_blow; end
8560 def CMan.stun_maneuvers; @@stun_maneuvers; end
8561 def CMan.subdual_strike; @@subdual_strike; end
8562 def CMan.subdue; @@subdue; end
8563 def CMan.sucker_punch; @@sucker_punch; end
8564 def CMan.sunder_shield; @@sunder_shield; end
8565 def CMan.surge_of_strength; @@surge_of_strength; end
8566 def CMan.sweep; @@sweep; end
8567 def CMan.tackle; @@tackle; end
8568 def CMan.trip; @@trip; end
8569 def CMan.truehand; @@truehand; end
8570 def CMan.twin_hammerfists; @@twin_hammerfists; end
8571 def CMan.weapon_bonding; @@weapon_bonding; end
8572 def CMan.vanish; @@vanish; end
8573 def CMan.duck_and_weave; @@duck_and_weave; end
8574 def CMan.slippery_mind; @@slippery_mind; end
8575 def CMan.predators_eye; @@predators_eye; end
8576
8577 def CMan.bearhug=(val); @@bearhug=val; end
8578 def CMan.berserk=(val); @@berserk=val; end
8579 def CMan.block_mastery=(val); @@block_mastery=val; end
8580 def CMan.bull_rush=(val); @@bull_rush=val; end
8581 def CMan.burst_of_swiftness=(val); @@burst_of_swiftness=val; end
8582 def CMan.charge=(val); @@charge=val; end
8583 def CMan.cheapshots=(val); @@cheapshots=val; end
8584 def CMan.combat_focus=(val); @@combat_focus=val; end
8585 def CMan.combat_mastery=(val); @@combat_mastery=val; end
8586 def CMan.combat_mobility=(val); @@combat_mobility=val; end
8587 def CMan.combat_movement=(val); @@combat_movement=val; end
8588 def CMan.combat_toughness=(val); @@combat_toughness=val; end
8589 def CMan.coup_de_grace=(val); @@coup_de_grace=val; end
8590 def CMan.crowd_press=(val); @@crowd_press=val; end
8591 def CMan.cunning_defense=(val); @@cunning_defense=val; end
8592 def CMan.cutthroat=(val); @@cutthroat=val; end
8593 def CMan.dirtkick=(val); @@dirtkick=val; end
8594 def CMan.disarm_weapon=(val); @@disarm_weapon=val; end
8595 def CMan.divert=(val); @@divert=val; end
8596 def CMan.dust_shroud=(val); @@dust_shroud=val; end
8597 def CMan.evade_mastery=(val); @@evade_mastery=val; end
8598 def CMan.feint=(val); @@feint=val; end
8599 def CMan.garrote=(val); @@garrote=val; end
8600 def CMan.groin_kick=(val); @@groin_kick=val; end
8601 def CMan.hamstring=(val); @@hamstring=val; end
8602 def CMan.haymaker=(val); @@haymaker=val; end
8603 def CMan.headbutt=(val); @@headbutt=val; end
8604 def CMan.mighty_blow=(val); @@mighty_blow=val; end
8605 def CMan.multi_fire=(val); @@multi_fire=val; end
8606 def CMan.parry_mastery=(val); @@parry_mastery=val; end
8607 def CMan.precision=(val); @@precision=val; end
8608 def CMan.quickstrike=(val); @@quickstrike=val; end
8609 def CMan.shadow_mastery=(val); @@shadow_mastery=val; end
8610 def CMan.shield_bash=(val); @@shield_bash=val; end
8611 def CMan.shield_charge=(val); @@shield_charge=val; end
8612 def CMan.side_by_side=(val); @@side_by_side=val; end
8613 def CMan.silent_strike=(val); @@silent_strike=val; end
8614 def CMan.specialization_i=(val); @@specialization_i=val; end
8615 def CMan.specialization_ii=(val); @@specialization_ii=val; end
8616 def CMan.specialization_iii=(val); @@specialization_iii=val; end
8617 def CMan.spin_attack=(val); @@spin_attack=val; end
8618 def CMan.staggering_blow=(val); @@staggering_blow=val; end
8619 def CMan.stun_maneuvers=(val); @@stun_maneuvers=val; end
8620 def CMan.subdual_strike=(val); @@subdual_strike=val; end
8621 def CMan.subdue=(val); @@subdue=val; end
8622 def CMan.sucker_punch=(val); @@sucker_punch=val; end
8623 def CMan.sunder_shield=(val); @@sunder_shield=val; end
8624 def CMan.surge_of_strength=(val); @@surge_of_strength=val; end
8625 def CMan.sweep=(val); @@sweep=val; end
8626 def CMan.tackle=(val); @@tackle=val; end
8627 def CMan.trip=(val); @@trip=val; end
8628 def CMan.truehand=(val); @@truehand=val; end
8629 def CMan.twin_hammerfists=(val); @@twin_hammerfists=val; end
8630 def CMan.weapon_bonding=(val); @@weapon_bonding=val; end
8631 def CMan.vanish=(val); @@vanish=val; end
8632 def CMan.duck_and_weave=(val); @@duck_and_weave=val; end
8633 def CMan.slippery_mind=(val); @@slippery_mind=val; end
8634 def CMan.predators_eye=(val); @@predators_eye=val; end
8635
8636 def CMan.method_missing(arg1, arg2=nil)
8637 nil
8638 end
8639 def CMan.[](name)
8640 CMan.send(name.gsub(/[\s\-]/, '_').gsub("'", "").downcase)
8641 end
8642 def CMan.[]=(name,val)
8643 CMan.send("#{name.gsub(/[\s\-]/, '_').gsub("'", "").downcase}=", val.to_i)
8644 end
8645 end
8646
8647 class Stats
8648 @@race ||= 'unknown'
8649 @@prof ||= 'unknown'
8650 @@gender ||= 'unknown'
8651 @@age ||= 0
8652 @@level ||= 0
8653 @@str ||= [0,0]
8654 @@con ||= [0,0]
8655 @@dex ||= [0,0]
8656 @@agi ||= [0,0]
8657 @@dis ||= [0,0]
8658 @@aur ||= [0,0]
8659 @@log ||= [0,0]
8660 @@int ||= [0,0]
8661 @@wis ||= [0,0]
8662 @@inf ||= [0,0]
8663 def Stats.race; @@race; end
8664 def Stats.race=(val); @@race=val; end
8665 def Stats.prof; @@prof; end
8666 def Stats.prof=(val); @@prof=val; end
8667 def Stats.gender; @@gender; end
8668 def Stats.gender=(val); @@gender=val; end
8669 def Stats.age; @@age; end
8670 def Stats.age=(val); @@age=val; end
8671 def Stats.level; @@level; end
8672 def Stats.level=(val); @@level=val; end
8673 def Stats.str; @@str; end
8674 def Stats.str=(val); @@str=val; end
8675 def Stats.con; @@con; end
8676 def Stats.con=(val); @@con=val; end
8677 def Stats.dex; @@dex; end
8678 def Stats.dex=(val); @@dex=val; end
8679 def Stats.agi; @@agi; end
8680 def Stats.agi=(val); @@agi=val; end
8681 def Stats.dis; @@dis; end
8682 def Stats.dis=(val); @@dis=val; end
8683 def Stats.aur; @@aur; end
8684 def Stats.aur=(val); @@aur=val; end
8685 def Stats.log; @@log; end
8686 def Stats.log=(val); @@log=val; end
8687 def Stats.int; @@int; end
8688 def Stats.int=(val); @@int=val; end
8689 def Stats.wis; @@wis; end
8690 def Stats.wis=(val); @@wis=val; end
8691 def Stats.inf; @@inf; end
8692 def Stats.inf=(val); @@inf=val; end
8693 def Stats.exp
8694 if XMLData.next_level_text =~ /until next level/
8695 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 ]
8696 exp_threshold[XMLData.level] - XMLData.next_level_text.slice(/[0-9]+/).to_i
8697 else
8698 XMLData.next_level_text.slice(/[0-9]+/).to_i
8699 end
8700 end
8701 def Stats.exp=(val); nil; end
8702 def Stats.serialize
8703 [@@race,@@prof,@@gender,@@age,Stats.exp,@@level,@@str,@@con,@@dex,@@agi,@@dis,@@aur,@@log,@@int,@@wis,@@inf]
8704 end
8705 def Stats.load_serialized=(array)
8706 @@race,@@prof,@@gender,@@age = array[0..3]
8707 @@level,@@str,@@con,@@dex,@@agi,@@dis,@@aur,@@log,@@int,@@wis,@@inf = array[5..15]
8708 end
8709 end
8710
8711 class Gift
8712 @@gift_start ||= Time.now
8713 @@pulse_count ||= 0
8714 def Gift.started
8715 @@gift_start = Time.now
8716 @@pulse_count = 0
8717 end
8718 def Gift.pulse
8719 @@pulse_count += 1
8720 end
8721 def Gift.remaining
8722 ([360 - @@pulse_count, 0].max * 60).to_f
8723 end
8724 def Gift.restarts_on
8725 @@gift_start + 594000
8726 end
8727 def Gift.serialize
8728 [@@gift_start, @@pulse_count]
8729 end
8730 def Gift.load_serialized=(array)
8731 @@gift_start = array[0]
8732 @@pulse_count = array[1].to_i
8733 end
8734 def Gift.ended
8735 @@pulse_count = 360
8736 end
8737 def Gift.stopwatch
8738 nil
8739 end
8740 end
8741
8742 class Wounds
8743 def Wounds.leftEye; fix_injury_mode; XMLData.injuries['leftEye']['wound']; end
8744 def Wounds.leye; fix_injury_mode; XMLData.injuries['leftEye']['wound']; end
8745 def Wounds.rightEye; fix_injury_mode; XMLData.injuries['rightEye']['wound']; end
8746 def Wounds.reye; fix_injury_mode; XMLData.injuries['rightEye']['wound']; end
8747 def Wounds.head; fix_injury_mode; XMLData.injuries['head']['wound']; end
8748 def Wounds.neck; fix_injury_mode; XMLData.injuries['neck']['wound']; end
8749 def Wounds.back; fix_injury_mode; XMLData.injuries['back']['wound']; end
8750 def Wounds.chest; fix_injury_mode; XMLData.injuries['chest']['wound']; end
8751 def Wounds.abdomen; fix_injury_mode; XMLData.injuries['abdomen']['wound']; end
8752 def Wounds.abs; fix_injury_mode; XMLData.injuries['abdomen']['wound']; end
8753 def Wounds.leftArm; fix_injury_mode; XMLData.injuries['leftArm']['wound']; end
8754 def Wounds.larm; fix_injury_mode; XMLData.injuries['leftArm']['wound']; end
8755 def Wounds.rightArm; fix_injury_mode; XMLData.injuries['rightArm']['wound']; end
8756 def Wounds.rarm; fix_injury_mode; XMLData.injuries['rightArm']['wound']; end
8757 def Wounds.rightHand; fix_injury_mode; XMLData.injuries['rightHand']['wound']; end
8758 def Wounds.rhand; fix_injury_mode; XMLData.injuries['rightHand']['wound']; end
8759 def Wounds.leftHand; fix_injury_mode; XMLData.injuries['leftHand']['wound']; end
8760 def Wounds.lhand; fix_injury_mode; XMLData.injuries['leftHand']['wound']; end
8761 def Wounds.leftLeg; fix_injury_mode; XMLData.injuries['leftLeg']['wound']; end
8762 def Wounds.lleg; fix_injury_mode; XMLData.injuries['leftLeg']['wound']; end
8763 def Wounds.rightLeg; fix_injury_mode; XMLData.injuries['rightLeg']['wound']; end
8764 def Wounds.rleg; fix_injury_mode; XMLData.injuries['rightLeg']['wound']; end
8765 def Wounds.leftFoot; fix_injury_mode; XMLData.injuries['leftFoot']['wound']; end
8766 def Wounds.rightFoot; fix_injury_mode; XMLData.injuries['rightFoot']['wound']; end
8767 def Wounds.nsys; fix_injury_mode; XMLData.injuries['nsys']['wound']; end
8768 def Wounds.nerves; fix_injury_mode; XMLData.injuries['nsys']['wound']; end
8769 def Wounds.arms
8770 fix_injury_mode
8771 [XMLData.injuries['leftArm']['wound'],XMLData.injuries['rightArm']['wound'],XMLData.injuries['leftHand']['wound'],XMLData.injuries['rightHand']['wound']].max
8772 end
8773 def Wounds.limbs
8774 fix_injury_mode
8775 [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
8776 end
8777 def Wounds.torso
8778 fix_injury_mode
8779 [XMLData.injuries['rightEye']['wound'],XMLData.injuries['leftEye']['wound'],XMLData.injuries['chest']['wound'],XMLData.injuries['abdomen']['wound'],XMLData.injuries['back']['wound']].max
8780 end
8781 def Wounds.method_missing(arg=nil)
8782 echo "Wounds: Invalid area, try one of these: arms, limbs, torso, #{XMLData.injuries.keys.join(', ')}"
8783 nil
8784 end
8785 end
8786
8787 class Scars
8788 def Scars.leftEye; fix_injury_mode; XMLData.injuries['leftEye']['scar']; end
8789 def Scars.leye; fix_injury_mode; XMLData.injuries['leftEye']['scar']; end
8790 def Scars.rightEye; fix_injury_mode; XMLData.injuries['rightEye']['scar']; end
8791 def Scars.reye; fix_injury_mode; XMLData.injuries['rightEye']['scar']; end
8792 def Scars.head; fix_injury_mode; XMLData.injuries['head']['scar']; end
8793 def Scars.neck; fix_injury_mode; XMLData.injuries['neck']['scar']; end
8794 def Scars.back; fix_injury_mode; XMLData.injuries['back']['scar']; end
8795 def Scars.chest; fix_injury_mode; XMLData.injuries['chest']['scar']; end
8796 def Scars.abdomen; fix_injury_mode; XMLData.injuries['abdomen']['scar']; end
8797 def Scars.abs; fix_injury_mode; XMLData.injuries['abdomen']['scar']; end
8798 def Scars.leftArm; fix_injury_mode; XMLData.injuries['leftArm']['scar']; end
8799 def Scars.larm; fix_injury_mode; XMLData.injuries['leftArm']['scar']; end
8800 def Scars.rightArm; fix_injury_mode; XMLData.injuries['rightArm']['scar']; end
8801 def Scars.rarm; fix_injury_mode; XMLData.injuries['rightArm']['scar']; end
8802 def Scars.rightHand; fix_injury_mode; XMLData.injuries['rightHand']['scar']; end
8803 def Scars.rhand; fix_injury_mode; XMLData.injuries['rightHand']['scar']; end
8804 def Scars.leftHand; fix_injury_mode; XMLData.injuries['leftHand']['scar']; end
8805 def Scars.lhand; fix_injury_mode; XMLData.injuries['leftHand']['scar']; end
8806 def Scars.leftLeg; fix_injury_mode; XMLData.injuries['leftLeg']['scar']; end
8807 def Scars.lleg; fix_injury_mode; XMLData.injuries['leftLeg']['scar']; end
8808 def Scars.rightLeg; fix_injury_mode; XMLData.injuries['rightLeg']['scar']; end
8809 def Scars.rleg; fix_injury_mode; XMLData.injuries['rightLeg']['scar']; end
8810 def Scars.leftFoot; fix_injury_mode; XMLData.injuries['leftFoot']['scar']; end
8811 def Scars.rightFoot; fix_injury_mode; XMLData.injuries['rightFoot']['scar']; end
8812 def Scars.nsys; fix_injury_mode; XMLData.injuries['nsys']['scar']; end
8813 def Scars.nerves; fix_injury_mode; XMLData.injuries['nsys']['scar']; end
8814 def Scars.arms
8815 fix_injury_mode
8816 [XMLData.injuries['leftArm']['scar'],XMLData.injuries['rightArm']['scar'],XMLData.injuries['leftHand']['scar'],XMLData.injuries['rightHand']['scar']].max
8817 end
8818 def Scars.limbs
8819 fix_injury_mode
8820 [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
8821 end
8822 def Scars.torso
8823 fix_injury_mode
8824 [XMLData.injuries['rightEye']['scar'],XMLData.injuries['leftEye']['scar'],XMLData.injuries['chest']['scar'],XMLData.injuries['abdomen']['scar'],XMLData.injuries['back']['scar']].max
8825 end
8826 def Scars.method_missing(arg=nil)
8827 echo "Scars: Invalid area, try one of these: arms, limbs, torso, #{XMLData.injuries.keys.join(', ')}"
8828 nil
8829 end
8830 end
8831 class GameObj
8832 @@loot = Array.new
8833 @@npcs = Array.new
8834 @@npc_status = Hash.new
8835 @@pcs = Array.new
8836 @@pc_status = Hash.new
8837 @@inv = Array.new
8838 @@contents = Hash.new
8839 @@right_hand = nil
8840 @@left_hand = nil
8841 @@room_desc = Array.new
8842 @@fam_loot = Array.new
8843 @@fam_npcs = Array.new
8844 @@fam_pcs = Array.new
8845 @@fam_room_desc = Array.new
8846 @@type_data = Hash.new
8847 @@sellable_data = Hash.new
8848 @@elevated_load = proc { GameObj.load_data }
8849
8850 attr_reader :id
8851 attr_accessor :noun, :name, :before_name, :after_name
8852 def initialize(id, noun, name, before=nil, after=nil)
8853 @id = id
8854 @noun = noun
8855 @noun = 'lapis' if @noun == 'lapis lazuli'
8856 @noun = 'hammer' if @noun == "Hammer of Kai"
8857 @noun = 'mother-of-pearl' if (@noun == 'pearl') and (@name =~ /mother\-of\-pearl/)
8858 @name = name
8859 @before_name = before
8860 @after_name = after
8861 end
8862 def type
8863 GameObj.load_data if @@type_data.empty?
8864 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]) }
8865 if list.empty?
8866 nil
8867 else
8868 list.join(',')
8869 end
8870 end
8871 def sellable
8872 GameObj.load_data if @@sellable_data.empty?
8873 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]) }
8874 if list.empty?
8875 nil
8876 else
8877 list.join(',')
8878 end
8879 end
8880 def status
8881 if @@npc_status.keys.include?(@id)
8882 @@npc_status[@id]
8883 elsif @@pc_status.keys.include?(@id)
8884 @@pc_status[@id]
8885 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 } }
8886 nil
8887 else
8888 'gone'
8889 end
8890 end
8891 def status=(val)
8892 if @@npcs.any? { |npc| npc.id == @id }
8893 @@npc_status[@id] = val
8894 elsif @@pcs.any? { |pc| pc.id == @id }
8895 @@pc_status[@id] = val
8896 else
8897 nil
8898 end
8899 end
8900 def to_s
8901 @noun
8902 end
8903 def empty?
8904 false
8905 end
8906 def contents
8907 @@contents[@id].dup
8908 end
8909 def GameObj.[](val)
8910 if val.class == String
8911 if val =~ /^\-?[0-9]+$/
8912 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 }
8913 elsif val.split(' ').length == 1
8914 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 }
8915 else
8916 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 }
8917 end
8918 elsif val.class == Regexp
8919 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 }
8920 end
8921 end
8922 def GameObj
8923 @noun
8924 end
8925 def full_name
8926 "#{@before_name}#{' ' unless @before_name.nil? or @before_name.empty?}#{name}#{' ' unless @after_name.nil? or @after_name.empty?}#{@after_name}"
8927 end
8928 def GameObj.new_npc(id, noun, name, status=nil)
8929 obj = GameObj.new(id, noun, name)
8930 @@npcs.push(obj)
8931 @@npc_status[id] = status
8932 obj
8933 end
8934 def GameObj.new_loot(id, noun, name)
8935 obj = GameObj.new(id, noun, name)
8936 @@loot.push(obj)
8937 obj
8938 end
8939 def GameObj.new_pc(id, noun, name, status=nil)
8940 obj = GameObj.new(id, noun, name)
8941 @@pcs.push(obj)
8942 @@pc_status[id] = status
8943 obj
8944 end
8945 def GameObj.new_inv(id, noun, name, container=nil, before=nil, after=nil)
8946 obj = GameObj.new(id, noun, name, before, after)
8947 if container
8948 @@contents[container].push(obj)
8949 else
8950 @@inv.push(obj)
8951 end
8952 obj
8953 end
8954 def GameObj.new_room_desc(id, noun, name)
8955 obj = GameObj.new(id, noun, name)
8956 @@room_desc.push(obj)
8957 obj
8958 end
8959 def GameObj.new_fam_room_desc(id, noun, name)
8960 obj = GameObj.new(id, noun, name)
8961 @@fam_room_desc.push(obj)
8962 obj
8963 end
8964 def GameObj.new_fam_loot(id, noun, name)
8965 obj = GameObj.new(id, noun, name)
8966 @@fam_loot.push(obj)
8967 obj
8968 end
8969 def GameObj.new_fam_npc(id, noun, name)
8970 obj = GameObj.new(id, noun, name)
8971 @@fam_npcs.push(obj)
8972 obj
8973 end
8974 def GameObj.new_fam_pc(id, noun, name)
8975 obj = GameObj.new(id, noun, name)
8976 @@fam_pcs.push(obj)
8977 obj
8978 end
8979 def GameObj.new_right_hand(id, noun, name)
8980 @@right_hand = GameObj.new(id, noun, name)
8981 end
8982 def GameObj.right_hand
8983 @@right_hand.dup
8984 end
8985 def GameObj.new_left_hand(id, noun, name)
8986 @@left_hand = GameObj.new(id, noun, name)
8987 end
8988 def GameObj.left_hand
8989 @@left_hand.dup
8990 end
8991 def GameObj.clear_loot
8992 @@loot.clear
8993 end
8994 def GameObj.clear_npcs
8995 @@npcs.clear
8996 @@npc_status.clear
8997 end
8998 def GameObj.clear_pcs
8999 @@pcs.clear
9000 @@pc_status.clear
9001 end
9002 def GameObj.clear_inv
9003 @@inv.clear
9004 end
9005 def GameObj.clear_room_desc
9006 @@room_desc.clear
9007 end
9008 def GameObj.clear_fam_room_desc
9009 @@fam_room_desc.clear
9010 end
9011 def GameObj.clear_fam_loot
9012 @@fam_loot.clear
9013 end
9014 def GameObj.clear_fam_npcs
9015 @@fam_npcs.clear
9016 end
9017 def GameObj.clear_fam_pcs
9018 @@fam_pcs.clear
9019 end
9020 def GameObj.npcs
9021 if @@npcs.empty?
9022 nil
9023 else
9024 @@npcs.dup
9025 end
9026 end
9027 def GameObj.loot
9028 if @@loot.empty?
9029 nil
9030 else
9031 @@loot.dup
9032 end
9033 end
9034 def GameObj.pcs
9035 if @@pcs.empty?
9036 nil
9037 else
9038 @@pcs.dup
9039 end
9040 end
9041 def GameObj.inv
9042 if @@inv.empty?
9043 nil
9044 else
9045 @@inv.dup
9046 end
9047 end
9048 def GameObj.room_desc
9049 if @@room_desc.empty?
9050 nil
9051 else
9052 @@room_desc.dup
9053 end
9054 end
9055 def GameObj.fam_room_desc
9056 if @@fam_room_desc.empty?
9057 nil
9058 else
9059 @@fam_room_desc.dup
9060 end
9061 end
9062 def GameObj.fam_loot
9063 if @@fam_loot.empty?
9064 nil
9065 else
9066 @@fam_loot.dup
9067 end
9068 end
9069 def GameObj.fam_npcs
9070 if @@fam_npcs.empty?
9071 nil
9072 else
9073 @@fam_npcs.dup
9074 end
9075 end
9076 def GameObj.fam_pcs
9077 if @@fam_pcs.empty?
9078 nil
9079 else
9080 @@fam_pcs.dup
9081 end
9082 end
9083 def GameObj.clear_container(container_id)
9084 @@contents[container_id] = Array.new
9085 end
9086 def GameObj.delete_container(container_id)
9087 @@contents.delete(container_id)
9088 end
9089 def GameObj.dead
9090 dead_list = Array.new
9091 for obj in @@npcs
9092 dead_list.push(obj) if obj.status == "dead"
9093 end
9094 return nil if dead_list.empty?
9095 return dead_list
9096 end
9097 def GameObj.containers
9098 @@contents.dup
9099 end
9100 def GameObj.load_data(filename=nil)
9101 if $SAFE == 0
9102 if filename.nil?
9103 if File.exists?("#{DATA_DIR}/gameobj-data.xml")
9104 filename = "#{DATA_DIR}/gameobj-data.xml"
9105 elsif File.exists?("#{SCRIPT_DIR}/gameobj-data.xml") # deprecated
9106 filename = "#{SCRIPT_DIR}/gameobj-data.xml"
9107 else
9108 filename = "#{DATA_DIR}/gameobj-data.xml"
9109 end
9110 end
9111 if File.exists?(filename)
9112 begin
9113 @@type_data = Hash.new
9114 @@sellable_data = Hash.new
9115 File.open(filename) { |file|
9116 doc = REXML::Document.new(file.read)
9117 doc.elements.each('data/type') { |e|
9118 if type = e.attributes['name']
9119 @@type_data[type] = Hash.new
9120 @@type_data[type][:name] = Regexp.new(e.elements['name'].text) unless e.elements['name'].text.nil? or e.elements['name'].text.empty?
9121 @@type_data[type][:noun] = Regexp.new(e.elements['noun'].text) unless e.elements['noun'].text.nil? or e.elements['noun'].text.empty?
9122 @@type_data[type][:exclude] = Regexp.new(e.elements['exclude'].text) unless e.elements['exclude'].text.nil? or e.elements['exclude'].text.empty?
9123 end
9124 }
9125 doc.elements.each('data/sellable') { |e|
9126 if sellable = e.attributes['name']
9127 @@sellable_data[sellable] = Hash.new
9128 @@sellable_data[sellable][:name] = Regexp.new(e.elements['name'].text) unless e.elements['name'].text.nil? or e.elements['name'].text.empty?
9129 @@sellable_data[sellable][:noun] = Regexp.new(e.elements['noun'].text) unless e.elements['noun'].text.nil? or e.elements['noun'].text.empty?
9130 @@sellable_data[sellable][:exclude] = Regexp.new(e.elements['exclude'].text) unless e.elements['exclude'].text.nil? or e.elements['exclude'].text.empty?
9131 end
9132 }
9133 }
9134 true
9135 rescue
9136 @@type_data = nil
9137 @@sellable_data = nil
9138 echo "error: GameObj.load_data: #{$!}"
9139 respond $!.backtrace[0..1]
9140 false
9141 end
9142 else
9143 @@type_data = nil
9144 @@sellable_data = nil
9145 echo "error: GameObj.load_data: file does not exist: #{filename}"
9146 false
9147 end
9148 else
9149 @@elevated_load.call
9150 end
9151 end
9152 def GameObj.type_data
9153 @@type_data
9154 end
9155 def GameObj.sellable_data
9156 @@sellable_data
9157 end
9158 end
9159 #
9160 # start deprecated stuff
9161 #
9162 class RoomObj < GameObj
9163 end
9164 #
9165 # end deprecated stuff
9166 #
9167 end
9168 module DragonRealms
9169 # fixme
9170 end
9171end
9172
9173include Games::Gemstone
9174
9175JUMP = Exception.exception('JUMP')
9176JUMP_ERROR = Exception.exception('JUMP_ERROR')
9177
9178DIRMAP = {
9179 'out' => 'K',
9180 'ne' => 'B',
9181 'se' => 'D',
9182 'sw' => 'F',
9183 'nw' => 'H',
9184 'up' => 'I',
9185 'down' => 'J',
9186 'n' => 'A',
9187 'e' => 'C',
9188 's' => 'E',
9189 'w' => 'G',
9190}
9191SHORTDIR = {
9192 'out' => 'out',
9193 'northeast' => 'ne',
9194 'southeast' => 'se',
9195 'southwest' => 'sw',
9196 'northwest' => 'nw',
9197 'up' => 'up',
9198 'down' => 'down',
9199 'north' => 'n',
9200 'east' => 'e',
9201 'south' => 's',
9202 'west' => 'w',
9203}
9204LONGDIR = {
9205 'out' => 'out',
9206 'ne' => 'northeast',
9207 'se' => 'southeast',
9208 'sw' => 'southwest',
9209 'nw' => 'northwest',
9210 'up' => 'up',
9211 'down' => 'down',
9212 'n' => 'north',
9213 'e' => 'east',
9214 's' => 'south',
9215 'w' => 'west',
9216}
9217MINDMAP = {
9218 'clear as a bell' => 'A',
9219 'fresh and clear' => 'B',
9220 'clear' => 'C',
9221 'muddled' => 'D',
9222 'becoming numbed' => 'E',
9223 'numbed' => 'F',
9224 'must rest' => 'G',
9225 'saturated' => 'H',
9226}
9227ICONMAP = {
9228 'IconKNEELING' => 'GH',
9229 'IconPRONE' => 'G',
9230 'IconSITTING' => 'H',
9231 'IconSTANDING' => 'T',
9232 'IconSTUNNED' => 'I',
9233 'IconHIDDEN' => 'N',
9234 'IconINVISIBLE' => 'D',
9235 'IconDEAD' => 'B',
9236 'IconWEBBED' => 'C',
9237 'IconJOINED' => 'P',
9238 'IconBLEEDING' => 'O',
9239}
9240
9241XMLData = XMLParser.new
9242
9243reconnect_if_wanted = proc {
9244 if ARGV.include?('--reconnect') and ARGV.include?('--login') and not $_CLIENTBUFFER_.any? { |cmd| cmd =~ /^(?:\[.*?\])?(?:<c>)?(?:quit|exit)/i }
9245 if reconnect_arg = ARGV.find { |arg| arg =~ /^\-\-reconnect\-delay=[0-9]+(?:\+[0-9]+)?$/ }
9246 reconnect_arg =~ /^\-\-reconnect\-delay=([0-9]+)(\+[0-9]+)?/
9247 reconnect_delay = $1.to_i
9248 reconnect_step = $2.to_i
9249 else
9250 reconnect_delay = 60
9251 reconnect_step = 0
9252 end
9253 Lich.log "info: waiting #{reconnect_delay} seconds to reconnect..."
9254 sleep reconnect_delay
9255 Lich.log 'info: reconnecting...'
9256 if (RUBY_PLATFORM =~ /mingw|win/i) and (RUBY_PLATFORM !~ /darwin/i)
9257 if $frontend == 'stormfront'
9258 system 'taskkill /FI "WINDOWTITLE eq [GSIV: ' + Char.name + '*"'
9259 end
9260 args = [ 'start rubyw.exe' ]
9261 else
9262 args = [ 'ruby' ]
9263 end
9264 args.push $PROGRAM_NAME.slice(/[^\\\/]+$/)
9265 args.concat ARGV
9266 args.push '--reconnected' unless args.include?('--reconnected')
9267 if reconnect_step > 0
9268 args.delete(reconnect_arg)
9269 args.concat ["--reconnect-delay=#{reconnect_delay+reconnect_step}+#{reconnect_step}"]
9270 end
9271 Lich.log "exec args.join(' '): exec #{args.join(' ')}"
9272 exec args.join(' ')
9273 end
9274}
9275
9276#
9277# Start deprecated stuff
9278#
9279
9280$version = LICH_VERSION
9281$room_count = 0
9282$psinet = false
9283$stormfront = true
9284
9285module Lich
9286 @@last_warn_deprecated = 0
9287 def Lich.method_missing(arg1, arg2='')
9288 if (Time.now.to_i - @@last_warn_deprecated) > 300
9289 respond "--- warning: Lich.* variables will stop working in a future version of Lich. Use Vars.* (offending script: #{Script.current.name || 'unknown'})"
9290 @@last_warn_deprecated = Time.now.to_i
9291 end
9292 Vars.method_missing(arg1, arg2)
9293 end
9294end
9295
9296class Script
9297 def Script.self
9298 Script.current
9299 end
9300 def Script.running
9301 list = Array.new
9302 for script in @@running
9303 list.push(script) unless script.hidden
9304 end
9305 return list
9306 end
9307 def Script.index
9308 Script.running
9309 end
9310 def Script.hidden
9311 list = Array.new
9312 for script in @@running
9313 list.push(script) if script.hidden
9314 end
9315 return list
9316 end
9317 def Script.namescript_incoming(line)
9318 Script.new_downstream(line)
9319 end
9320end
9321
9322class Spellsong
9323 def Spellsong.cost
9324 Spellsong.renew_cost
9325 end
9326 def Spellsong.tonisdodgebonus
9327 thresholds = [1,2,3,5,8,10,14,17,21,26,31,36,42,49,55,63,70,78,87,96]
9328 bonus = 20
9329 thresholds.each { |val| if Skills.elair >= val then bonus += 1 end }
9330 bonus
9331 end
9332 def Spellsong.mirrorsdodgebonus
9333 20 + ((Spells.bard - 19) / 2).round
9334 end
9335 def Spellsong.mirrorscost
9336 [19 + ((Spells.bard - 19) / 5).truncate, 8 + ((Spells.bard - 19) / 10).truncate]
9337 end
9338 def Spellsong.sonicbonus
9339 (Spells.bard / 2).round
9340 end
9341 def Spellsong.sonicarmorbonus
9342 Spellsong.sonicbonus + 15
9343 end
9344 def Spellsong.sonicbladebonus
9345 Spellsong.sonicbonus + 10
9346 end
9347 def Spellsong.sonicweaponbonus
9348 Spellsong.sonicbladebonus
9349 end
9350 def Spellsong.sonicshieldbonus
9351 Spellsong.sonicbonus + 10
9352 end
9353 def Spellsong.valorbonus
9354 10 + (([Spells.bard, Stats.level].min - 10) / 2).round
9355 end
9356 def Spellsong.valorcost
9357 [10 + (Spellsong.valorbonus / 2), 3 + (Spellsong.valorbonus / 5)]
9358 end
9359 def Spellsong.luckcost
9360 [6 + ((Spells.bard - 6) / 4),(6 + ((Spells.bard - 6) / 4) / 2).round]
9361 end
9362 def Spellsong.manacost
9363 [18,15]
9364 end
9365 def Spellsong.fortcost
9366 [3,1]
9367 end
9368 def Spellsong.shieldcost
9369 [9,4]
9370 end
9371 def Spellsong.weaponcost
9372 [12,4]
9373 end
9374 def Spellsong.armorcost
9375 [14,5]
9376 end
9377 def Spellsong.swordcost
9378 [25,15]
9379 end
9380end
9381
9382class Map
9383 def desc
9384 @description
9385 end
9386 def map_name
9387 @image
9388 end
9389 def map_x
9390 if @image_coords.nil?
9391 nil
9392 else
9393 ((image_coords[0] + image_coords[2])/2.0).round
9394 end
9395 end
9396 def map_y
9397 if @image_coords.nil?
9398 nil
9399 else
9400 ((image_coords[1] + image_coords[3])/2.0).round
9401 end
9402 end
9403 def map_roomsize
9404 if @image_coords.nil?
9405 nil
9406 else
9407 image_coords[2] - image_coords[0]
9408 end
9409 end
9410 def geo
9411 nil
9412 end
9413end
9414
9415def start_script(script_name, cli_vars=[], flags=Hash.new)
9416 if flags == true
9417 flags = { :quiet => true }
9418 end
9419 Script.start(script_name, cli_vars.join(' '), flags)
9420end
9421
9422def start_scripts(*script_names)
9423 script_names.flatten.each { |script_name|
9424 start_script(script_name)
9425 sleep 0.02
9426 }
9427end
9428
9429def force_start_script(script_name,cli_vars=[], flags={})
9430 flags = Hash.new unless flags.class == Hash
9431 flags[:force] = true
9432 start_script(script_name,cli_vars,flags)
9433end
9434
9435def survivepoison?
9436 echo 'survivepoison? called, but there is no XML for poison rate'
9437 return true
9438end
9439
9440def survivedisease?
9441 echo 'survivepoison? called, but there is no XML for disease rate'
9442 return true
9443end
9444
9445def before_dying(&code)
9446 Script.at_exit(&code)
9447end
9448
9449def undo_before_dying
9450 Script.clear_exit_procs
9451end
9452
9453def abort!
9454 Script.exit!
9455end
9456
9457def fetchloot(userbagchoice=UserVars.lootsack)
9458 if GameObj.loot.empty?
9459 return false
9460 end
9461 if UserVars.excludeloot.empty?
9462 regexpstr = nil
9463 else
9464 regexpstr = UserVars.excludeloot.split(', ').join('|')
9465 end
9466 if checkright and checkleft
9467 stowed = GameObj.right_hand.noun
9468 fput "put my #{stowed} in my #{UserVars.lootsack}"
9469 else
9470 stowed = nil
9471 end
9472 GameObj.loot.each { |loot|
9473 unless not regexpstr.nil? and loot.name =~ /#{regexpstr}/
9474 fput "get #{loot.noun}"
9475 fput("put my #{loot.noun} in my #{userbagchoice}") if (checkright || checkleft)
9476 end
9477 }
9478 if stowed
9479 fput "take my #{stowed} from my #{UserVars.lootsack}"
9480 end
9481end
9482
9483def take(*items)
9484 items.flatten!
9485 if (righthand? && lefthand?)
9486 weap = checkright
9487 fput "put my #{checkright} in my #{UserVars.lootsack}"
9488 unsh = true
9489 else
9490 unsh = false
9491 end
9492 items.each { |trinket|
9493 fput "take #{trinket}"
9494 fput("put my #{trinket} in my #{UserVars.lootsack}") if (righthand? || lefthand?)
9495 }
9496 if unsh then fput("take my #{weap} from my #{UserVars.lootsack}") end
9497end
9498
9499def stop_script(*target_names)
9500 numkilled = 0
9501 target_names.each { |target_name|
9502 condemned = Script.list.find { |s_sock| s_sock.name =~ /^#{target_name}/i }
9503 if condemned.nil?
9504 respond("--- Lich: '#{Script.current}' tried to stop '#{target_name}', but it isn't running!")
9505 else
9506 if condemned.name =~ /^#{Script.current.name}$/i
9507 exit
9508 end
9509 condemned.kill
9510 respond("--- Lich: '#{condemned}' has been stopped by #{Script.current}.")
9511 numkilled += 1
9512 end
9513 }
9514 if numkilled == 0
9515 return false
9516 else
9517 return numkilled
9518 end
9519end
9520
9521def running?(*snames)
9522 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 }) }
9523 true
9524end
9525
9526module Settings
9527 def Settings.load; end
9528 def Settings.save_all; end
9529 def Settings.clear; end
9530 def Settings.auto=(val); end
9531 def Settings.auto; end
9532 def Settings.autoload; end
9533end
9534
9535module GameSettings
9536 def GameSettings.load; end
9537 def GameSettings.save; end
9538 def GameSettings.save_all; end
9539 def GameSettings.clear; end
9540 def GameSettings.auto=(val); end
9541 def GameSettings.auto; end
9542 def GameSettings.autoload; end
9543end
9544
9545module CharSettings
9546 def CharSettings.load; end
9547 def CharSettings.save; end
9548 def CharSettings.save_all; end
9549 def CharSettings.clear; end
9550 def CharSettings.auto=(val); end
9551 def CharSettings.auto; end
9552 def CharSettings.autoload; end
9553end
9554
9555module UserVars
9556 def UserVars.list
9557 Vars.list
9558 end
9559 def UserVars.method_missing(arg1, arg2='')
9560 Vars.method_missing(arg1, arg2)
9561 end
9562 def UserVars.change(var_name, value, t=nil)
9563 Vars[var_name] = value
9564 end
9565 def UserVars.add(var_name, value, t=nil)
9566 Vars[var_name] = Vars[var_name].split(', ').push(value).join(', ')
9567 end
9568 def UserVars.delete(var_name, t=nil)
9569 Vars[var_name] = nil
9570 end
9571 def UserVars.list_global
9572 Array.new
9573 end
9574 def UserVars.list_char
9575 Vars.list
9576 end
9577end
9578
9579def start_exec_script(cmd_data, options=Hash.new)
9580 ExecScript.start(cmd_data, options)
9581end
9582
9583module Setting
9584 def Setting.[](name)
9585 Settings[name]
9586 end
9587 def Setting.[]=(name, value)
9588 Settings[name] = value
9589 end
9590 def Setting.to_hash(scope=':')
9591 Settings.to_hash
9592 end
9593end
9594module GameSetting
9595 def GameSetting.[](name)
9596 GameSettings[name]
9597 end
9598 def GameSetting.[]=(name, value)
9599 GameSettings[name] = value
9600 end
9601 def GameSetting.to_hash(scope=':')
9602 GameSettings.to_hash
9603 end
9604end
9605module CharSetting
9606 def CharSetting.[](name)
9607 CharSettings[name]
9608 end
9609 def CharSetting.[]=(name, value)
9610 CharSettings[name] = value
9611 end
9612 def CharSetting.to_hash(scope=':')
9613 CharSettings.to_hash
9614 end
9615end
9616module Vars
9617 def Vars.save
9618 end
9619end
9620class StringProc
9621 def StringProc._load(string)
9622 StringProc.new(string)
9623 end
9624end
9625class String
9626 def to_a # for compatibility with Ruby 1.8
9627 [self]
9628 end
9629 def silent
9630 false
9631 end
9632 def split_as_list
9633 string = self
9634 string.sub!(/^You (?:also see|notice) |^In the .+ you see /, ',')
9635 string.sub('.','').sub(/ and (an?|some|the)/, ', \1').split(',').reject { |str| str.strip.empty? }.collect { |str| str.lstrip }
9636 end
9637end
9638#
9639# End deprecated stuff
9640#
9641
9642undef :abort
9643alias :mana :checkmana
9644alias :mana? :checkmana
9645alias :max_mana :maxmana
9646alias :health :checkhealth
9647alias :health? :checkhealth
9648alias :spirit :checkspirit
9649alias :spirit? :checkspirit
9650alias :stamina :checkstamina
9651alias :stamina? :checkstamina
9652alias :stunned? :checkstunned
9653alias :bleeding? :checkbleeding
9654alias :reallybleeding? :checkreallybleeding
9655alias :dead? :checkdead
9656alias :hiding? :checkhidden
9657alias :hidden? :checkhidden
9658alias :hidden :checkhidden
9659alias :checkhiding :checkhidden
9660alias :invisible? :checkinvisible
9661alias :standing? :checkstanding
9662alias :kneeling? :checkkneeling
9663alias :sitting? :checksitting
9664alias :stance? :checkstance
9665alias :stance :checkstance
9666alias :joined? :checkgrouped
9667alias :checkjoined :checkgrouped
9668alias :group? :checkgrouped
9669alias :myname? :checkname
9670alias :active? :checkspell
9671alias :righthand? :checkright
9672alias :lefthand? :checkleft
9673alias :righthand :checkright
9674alias :lefthand :checkleft
9675alias :mind? :checkmind
9676alias :checkactive :checkspell
9677alias :forceput :fput
9678alias :send_script :send_scripts
9679alias :stop_scripts :stop_script
9680alias :kill_scripts :stop_script
9681alias :kill_script :stop_script
9682alias :fried? :checkfried
9683alias :saturated? :checksaturated
9684alias :webbed? :checkwebbed
9685alias :pause_scripts :pause_script
9686alias :roomdescription? :checkroomdescrip
9687alias :prepped? :checkprep
9688alias :checkprepared :checkprep
9689alias :unpause_scripts :unpause_script
9690alias :priority? :setpriority
9691alias :checkoutside :outside?
9692alias :toggle_status :status_tags
9693alias :encumbrance? :checkencumbrance
9694alias :bounty? :checkbounty
9695
9696
9697
9698#
9699# Program start
9700#
9701
9702ARGV.delete_if { |arg| arg =~ /launcher\.exe/i } # added by Simutronics Game Entry
9703
9704argv_options = Hash.new
9705bad_args = Array.new
9706
9707for arg in ARGV
9708 if (arg == '-h') or (arg == '--help')
9709 puts "
9710 -h, --help Display this message and exit
9711 -v, --version Display version number and credits and exit
9712
9713 --home=<directory> Set home directory for Lich (default: location of this file)
9714 --scripts=<directory> Set directory for script files (default: home/scripts)
9715 --data=<directory> Set directory for data files (default: home/data)
9716 --temp=<directory> Set directory for temp files (default: home/temp)
9717 --logs=<directory> Set directory for log files (default: home/logs)
9718 --maps=<directory> Set directory for map images (default: home/maps)
9719 --backup=<directory> Set directory for backups (default: home/backup)
9720
9721 --start-scripts=<script1,script2,etc> Start the specified scripts after login
9722
9723"
9724 exit
9725 elsif (arg == '-v') or (arg == '--version')
9726 puts "The Lich, version #{LICH_VERSION}"
9727 puts ' (an implementation of the Ruby interpreter by Yukihiro Matsumoto designed to be a \'script engine\' for text-based MUDs)'
9728 puts ''
9729 puts '- The Lich program and all material collectively referred to as "The Lich project" is copyright (C) 2005-2006 Murray Miron.'
9730 puts '- The Gemstone IV and DragonRealms games are copyright (C) Simutronics Corporation.'
9731 puts '- The Wizard front-end and the StormFront front-end are also copyrighted by the Simutronics Corporation.'
9732 puts '- Ruby is (C) Yukihiro \'Matz\' Matsumoto.'
9733 puts ''
9734 puts 'Thanks to all those who\'ve reported bugs and helped me track down problems on both Windows and Linux.'
9735 exit
9736 elsif arg == '--link-to-sge'
9737 result = Lich.link_to_sge
9738 if $stdout.isatty
9739 if result
9740 $stdout.puts "Successfully linked to SGE."
9741 else
9742 $stdout.puts "Failed to link to SGE."
9743 end
9744 end
9745 exit
9746 elsif arg == '--unlink-from-sge'
9747 result = Lich.unlink_from_sge
9748 if $stdout.isatty
9749 if result
9750 $stdout.puts "Successfully unlinked from SGE."
9751 else
9752 $stdout.puts "Failed to unlink from SGE."
9753 end
9754 end
9755 exit
9756 elsif arg == '--link-to-sal'
9757 result = Lich.link_to_sal
9758 if $stdout.isatty
9759 if result
9760 $stdout.puts "Successfully linked to SAL files."
9761 else
9762 $stdout.puts "Failed to link to SAL files."
9763 end
9764 end
9765 exit
9766 elsif arg == '--unlink-from-sal'
9767 result = Lich.unlink_from_sal
9768 if $stdout.isatty
9769 if result
9770 $stdout.puts "Successfully unlinked from SAL files."
9771 else
9772 $stdout.puts "Failed to unlink from SAL files."
9773 end
9774 end
9775 exit
9776 elsif arg == '--install' # deprecated
9777 if Lich.link_to_sge and Lich.link_to_sal
9778 $stdout.puts 'Install was successful.'
9779 Lich.log 'Install was successful.'
9780 else
9781 $stdout.puts 'Install failed.'
9782 Lich.log 'Install failed.'
9783 end
9784 exit
9785 elsif arg == '--uninstall' # deprecated
9786 if Lich.unlink_from_sge and Lich.unlink_from_sal
9787 $stdout.puts 'Uninstall was successful.'
9788 Lich.log 'Uninstall was successful.'
9789 else
9790 $stdout.puts 'Uninstall failed.'
9791 Lich.log 'Uninstall failed.'
9792 end
9793 exit
9794 elsif arg =~ /^--(?:home)=(.+)$/i
9795 LICH_DIR = $1.sub(/[\\\/]$/, '')
9796 elsif arg =~ /^--temp=(.+)$/i
9797 TEMP_DIR = $1.sub(/[\\\/]$/, '')
9798 elsif arg =~ /^--scripts=(.+)$/i
9799 SCRIPT_DIR = $1.sub(/[\\\/]$/, '')
9800 elsif arg =~ /^--maps=(.+)$/i
9801 MAP_DIR = $1.sub(/[\\\/]$/, '')
9802 elsif arg =~ /^--logs=(.+)$/i
9803 LOG_DIR = $1.sub(/[\\\/]$/, '')
9804 elsif arg =~ /^--backup=(.+)$/i
9805 BACKUP_DIR = $1.sub(/[\\\/]$/, '')
9806 elsif arg =~ /^--data=(.+)$/i
9807 DATA_DIR = $1.sub(/[\\\/]$/, '')
9808 elsif arg =~ /^--start-scripts=(.+)$/i
9809 argv_options[:start_scripts] = $1
9810 elsif arg =~ /^--reconnect$/i
9811 argv_options[:reconnect] = true
9812 elsif arg =~ /^--reconnect-delay=(.+)$/i
9813 argv_options[:reconnect_delay] = $1
9814 elsif arg =~ /^--host=(.+):(.+)$/
9815 argv_options[:host] = { :domain => $1, :port => $2.to_i }
9816 elsif arg =~ /^--hosts-file=(.+)$/i
9817 argv_options[:hosts_file] = $1
9818 elsif arg =~ /^--gui$/i
9819 argv_options[:gui] = true
9820 elsif arg =~ /^--game=(.+)$/i
9821 argv_options[:game] = $1
9822 elsif arg =~ /^--account=(.+)$/i
9823 argv_options[:account] = $1
9824 elsif arg =~ /^--password=(.+)$/i
9825 argv_options[:password] = $1
9826 elsif arg =~ /^--character=(.+)$/i
9827 argv_options[:character] = $1
9828 elsif arg =~ /^--frontend=(.+)$/i
9829 argv_options[:frontend] = $1
9830 elsif arg =~ /^--frontend-command=(.+)$/i
9831 argv_options[:frontend_command] = $1
9832 elsif arg =~ /^--save$/i
9833 argv_options[:save] = true
9834 elsif arg =~ /^--wine(?:\-prefix)?=.+$/i
9835 nil # already used when defining the Wine module
9836 elsif arg =~ /\.sal$|Gse\.~xt$/i
9837 argv_options[:sal] = arg
9838 unless File.exists?(argv_options[:sal])
9839 if ARGV.join(' ') =~ /([A-Z]:\\.+?\.(?:sal|~xt))/i
9840 argv_options[:sal] = $1
9841 end
9842 end
9843 unless File.exists?(argv_options[:sal])
9844 if defined?(Wine)
9845 argv_options[:sal] = "#{Wine::PREFIX}/drive_c/#{argv_options[:sal][3..-1].split('\\').join('/')}"
9846 end
9847 end
9848 bad_args.clear
9849 else
9850 bad_args.push(arg)
9851 end
9852end
9853
9854LICH_DIR ||= File.dirname(File.expand_path($PROGRAM_NAME))
9855TEMP_DIR ||= "#{LICH_DIR}/temp"
9856DATA_DIR ||= "#{LICH_DIR}/data"
9857SCRIPT_DIR ||= "#{LICH_DIR}/scripts"
9858MAP_DIR ||= "#{LICH_DIR}/maps"
9859LOG_DIR ||= "#{LICH_DIR}/logs"
9860BACKUP_DIR ||= "#{LICH_DIR}/backup"
9861
9862unless File.exists?(LICH_DIR)
9863 begin
9864 Dir.mkdir(LICH_DIR)
9865 rescue
9866 message = "An error occured while attempting to create directory #{LICH_DIR}\n\n"
9867 if not File.exists?(LICH_DIR.sub(/[\\\/]$/, '').slice(/^.+[\\\/]/).chop)
9868 message.concat "This was likely because the parent directory (#{LICH_DIR.sub(/[\\\/]$/, '').slice(/^.+[\\\/]/).chop}) doesn't exist."
9869 elsif defined?(Win32) and (Win32.GetVersionEx[:dwMajorVersion] >= 6) and (dir !~ /^[A-z]\:\\(Users|Documents and Settings)/)
9870 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."
9871 else
9872 message.concat $!
9873 end
9874 Lich.msgbox(:message => message, :icon => :error)
9875 exit
9876 end
9877end
9878
9879Dir.chdir(LICH_DIR)
9880
9881unless File.exists?(TEMP_DIR)
9882 begin
9883 Dir.mkdir(TEMP_DIR)
9884 rescue
9885 message = "An error occured while attempting to create directory #{TEMP_DIR}\n\n"
9886 if not File.exists?(TEMP_DIR.sub(/[\\\/]$/, '').slice(/^.+[\\\/]/).chop)
9887 message.concat "This was likely because the parent directory (#{TEMP_DIR.sub(/[\\\/]$/, '').slice(/^.+[\\\/]/).chop}) doesn't exist."
9888 elsif defined?(Win32) and (Win32.GetVersionEx[:dwMajorVersion] >= 6) and (dir !~ /^[A-z]\:\\(Users|Documents and Settings)/)
9889 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."
9890 else
9891 message.concat $!
9892 end
9893 Lich.msgbox(:message => message, :icon => :error)
9894 exit
9895 end
9896end
9897
9898begin
9899 debug_filename = "#{TEMP_DIR}/debug-#{Time.now.strftime("%Y-%m-%d-%H-%M-%S")}.log"
9900 $stderr = File.open(debug_filename, 'w')
9901rescue
9902 message = "An error occured while attempting to create file #{debug_filename}\n\n"
9903 if defined?(Win32) and (TEMP_DIR !~ /^[A-z]\:\\(Users|Documents and Settings)/) and not Win32.isXP?
9904 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."
9905 else
9906 message.concat $!
9907 end
9908 Lich.msgbox(:message => message, :icon => :error)
9909 exit
9910end
9911
9912$stderr.sync = true
9913Lich.log "info: Lich #{LICH_VERSION}"
9914Lich.log "info: Ruby #{RUBY_VERSION}"
9915Lich.log "info: #{RUBY_PLATFORM}"
9916Lich.log early_gtk_error if early_gtk_error
9917early_gtk_error = nil
9918
9919unless File.exists?(DATA_DIR)
9920 begin
9921 Dir.mkdir(DATA_DIR)
9922 rescue
9923 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
9924 Lich.msgbox(:message => "An error occured while attempting to create directory #{DATA_DIR}\n\n#{$!}", :icon => :error)
9925 exit
9926 end
9927end
9928unless File.exists?(SCRIPT_DIR)
9929 begin
9930 Dir.mkdir(SCRIPT_DIR)
9931 rescue
9932 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
9933 Lich.msgbox(:message => "An error occured while attempting to create directory #{SCRIPT_DIR}\n\n#{$!}", :icon => :error)
9934 exit
9935 end
9936end
9937unless File.exists?(MAP_DIR)
9938 begin
9939 Dir.mkdir(MAP_DIR)
9940 rescue
9941 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
9942 Lich.msgbox(:message => "An error occured while attempting to create directory #{MAP_DIR}\n\n#{$!}", :icon => :error)
9943 exit
9944 end
9945end
9946unless File.exists?(LOG_DIR)
9947 begin
9948 Dir.mkdir(LOG_DIR)
9949 rescue
9950 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
9951 Lich.msgbox(:message => "An error occured while attempting to create directory #{LOG_DIR}\n\n#{$!}", :icon => :error)
9952 exit
9953 end
9954end
9955unless File.exists?(BACKUP_DIR)
9956 begin
9957 Dir.mkdir(BACKUP_DIR)
9958 rescue
9959 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
9960 Lich.msgbox(:message => "An error occured while attempting to create directory #{BACKUP_DIR}\n\n#{$!}", :icon => :error)
9961 exit
9962 end
9963end
9964
9965Lich.init_db
9966
9967# deprecated
9968$lich_dir = "#{LICH_DIR}/"
9969$temp_dir = "#{TEMP_DIR}/"
9970$script_dir = "#{SCRIPT_DIR}/"
9971$data_dir = "#{DATA_DIR}/"
9972
9973#
9974# only keep the last 20 debug files
9975#
9976Dir.entries(TEMP_DIR).find_all { |fn| fn =~ /^debug-\d+-\d+-\d+-\d+-\d+-\d+\.log$/ }.sort.reverse[20..-1].each { |oldfile|
9977 begin
9978 File.delete("#{TEMP_DIR}/#{oldfile}")
9979 rescue
9980 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
9981 end
9982}
9983
9984
9985
9986
9987
9988
9989
9990
9991begin
9992 did_trusted_defaults = Lich.db.get_first_value("SELECT value FROM lich_settings WHERE name='did_trusted_defaults';")
9993rescue SQLite3::BusyException
9994 sleep 0.1
9995 retry
9996end
9997if did_trusted_defaults.nil?
9998 Script.trust('repository')
9999 Script.trust('lnet')
10000 Script.trust('narost')
10001 begin
10002 Lich.db.execute("INSERT INTO lich_settings(name,value) VALUES('did_trusted_defaults', 'yes');")
10003 rescue SQLite3::BusyException
10004 sleep 0.1
10005 retry
10006 end
10007end
10008
10009if ARGV.any? { |arg| (arg == '-h') or (arg == '--help') }
10010 puts 'Usage: lich [OPTION]'
10011 puts ''
10012 puts 'Options are:'
10013 puts ' -h, --help Display this list.'
10014 puts ' -V, --version Display the program version number and credits.'
10015 puts ''
10016 puts ' -d, --directory Set the main Lich program directory.'
10017 puts ' --script-dir Set the directoy where Lich looks for scripts.'
10018 puts ' --data-dir Set the directory where Lich will store script data.'
10019 puts ' --temp-dir Set the directory where Lich will store temporary files.'
10020 puts ''
10021 puts ' -w, --wizard Run in Wizard mode (default)'
10022 puts ' -s, --stormfront Run in StormFront mode.'
10023 puts ' --avalon Run in Avalon mode.'
10024 puts ''
10025 puts ' --gemstone Connect to the Gemstone IV Prime server (default).'
10026 puts ' --dragonrealms Connect to the DragonRealms server.'
10027 puts ' --platinum Connect to the Gemstone IV/DragonRealms Platinum server.'
10028 puts ' -g, --game Set the IP address and port of the game. See example below.'
10029 puts ''
10030 puts ' --install Edits the Windows/WINE registry so that Lich is started when logging in using the website or SGE.'
10031 puts ' --uninstall Removes Lich from the registry.'
10032 puts ''
10033 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).'
10034 puts ''
10035 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.'
10036 puts ''
10037 puts ''
10038 puts 'Examples:'
10039 puts ' lich -w -d /usr/bin/lich/ (run Lich in Wizard mode using the dir \'/usr/bin/lich/\' as the program\'s home)'
10040 puts ' lich -g gs3.simutronics.net:4000 (run Lich using the IP address \'gs3.simutronics.net\' and the port number \'4000\')'
10041 puts ' lich --script-dir /mydir/scripts (run Lich with its script directory set to \'/mydir/scripts\')'
10042 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\')'
10043 puts ''
10044 exit
10045end
10046
10047
10048
10049if arg = ARGV.find { |a| a == '--hosts-dir' }
10050 i = ARGV.index(arg)
10051 ARGV.delete_at(i)
10052 hosts_dir = ARGV[i]
10053 ARGV.delete_at(i)
10054 if hosts_dir and File.exists?(hosts_dir)
10055 hosts_dir = hosts_dir.tr('\\', '/')
10056 hosts_dir += '/' unless hosts_dir[-1..-1] == '/'
10057 else
10058 $stdout.puts "warning: given hosts directory does not exist: #{hosts_dir}"
10059 hosts_dir = nil
10060 end
10061else
10062 hosts_dir = nil
10063end
10064
10065detachable_client_port = nil
10066if arg = ARGV.find { |a| a =~ /^\-\-detachable\-client=[0-9]+$/ }
10067 detachable_client_port = /^\-\-detachable\-client=([0-9]+)$/.match(arg).captures.first
10068end
10069
10070
10071
10072#
10073# import Lich 4.4 settings to Lich 4.6
10074#
10075begin
10076 did_import = Lich.db.get_first_value("SELECT value FROM lich_settings WHERE name='imported_44_data';")
10077rescue SQLite3::BusyException
10078 sleep 0.1
10079 retry
10080end
10081if did_import.nil?
10082 begin
10083 Lich.db.execute('BEGIN')
10084 rescue SQLite3::BusyException
10085 sleep 0.1
10086 retry
10087 end
10088 begin
10089 Lich.db.execute("INSERT INTO lich_settings(name,value) VALUES('imported_44_data', 'yes');")
10090 rescue SQLite3::BusyException
10091 sleep 0.1
10092 retry
10093 end
10094 backup_dir = 'data44/'
10095 Dir.mkdir(backup_dir) unless File.exists?(backup_dir)
10096 Dir.entries(DATA_DIR).find_all { |fn| fn =~ /\.sav$/i }.each { |fn|
10097 next if fn == 'lich.sav'
10098 s = fn.match(/^(.+)\.sav$/i).captures.first
10099 data = File.open("#{DATA_DIR}/#{fn}", 'rb') { |f| f.read }
10100 blob = SQLite3::Blob.new(data)
10101 begin
10102 Lich.db.execute("INSERT OR REPLACE INTO script_auto_settings(script,scope,hash) VALUES(?,':',?);", s.encode('UTF-8'), blob)
10103 rescue SQLite3::BusyException
10104 sleep 0.1
10105 retry
10106 end
10107 File.rename("#{DATA_DIR}/#{fn}", "#{backup_dir}#{fn}")
10108 File.rename("#{DATA_DIR}/#{fn}~", "#{backup_dir}#{fn}~") if File.exists?("#{DATA_DIR}/#{fn}~")
10109 }
10110 Dir.entries(DATA_DIR).find_all { |fn| File.directory?("#{DATA_DIR}/#{fn}") and fn !~ /^\.\.?$/}.each { |game|
10111 Dir.mkdir("#{backup_dir}#{game}") unless File.exists?("#{backup_dir}#{game}")
10112 Dir.entries("#{DATA_DIR}/#{game}").find_all { |fn| fn =~ /\.sav$/i }.each { |fn|
10113 s = fn.match(/^(.+)\.sav$/i).captures.first
10114 data = File.open("#{DATA_DIR}/#{game}/#{fn}", 'rb') { |f| f.read }
10115 blob = SQLite3::Blob.new(data)
10116 begin
10117 Lich.db.execute('INSERT OR REPLACE INTO script_auto_settings(script,scope,hash) VALUES(?,?,?);', s.encode('UTF-8'), game.encode('UTF-8'), blob)
10118 rescue SQLite3::BusyException
10119 sleep 0.1
10120 retry
10121 end
10122 File.rename("#{DATA_DIR}/#{game}/#{fn}", "#{backup_dir}#{game}/#{fn}")
10123 File.rename("#{DATA_DIR}/#{game}/#{fn}~", "#{backup_dir}#{game}/#{fn}~") if File.exists?("#{DATA_DIR}/#{game}/#{fn}~")
10124 }
10125 Dir.entries("#{DATA_DIR}/#{game}").find_all { |fn| File.directory?("#{DATA_DIR}/#{game}/#{fn}") and fn !~ /^\.\.?$/ }.each { |char|
10126 Dir.mkdir("#{backup_dir}#{game}/#{char}") unless File.exists?("#{backup_dir}#{game}/#{char}")
10127 Dir.entries("#{DATA_DIR}/#{game}/#{char}").find_all { |fn| fn =~ /\.sav$/i }.each { |fn|
10128 s = fn.match(/^(.+)\.sav$/i).captures.first
10129 data = File.open("#{DATA_DIR}/#{game}/#{char}/#{fn}", 'rb') { |f| f.read }
10130 blob = SQLite3::Blob.new(data)
10131 begin
10132 Lich.db.execute('INSERT OR REPLACE INTO script_auto_settings(script,scope,hash) VALUES(?,?,?);', s.encode('UTF-8'), "#{game}:#{char}".encode('UTF-8'), blob)
10133 rescue SQLite3::BusyException
10134 sleep 0.1
10135 retry
10136 end
10137 File.rename("#{DATA_DIR}/#{game}/#{char}/#{fn}", "#{backup_dir}#{game}/#{char}/#{fn}")
10138 File.rename("#{DATA_DIR}/#{game}/#{char}/#{fn}~", "#{backup_dir}#{game}/#{char}/#{fn}~") if File.exists?("#{DATA_DIR}/#{game}/#{char}/#{fn}~")
10139 }
10140 if File.exists?("#{DATA_DIR}/#{game}/#{char}/uservars.dat")
10141 blob = SQLite3::Blob.new(File.open("#{DATA_DIR}/#{game}/#{char}/uservars.dat", 'rb') { |f| f.read })
10142 begin
10143 Lich.db.execute('INSERT OR REPLACE INTO uservars(scope,hash) VALUES(?,?);', "#{game}:#{char}".encode('UTF-8'), blob)
10144 rescue SQLite3::BusyException
10145 sleep 0.1
10146 retry
10147 end
10148 blob = nil
10149 File.rename("#{DATA_DIR}/#{game}/#{char}/uservars.dat", "#{backup_dir}#{game}/#{char}/uservars.dat")
10150 end
10151 }
10152 }
10153 begin
10154 Lich.db.execute('END')
10155 rescue SQLite3::BusyException
10156 sleep 0.1
10157 retry
10158 end
10159 backup_dir = nil
10160 characters = Array.new
10161 begin
10162 Lich.db.execute("SELECT DISTINCT(scope) FROM script_auto_settings;").each { |row| characters.push(row[0]) if row[0] =~ /^.+:.+$/ }
10163 rescue SQLite3::BusyException
10164 sleep 0.1
10165 retry
10166 end
10167 if File.exists?("#{DATA_DIR}/lich.sav")
10168 data = File.open("#{DATA_DIR}/lich.sav", 'rb') { |f| Marshal.load(f.read) }
10169 favs = data['favorites']
10170 aliases = data['alias']
10171 trusted = data['lichsettings']['trusted_scripts']
10172 if favs.class == Hash
10173 begin
10174 Lich.db.execute('BEGIN')
10175 rescue SQLite3::BusyException
10176 sleep 0.1
10177 retry
10178 end
10179 favs.each { |scope,script_list|
10180 hash = { 'scripts' => Array.new }
10181 script_list.each { |name,args| hash['scripts'].push(:name => name, :args => args) }
10182 blob = SQLite3::Blob.new(Marshal.dump(hash))
10183 if scope == 'global'
10184 begin
10185 Lich.db.execute("INSERT OR REPLACE INTO script_auto_settings(script,scope,hash) VALUES('autostart',':',?);", blob)
10186 rescue SQLite3::BusyException
10187 sleep 0.1
10188 retry
10189 end
10190 else
10191 characters.find_all { |c| c =~ /^.+:#{scope}$/ }.each { |c|
10192 begin
10193 Lich.db.execute("INSERT OR REPLACE INTO script_auto_settings(script,scope,hash) VALUES('autostart',?,?);", c.encode('UTF-8'), blob)
10194 rescue SQLite3::BusyException
10195 sleep 0.1
10196 retry
10197 end
10198 }
10199 end
10200 }
10201 begin
10202 Lich.db.execute('END')
10203 rescue SQLite3::BusyException
10204 sleep 0.1
10205 retry
10206 end
10207 end
10208 favs = nil
10209
10210 db = SQLite3::Database.new("#{DATA_DIR}/alias.db3")
10211 begin
10212 db.execute("CREATE TABLE IF NOT EXISTS global (trigger TEXT NOT NULL, target TEXT NOT NULL, UNIQUE(trigger));")
10213 rescue SQLite3::BusyException
10214 sleep 0.1
10215 retry
10216 end
10217 begin
10218 db.execute('BEGIN')
10219 rescue SQLite3::BusyException
10220 sleep 0.1
10221 retry
10222 end
10223 if aliases.class == Hash
10224 aliases.each { |scope,alias_hash|
10225 if scope == 'global'
10226 tables = ['global']
10227 else
10228 tables = characters.find_all { |c| c =~ /^.+:#{scope}$/ }.collect { |t| t.downcase.sub(':', '_').gsub(/[^a-z_]/, '').encode('UTF-8') }
10229 end
10230 tables.each { |t|
10231 begin
10232 db.execute("CREATE TABLE IF NOT EXISTS #{t} (trigger TEXT NOT NULL, target TEXT NOT NULL, UNIQUE(trigger));")
10233 rescue SQLite3::BusyException
10234 sleep 0.1
10235 retry
10236 end
10237 }
10238 alias_hash.each { |trigger,target|
10239 tables.each { |t|
10240 begin
10241 db.execute("INSERT OR REPLACE INTO #{t} (trigger,target) VALUES(?,?);", trigger.gsub(/\\(.)/) { $1 }.encode('UTF-8'), target.encode('UTF-8'))
10242 rescue SQLite3::BusyException
10243 sleep 0.1
10244 retry
10245 end
10246 }
10247 }
10248 }
10249 end
10250 begin
10251 db.execute('END')
10252 rescue SQLite3::BusyException
10253 sleep 0.1
10254 retry
10255 end
10256
10257 begin
10258 Lich.db.execute('BEGIN')
10259 rescue SQLite3::BusyException
10260 sleep 0.1
10261 retry
10262 end
10263 trusted.each { |script_name|
10264 begin
10265 Lich.db.execute('INSERT OR REPLACE INTO trusted_scripts(name) values(?);', script_name.encode('UTF-8'))
10266 rescue SQLite3::BusyException
10267 sleep 0.1
10268 retry
10269 end
10270 }
10271 begin
10272 Lich.db.execute('END')
10273 rescue SQLite3::BusyException
10274 sleep 0.1
10275 retry
10276 end
10277 db.close rescue nil
10278 db = nil
10279 data = nil
10280 aliases = nil
10281 characters = nil
10282 trusted = nil
10283 File.rename("#{DATA_DIR}/lich.sav", "#{backup_dir}lich.sav")
10284 end
10285end
10286
10287if argv_options[:sal]
10288 unless File.exists?(argv_options[:sal])
10289 Lich.log "error: launch file does not exist: #{argv_options[:sal]}"
10290 Lich.msgbox "error: launch file does not exist: #{argv_options[:sal]}"
10291 exit
10292 end
10293 Lich.log "info: launch file: #{argv_options[:sal]}"
10294 if argv_options[:sal] =~ /SGE\.sal/i
10295 unless launcher_cmd = Lich.get_simu_launcher
10296 $stdout.puts 'error: failed to find the Simutronics launcher'
10297 Lich.log 'error: failed to find the Simutronics launcher'
10298 exit
10299 end
10300 launcher_cmd.sub!('%1', argv_options[:sal])
10301 Lich.log "info: launcher_cmd: #{launcher_cmd}"
10302 if defined?(Win32) and launcher_cmd =~ /^"(.*?)"\s*(.*)$/
10303 dir_file = $1
10304 param = $2
10305 dir = dir_file.slice(/^.*[\\\/]/)
10306 file = dir_file.sub(/^.*[\\\/]/, '')
10307 operation = (Win32.isXP? ? 'open' : 'runas')
10308 Win32.ShellExecute(:lpOperation => operation, :lpFile => file, :lpDirectory => dir, :lpParameters => param)
10309 if r < 33
10310 Lich.log "error: Win32.ShellExecute returned #{r}; Win32.GetLastError: #{Win32.GetLastError}"
10311 end
10312 elsif defined?(Wine)
10313 system("#{Wine::BIN} #{launcher_cmd}")
10314 else
10315 system(launcher_cmd)
10316 end
10317 exit
10318 end
10319end
10320
10321if arg = ARGV.find { |a| (a == '-g') or (a == '--game') }
10322 game_host, game_port = ARGV[ARGV.index(arg)+1].split(':')
10323 game_port = game_port.to_i
10324 if ARGV.any? { |arg| (arg == '-s') or (arg == '--stormfront') }
10325 $frontend = 'stormfront'
10326 elsif ARGV.any? { |arg| (arg == '-w') or (arg == '--wizard') }
10327 $frontend = 'wizard'
10328 elsif ARGV.any? { |arg| arg == '--avalon' }
10329 $frontend = 'avalon'
10330 else
10331 $frontend = 'unknown'
10332 end
10333elsif ARGV.include?('--gemstone')
10334 if ARGV.include?('--platinum')
10335 $platinum = true
10336 if ARGV.any? { |arg| (arg == '-s') or (arg == '--stormfront') }
10337 game_host = 'storm.gs4.game.play.net'
10338 game_port = 10124
10339 $frontend = 'stormfront'
10340 else
10341 game_host = 'gs-plat.simutronics.net'
10342 game_port = 10121
10343 if ARGV.any? { |arg| arg == '--avalon' }
10344 $frontend = 'avalon'
10345 else
10346 $frontend = 'wizard'
10347 end
10348 end
10349 else
10350 $platinum = false
10351 if ARGV.any? { |arg| (arg == '-s') or (arg == '--stormfront') }
10352 game_host = 'storm.gs4.game.play.net'
10353 game_port = 10024
10354 $frontend = 'stormfront'
10355 else
10356 game_host = 'gs3.simutronics.net'
10357 game_port = 4900
10358 if ARGV.any? { |arg| arg == '--avalon' }
10359 $frontend = 'avalon'
10360 else
10361 $frontend = 'wizard'
10362 end
10363 end
10364 end
10365elsif ARGV.include?('--shattered')
10366 $platinum = false
10367 if ARGV.any? { |arg| (arg == '-s') or (arg == '--stormfront') }
10368 game_host = 'storm.gs4.game.play.net'
10369 game_port = 10324
10370 $frontend = 'stormfront'
10371 else
10372 game_host = 'gs4.simutronics.net'
10373 game_port = 10321
10374 if ARGV.any? { |arg| arg == '--avalon' }
10375 $frontend = 'avalon'
10376 else
10377 $frontend = 'wizard'
10378 end
10379 end
10380elsif ARGV.include?('--dragonrealms')
10381 if ARGV.include?('--platinum')
10382 $platinum = true
10383 if ARGV.any? { |arg| (arg == '-s') or (arg == '--stormfront') }
10384 $stdout.puts "fixme"
10385 Lich.log "fixme"
10386 exit
10387 $frontend = 'stormfront'
10388 else
10389 $stdout.puts "fixme"
10390 Lich.log "fixme"
10391 exit
10392 $frontend = 'wizard'
10393 end
10394 else
10395 $platinum = false
10396 if ARGV.any? { |arg| (arg == '-s') or (arg == '--stormfront') }
10397 $frontend = 'stormfront'
10398 $stdout.puts "fixme"
10399 Lich.log "fixme"
10400 exit
10401 else
10402 game_host = 'dr.simutronics.net'
10403 game_port = 4901
10404 if ARGV.any? { |arg| arg == '--avalon' }
10405 $frontend = 'avalon'
10406 else
10407 $frontend = 'wizard'
10408 end
10409 end
10410 end
10411else
10412 game_host, game_port = nil, nil
10413 Lich.log "info: no force-mode info given"
10414end
10415
10416if defined?(Gtk)
10417 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) }
10418end
10419
10420main_thread = Thread.new {
10421 test_mode = false
10422 $SEND_CHARACTER = '>'
10423 $cmd_prefix = '<c>'
10424 $clean_lich_char = ';' # fixme
10425 $lich_char = Regexp.escape($clean_lich_char)
10426
10427 launch_data = nil
10428
10429 if ARGV.include?('--login')
10430 if File.exists?("#{DATA_DIR}/entry.dat")
10431 entry_data = File.open("#{DATA_DIR}/entry.dat", 'r') { |file|
10432 begin
10433 Marshal.load(file.read.unpack('m').first)
10434 rescue
10435 Array.new
10436 end
10437 }
10438 else
10439 entry_data = Array.new
10440 end
10441 char_name = ARGV[ARGV.index('--login')+1].capitalize
10442 if ARGV.include?('--gemstone')
10443 if ARGV.include?('--platinum')
10444 data = entry_data.find { |d| (d[:char_name] == char_name) and (d[:game_code] == 'GSX') }
10445 elsif ARGV.include?('--shattered')
10446 data = entry_data.find { |d| (d[:char_name] == char_name) and (d[:game_code] == 'GSF') }
10447 else
10448 data = entry_data.find { |d| (d[:char_name] == char_name) and (d[:game_code] == 'GS3') }
10449 end
10450 elsif ARGV.include?('--shattered')
10451 data = entry_data.find { |d| (d[:char_name] == char_name) and (d[:game_code] == 'GSF') }
10452 else
10453 data = entry_data.find { |d| (d[:char_name] == char_name) }
10454 end
10455 if data
10456 Lich.log "info: using quick game entry settings for #{char_name}"
10457 msgbox = proc { |msg|
10458 if defined?(Gtk)
10459 done = false
10460 Gtk.queue {
10461 dialog = Gtk::MessageDialog.new(nil, Gtk::Dialog::DESTROY_WITH_PARENT, Gtk::MessageDialog::QUESTION, Gtk::MessageDialog::BUTTONS_CLOSE, msg)
10462 dialog.run
10463 dialog.destroy
10464 done = true
10465 }
10466 sleep 0.1 until done
10467 else
10468 $stdout.puts(msg)
10469 Lich.log(msg)
10470 end
10471 }
10472
10473 login_server = nil
10474 connect_thread = nil
10475 timeout_thread = Thread.new {
10476 sleep 30
10477 $stdout.puts "error: timed out connecting to eaccess.play.net:7900"
10478 Lich.log "error: timed out connecting to eaccess.play.net:7900"
10479 connect_thread.kill rescue nil
10480 login_server = nil
10481 }
10482 connect_thread = Thread.new {
10483 begin
10484 login_server = TCPSocket.new('eaccess.play.net', 7900)
10485 rescue
10486 login_server = nil
10487 $stdout.puts "error connecting to server: #{$!}"
10488 Lich.log "error connecting to server: #{$!}"
10489 end
10490 }
10491 connect_thread.join
10492 timeout_thread.kill rescue nil
10493
10494 if login_server
10495 login_server.puts "K\n"
10496 hashkey = login_server.gets
10497 if 'test'[0].class == String
10498 password = data[:password].split('').collect { |c| c.getbyte(0) }
10499 hashkey = hashkey.split('').collect { |c| c.getbyte(0) }
10500 else
10501 password = data[:password].split('').collect { |c| c[0] }
10502 hashkey = hashkey.split('').collect { |c| c[0] }
10503 end
10504 password.each_index { |i| password[i] = ((password[i]-32)^hashkey[i])+32 }
10505 password = password.collect { |c| c.chr }.join
10506 login_server.puts "A\t#{data[:user_id]}\t#{password}\n"
10507 password = nil
10508 response = login_server.gets
10509 login_key = /KEY\t([^\t]+)\t/.match(response).captures.first
10510 if login_key
10511 login_server.puts "M\n"
10512 response = login_server.gets
10513 if response =~ /^M\t/
10514 login_server.puts "F\t#{data[:game_code]}\n"
10515 response = login_server.gets
10516 if response =~ /NORMAL|PREMIUM|TRIAL|INTERNAL|FREE/
10517 login_server.puts "G\t#{data[:game_code]}\n"
10518 login_server.gets
10519 login_server.puts "P\t#{data[:game_code]}\n"
10520 login_server.gets
10521 login_server.puts "C\n"
10522 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]
10523 login_server.puts "L\t#{char_code}\tSTORM\n"
10524 response = login_server.gets
10525 if response =~ /^L\t/
10526 login_server.close unless login_server.closed?
10527 launch_data = response.sub(/^L\tOK\t/, '').split("\t")
10528 if data[:frontend] == 'wizard'
10529 launch_data.collect! { |line| line.sub(/GAMEFILE=.+/, 'GAMEFILE=WIZARD.EXE').sub(/GAME=.+/, 'GAME=WIZ').sub(/FULLGAMENAME=.+/, 'FULLGAMENAME=Wizard Front End') }
10530 end
10531 if data[:custom_launch]
10532 launch_data.push "CUSTOMLAUNCH=#{data[:custom_launch]}"
10533 if data[:custom_launch_dir]
10534 launch_data.push "CUSTOMLAUNCHDIR=#{data[:custom_launch_dir]}"
10535 end
10536 end
10537 else
10538 login_server.close unless login_server.closed?
10539 $stdout.puts "error: unrecognized response from server. (#{response})"
10540 Lich.log "error: unrecognized response from server. (#{response})"
10541 end
10542 else
10543 login_server.close unless login_server.closed?
10544 $stdout.puts "error: unrecognized response from server. (#{response})"
10545 Lich.log "error: unrecognized response from server. (#{response})"
10546 end
10547 else
10548 login_server.close unless login_server.closed?
10549 $stdout.puts "error: unrecognized response from server. (#{response})"
10550 Lich.log "error: unrecognized response from server. (#{response})"
10551 end
10552 else
10553 login_server.close unless login_server.closed?
10554 $stdout.puts "Something went wrong... probably invalid user id and/or password.\nserver response: #{response}"
10555 Lich.log "Something went wrong... probably invalid user id and/or password.\nserver response: #{response}"
10556 reconnect_if_wanted.call
10557 end
10558 else
10559 $stdout.puts "error: failed to connect to server"
10560 Lich.log "error: failed to connect to server"
10561 reconnect_if_wanted.call
10562 Lich.log "info: exiting..."
10563 Gtk.queue { Gtk.main_quit } if defined?(Gtk)
10564 exit
10565 end
10566 else
10567 $stdout.puts "error: failed to find login data for #{char_name}"
10568 Lich.log "error: failed to find login data for #{char_name}"
10569 end
10570 elsif defined?(Gtk) and ARGV.empty?
10571 if File.exists?("#{DATA_DIR}/entry.dat")
10572 entry_data = File.open("#{DATA_DIR}/entry.dat", 'r') { |file|
10573 begin
10574 Marshal.load(file.read.unpack('m').first).sort { |a,b| [a[:user_id].downcase, a[:char_name]] <=> [b[:user_id].downcase, b[:char_name]] }
10575 rescue
10576 Array.new
10577 end
10578 }
10579 else
10580 entry_data = Array.new
10581 end
10582 save_entry_data = false
10583 done = false
10584 Gtk.queue {
10585
10586 login_server = nil
10587 window = nil
10588 install_tab_loaded = false
10589
10590 msgbox = proc { |msg|
10591 dialog = Gtk::MessageDialog.new(window, Gtk::Dialog::DESTROY_WITH_PARENT, Gtk::MessageDialog::QUESTION, Gtk::MessageDialog::BUTTONS_CLOSE, msg)
10592 dialog.run
10593 dialog.destroy
10594 }
10595
10596 #
10597 # quick game entry tab
10598 #
10599 if entry_data.empty?
10600 box = Gtk::HBox.new
10601 box.pack_start(Gtk::Label.new('You have no saved login info.'), true, true, 0)
10602 quick_game_entry_tab = Gtk::VBox.new
10603 quick_game_entry_tab.border_width = 5
10604 quick_game_entry_tab.pack_start(box, true, true, 0)
10605 else
10606 quick_box = Gtk::VBox.new
10607 last_user_id = nil
10608 entry_data.each { |login_info|
10609 if login_info[:user_id].downcase != last_user_id
10610 last_user_id = login_info[:user_id].downcase
10611 quick_box.pack_start(Gtk::Label.new("Account: " + last_user_id), false, false, 6)
10612 end
10613
10614 label = Gtk::Label.new("#{login_info[:char_name]} (#{login_info[:game_name]}, #{login_info[:frontend]})")
10615 play_button = Gtk::Button.new('Play')
10616 remove_button = Gtk::Button.new('X')
10617 char_box = Gtk::HBox.new
10618 char_box.pack_start(label, false, false, 6)
10619 char_box.pack_end(remove_button, false, false, 0)
10620 char_box.pack_end(play_button, false, false, 0)
10621 quick_box.pack_start(char_box, false, false, 0)
10622 play_button.signal_connect('clicked') {
10623 play_button.sensitive = false
10624 begin
10625 login_server = nil
10626 connect_thread = Thread.new {
10627 login_server = TCPSocket.new('eaccess.play.net', 7900)
10628 }
10629 300.times {
10630 sleep 0.1
10631 break unless connect_thread.status
10632 }
10633 if connect_thread.status
10634 connect_thread.kill rescue nil
10635 msgbox.call "error: timed out connecting to eaccess.play.net:7900"
10636 end
10637 rescue
10638 msgbox.call "error connecting to server: #{$!}"
10639 play_button.sensitive = true
10640 end
10641 if login_server
10642 login_server.puts "K\n"
10643 hashkey = login_server.gets
10644 if 'test'[0].class == String
10645 password = login_info[:password].split('').collect { |c| c.getbyte(0) }
10646 hashkey = hashkey.split('').collect { |c| c.getbyte(0) }
10647 else
10648 password = login_info[:password].split('').collect { |c| c[0] }
10649 hashkey = hashkey.split('').collect { |c| c[0] }
10650 end
10651 password.each_index { |i| password[i] = ((password[i]-32)^hashkey[i])+32 }
10652 password = password.collect { |c| c.chr }.join
10653 login_server.puts "A\t#{login_info[:user_id]}\t#{password}\n"
10654 password = nil
10655 response = login_server.gets
10656 login_key = /KEY\t([^\t]+)\t/.match(response).captures.first
10657 if login_key
10658 login_server.puts "M\n"
10659 response = login_server.gets
10660 if response =~ /^M\t/
10661 login_server.puts "F\t#{login_info[:game_code]}\n"
10662 response = login_server.gets
10663 if response =~ /NORMAL|PREMIUM|TRIAL|INTERNAL|FREE/
10664 login_server.puts "G\t#{login_info[:game_code]}\n"
10665 login_server.gets
10666 login_server.puts "P\t#{login_info[:game_code]}\n"
10667 login_server.gets
10668 login_server.puts "C\n"
10669 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]
10670 login_server.puts "L\t#{char_code}\tSTORM\n"
10671 response = login_server.gets
10672 if response =~ /^L\t/
10673 login_server.close unless login_server.closed?
10674 launch_data = response.sub(/^L\tOK\t/, '').split("\t")
10675 if login_info[:frontend] == 'wizard'
10676 launch_data.collect! { |line| line.sub(/GAMEFILE=.+/, 'GAMEFILE=WIZARD.EXE').sub(/GAME=.+/, 'GAME=WIZ').sub(/FULLGAMENAME=.+/, 'FULLGAMENAME=Wizard Front End') }
10677 end
10678 if login_info[:custom_launch]
10679 launch_data.push "CUSTOMLAUNCH=#{login_info[:custom_launch]}"
10680 if login_info[:custom_launch_dir]
10681 launch_data.push "CUSTOMLAUNCHDIR=#{login_info[:custom_launch_dir]}"
10682 end
10683 end
10684 window.destroy
10685 done = true
10686 else
10687 login_server.close unless login_server.closed?
10688 msgbox.call("Unrecognized response from server. (#{response})")
10689 play_button.sensitive = true
10690 end
10691 else
10692 login_server.close unless login_server.closed?
10693 msgbox.call("Unrecognized response from server. (#{response})")
10694 play_button.sensitive = true
10695 end
10696 else
10697 login_server.close unless login_server.closed?
10698 msgbox.call("Unrecognized response from server. (#{response})")
10699 play_button.sensitive = true
10700 end
10701 else
10702 login_server.close unless login_server.closed?
10703 msgbox.call "Something went wrong... probably invalid user id and/or password.\nserver response: #{response}"
10704 play_button.sensitive = true
10705 end
10706 else
10707 msgbox.call "error: failed to connect to server"
10708 play_button.sensitive = true
10709 end
10710 }
10711 remove_button.signal_connect('clicked') {
10712 entry_data.delete(login_info)
10713 save_entry_data = true
10714 char_box.visible = false
10715 }
10716 }
10717
10718 adjustment = Gtk::Adjustment.new(0, 0, 1000, 5, 20, 500)
10719 quick_vp = Gtk::Viewport.new(adjustment, adjustment)
10720 quick_vp.add(quick_box)
10721
10722 quick_sw = Gtk::ScrolledWindow.new
10723 quick_sw.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_ALWAYS)
10724 quick_sw.add(quick_vp)
10725
10726 quick_game_entry_tab = Gtk::VBox.new
10727 quick_game_entry_tab.border_width = 5
10728 quick_game_entry_tab.pack_start(quick_sw, true, true, 5)
10729 end
10730
10731=begin
10732 #
10733 # game entry tab
10734 #
10735
10736 checked_frontends = false
10737 wizard_dir = nil
10738 stormfront_dir = nil
10739 found_profanity = false
10740
10741 account_name_label = Gtk::Label.new('Account Name:')
10742 account_name_entry = Gtk::Entry.new
10743 password_label = Gtk::Label.new('Password:')
10744 password_entry = Gtk::Entry.new
10745 password_entry.visibility = false
10746
10747 account_name_label_box = Gtk::HBox.new
10748 account_name_label_box.pack_end(account_name_label, false, false, 0)
10749
10750 password_label_box = Gtk::HBox.new
10751 password_label_box.pack_end(password_label, false, false, 0)
10752
10753 login_table = Gtk::Table.new(2, 2, false)
10754 login_table.attach(account_name_label_box, 0, 1, 0, 1, Gtk::FILL, Gtk::FILL, 5, 5)
10755 login_table.attach(account_name_entry, 1, 2, 0, 1, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 5, 5)
10756 login_table.attach(password_label_box, 0, 1, 1, 2, Gtk::FILL, Gtk::FILL, 5, 5)
10757 login_table.attach(password_entry, 1, 2, 1, 2, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 5, 5)
10758
10759 disconnect_button = Gtk::Button.new(' Disconnect ')
10760 disconnect_button.sensitive = false
10761
10762 connect_button = Gtk::Button.new(' Connect ')
10763
10764 login_button_box = Gtk::HBox.new
10765 login_button_box.pack_end(connect_button, false, false, 5)
10766 login_button_box.pack_end(disconnect_button, false, false, 5)
10767
10768 liststore = Gtk::ListStore.new(String, String, String, String)
10769 liststore.set_sort_column_id(1, Gtk::SORT_ASCENDING)
10770
10771 renderer = Gtk::CellRendererText.new
10772
10773 treeview = Gtk::TreeView.new(liststore)
10774 treeview.height_request = 160
10775
10776 col = Gtk::TreeViewColumn.new("Game", renderer, :text => 1)
10777 col.resizable = true
10778 treeview.append_column(col)
10779
10780 col = Gtk::TreeViewColumn.new("Character", renderer, :text => 3)
10781 col.resizable = true
10782 treeview.append_column(col)
10783
10784 sw = Gtk::ScrolledWindow.new
10785 sw.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_ALWAYS)
10786 sw.add(treeview)
10787
10788 wizard_option = Gtk::RadioButton.new('WizardFE')
10789 stormfront_option = Gtk::RadioButton.new(wizard_option, 'Stormfront')
10790 profanity_option = Gtk::RadioButton.new(wizard_option, 'ProfanityFE')
10791 other_fe_option = Gtk::RadioButton.new(wizard_option, '(other)')
10792
10793 frontend_label = Gtk::Label.new('Frontend: ')
10794
10795 frontend_option = Gtk::ComboBox.new(is_text_only = true)
10796 frontend_option.append_text('WizardFE')
10797 frontend_option.append_text('Stormfront')
10798 frontend_option.append_text('ProfanityFE')
10799 frontend_option.append_text('(other)')
10800
10801 frontend_box2 = Gtk::HBox.new(false, 10)
10802 frontend_box2.pack_start(frontend_label, false, false, 0)
10803 frontend_box2.pack_start(frontend_option, false, false, 0)
10804
10805 launch_label = Gtk::Label.new('Launch method: ')
10806
10807 launch_option = Gtk::ComboBox.new(is_text_only = true)
10808 launch_option.append_text('ShellExecute')
10809 launch_option.append_text('spawn')
10810 launch_option.append_text('system')
10811 launch_option.active = 0
10812
10813 launch_box = Gtk::HBox.new(false, 10)
10814 launch_box.pack_start(launch_label, false, false, 0)
10815 launch_box.pack_start(launch_option, false, false, 0)
10816
10817 frontend_box = Gtk::HBox.new(false, 10)
10818 frontend_box.pack_start(wizard_option, false, false, 0)
10819 frontend_box.pack_start(stormfront_option, false, false, 0)
10820 frontend_box.pack_start(profanity_option, false, false, 0)
10821 frontend_box.pack_start(other_fe_option, false, false, 0)
10822
10823 use_simu_launcher_option = Gtk::CheckButton.new('Use the Simutronics Launcher')
10824 use_simu_launcher_option.active = true
10825
10826 custom_launch_option = Gtk::CheckButton.new('Use a custom launch command')
10827 custom_launch_entry = Gtk::ComboBoxEntry.new()
10828 custom_launch_entry.child.text = "(enter custom launch command)"
10829 custom_launch_entry.append_text("Wizard.Exe /GGS /H127.0.0.1 /P%port% /K%key%")
10830 custom_launch_entry.append_text("Stormfront.exe /GGS /H127.0.0.1 /P%port% /K%key%")
10831 custom_launch_dir = Gtk::ComboBoxEntry.new()
10832 custom_launch_dir.child.text = "(enter working directory for command)"
10833 custom_launch_dir.append_text("../wizard")
10834 custom_launch_dir.append_text("../StormFront")
10835
10836 remember_use_simu_launcher_active = nil
10837 revert_custom_launch_active = nil
10838 frontend_option.signal_connect('changed') {
10839 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)
10840# if (frontend_option.active != 0) and (frontend_option.active != 1) # Wizard or Stormfront
10841 if use_simu_launcher_option.sensitive?
10842 remember_use_simu_launcher_active = use_simu_launcher_option.active?
10843 use_simu_launcher_option.active = true
10844 use_simu_launcher_option.sensitive = false
10845 end
10846 elsif not use_simu_launcher_option.sensitive? and not custom_launch_option.active?
10847 use_simu_launcher_option.sensitive = true
10848 use_simu_launcher_option.active = remember_use_simu_launcher_active
10849 end
10850 if (frontend_option.active == 3) or ((frontend_option.active == 2) and not found_profanity)
10851 if custom_launch_option.sensitive?
10852 if not custom_launch_option.active?
10853 revert_custom_launch_active = true
10854 else
10855 revert_custom_launch_active = false
10856 end
10857 custom_launch_option.active = true
10858 custom_launch_option.sensitive = false
10859 end
10860 elsif not custom_launch_option.sensitive?
10861 custom_launch_option.sensitive = true
10862 if revert_custom_launch_active
10863 revert_custom_launch_active = false
10864 custom_launch_option.active = false
10865 end
10866 end
10867 }
10868 frontend_option.active = 0
10869
10870 make_quick_option = Gtk::CheckButton.new('Save this info for quick game entry')
10871
10872 play_button = Gtk::Button.new(' Play ')
10873 play_button.sensitive = false
10874
10875 play_button_box = Gtk::HBox.new
10876 play_button_box.pack_end(play_button, false, false, 5)
10877
10878 game_entry_tab = Gtk::VBox.new
10879 game_entry_tab.border_width = 5
10880 game_entry_tab.pack_start(login_table, false, false, 0)
10881 game_entry_tab.pack_start(login_button_box, false, false, 0)
10882 game_entry_tab.pack_start(sw, true, true, 3)
10883# game_entry_tab.pack_start(frontend_box, false, false, 3)
10884 game_entry_tab.pack_start(frontend_box2, false, false, 3)
10885 game_entry_tab.pack_start(launch_box, false, false, 3)
10886 game_entry_tab.pack_start(use_simu_launcher_option, false, false, 3)
10887 game_entry_tab.pack_start(custom_launch_option, false, false, 3)
10888 game_entry_tab.pack_start(custom_launch_entry, false, false, 3)
10889 game_entry_tab.pack_start(custom_launch_dir, false, false, 3)
10890 game_entry_tab.pack_start(make_quick_option, false, false, 3)
10891 game_entry_tab.pack_start(play_button_box, false, false, 3)
10892
10893 custom_launch_option.signal_connect('toggled') {
10894 custom_launch_entry.visible = custom_launch_option.active?
10895 custom_launch_dir.visible = custom_launch_option.active?
10896 if custom_launch_option.active?
10897 if use_simu_launcher_option.sensitive?
10898 remember_use_simu_launcher_active = use_simu_launcher_option.active?
10899 use_simu_launcher_option.active = false
10900 use_simu_launcher_option.sensitive = false
10901 end
10902 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))
10903 use_simu_launcher_option.sensitive = true
10904 use_simu_launcher_option.active = remember_use_simu_launcher_active
10905 end
10906 }
10907
10908 connect_button.signal_connect('clicked') {
10909 connect_button.sensitive = false
10910 account_name_entry.sensitive = false
10911 password_entry.sensitive = false
10912 iter = liststore.append
10913 iter[1] = 'working...'
10914 Gtk.queue {
10915 begin
10916 login_server = nil
10917 connect_thread = Thread.new {
10918 login_server = TCPSocket.new('eaccess.play.net', 7900)
10919 }
10920 300.times {
10921 sleep 0.1
10922 break unless connect_thread.status
10923 }
10924 if connect_thread.status
10925 connect_thread.kill rescue nil
10926 msgbox.call "error: timed out connecting to eaccess.play.net:7900"
10927 end
10928 rescue
10929 msgbox.call "error connecting to server: #{$!}"
10930 connect_button.sensitive = true
10931 account_name_entry.sensitive = true
10932 password_entry.sensitive = true
10933 end
10934 disconnect_button.sensitive = true
10935 if login_server
10936 login_server.puts "K\n"
10937 hashkey = login_server.gets
10938 if 'test'[0].class == String
10939 password = password_entry.text.split('').collect { |c| c.getbyte(0) }
10940 hashkey = hashkey.split('').collect { |c| c.getbyte(0) }
10941 else
10942 password = password_entry.text.split('').collect { |c| c[0] }
10943 hashkey = hashkey.split('').collect { |c| c[0] }
10944 end
10945 # password_entry.text = String.new
10946 password.each_index { |i| password[i] = ((password[i]-32)^hashkey[i])+32 }
10947 password = password.collect { |c| c.chr }.join
10948 login_server.puts "A\t#{account_name_entry.text}\t#{password}\n"
10949 password = nil
10950 response = login_server.gets
10951 login_key = /KEY\t([^\t]+)\t/.match(response).captures.first
10952 if login_key
10953 login_server.puts "M\n"
10954 response = login_server.gets
10955 if response =~ /^M\t/
10956 liststore.clear
10957 for game in response.sub(/^M\t/, '').scan(/[^\t]+\t[^\t^\n]+/)
10958 game_code, game_name = game.split("\t")
10959 login_server.puts "N\t#{game_code}\n"
10960 if login_server.gets =~ /STORM/
10961 login_server.puts "F\t#{game_code}\n"
10962 if login_server.gets =~ /NORMAL|PREMIUM|TRIAL|INTERNAL|FREE/
10963 login_server.puts "G\t#{game_code}\n"
10964 login_server.gets
10965 login_server.puts "P\t#{game_code}\n"
10966 login_server.gets
10967 login_server.puts "C\n"
10968 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]+/)
10969 char_code, char_name = code_name.split("\t")
10970 iter = liststore.append
10971 iter[0] = game_code
10972 iter[1] = game_name
10973 iter[2] = char_code
10974 iter[3] = char_name
10975 end
10976 end
10977 end
10978 end
10979 disconnect_button.sensitive = true
10980 else
10981 login_server.close unless login_server.closed?
10982 msgbox.call "Unrecognized response from server (#{response})"
10983 end
10984 else
10985 login_server.close unless login_server.closed?
10986 disconnect_button.sensitive = false
10987 connect_button.sensitive = true
10988 account_name_entry.sensitive = true
10989 password_entry.sensitive = true
10990 msgbox.call "Something went wrong... probably invalid user id and/or password.\nserver response: #{response}"
10991 end
10992 end
10993 }
10994 }
10995 treeview.signal_connect('cursor-changed') {
10996 if login_server
10997 play_button.sensitive = true
10998 end
10999 }
11000 disconnect_button.signal_connect('clicked') {
11001 disconnect_button.sensitive = false
11002 play_button.sensitive = false
11003 liststore.clear
11004 login_server.close unless login_server.closed?
11005 connect_button.sensitive = true
11006 account_name_entry.sensitive = true
11007 password_entry.sensitive = true
11008 }
11009 play_button.signal_connect('clicked') {
11010 play_button.sensitive = false
11011 game_code = treeview.selection.selected[0]
11012 char_code = treeview.selection.selected[2]
11013 if login_server and not login_server.closed?
11014 login_server.puts "F\t#{game_code}\n"
11015 login_server.gets
11016 login_server.puts "G\t#{game_code}\n"
11017 login_server.gets
11018 login_server.puts "P\t#{game_code}\n"
11019 login_server.gets
11020 login_server.puts "C\n"
11021 login_server.gets
11022 login_server.puts "L\t#{char_code}\tSTORM\n"
11023 response = login_server.gets
11024 if response =~ /^L\t/
11025 login_server.close unless login_server.closed?
11026 port = /GAMEPORT=([0-9]+)/.match(response).captures.first
11027 host = /GAMEHOST=([^\t\n]+)/.match(response).captures.first
11028 key = /KEY=([^\t\n]+)/.match(response).captures.first
11029 launch_data = response.sub(/^L\tOK\t/, '').split("\t")
11030 login_server.close unless login_server.closed?
11031 if wizard_option.active?
11032 launch_data.collect! { |line| line.sub(/GAMEFILE=.+/, "GAMEFILE=WIZARD.EXE").sub(/GAME=.+/, "GAME=WIZ") }
11033 elsif suks_option.active?
11034 launch_data.collect! { |line| line.sub(/GAMEFILE=.+/, "GAMEFILE=WIZARD.EXE").sub(/GAME=.+/, "GAME=SUKS") }
11035 end
11036 if custom_launch_option.active?
11037 launch_data.push "CUSTOMLAUNCH=#{custom_launch_entry.child.text}"
11038 unless custom_launch_dir.child.text.empty? or custom_launch_dir.child.text == "(enter working directory for command)"
11039 launch_data.push "CUSTOMLAUNCHDIR=#{custom_launch_dir.child.text}"
11040 end
11041 end
11042 if make_quick_option.active?
11043 if wizard_option.active?
11044 frontend = 'wizard'
11045 else
11046 frontend = 'stormfront'
11047 end
11048 if custom_launch_option.active?
11049 custom_launch = custom_launch_entry.child.text
11050 if custom_launch_dir.child.text.empty? or custom_launch_dir.child.text == "(enter working directory for command)"
11051 custom_launch_dir = nil
11052 else
11053 custom_launch_dir = custom_launch_dir.child.text
11054 end
11055 else
11056 custom_launch = nil
11057 custom_launch_dir = nil
11058 end
11059 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 }
11060 save_entry_data = true
11061 end
11062 account_name_entry.text = String.new
11063 password_entry.text = String.new
11064 window.destroy
11065 done = true
11066 else
11067 login_server.close unless login_server.closed?
11068 disconnect_button.sensitive = false
11069 play_button.sensitive = false
11070 connect_button.sensitive = true
11071 account_name_entry.sensitive = true
11072 password_entry.sensitive = true
11073 end
11074 else
11075 disconnect_button.sensitive = false
11076 play_button.sensitive = false
11077 connect_button.sensitive = true
11078 account_name_entry.sensitive = true
11079 password_entry.sensitive = true
11080 end
11081 }
11082 account_name_entry.signal_connect('activate') {
11083 password_entry.grab_focus
11084 }
11085 password_entry.signal_connect('activate') {
11086 connect_button.clicked
11087 }
11088=end
11089
11090 #
11091 # old game entry tab
11092 #
11093
11094 user_id_entry = Gtk::Entry.new
11095
11096 pass_entry = Gtk::Entry.new
11097 pass_entry.visibility = false
11098
11099 login_table = Gtk::Table.new(2, 2, false)
11100 login_table.attach(Gtk::Label.new('User ID:'), 0, 1, 0, 1, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 5, 5)
11101 login_table.attach(user_id_entry, 1, 2, 0, 1, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 5, 5)
11102 login_table.attach(Gtk::Label.new('Password:'), 0, 1, 1, 2, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 5, 5)
11103 login_table.attach(pass_entry, 1, 2, 1, 2, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 5, 5)
11104
11105 disconnect_button = Gtk::Button.new(' Disconnect ')
11106 disconnect_button.sensitive = false
11107
11108 connect_button = Gtk::Button.new(' Connect ')
11109
11110 login_button_box = Gtk::HBox.new
11111 login_button_box.pack_end(connect_button, false, false, 5)
11112 login_button_box.pack_end(disconnect_button, false, false, 5)
11113
11114 liststore = Gtk::ListStore.new(String, String, String, String)
11115 liststore.set_sort_column_id(1, Gtk::SORT_ASCENDING)
11116
11117 renderer = Gtk::CellRendererText.new
11118# renderer.background = 'white'
11119
11120 treeview = Gtk::TreeView.new(liststore)
11121 treeview.height_request = 160
11122
11123 col = Gtk::TreeViewColumn.new("Game", renderer, :text => 1)
11124 col.resizable = true
11125 treeview.append_column(col)
11126
11127 col = Gtk::TreeViewColumn.new("Character", renderer, :text => 3)
11128 col.resizable = true
11129 treeview.append_column(col)
11130
11131 sw = Gtk::ScrolledWindow.new
11132 sw.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_ALWAYS)
11133 sw.add(treeview)
11134
11135 wizard_option = Gtk::RadioButton.new('Wizard')
11136 stormfront_option = Gtk::RadioButton.new(wizard_option, 'Stormfront')
11137 suks_option = Gtk::RadioButton.new(wizard_option, 'suks')
11138
11139 frontend_box = Gtk::HBox.new(false, 10)
11140 frontend_box.pack_start(wizard_option, false, false, 0)
11141 frontend_box.pack_start(stormfront_option, false, false, 0)
11142 #frontend_box.pack_start(suks_option, false, false, 0)
11143
11144 custom_launch_option = Gtk::CheckButton.new('Custom launch command')
11145 custom_launch_entry = Gtk::ComboBoxEntry.new()
11146 custom_launch_entry.child.text = "(enter custom launch command)"
11147 custom_launch_entry.append_text("Wizard.Exe /GGS /H127.0.0.1 /P%port% /K%key%")
11148 custom_launch_entry.append_text("Stormfront.exe /GGS /H127.0.0.1 /P%port% /K%key%")
11149 custom_launch_dir = Gtk::ComboBoxEntry.new()
11150 custom_launch_dir.child.text = "(enter working directory for command)"
11151 custom_launch_dir.append_text("../wizard")
11152 custom_launch_dir.append_text("../StormFront")
11153
11154 make_quick_option = Gtk::CheckButton.new('Save this info for quick game entry')
11155
11156 play_button = Gtk::Button.new(' Play ')
11157 play_button.sensitive = false
11158
11159 play_button_box = Gtk::HBox.new
11160 play_button_box.pack_end(play_button, false, false, 5)
11161
11162 game_entry_tab = Gtk::VBox.new
11163 game_entry_tab.border_width = 5
11164 game_entry_tab.pack_start(login_table, false, false, 0)
11165 game_entry_tab.pack_start(login_button_box, false, false, 0)
11166 game_entry_tab.pack_start(sw, true, true, 3)
11167 game_entry_tab.pack_start(frontend_box, false, false, 3)
11168 game_entry_tab.pack_start(custom_launch_option, false, false, 3)
11169 game_entry_tab.pack_start(custom_launch_entry, false, false, 3)
11170 game_entry_tab.pack_start(custom_launch_dir, false, false, 3)
11171 game_entry_tab.pack_start(make_quick_option, false, false, 3)
11172 game_entry_tab.pack_start(play_button_box, false, false, 3)
11173
11174 custom_launch_option.signal_connect('toggled') {
11175 custom_launch_entry.visible = custom_launch_option.active?
11176 custom_launch_dir.visible = custom_launch_option.active?
11177 }
11178
11179 connect_button.signal_connect('clicked') {
11180 connect_button.sensitive = false
11181 user_id_entry.sensitive = false
11182 pass_entry.sensitive = false
11183 iter = liststore.append
11184 iter[1] = 'working...'
11185 Gtk.queue {
11186 begin
11187 login_server = nil
11188 connect_thread = Thread.new {
11189 login_server = TCPSocket.new('eaccess.play.net', 7900)
11190 }
11191 300.times {
11192 sleep 0.1
11193 break unless connect_thread.status
11194 }
11195 if connect_thread.status
11196 connect_thread.kill rescue nil
11197 msgbox.call "error: timed out connecting to eaccess.play.net:7900"
11198 end
11199 rescue
11200 msgbox.call "error connecting to server: #{$!}"
11201 connect_button.sensitive = true
11202 user_id_entry.sensitive = true
11203 pass_entry.sensitive = true
11204 end
11205 disconnect_button.sensitive = true
11206 if login_server
11207 login_server.puts "K\n"
11208 hashkey = login_server.gets
11209 if 'test'[0].class == String
11210 password = pass_entry.text.split('').collect { |c| c.getbyte(0) }
11211 hashkey = hashkey.split('').collect { |c| c.getbyte(0) }
11212 else
11213 password = pass_entry.text.split('').collect { |c| c[0] }
11214 hashkey = hashkey.split('').collect { |c| c[0] }
11215 end
11216 # pass_entry.text = String.new
11217 password.each_index { |i| password[i] = ((password[i]-32)^hashkey[i])+32 }
11218 password = password.collect { |c| c.chr }.join
11219 login_server.puts "A\t#{user_id_entry.text}\t#{password}\n"
11220 password = nil
11221 response = login_server.gets
11222 login_key = /KEY\t([^\t]+)\t/.match(response).captures.first
11223 if login_key
11224 login_server.puts "M\n"
11225 response = login_server.gets
11226 if response =~ /^M\t/
11227 liststore.clear
11228 for game in response.sub(/^M\t/, '').scan(/[^\t]+\t[^\t^\n]+/)
11229 game_code, game_name = game.split("\t")
11230 login_server.puts "N\t#{game_code}\n"
11231 if login_server.gets =~ /STORM/
11232 login_server.puts "F\t#{game_code}\n"
11233 if login_server.gets =~ /NORMAL|PREMIUM|TRIAL|INTERNAL|FREE/
11234 login_server.puts "G\t#{game_code}\n"
11235 login_server.gets
11236 login_server.puts "P\t#{game_code}\n"
11237 login_server.gets
11238 login_server.puts "C\n"
11239 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]+/)
11240 char_code, char_name = code_name.split("\t")
11241 iter = liststore.append
11242 iter[0] = game_code
11243 iter[1] = game_name
11244 iter[2] = char_code
11245 iter[3] = char_name
11246 end
11247 end
11248 end
11249 end
11250 disconnect_button.sensitive = true
11251 else
11252 login_server.close unless login_server.closed?
11253 msgbox.call "Unrecognized response from server (#{response})"
11254 end
11255 else
11256 login_server.close unless login_server.closed?
11257 disconnect_button.sensitive = false
11258 connect_button.sensitive = true
11259 user_id_entry.sensitive = true
11260 pass_entry.sensitive = true
11261 msgbox.call "Something went wrong... probably invalid user id and/or password.\nserver response: #{response}"
11262 end
11263 end
11264 }
11265 }
11266 treeview.signal_connect('cursor-changed') {
11267 if login_server
11268 play_button.sensitive = true
11269 end
11270 }
11271 disconnect_button.signal_connect('clicked') {
11272 disconnect_button.sensitive = false
11273 play_button.sensitive = false
11274 liststore.clear
11275 login_server.close unless login_server.closed?
11276 connect_button.sensitive = true
11277 user_id_entry.sensitive = true
11278 pass_entry.sensitive = true
11279 }
11280 play_button.signal_connect('clicked') {
11281 play_button.sensitive = false
11282 game_code = treeview.selection.selected[0]
11283 char_code = treeview.selection.selected[2]
11284 if login_server and not login_server.closed?
11285 login_server.puts "F\t#{game_code}\n"
11286 login_server.gets
11287 login_server.puts "G\t#{game_code}\n"
11288 login_server.gets
11289 login_server.puts "P\t#{game_code}\n"
11290 login_server.gets
11291 login_server.puts "C\n"
11292 login_server.gets
11293 login_server.puts "L\t#{char_code}\tSTORM\n"
11294 response = login_server.gets
11295 if response =~ /^L\t/
11296 login_server.close unless login_server.closed?
11297 port = /GAMEPORT=([0-9]+)/.match(response).captures.first
11298 host = /GAMEHOST=([^\t\n]+)/.match(response).captures.first
11299 key = /KEY=([^\t\n]+)/.match(response).captures.first
11300 launch_data = response.sub(/^L\tOK\t/, '').split("\t")
11301 login_server.close unless login_server.closed?
11302 if wizard_option.active?
11303 launch_data.collect! { |line| line.sub(/GAMEFILE=.+/, "GAMEFILE=WIZARD.EXE").sub(/GAME=.+/, "GAME=WIZ") }
11304 elsif suks_option.active?
11305 launch_data.collect! { |line| line.sub(/GAMEFILE=.+/, "GAMEFILE=WIZARD.EXE").sub(/GAME=.+/, "GAME=SUKS") }
11306 end
11307 if custom_launch_option.active?
11308 launch_data.push "CUSTOMLAUNCH=#{custom_launch_entry.child.text}"
11309 unless custom_launch_dir.child.text.empty? or custom_launch_dir.child.text == "(enter working directory for command)"
11310 launch_data.push "CUSTOMLAUNCHDIR=#{custom_launch_dir.child.text}"
11311 end
11312 end
11313 if make_quick_option.active?
11314 if wizard_option.active?
11315 frontend = 'wizard'
11316 else
11317 frontend = 'stormfront'
11318 end
11319 if custom_launch_option.active?
11320 custom_launch = custom_launch_entry.child.text
11321 if custom_launch_dir.child.text.empty? or custom_launch_dir.child.text == "(enter working directory for command)"
11322 custom_launch_dir = nil
11323 else
11324 custom_launch_dir = custom_launch_dir.child.text
11325 end
11326 else
11327 custom_launch = nil
11328 custom_launch_dir = nil
11329 end
11330 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 }
11331 save_entry_data = true
11332 end
11333 user_id_entry.text = String.new
11334 pass_entry.text = String.new
11335 window.destroy
11336 done = true
11337 else
11338 login_server.close unless login_server.closed?
11339 disconnect_button.sensitive = false
11340 play_button.sensitive = false
11341 connect_button.sensitive = true
11342 user_id_entry.sensitive = true
11343 pass_entry.sensitive = true
11344 end
11345 else
11346 disconnect_button.sensitive = false
11347 play_button.sensitive = false
11348 connect_button.sensitive = true
11349 user_id_entry.sensitive = true
11350 pass_entry.sensitive = true
11351 end
11352 }
11353 user_id_entry.signal_connect('activate') {
11354 pass_entry.grab_focus
11355 }
11356 pass_entry.signal_connect('activate') {
11357 connect_button.clicked
11358 }
11359
11360 #
11361 # link tab
11362 #
11363
11364 link_to_web_button = Gtk::Button.new('Link to Website')
11365 unlink_from_web_button = Gtk::Button.new('Unlink from Website')
11366 web_button_box = Gtk::HBox.new
11367 web_button_box.pack_start(link_to_web_button, true, true, 5)
11368 web_button_box.pack_start(unlink_from_web_button, true, true, 5)
11369
11370 web_order_label = Gtk::Label.new
11371 web_order_label.text = "Unknown"
11372
11373 web_box = Gtk::VBox.new
11374 web_box.pack_start(web_order_label, true, true, 5)
11375 web_box.pack_start(web_button_box, true, true, 5)
11376
11377 web_frame = Gtk::Frame.new('Website Launch Chain')
11378 web_frame.add(web_box)
11379
11380 link_to_sge_button = Gtk::Button.new('Link to SGE')
11381 unlink_from_sge_button = Gtk::Button.new('Unlink from SGE')
11382 sge_button_box = Gtk::HBox.new
11383 sge_button_box.pack_start(link_to_sge_button, true, true, 5)
11384 sge_button_box.pack_start(unlink_from_sge_button, true, true, 5)
11385
11386 sge_order_label = Gtk::Label.new
11387 sge_order_label.text = "Unknown"
11388
11389 sge_box = Gtk::VBox.new
11390 sge_box.pack_start(sge_order_label, true, true, 5)
11391 sge_box.pack_start(sge_button_box, true, true, 5)
11392
11393 sge_frame = Gtk::Frame.new('SGE Launch Chain')
11394 sge_frame.add(sge_box)
11395
11396
11397 refresh_button = Gtk::Button.new(' Refresh ')
11398
11399 refresh_box = Gtk::HBox.new
11400 refresh_box.pack_end(refresh_button, false, false, 5)
11401
11402 install_tab = Gtk::VBox.new
11403 install_tab.border_width = 5
11404 install_tab.pack_start(web_frame, false, false, 5)
11405 install_tab.pack_start(sge_frame, false, false, 5)
11406 install_tab.pack_start(refresh_box, false, false, 5)
11407
11408 refresh_button.signal_connect('clicked') {
11409 install_tab_loaded = true
11410 if defined?(Win32)
11411 begin
11412 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]
11413 web_launch_cmd = Win32.RegQueryValueEx(:hKey => key)[:lpData]
11414 real_web_launch_cmd = Win32.RegQueryValueEx(:hKey => key, :lpValueName => 'RealCommand')[:lpData]
11415 rescue
11416 web_launch_cmd = String.new
11417 real_web_launch_cmd = String.new
11418 ensure
11419 Win32.RegCloseKey(:hKey => key) rescue nil
11420 end
11421 begin
11422 key = Win32.RegOpenKeyEx(:hKey => Win32::HKEY_LOCAL_MACHINE, :lpSubKey => 'Software\\Simutronics\\Launcher', :samDesired => (Win32::KEY_ALL_ACCESS|Win32::KEY_WOW64_32KEY))[:phkResult]
11423 sge_launch_cmd = Win32.RegQueryValueEx(:hKey => key, :lpValueName => 'Directory')[:lpData]
11424 real_sge_launch_cmd = Win32.RegQueryValueEx(:hKey => key, :lpValueName => 'RealDirectory')[:lpData]
11425 rescue
11426 sge_launch_cmd = String.new
11427 real_launch_cmd = String.new
11428 ensure
11429 Win32.RegCloseKey(:hKey => key) rescue nil
11430 end
11431 elsif defined?(Wine)
11432 web_launch_cmd = Wine.registry_gets('HKEY_LOCAL_MACHINE\\Software\\Classes\\Simutronics.Autolaunch\\Shell\\Open\\command\\').to_s
11433 real_web_launch_cmd = Wine.registry_gets('HKEY_LOCAL_MACHINE\\Software\\Classes\\Simutronics.Autolaunch\\Shell\\Open\\command\\RealCommand').to_s
11434 sge_launch_cmd = Wine.registry_gets('HKEY_LOCAL_MACHINE\\Software\\Simutronics\\Launcher\\Directory').to_s
11435 real_sge_launch_cmd = Wine.registry_gets('HKEY_LOCAL_MACHINE\\Software\\Simutronics\\Launcher\\RealDirectory').to_s
11436 else
11437 web_launch_cmd = String.new
11438 sge_launch_cmd = String.new
11439 end
11440 if web_launch_cmd =~ /lich/i
11441 link_to_web_button.sensitive = false
11442 unlink_from_web_button.sensitive = true
11443 if real_web_launch_cmd =~ /launcher.exe/i
11444 web_order_label.text = "Website => Lich => Simu Launcher => Frontend"
11445 else
11446 web_order_label.text = "Website => Lich => Unknown"
11447 end
11448 elsif web_launch_cmd =~ /launcher.exe/i
11449 web_order_label.text = "Website => Simu Launcher => Frontend"
11450 link_to_web_button.sensitive = true
11451 unlink_from_web_button.sensitive = false
11452 else
11453 web_order_label.text = "Website => Unknown"
11454 link_to_web_button.sensitive = false
11455 unlink_from_web_button.sensitive = false
11456 end
11457 if sge_launch_cmd =~ /lich/i
11458 link_to_sge_button.sensitive = false
11459 unlink_from_sge_button.sensitive = true
11460 if real_sge_launch_cmd and (defined?(Wine) or File.exists?("#{real_sge_launch_cmd}\\launcher.exe"))
11461 sge_order_label.text = "SGE => Lich => Simu Launcher => Frontend"
11462 else
11463 sge_order_label.text = "SGE => Lich => Unknown"
11464 end
11465 elsif sge_launch_cmd and (defined?(Wine) or File.exists?("#{sge_launch_cmd}\\launcher.exe"))
11466 sge_order_label.text = "SGE => Simu Launcher => Frontend"
11467 link_to_sge_button.sensitive = true
11468 unlink_from_sge_button.sensitive = false
11469 else
11470 sge_order_label.text = "SGE => Unknown"
11471 link_to_sge_button.sensitive = false
11472 unlink_from_sge_button.sensitive = false
11473 end
11474 }
11475 link_to_web_button.signal_connect('clicked') {
11476 link_to_web_button.sensitive = false
11477 Lich.link_to_sal
11478 if defined?(Win32)
11479 refresh_button.clicked
11480 else
11481 Lich.msgbox(:message => 'WINE will take 5-30 seconds to update the registry. Wait a while and click the refresh button.')
11482 end
11483 }
11484 unlink_from_web_button.signal_connect('clicked') {
11485 unlink_from_web_button.sensitive = false
11486 Lich.unlink_from_sal
11487 if defined?(Win32)
11488 refresh_button.clicked
11489 else
11490 Lich.msgbox(:message => 'WINE will take 5-30 seconds to update the registry. Wait a while and click the refresh button.')
11491 end
11492 }
11493 link_to_sge_button.signal_connect('clicked') {
11494 link_to_sge_button.sensitive = false
11495 Lich.link_to_sge
11496 if defined?(Win32)
11497 refresh_button.clicked
11498 else
11499 Lich.msgbox(:message => 'WINE will take 5-30 seconds to update the registry. Wait a while and click the refresh button.')
11500 end
11501 }
11502 unlink_from_sge_button.signal_connect('clicked') {
11503 unlink_from_sge_button.sensitive = false
11504 Lich.unlink_from_sge
11505 if defined?(Win32)
11506 refresh_button.clicked
11507 else
11508 Lich.msgbox(:message => 'WINE will take 5-30 seconds to update the registry. Wait a while and click the refresh button.')
11509 end
11510 }
11511
11512=begin
11513 #
11514 # options tab
11515 #
11516
11517 lich_char_label = Gtk::Label.new('Lich char:')
11518 lich_char_label.xalign = 1
11519 lich_char_entry = Gtk::Entry.new
11520 lich_char_entry.text = ';' # fixme LichSettings['lich_char'].to_s
11521 lich_box = Gtk::HBox.new
11522 lich_box.pack_end(lich_char_entry, true, true, 5)
11523 lich_box.pack_end(lich_char_label, true, true, 5)
11524
11525 cache_serverbuffer_button = Gtk::CheckButton.new('Cache to disk')
11526 cache_serverbuffer_button.active = LichSettings['cache_serverbuffer']
11527
11528 serverbuffer_max_label = Gtk::Label.new('Maximum lines in memory:')
11529 serverbuffer_max_entry = Gtk::Entry.new
11530 serverbuffer_max_entry.text = LichSettings['serverbuffer_max_size'].to_s
11531 serverbuffer_min_label = Gtk::Label.new('Minumum lines in memory:')
11532 serverbuffer_min_entry = Gtk::Entry.new
11533 serverbuffer_min_entry.text = LichSettings['serverbuffer_min_size'].to_s
11534 serverbuffer_min_entry.sensitive = cache_serverbuffer_button.active?
11535
11536 serverbuffer_table = Gtk::Table.new(2, 2, false)
11537 serverbuffer_table.attach(serverbuffer_max_label, 0, 1, 0, 1, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 5, 5)
11538 serverbuffer_table.attach(serverbuffer_max_entry, 1, 2, 0, 1, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 5, 5)
11539 serverbuffer_table.attach(serverbuffer_min_label, 0, 1, 1, 2, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 5, 5)
11540 serverbuffer_table.attach(serverbuffer_min_entry, 1, 2, 1, 2, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 5, 5)
11541
11542 serverbuffer_box = Gtk::VBox.new
11543 serverbuffer_box.pack_start(cache_serverbuffer_button, false, false, 5)
11544 serverbuffer_box.pack_start(serverbuffer_table, false, false, 5)
11545
11546 serverbuffer_frame = Gtk::Frame.new('Server Buffer')
11547 serverbuffer_frame.add(serverbuffer_box)
11548
11549 cache_clientbuffer_button = Gtk::CheckButton.new('Cache to disk')
11550 cache_clientbuffer_button.active = LichSettings['cache_clientbuffer']
11551
11552 clientbuffer_max_label = Gtk::Label.new('Maximum lines in memory:')
11553 clientbuffer_max_entry = Gtk::Entry.new
11554 clientbuffer_max_entry.text = LichSettings['clientbuffer_max_size'].to_s
11555 clientbuffer_min_label = Gtk::Label.new('Minumum lines in memory:')
11556 clientbuffer_min_entry = Gtk::Entry.new
11557 clientbuffer_min_entry.text = LichSettings['clientbuffer_min_size'].to_s
11558 clientbuffer_min_entry.sensitive = cache_clientbuffer_button.active?
11559
11560 clientbuffer_table = Gtk::Table.new(2, 2, false)
11561 clientbuffer_table.attach(clientbuffer_max_label, 0, 1, 0, 1, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 5, 5)
11562 clientbuffer_table.attach(clientbuffer_max_entry, 1, 2, 0, 1, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 5, 5)
11563 clientbuffer_table.attach(clientbuffer_min_label, 0, 1, 1, 2, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 5, 5)
11564 clientbuffer_table.attach(clientbuffer_min_entry, 1, 2, 1, 2, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 5, 5)
11565
11566 clientbuffer_box = Gtk::VBox.new
11567 clientbuffer_box.pack_start(cache_clientbuffer_button, false, false, 5)
11568 clientbuffer_box.pack_start(clientbuffer_table, false, false, 5)
11569
11570 clientbuffer_frame = Gtk::Frame.new('Client Buffer')
11571 clientbuffer_frame.add(clientbuffer_box)
11572
11573 save_button = Gtk::Button.new(' Save ')
11574 save_button.sensitive = false
11575
11576 save_button_box = Gtk::HBox.new
11577 save_button_box.pack_end(save_button, false, false, 5)
11578
11579 options_tab = Gtk::VBox.new
11580 options_tab.border_width = 5
11581 options_tab.pack_start(lich_box, false, false, 5)
11582 options_tab.pack_start(serverbuffer_frame, false, false, 5)
11583 options_tab.pack_start(clientbuffer_frame, false, false, 5)
11584 options_tab.pack_start(save_button_box, false, false, 5)
11585
11586 check_changed = proc {
11587 Gtk.queue {
11588 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)
11589 save_button.sensitive = false
11590 else
11591 save_button.sensitive = true
11592 end
11593 }
11594 }
11595
11596 lich_char_entry.signal_connect('key-press-event') {
11597 check_changed.call
11598 false
11599 }
11600 serverbuffer_max_entry.signal_connect('key-press-event') {
11601 check_changed.call
11602 false
11603 }
11604 serverbuffer_min_entry.signal_connect('key-press-event') {
11605 check_changed.call
11606 false
11607 }
11608 clientbuffer_max_entry.signal_connect('key-press-event') {
11609 check_changed.call
11610 false
11611 }
11612 clientbuffer_min_entry.signal_connect('key-press-event') {
11613 check_changed.call
11614 false
11615 }
11616 cache_serverbuffer_button.signal_connect('clicked') {
11617 serverbuffer_min_entry.sensitive = cache_serverbuffer_button.active?
11618 check_changed.call
11619 }
11620 cache_clientbuffer_button.signal_connect('clicked') {
11621 clientbuffer_min_entry.sensitive = cache_clientbuffer_button.active?
11622 check_changed.call
11623 }
11624 save_button.signal_connect('clicked') {
11625 LichSettings['lich_char'] = lich_char_entry.text
11626 LichSettings['cache_serverbuffer'] = cache_serverbuffer_button.active?
11627 LichSettings['serverbuffer_max_size'] = serverbuffer_max_entry.text.to_i
11628 LichSettings['serverbuffer_min_size'] = serverbuffer_min_entry.text.to_i
11629 LichSettings['cache_clientbuffer'] = cache_clientbuffer_button.active?
11630 LichSettings['clientbuffer_max_size'] = clientbuffer_max_entry.text.to_i
11631 LichSettings['clientbuffer_min_size'] = clientbuffer_min_entry.text.to_i
11632 LichSettings.save
11633 save_button.sensitive = false
11634 }
11635=end
11636
11637 #
11638 # put it together and show the window
11639 #
11640
11641 notebook = Gtk::Notebook.new
11642 notebook.append_page(quick_game_entry_tab, Gtk::Label.new('Quick Game Entry'))
11643 notebook.append_page(game_entry_tab, Gtk::Label.new('Game Entry'))
11644 notebook.append_page(install_tab, Gtk::Label.new('Link'))
11645# notebook.append_page(options_tab, Gtk::Label.new('Options'))
11646 notebook.signal_connect('switch-page') { |who,page,page_num|
11647 if (page_num == 2) and not install_tab_loaded
11648 refresh_button.clicked
11649=begin
11650 elsif (page_num == 1) and not checked_frontends
11651 checked_frontends = true
11652 found_profanity = File.exists?("#{LICH_DIR}/profanity.rb")
11653 if defined?(Win32)
11654 begin
11655 key = Win32.RegOpenKeyEx(:hKey => Win32::HKEY_LOCAL_MACHINE, :lpSubKey => 'Software\\Simutronics\\STORM32', :samDesired => (Win32::KEY_ALL_ACCESS|Win32::KEY_WOW64_32KEY))[:phkResult]
11656 stormfront_dir = Win32.RegQueryValueEx(:hKey => key, :lpValueName => 'Directory')[:lpData]
11657 rescue
11658 stormfront_dir = nil
11659 ensure
11660 Win32.RegCloseKey(:hKey => key) rescue nil
11661 end
11662 begin
11663 key = Win32.RegOpenKeyEx(:hKey => Win32::HKEY_LOCAL_MACHINE, :lpSubKey => 'Software\\Simutronics\\WIZ32', :samDesired => (Win32::KEY_ALL_ACCESS|Win32::KEY_WOW64_32KEY))[:phkResult]
11664 wizard_dir = Win32.RegQueryValueEx(:hKey => key, :lpValueName => 'Directory')[:lpData]
11665 rescue
11666 wizard_dir = nil
11667 ensure
11668 Win32.RegCloseKey(:hKey => key) rescue nil
11669 end
11670 elsif defined?(Wine)
11671 stormfront_dir = Wine.registry_gets('HKEY_LOCAL_MACHINE\\Software\\Simutronics\\STORM32\\Directory').gsub("\\", "/")
11672 wizard_dir = Wine.registry_gets('HKEY_LOCAL_MACHINE\\Software\\Simutronics\\WIZ32\\Directory').gsub("\\", "/")
11673 else
11674 stormfront_dir = nil
11675 wizard_dir = nil
11676 end
11677 Lich.log "wizard_dir: #{wizard_dir}"
11678 Lich.log "stormfront_dir: #{stormfront_dir}"
11679 unless File.exists?("#{stormfront_dir}\\Stormfront.exe")
11680 Lich.log "stormfront doesn't exist"
11681 stormfront_dir = nil
11682 end
11683 unless File.exists?("#{wizard_dir}\\Wizard.Exe")
11684 Lich.log "wizard doesn't exist"
11685 wizard_dir = nil
11686 end
11687=end
11688 end
11689 }
11690
11691 window = Gtk::Window.new
11692 window.title = "Lich v#{LICH_VERSION}"
11693 window.border_width = 5
11694 window.add(notebook)
11695 window.signal_connect('delete_event') { window.destroy; done = true }
11696 window.default_width = 400
11697
11698 window.show_all
11699
11700 custom_launch_entry.visible = false
11701 custom_launch_dir.visible = false
11702
11703 notebook.set_page(1) if entry_data.empty?
11704 }
11705
11706 wait_until { done }
11707
11708 if save_entry_data
11709 File.open("#{DATA_DIR}/entry.dat", 'w') { |file|
11710 file.write([Marshal.dump(entry_data)].pack('m'))
11711 }
11712 end
11713 entry_data = nil
11714
11715 unless launch_data
11716 Gtk.queue { Gtk.main_quit }
11717 Thread.kill
11718 end
11719 end
11720 $_SERVERBUFFER_ = LimitedArray.new
11721 $_SERVERBUFFER_.max_size = 400
11722 $_CLIENTBUFFER_ = LimitedArray.new
11723 $_CLIENTBUFFER_.max_size = 100
11724
11725 Socket.do_not_reverse_lookup = true
11726
11727 #
11728 # open the client and have it connect to us
11729 #
11730 if argv_options[:sal]
11731 begin
11732 launch_data = File.open(argv_options[:sal]) { |file| file.readlines }.collect { |line| line.chomp }
11733 rescue
11734 $stdout.puts "error: failed to read launch_file: #{$!}"
11735 Lich.log "info: launch_file: #{argv_options[:sal]}"
11736 Lich.log "error: failed to read launch_file: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
11737 exit
11738 end
11739 end
11740 if launch_data
11741 unless gamecode = launch_data.find { |line| line =~ /GAMECODE=/ }
11742 $stdout.puts "error: launch_data contains no GAMECODE info"
11743 Lich.log "error: launch_data contains no GAMECODE info"
11744 exit(1)
11745 end
11746 unless gameport = launch_data.find { |line| line =~ /GAMEPORT=/ }
11747 $stdout.puts "error: launch_data contains no GAMEPORT info"
11748 Lich.log "error: launch_data contains no GAMEPORT info"
11749 exit(1)
11750 end
11751 unless gamehost = launch_data.find { |opt| opt =~ /GAMEHOST=/ }
11752 $stdout.puts "error: launch_data contains no GAMEHOST info"
11753 Lich.log "error: launch_data contains no GAMEHOST info"
11754 exit(1)
11755 end
11756 unless game = launch_data.find { |opt| opt =~ /GAME=/ }
11757 $stdout.puts "error: launch_data contains no GAME info"
11758 Lich.log "error: launch_data contains no GAME info"
11759 exit(1)
11760 end
11761 if custom_launch = launch_data.find { |opt| opt =~ /CUSTOMLAUNCH=/ }
11762 custom_launch.sub!(/^.*?\=/, '')
11763 Lich.log "info: using custom launch command: #{custom_launch}"
11764 end
11765 if custom_launch_dir = launch_data.find { |opt| opt =~ /CUSTOMLAUNCHDIR=/ }
11766 custom_launch_dir.sub!(/^.*?\=/, '')
11767 Lich.log "info: using working directory for custom launch command: #{custom_launch_dir}"
11768 end
11769 if ARGV.include?('--without-frontend')
11770 $frontend = 'unknown'
11771 unless (game_key = launch_data.find { |opt| opt =~ /KEY=/ }) && (game_key = game_key.split('=').last.chomp)
11772 $stdout.puts "error: launch_data contains no KEY info"
11773 Lich.log "error: launch_data contains no KEY info"
11774 exit(1)
11775 end
11776 elsif game =~ /SUKS/i
11777 $frontend = 'suks'
11778 unless (game_key = launch_data.find { |opt| opt =~ /KEY=/ }) && (game_key = game_key.split('=').last.chomp)
11779 $stdout.puts "error: launch_data contains no KEY info"
11780 Lich.log "error: launch_data contains no KEY info"
11781 exit(1)
11782 end
11783 elsif custom_launch
11784 unless (game_key = launch_data.find { |opt| opt =~ /KEY=/ }) && (game_key = game_key.split('=').last.chomp)
11785 $stdout.puts "error: launch_data contains no KEY info"
11786 Lich.log "error: launch_data contains no KEY info"
11787 exit(1)
11788 end
11789 else
11790 unless launcher_cmd = Lich.get_simu_launcher
11791 $stdout.puts 'error: failed to find the Simutronics launcher'
11792 Lich.log 'error: failed to find the Simutronics launcher'
11793 exit(1)
11794 end
11795 end
11796 gamecode = gamecode.split('=').last
11797 gameport = gameport.split('=').last
11798 gamehost = gamehost.split('=').last
11799 game = game.split('=').last
11800
11801 if (gameport == '10121') or (gameport == '10124')
11802 $platinum = true
11803 else
11804 $platinum = false
11805 end
11806 Lich.log "info: gamehost: #{gamehost}"
11807 Lich.log "info: gameport: #{gameport}"
11808 Lich.log "info: game: #{game}"
11809 if ARGV.include?('--without-frontend')
11810 $_CLIENT_ = nil
11811 elsif $frontend == 'suks'
11812 nil
11813 else
11814 if game =~ /WIZ/i
11815 $frontend = 'wizard'
11816 elsif game =~ /STORM/i
11817 $frontend = 'stormfront'
11818 else
11819 $frontend = 'unknown'
11820 end
11821 begin
11822 listener = TCPServer.new('127.0.0.1', nil)
11823 rescue
11824 $stdout.puts "--- error: cannot bind listen socket to local port: #{$!}"
11825 Lich.log "error: cannot bind listen socket to local port: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
11826 exit(1)
11827 end
11828 accept_thread = Thread.new { $_CLIENT_ = SynchronizedSocket.new(listener.accept) }
11829 localport = listener.addr[1]
11830 if custom_launch
11831 sal_filename = nil
11832 launcher_cmd = custom_launch.sub(/\%port\%/, localport.to_s).sub(/\%key\%/, game_key.to_s)
11833 scrubbed_launcher_cmd = custom_launch.sub(/\%port\%/, localport.to_s).sub(/\%key\%/, '[scrubbed key]')
11834 Lich.log "info: launcher_cmd: #{scrubbed_launcher_cmd}"
11835 else
11836 launch_data.collect! { |line| line.sub(/GAMEPORT=.+/, "GAMEPORT=#{localport}").sub(/GAMEHOST=.+/, "GAMEHOST=localhost") }
11837 sal_filename = "#{TEMP_DIR}/lich#{rand(10000)}.sal"
11838 while File.exists?(sal_filename)
11839 sal_filename = "#{TEMP_DIR}/lich#{rand(10000)}.sal"
11840 end
11841 File.open(sal_filename, 'w') { |f| f.puts launch_data }
11842 launcher_cmd = launcher_cmd.sub('%1', sal_filename)
11843 launcher_cmd = launcher_cmd.tr('/', "\\") if (RUBY_PLATFORM =~ /mingw|win/i) and (RUBY_PLATFORM !~ /darwin/i)
11844 end
11845 begin
11846 if custom_launch_dir
11847 Dir.chdir(custom_launch_dir)
11848 end
11849 if defined?(Win32)
11850 launcher_cmd =~ /^"(.*?)"\s*(.*)$/
11851 dir_file = $1
11852 param = $2
11853 dir = dir_file.slice(/^.*[\\\/]/)
11854 file = dir_file.sub(/^.*[\\\/]/, '')
11855 if Lich.win32_launch_method and Lich.win32_launch_method =~ /^(\d+):(.+)$/
11856 method_num = $1.to_i
11857 if $2 == 'fail'
11858 method_num = (method_num + 1) % 6
11859 end
11860 else
11861 method_num = 5
11862 end
11863 if method_num == 5
11864 begin
11865 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]
11866 if Win32.RegQueryValueEx(:hKey => key)[:lpData] =~ /Launcher\.exe/i
11867 associated = true
11868 else
11869 associated = false
11870 end
11871 rescue
11872 associated = false
11873 ensure
11874 Win32.RegCloseKey(:hKey => key) rescue nil
11875 end
11876 unless associated
11877 Lich.log "warning: skipping launch method #{method_num + 1} because .sal files are not associated with the Simutronics Launcher"
11878 method_num = (method_num + 1) % 6
11879 end
11880 end
11881 Lich.win32_launch_method = "#{method_num}:fail"
11882 if method_num == 0
11883 Lich.log "info: launcher_cmd: #{launcher_cmd}"
11884 spawn launcher_cmd
11885 elsif method_num == 1
11886 Lich.log "info: launcher_cmd: Win32.ShellExecute(:lpOperation => \"open\", :lpFile => #{file.inspect}, :lpDirectory => #{dir.inspect}, :lpParameters => #{param.inspect})"
11887 Win32.ShellExecute(:lpOperation => 'open', :lpFile => file, :lpDirectory => dir, :lpParameters => param)
11888 elsif method_num == 2
11889 Lich.log "info: launcher_cmd: Win32.ShellExecuteEx(:lpOperation => \"runas\", :lpFile => #{file.inspect}, :lpDirectory => #{dir.inspect}, :lpParameters => #{param.inspect})"
11890 Win32.ShellExecuteEx(:lpOperation => 'runas', :lpFile => file, :lpDirectory => dir, :lpParameters => param)
11891 elsif method_num == 3
11892 Lich.log "info: launcher_cmd: Win32.AdminShellExecute(:op => \"open\", :file => #{file.inspect}, :dir => #{dir.inspect}, :params => #{param.inspect})"
11893 Win32.AdminShellExecute(:op => 'open', :file => file, :dir => dir, :params => param)
11894 elsif method_num == 4
11895 Lich.log "info: launcher_cmd: Win32.AdminShellExecute(:op => \"runas\", :file => #{file.inspect}, :dir => #{dir.inspect}, :params => #{param.inspect})"
11896 Win32.AdminShellExecute(:op => 'runas', :file => file, :dir => dir, :params => param)
11897 else # method_num == 5
11898 file = File.expand_path(sal_filename).tr('/', "\\")
11899 dir = File.expand_path(File.dirname(sal_filename)).tr('/', "\\")
11900 Lich.log "info: launcher_cmd: Win32.ShellExecute(:lpOperation => \"open\", :lpFile => #{file.inspect}, :lpDirectory => #{dir.inspect})"
11901 Win32.ShellExecute(:lpOperation => 'open', :lpFile => file, :lpDirectory => dir)
11902 end
11903 elsif defined?(Wine)
11904 Lich.log "info: launcher_cmd: #{Wine::BIN} #{launcher_cmd}"
11905 spawn "#{Wine::BIN} #{launcher_cmd}"
11906 else
11907 Lich.log "info: launcher_cmd: #{launcher_cmd}"
11908 spawn launcher_cmd
11909 end
11910 rescue
11911 Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
11912 Lich.msgbox(:message => "error: #{$!}", :icon => :error)
11913 end
11914 Lich.log 'info: waiting for client to connect...'
11915 300.times { sleep 0.1; break unless accept_thread.status }
11916 accept_thread.kill if accept_thread.status
11917 Dir.chdir(LICH_DIR)
11918 unless $_CLIENT_
11919 Lich.log "error: timeout waiting for client to connect"
11920 if defined?(Win32)
11921 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)
11922 else
11923 Lich.msgbox(:message => "error: timeout waiting for client to connect", :icon => :error)
11924 end
11925 if sal_filename
11926 File.delete(sal_filename) rescue()
11927 end
11928 listener.close rescue()
11929 $_CLIENT_.close rescue()
11930 reconnect_if_wanted.call
11931 Lich.log "info: exiting..."
11932 Gtk.queue { Gtk.main_quit } if defined?(Gtk)
11933 exit
11934 end
11935 if defined?(Win32)
11936 Lich.win32_launch_method = "#{method_num}:success"
11937 end
11938 Lich.log 'info: connected'
11939 listener.close rescue nil
11940 if sal_filename
11941 File.delete(sal_filename) rescue nil
11942 end
11943 end
11944 gamehost, gameport = Lich.fix_game_host_port(gamehost, gameport)
11945 Lich.log "info: connecting to game server (#{gamehost}:#{gameport})"
11946 begin
11947 connect_thread = Thread.new {
11948 Game.open(gamehost, gameport)
11949 }
11950 300.times {
11951 sleep 0.1
11952 break unless connect_thread.status
11953 }
11954 if connect_thread.status
11955 connect_thread.kill rescue nil
11956 raise "error: timed out connecting to #{gamehost}:#{gameport}"
11957 end
11958 rescue
11959 Lich.log "error: #{$!}"
11960 gamehost, gameport = Lich.break_game_host_port(gamehost, gameport)
11961 Lich.log "info: connecting to game server (#{gamehost}:#{gameport})"
11962 begin
11963 connect_thread = Thread.new {
11964 Game.open(gamehost, gameport)
11965 }
11966 300.times {
11967 sleep 0.1
11968 break unless connect_thread.status
11969 }
11970 if connect_thread.status
11971 connect_thread.kill rescue nil
11972 raise "error: timed out connecting to #{gamehost}:#{gameport}"
11973 end
11974 rescue
11975 Lich.log "error: #{$!}"
11976 $_CLIENT_.close rescue nil
11977 reconnect_if_wanted.call
11978 Lich.log "info: exiting..."
11979 Gtk.queue { Gtk.main_quit } if defined?(Gtk)
11980 exit
11981 end
11982 end
11983 Lich.log 'info: connected'
11984 elsif game_host and game_port
11985 unless Lich.hosts_file
11986 Lich.log "error: cannot find hosts file"
11987 $stdout.puts "error: cannot find hosts file"
11988 exit
11989 end
11990 game_quad_ip = IPSocket.getaddress(game_host)
11991 error_count = 0
11992 begin
11993 listener = TCPServer.new('127.0.0.1', game_port)
11994 begin
11995 listener.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR,1)
11996 rescue
11997 Lich.log "warning: setsockopt with SO_REUSEADDR failed: #{$!}"
11998 end
11999 rescue
12000 sleep 1
12001 if (error_count += 1) >= 30
12002 $stdout.puts 'error: failed to bind to the proper port'
12003 Lich.log 'error: failed to bind to the proper port'
12004 exit!
12005 else
12006 retry
12007 end
12008 end
12009 Lich.modify_hosts(game_host)
12010
12011 $stdout.puts "Pretending to be #{game_host}"
12012 $stdout.puts "Listening on port #{game_port}"
12013 $stdout.puts "Waiting for the client to connect..."
12014 Lich.log "info: pretending to be #{game_host}"
12015 Lich.log "info: listening on port #{game_port}"
12016 Lich.log "info: waiting for the client to connect..."
12017
12018 timeout_thread = Thread.new {
12019 sleep 120
12020 listener.close rescue nil
12021 $stdout.puts 'error: timed out waiting for client to connect'
12022 Lich.log 'error: timed out waiting for client to connect'
12023 Lich.restore_hosts
12024 exit
12025 }
12026# $_CLIENT_ = listener.accept
12027 $_CLIENT_ = SynchronizedSocket.new(listener.accept)
12028 listener.close rescue nil
12029 timeout_thread.kill
12030 $stdout.puts "Connection with the local game client is open."
12031 Lich.log "info: connection with the game client is open"
12032 Lich.restore_hosts
12033 if test_mode
12034 $_SERVER_ = $stdin # fixme
12035 $_CLIENT_.puts "Running in test mode: host socket set to stdin."
12036 else
12037 Lich.log 'info: connecting to the real game host...'
12038 game_host, game_port = Lich.fix_game_host_port(game_host, game_port)
12039 begin
12040 timeout_thread = Thread.new {
12041 sleep 30
12042 Lich.log "error: timed out connecting to #{game_host}:#{game_port}"
12043 $stdout.puts "error: timed out connecting to #{game_host}:#{game_port}"
12044 exit
12045 }
12046 begin
12047 Game.open(game_host, game_port)
12048 rescue
12049 Lich.log "error: #{$!}"
12050 $stdout.puts "error: #{$!}"
12051 exit
12052 end
12053 timeout_thread.kill rescue nil
12054 Lich.log 'info: connection with the game host is open'
12055 end
12056 end
12057 else
12058 # offline mode removed
12059 Lich.log "error: don't know what to do"
12060 exit
12061 end
12062
12063 listener = timeout_thr = nil
12064
12065 #
12066 # drop superuser privileges
12067 #
12068 unless (RUBY_PLATFORM =~ /mingw|win/i) and (RUBY_PLATFORM !~ /darwin/i)
12069 Lich.log "info: dropping superuser privileges..."
12070 begin
12071 Process.uid = `id -ru`.strip.to_i
12072 Process.gid = `id -rg`.strip.to_i
12073 Process.egid = `id -rg`.strip.to_i
12074 Process.euid = `id -ru`.strip.to_i
12075 rescue SecurityError
12076 Lich.log "error: failed to drop superuser privileges: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
12077 rescue SystemCallError
12078 Lich.log "error: failed to drop superuser privileges: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
12079 rescue
12080 Lich.log "error: failed to drop superuser privileges: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
12081 end
12082 end
12083
12084 # backward compatibility
12085 if $frontend =~ /^(?:wizard|avalon)$/
12086 $fake_stormfront = true
12087 else
12088 $fake_stormfront = false
12089 end
12090
12091 undef :exit!
12092
12093 if ARGV.include?('--without-frontend')
12094 Thread.new {
12095 client_thread = nil
12096 #
12097 # send the login key
12098 #
12099 Game._puts(game_key)
12100 game_key = nil
12101 #
12102 # send version string
12103 #
12104 client_string = "/FE:WIZARD /VERSION:1.0.1.22 /P:#{RUBY_PLATFORM} /XML"
12105 $_CLIENTBUFFER_.push(client_string.dup)
12106 Game._puts(client_string)
12107 #
12108 # tell the server we're ready
12109 #
12110 2.times {
12111 sleep 0.3
12112 $_CLIENTBUFFER_.push("<c>\r\n")
12113 Game._puts("<c>")
12114 }
12115 $login_time = Time.now
12116 }
12117 else
12118 #
12119 # shutdown listening socket
12120 #
12121 error_count = 0
12122 begin
12123 # Somehow... for some ridiculous reason... Windows doesn't let us close the socket if we shut it down first...
12124 # listener.shutdown
12125 listener.close unless listener.closed?
12126 rescue
12127 Lich.log "warning: failed to close listener socket: #{$!}"
12128 if (error_count += 1) > 20
12129 Lich.log 'warning: giving up...'
12130 else
12131 sleep 0.05
12132 retry
12133 end
12134 end
12135
12136 $stdout = $_CLIENT_
12137 $_CLIENT_.sync = true
12138
12139 client_thread = Thread.new {
12140 $login_time = Time.now
12141
12142 if $offline_mode
12143 nil
12144 elsif $frontend =~ /^(?:wizard|avalon)$/
12145 #
12146 # send the login key
12147 #
12148 client_string = $_CLIENT_.gets
12149 Game._puts(client_string)
12150 #
12151 # take the version string from the client, ignore it, and ask the server for xml
12152 #
12153 $_CLIENT_.gets
12154 client_string = "/FE:WIZARD /VERSION:1.0.1.22 /P:#{RUBY_PLATFORM} /XML"
12155 $_CLIENTBUFFER_.push(client_string.dup)
12156 Game._puts(client_string)
12157 #
12158 # tell the server we're ready
12159 #
12160 2.times {
12161 sleep 0.3
12162 $_CLIENTBUFFER_.push("#{$cmd_prefix}\r\n")
12163 Game._puts($cmd_prefix)
12164 }
12165 #
12166 # set up some stuff
12167 #
12168 for client_string in [ "#{$cmd_prefix}_injury 2", "#{$cmd_prefix}_flag Display Inventory Boxes 1", "#{$cmd_prefix}_flag Display Dialog Boxes 0" ]
12169 $_CLIENTBUFFER_.push(client_string)
12170 Game._puts(client_string)
12171 end
12172 #
12173 # client wants to send "GOOD", xml server won't recognize it
12174 #
12175 $_CLIENT_.gets
12176 else
12177 inv_off_proc = proc { |server_string|
12178 if server_string =~ /^<(?:container|clearContainer|exposeContainer)/
12179 server_string.gsub!(/<(?:container|clearContainer|exposeContainer)[^>]*>|<inv.+\/inv>/, '')
12180 if server_string.empty?
12181 nil
12182 else
12183 server_string
12184 end
12185 elsif server_string =~ /^<flag id="Display Inventory Boxes" status='on' desc="Display all inventory and container windows."\/>/
12186 server_string.sub("status='on'", "status='off'")
12187 elsif server_string =~ /^\s*<d cmd="flag Inventory off">Inventory<\/d>\s+ON/
12188 server_string.sub("flag Inventory off", "flag Inventory on").sub('ON', 'OFF')
12189 else
12190 server_string
12191 end
12192 }
12193 DownstreamHook.add('inventory_boxes_off', inv_off_proc)
12194 inv_toggle_proc = proc { |client_string|
12195 if client_string =~ /^(?:<c>)?_flag Display Inventory Boxes ([01])/
12196 if $1 == '1'
12197 DownstreamHook.remove('inventory_boxes_off')
12198 Lich.set_inventory_boxes(XMLData.player_id, true)
12199 else
12200 DownstreamHook.add('inventory_boxes_off', inv_off_proc)
12201 Lich.set_inventory_boxes(XMLData.player_id, false)
12202 end
12203 nil
12204 elsif client_string =~ /^(?:<c>)?\s*(?:set|flag)\s+inv(?:e|en|ent|ento|entor|entory)?\s+(on|off)/i
12205 if $1.downcase == 'on'
12206 DownstreamHook.remove('inventory_boxes_off')
12207 respond 'You have enabled viewing of inventory and container windows.'
12208 Lich.set_inventory_boxes(XMLData.player_id, true)
12209 else
12210 DownstreamHook.add('inventory_boxes_off', inv_off_proc)
12211 respond 'You have disabled viewing of inventory and container windows.'
12212 Lich.set_inventory_boxes(XMLData.player_id, false)
12213 end
12214 nil
12215 else
12216 client_string
12217 end
12218 }
12219 UpstreamHook.add('inventory_boxes_toggle', inv_toggle_proc)
12220
12221 unless $offline_mode
12222 client_string = $_CLIENT_.gets
12223 Game._puts(client_string)
12224 client_string = $_CLIENT_.gets
12225 $_CLIENTBUFFER_.push(client_string.dup)
12226 Game._puts(client_string)
12227 end
12228 end
12229
12230 begin
12231 while client_string = $_CLIENT_.gets
12232 client_string = "#{$cmd_prefix}#{client_string}" if $frontend =~ /^(?:wizard|avalon)$/
12233 begin
12234 $_IDLETIMESTAMP_ = Time.now
12235 do_client(client_string)
12236 rescue
12237 respond "--- Lich: error: client_thread: #{$!}"
12238 respond $!.backtrace.first
12239 Lich.log "error: client_thread: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
12240 end
12241 end
12242 rescue
12243 respond "--- Lich: error: client_thread: #{$!}"
12244 respond $!.backtrace.first
12245 Lich.log "error: client_thread: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
12246 sleep 0.2
12247 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)
12248 end
12249 Game.close
12250 }
12251 end
12252
12253 if detachable_client_port
12254 detachable_client_thread = Thread.new {
12255 loop {
12256 begin
12257 server = TCPServer.new('127.0.0.1', detachable_client_port)
12258 $_DETACHABLE_CLIENT_ = SynchronizedSocket.new(server.accept)
12259 $_DETACHABLE_CLIENT_.sync = true
12260 rescue
12261 Lich.log "#{$!}\n\t#{$!.backtrace.join("\n\t")}"
12262 server.close rescue nil
12263 $_DETACHABLE_CLIENT_.close rescue nil
12264 $_DETACHABLE_CLIENT_ = nil
12265 sleep 5
12266 next
12267 ensure
12268 server.close rescue nil
12269 end
12270 if $_DETACHABLE_CLIENT_
12271 begin
12272 $frontend = 'profanity'
12273 Thread.new {
12274 100.times { sleep 0.1; break if XMLData.indicator['IconJOINED'] }
12275 init_str = "<progressBar id='mana' value='0' text='mana #{XMLData.mana}/#{XMLData.max_mana}'/>"
12276 init_str.concat "<progressBar id='health' value='0' text='health #{XMLData.health}/#{XMLData.max_health}'/>"
12277 init_str.concat "<progressBar id='spirit' value='0' text='spirit #{XMLData.spirit}/#{XMLData.max_spirit}'/>"
12278 init_str.concat "<progressBar id='stamina' value='0' text='stamina #{XMLData.stamina}/#{XMLData.max_stamina}'/>"
12279 init_str.concat "<progressBar id='encumlevel' value='#{XMLData.encumbrance_value}' text='#{XMLData.encumbrance_text}'/>"
12280 init_str.concat "<progressBar id='pbarStance' value='#{XMLData.stance_value}'/>"
12281 init_str.concat "<progressBar id='mindState' value='#{XMLData.mind_value}' text='#{XMLData.mind_text}'/>"
12282 init_str.concat "<spell>#{XMLData.prepared_spell}</spell>"
12283 init_str.concat "<right>#{GameObj.right_hand.name}</right>"
12284 init_str.concat "<left>#{GameObj.left_hand.name}</left>"
12285 for indicator in [ 'IconBLEEDING', 'IconPOISONED', 'IconDISEASED', 'IconSTANDING', 'IconKNEELING', 'IconSITTING', 'IconPRONE' ]
12286 init_str.concat "<indicator id='#{indicator}' visible='#{XMLData.indicator[indicator]}'/>"
12287 end
12288 for area in [ 'back', 'leftHand', 'rightHand', 'head', 'rightArm', 'abdomen', 'leftEye', 'leftArm', 'chest', 'rightLeg', 'neck', 'leftLeg', 'nsys', 'rightEye' ]
12289 if Wounds.send(area) > 0
12290 init_str.concat "<image id=\"#{area}\" name=\"Injury#{Wounds.send(area)}\"/>"
12291 elsif Scars.send(area) > 0
12292 init_str.concat "<image id=\"#{area}\" name=\"Scar#{Scars.send(area)}\"/>"
12293 end
12294 end
12295 init_str.concat '<compass>'
12296 shorten_dir = { 'north' => 'n', 'northeast' => 'ne', 'east' => 'e', 'southeast' => 'se', 'south' => 's', 'southwest' => 'sw', 'west' => 'w', 'northwest' => 'nw', 'up' => 'up', 'down' => 'down', 'out' => 'out' }
12297 for dir in XMLData.room_exits
12298 if short_dir = shorten_dir[dir]
12299 init_str.concat "<dir value='#{short_dir}'/>"
12300 end
12301 end
12302 init_str.concat '</compass>'
12303 $_DETACHABLE_CLIENT_.puts init_str
12304 init_str = nil
12305 }
12306 while client_string = $_DETACHABLE_CLIENT_.gets
12307 client_string = "#{$cmd_prefix}#{client_string}" # if $frontend =~ /^(?:wizard|avalon)$/
12308 begin
12309 $_IDLETIMESTAMP_ = Time.now
12310 do_client(client_string)
12311 rescue
12312 respond "--- Lich: error: client_thread: #{$!}"
12313 respond $!.backtrace.first
12314 Lich.log "error: client_thread: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
12315 end
12316 end
12317 rescue
12318 respond "--- Lich: error: client_thread: #{$!}"
12319 respond $!.backtrace.first
12320 Lich.log "error: client_thread: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
12321 $_DETACHABLE_CLIENT_.close rescue nil
12322 $_DETACHABLE_CLIENT_ = nil
12323 ensure
12324 $_DETACHABLE_CLIENT_.close rescue nil
12325 $_DETACHABLE_CLIENT_ = nil
12326 end
12327 end
12328 sleep 0.1
12329 }
12330 }
12331 else
12332 detachable_client_thread = nil
12333 end
12334
12335 wait_while { $offline_mode }
12336
12337 if $frontend == 'wizard'
12338 $link_highlight_start = "\207"
12339 $link_highlight_end = "\240"
12340 $speech_highlight_start = "\212"
12341 $speech_highlight_end = "\240"
12342 end
12343
12344 client_thread.priority = 3
12345
12346 $_CLIENT_.puts "\n--- Lich v#{LICH_VERSION} is active. Type #{$clean_lich_char}help for usage info.\n\n"
12347
12348 Game.thread.join
12349 client_thread.kill rescue nil
12350 detachable_client_thread.kill rescue nil
12351
12352 Lich.log 'info: stopping scripts...'
12353 Script.running.each { |script| script.kill }
12354 Script.hidden.each { |script| script.kill }
12355 200.times { sleep 0.1; break if Script.running.empty? and Script.hidden.empty? }
12356 Lich.log 'info: saving script settings...'
12357 Settings.save
12358 Vars.save
12359 Lich.log 'info: closing connections...'
12360 Game.close
12361 $_CLIENT_.close rescue nil
12362# Lich.db.close rescue nil
12363 reconnect_if_wanted.call
12364 Lich.log "info: exiting..."
12365 Gtk.queue { Gtk.main_quit } if defined?(Gtk)
12366 exit
12367}
12368
12369if defined?(Gtk)
12370 Thread.current.priority = -10
12371 Gtk.main
12372else
12373 main_thread.join
12374end
12375exit