PageRenderTime 129ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 2ms

/lich.rbw

https://github.com/lich-hub/lich
Ruby | 11183 lines | 10826 code | 253 blank | 104 comment | 1089 complexity | 4c9d08048fd30f391f1b3725a3951214 MD5 | raw file
  1. #!/usr/bin/env ruby
  2. #####
  3. # Copyright (C) 2005-2006 Murray Miron
  4. # All rights reserved.
  5. #
  6. # Redistribution and use in source and binary forms, with or without
  7. # modification, are permitted provided that the following conditions
  8. # are met:
  9. #
  10. # Redistributions of source code must retain the above copyright
  11. # notice, this list of conditions and the following disclaimer.
  12. #
  13. # Redistributions in binary form must reproduce the above copyright
  14. # notice, this list of conditions and the following disclaimer in the
  15. # documentation and/or other materials provided with the distribution.
  16. #
  17. # Neither the name of the organization nor the names of its contributors
  18. # may be used to endorse or promote products derived from this software
  19. # without specific prior written permission.
  20. #
  21. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  22. # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  23. # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  24. # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  25. # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  26. # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  27. # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  28. # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  29. # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  30. # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  31. # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  32. #####
  33. =begin
  34. Lich is currently maintained by Tillmen (tillmen@lichproject.org)
  35. =end
  36. # I hate Windows...
  37. begin
  38. $stderr.write(' ')
  39. rescue
  40. require 'stringio'
  41. $stderr = StringIO.new('')
  42. $stdout = StringIO.new('')
  43. STDERR = $stderr rescue()
  44. STDOUT = $stderr rescue()
  45. end
  46. $version = '4.4.9'
  47. if ARGV.any? { |arg| (arg == '-h') or (arg == '--help') }
  48. puts 'Usage: lich [OPTION]'
  49. puts ''
  50. puts 'Options are:'
  51. puts ' -h, --help Display this list.'
  52. puts ' -V, --version Display the program version number and credits.'
  53. puts ''
  54. puts ' -d, --directory Set the main Lich program directory.'
  55. puts ' --script-dir Set the directoy where Lich looks for scripts.'
  56. puts ' --data-dir Set the directory where Lich will store script data.'
  57. puts ' --temp-dir Set the directory where Lich will store temporary files.'
  58. puts ''
  59. puts ' -w, --wizard Run in Wizard mode (default)'
  60. puts ' -s, --stormfront Run in StormFront mode.'
  61. puts ' --avalon Run in Avalon mode.'
  62. puts ''
  63. puts ' --gemstone Connect to the Gemstone IV Prime server (default).'
  64. puts ' --dragonrealms Connect to the DragonRealms server.'
  65. puts ' --platinum Connect to the Gemstone IV/DragonRealms Platinum server.'
  66. puts ' -g, --game Set the IP address and port of the game. See example below.'
  67. puts ''
  68. puts ' --install Edits the Windows/WINE registry so that Lich is started when logging in using the website or SGE.'
  69. puts ' --uninstall Removes Lich from the registry.'
  70. puts ''
  71. 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).'
  72. puts ''
  73. 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.'
  74. puts ''
  75. puts ''
  76. puts 'Examples:'
  77. puts ' lich -w -d /usr/bin/lich/ (run Lich in Wizard mode using the dir \'/usr/bin/lich/\' as the program\'s home)'
  78. puts ' lich -g gs3.simutronics.net:4000 (run Lich using the IP address \'gs3.simutronics.net\' and the port number \'4000\')'
  79. puts ' lich --script-dir /mydir/scripts (run Lich with its script directory set to \'/mydir/scripts\')'
  80. 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\')'
  81. puts ''
  82. exit
  83. end
  84. if ARGV.any? { |arg| (arg == '-v') or (arg == '--version') }
  85. # puts "The Lich, version #{$version}"
  86. puts ' (an implementation of the Ruby interpreter by Yukihiro Matsumoto designed to be a \'script engine\' for text-based MUDs)'
  87. puts ''
  88. puts '- The Lich program and all material collectively referred to as "The Lich project" is copyright (C) 2005-2006 Murray Miron.'
  89. puts '- The Gemstone IV and DragonRealms games are copyright (C) Simutronics Corporation.'
  90. puts '- The Wizard front-end and the StormFront front-end are also copyrighted by the Simutronics Corporation.'
  91. puts '- Ruby is (C) Yukihiro \'Matz\' Matsumoto.'
  92. puts '- Inno Setup Compiler 5 is (C) 1997-2005 Jordan Russell (used for the Windows installation package).'
  93. puts ''
  94. puts 'Thanks to all those who\'ve reported bugs and helped me track down problems on both Windows and Linux.'
  95. exit
  96. end
  97. ARGV.delete_if { |arg| arg =~ /launcher\.exe/i }
  98. if arg = ARGV.find { |a| (a == '-d') or (a == '--directory') }
  99. i = ARGV.index(arg)
  100. ARGV.delete_at(i)
  101. $lich_dir = ARGV[i]
  102. ARGV.delete_at(i)
  103. unless $lich_dir and File.exists?($lich_dir)
  104. $stdout.puts "warning: given Lich directory does not exist: #{$lich_dir}"
  105. $lich_dir = nil
  106. end
  107. end
  108. unless $lich_dir
  109. Dir.chdir(File.dirname($PROGRAM_NAME))
  110. $lich_dir = Dir.pwd
  111. end
  112. $lich_dir = $lich_dir.tr('\\', '/')
  113. $lich_dir += '/' unless $lich_dir[-1..-1] == '/'
  114. Dir.chdir($lich_dir)
  115. if arg = ARGV.find { |a| a == '--script-dir' }
  116. i = ARGV.index(arg)
  117. ARGV.delete_at(i)
  118. $script_dir = ARGV[i]
  119. ARGV.delete_at(i)
  120. if $script_dir and File.exists?($script_dir)
  121. $script_dir = $script_dir.tr('\\', '/')
  122. $script_dir += '/' unless $script_dir[-1..-1] == '/'
  123. else
  124. $stdout.puts "warning: given script directory does not exist: #{$script_dir}"
  125. $script_dir = nil
  126. end
  127. end
  128. unless $script_dir
  129. $script_dir = "#{$lich_dir}scripts/"
  130. unless File.exists?($script_dir)
  131. $stdout.puts "info: creating directory: #{$script_dir}"
  132. Dir.mkdir($script_dir)
  133. end
  134. end
  135. if arg = ARGV.find { |a| a == '--data-dir' }
  136. i = ARGV.index(arg)
  137. ARGV.delete_at(i)
  138. $data_dir = ARGV[i]
  139. ARGV.delete_at(i)
  140. if $data_dir and File.exists?($data_dir)
  141. $data_dir = $data_dir.tr('\\', '/')
  142. $data_dir += '/' unless $data_dir[-1..-1] == '/'
  143. else
  144. $stdout.puts "warning: given data directory does not exist: #{$data_dir}"
  145. $data_dir = nil
  146. end
  147. end
  148. unless $data_dir
  149. $data_dir = "#{$lich_dir}data/"
  150. unless File.exists?($data_dir)
  151. $stdout.puts "info: creating directory: #{$data_dir}"
  152. Dir.mkdir($data_dir)
  153. end
  154. end
  155. if arg = ARGV.find { |a| a == '--temp-dir' }
  156. i = ARGV.index(arg)
  157. ARGV.delete_at(i)
  158. $temp_dir = ARGV[i]
  159. ARGV.delete_at(i)
  160. if $temp_dir and File.exists?($temp_dir)
  161. $temp_dir = $temp_dir.tr('\\', '/')
  162. $temp_dir += '/' unless $temp_dir[-1..-1] == '/'
  163. else
  164. $stdout.puts "warning: given temp directory does not exist: #{$temp_dir}"
  165. $temp_dir = nil
  166. end
  167. end
  168. unless $temp_dir
  169. $temp_dir = "#{$lich_dir}temp/"
  170. unless File.exists?($temp_dir)
  171. $stdout.puts "info: creating directory: #{$temp_dir}"
  172. Dir.mkdir($temp_dir)
  173. end
  174. end
  175. if arg = ARGV.find { |a| a == '--hosts-dir' }
  176. i = ARGV.index(arg)
  177. ARGV.delete_at(i)
  178. hosts_dir = ARGV[i]
  179. ARGV.delete_at(i)
  180. if hosts_dir and File.exists?(hosts_dir)
  181. hosts_dir = hosts_dir.tr('\\', '/')
  182. hosts_dir += '/' unless hosts_dir[-1..-1] == '/'
  183. else
  184. $stdout.puts "warning: given hosts directory does not exist: #{hosts_dir}"
  185. hosts_dir = nil
  186. end
  187. else
  188. hosts_dir = nil
  189. end
  190. num = Time.now.to_i
  191. debug_filename = "#{$temp_dir}debug-#{num}.txt"
  192. debug_filename = "#{$temp_dir}debug-#{num+=1}.txt" while File.exists?(debug_filename)
  193. $stderr = File.open(debug_filename, 'w')
  194. $stderr.sync = true
  195. $stderr.puts "info: #{Time.now}"
  196. $stderr.puts "info: Lich #{$version}"
  197. $stderr.puts "info: Ruby #{RUBY_VERSION}"
  198. $stderr.puts "info: #{RUBY_PLATFORM}"
  199. $stderr.puts "info: $lich_dir: #{$lich_dir}"
  200. $stderr.puts "info: $script_dir: #{$script_dir}"
  201. $stderr.puts "info: $data_dir: #{$data_dir}"
  202. $stderr.puts "info: $temp_dir: #{$temp_dir}"
  203. #
  204. # delete cache and debug files that are more than 24 hours old
  205. #
  206. Dir.entries($lich_dir).delete_if { |fn| (fn == '.') or (fn == '..') }.each { |filename|
  207. if filename =~ /^cache-([0-9]+).txt$/
  208. if $1.to_i + 86400 < Time.now.to_i
  209. File.delete($lich_dir + filename) rescue()
  210. end
  211. end
  212. }
  213. Dir.entries($temp_dir).delete_if { |fn| (fn == '.') or (fn == '..') }.each { |filename|
  214. if filename =~ /^(?:cache|debug)-([0-9]+).txt$/
  215. if $1.to_i + 86400 < Time.now.to_i
  216. File.delete($temp_dir + filename) rescue()
  217. end
  218. end
  219. }
  220. =begin
  221. # fixme: Somehow this makes gtk2 fail to load?
  222. begin
  223. require 'rubygems'
  224. rescue LoadError
  225. $stdout.puts "warning: failed to load rubygems: #{$!}"
  226. $stderr.puts "warning: failed to load rubygems: #{$!}"
  227. rescue
  228. $stdout.puts "warning: failed to load rubygems: #{$!}"
  229. $stderr.puts "warning: failed to load rubygems: #{$!}"
  230. end
  231. =end
  232. require 'time'
  233. require 'socket'
  234. include Socket::Constants
  235. require 'rexml/document'
  236. require 'rexml/streamlistener'
  237. include REXML
  238. require 'stringio'
  239. require 'dl'
  240. require 'zlib'
  241. require 'drb'
  242. require 'resolv'
  243. require 'digest/md5'
  244. # stupid workaround for Windows to avoid 10 second or so freeze when lnet starts up
  245. # no idea why this works
  246. if (RUBY_PLATFORM =~ /mingw|win/i) and (RUBY_PLATFORM !~ /darwin/i)
  247. begin
  248. require 'openssl'
  249. OpenSSL::PKey::RSA.new(512)
  250. rescue LoadError
  251. $stderr.puts "warning: failed to load openssl: #{$!}"
  252. rescue
  253. $stderr.puts "warning: failed to load openssl: #{$!}"
  254. end
  255. end
  256. begin
  257. require 'gtk2'
  258. module Gtk
  259. @@pending_blocks = Array.new
  260. def Gtk.queue &block
  261. @@pending_blocks.push(block)
  262. GLib::Timeout.add(1) { Gtk.do_queue; false }
  263. end
  264. def Gtk.do_queue
  265. if block = @@pending_blocks.shift
  266. begin
  267. block.call
  268. rescue
  269. respond "error in Gtk.queue: #{$!}"
  270. $stderr.puts "error in Gtk.queue: #{$!}"
  271. $stderr.puts $!.backtrace
  272. rescue SyntaxError
  273. respond "error in Gtk.queue: #{$!}"
  274. $stderr.puts "error in Gtk.queue: #{$!}"
  275. $stderr.puts $!.backtrace
  276. rescue SystemExit
  277. nil
  278. rescue SecurityError
  279. respond "error in Gtk.queue: #{$!}"
  280. $stderr.puts "error in Gtk.queue: #{$!}"
  281. $stderr.puts $!.backtrace
  282. rescue ThreadError
  283. respond "error in Gtk.queue: #{$!}"
  284. $stderr.puts "error in Gtk.queue: #{$!}"
  285. $stderr.puts $!.backtrace
  286. rescue SystemStackError
  287. respond "error in Gtk.queue: #{$!}"
  288. $stderr.puts "error in Gtk.queue: #{$!}"
  289. $stderr.puts $!.backtrace
  290. rescue Exception
  291. respond "error in Gtk.queue: #{$!}"
  292. $stderr.puts "error in Gtk.queue: #{$!}"
  293. $stderr.puts $!.backtrace
  294. rescue ScriptError
  295. respond "error in Gtk.queue: #{$!}"
  296. $stderr.puts "error in Gtk.queue: #{$!}"
  297. $stderr.puts $!.backtrace
  298. rescue LoadError
  299. respond "error in Gtk.queue: #{$!}"
  300. $stderr.puts "error in Gtk.queue: #{$!}"
  301. $stderr.puts $!.backtrace
  302. rescue NoMemoryError
  303. respond "error in Gtk.queue: #{$!}"
  304. $stderr.puts "error in Gtk.queue: #{$!}"
  305. $stderr.puts $!.backtrace
  306. rescue
  307. respond "error in Gtk.queue: #{$!}"
  308. $stderr.puts "error in Gtk.queue: #{$!}"
  309. $stderr.puts $!.backtrace
  310. end
  311. end
  312. end
  313. end
  314. $warned_depreciated_gtk_do = false
  315. class BeBackwardCompatible1
  316. def do(what)
  317. unless $warned_depreciated_gtk_do
  318. unless script_name = Script.self.name
  319. script_name = 'unknown script'
  320. end
  321. respond "--- warning: #{script_name} is using depreciated method $gtk.do"
  322. $warned_depreciated_gtk_do = true
  323. end
  324. Gtk.queue { eval(what) }
  325. end
  326. end
  327. $gtk = BeBackwardCompatible1.new
  328. $warned_depreciated_lich_do = false
  329. class BeBackwardCompatible2
  330. def do(what)
  331. unless $warned_depreciated_lich_do
  332. unless script_name = Script.self.name
  333. script_name = 'unknown script'
  334. end
  335. respond "--- warning: #{script_name} is using depreciated method $lich.do"
  336. $warned_depreciated_lich_do = true
  337. end
  338. eval(what)
  339. end
  340. end
  341. $lich = BeBackwardCompatible2.new
  342. HAVE_GTK = true
  343. $stderr.puts "info: HAVE_GTK: true"
  344. rescue LoadError
  345. HAVE_GTK = false
  346. $stdout.puts "warning: failed to load GTK bindings: #{$!}"
  347. $stderr.puts "warning: failed to load GTK bindings: #{$!}"
  348. rescue
  349. HAVE_GTK = false
  350. $stdout.puts "warning: failed to load GTK bindings: #{$!}"
  351. $stderr.puts "warning: failed to load GTK bindings: #{$!}"
  352. end
  353. # fixme: warlock
  354. # fixme: terminal mode
  355. # fixme: maybe add script dir to load path
  356. # at_exit { Process.waitall }
  357. $room_count = 0
  358. #
  359. # Allow untrusted scripts to do a few things
  360. #
  361. UNTRUSTED_SETTINGS_LOAD = proc { Settings.load }
  362. UNTRUSTED_SETTINGS_SAVE = proc { Settings.save }
  363. UNTRUSTED_SETTINGS_SAVE_ALL = proc { Settings.save_all }
  364. UNTRUSTED_GAMESETTINGS_LOAD = proc { GameSettings.load }
  365. UNTRUSTED_GAMESETTINGS_SAVE = proc { GameSettings.save }
  366. UNTRUSTED_GAMESETTINGS_SAVE_ALL = proc { GameSettings.save_all }
  367. UNTRUSTED_CHARSETTINGS_LOAD = proc { CharSettings.load }
  368. UNTRUSTED_CHARSETTINGS_SAVE = proc { CharSettings.save }
  369. UNTRUSTED_CHARSETTINGS_SAVE_ALL = proc { CharSettings.save_all }
  370. UNTRUSTED_MAP_LOAD = proc { Map.load }
  371. UNTRUSTED_MAP_LOAD_DAT = proc { Map.load_dat }
  372. UNTRUSTED_MAP_LOAD_XML = proc { Map.load_xml }
  373. UNTRUSTED_MAP_SAVE = proc { Map.save }
  374. UNTRUSTED_MAP_SAVE_XML = proc { Map.save_xml }
  375. UNTRUSTED_SPELL_LOAD = proc { Spell.load }
  376. UNTRUSTED_START_SCRIPT = proc { |script_name,cli_vars,force| start_script(script_name,cli_vars,force) }
  377. UNTRUSTED_START_EXEC_SCRIPT = proc { |cmd_data,flags| flags = Hash.new unless flags.class == Hash; flags[:trusted] = false; start_exec_script(cmd_data, flags) }
  378. UNTRUSTED_SCRIPT_EXISTS = proc { |scriptname| Script.exists?(scriptname) }
  379. UNTRUSTED_UNTAINT = proc { |str| str.untaint }
  380. UNTRUSTED_USERVARIABLES_METHOD_MISSING = proc { |arg1, arg2| UserVariables.method_missing(arg1, arg2) }
  381. UNTRUSTED_USERVARIABLES_DELETE = proc { |var_name, type| UserVariables.delete(var_name, type) }
  382. UNTRUSTED_USERVARIABLES_ADD = proc { |var_name, value, type| UserVariables.add(var_name, value, type) }
  383. UNTRUSTED_USERVARIABLES_CHANGE = proc { |var_name, value, type| UserVariables.change(var_name, value, type) }
  384. UNTRUSTED_USERVARIABLES_SAVE = proc { UserVariables.save }
  385. UNTRUSTED_SPELL_RANKS_LOAD = proc { SpellRanks.load }
  386. UNTRUSTED_SPELL_RANKS_SAVE = proc { SpellRanks.save }
  387. UNTRUSTED_SCRIPT_LOG = proc { |data| Script.log(data) }
  388. UNTRUSTED_GAMEOBJ_LOAD_DATA = proc { GameObj.load_data }
  389. JUMP = Exception.exception('JUMP')
  390. JUMP_ERROR = Exception.exception('JUMP_ERROR')
  391. DIRMAP = {
  392. 'out' => 'K',
  393. 'ne' => 'B',
  394. 'se' => 'D',
  395. 'sw' => 'F',
  396. 'nw' => 'H',
  397. 'up' => 'I',
  398. 'down' => 'J',
  399. 'n' => 'A',
  400. 'e' => 'C',
  401. 's' => 'E',
  402. 'w' => 'G',
  403. }
  404. SHORTDIR = {
  405. 'out' => 'out',
  406. 'northeast' => 'ne',
  407. 'southeast' => 'se',
  408. 'southwest' => 'sw',
  409. 'northwest' => 'nw',
  410. 'up' => 'up',
  411. 'down' => 'down',
  412. 'north' => 'n',
  413. 'east' => 'e',
  414. 'south' => 's',
  415. 'west' => 'w',
  416. }
  417. LONGDIR = {
  418. 'out' => 'out',
  419. 'ne' => 'northeast',
  420. 'se' => 'southeast',
  421. 'sw' => 'southwest',
  422. 'nw' => 'northwest',
  423. 'up' => 'up',
  424. 'down' => 'down',
  425. 'n' => 'north',
  426. 'e' => 'east',
  427. 's' => 'south',
  428. 'w' => 'west',
  429. }
  430. MINDMAP = {
  431. 'clear as a bell' => 'A',
  432. 'fresh and clear' => 'B',
  433. 'clear' => 'C',
  434. 'muddled' => 'D',
  435. 'becoming numbed' => 'E',
  436. 'numbed' => 'F',
  437. 'must rest' => 'G',
  438. 'saturated' => 'H',
  439. }
  440. ICONMAP = {
  441. 'IconKNEELING' => 'GH',
  442. 'IconPRONE' => 'G',
  443. 'IconSITTING' => 'H',
  444. 'IconSTANDING' => 'T',
  445. 'IconSTUNNED' => 'I',
  446. 'IconHIDDEN' => 'N',
  447. 'IconINVISIBLE' => 'D',
  448. 'IconDEAD' => 'B',
  449. 'IconWEBBED' => 'C',
  450. 'IconJOINED' => 'P',
  451. 'IconBLEEDING' => 'O',
  452. }
  453. class NilClass
  454. def dup
  455. nil
  456. end
  457. def method_missing(*args)
  458. nil
  459. end
  460. def split(*val)
  461. Array.new
  462. end
  463. def to_s
  464. ""
  465. end
  466. def strip
  467. ""
  468. end
  469. def +(val)
  470. val
  471. end
  472. def closed?
  473. true
  474. end
  475. end
  476. class Array
  477. def method_missing(*usersave)
  478. self
  479. end
  480. end
  481. class LimitedArray < Array
  482. attr_accessor :max_size
  483. def initialize(size=0, obj=nil)
  484. @max_size = 200
  485. super
  486. end
  487. def push(line)
  488. self.shift while self.length >= @max_size
  489. super
  490. end
  491. def shove(line)
  492. push(line)
  493. end
  494. def history
  495. Array.new
  496. end
  497. end
  498. # fixme: causes slowdown on Windows (maybe)
  499. class CachedArray < Array
  500. attr_accessor :min_size, :max_size
  501. def initialize(size=0, obj=nil)
  502. @min_size = 200
  503. @max_size = 250
  504. num = Time.now.to_i-1
  505. @filename = "#{$temp_dir}cache-#{num}.txt"
  506. @filename = "#{$temp_dir}cache-#{num+=1}.txt" while File.exists?(@filename)
  507. @file = File.open(@filename, 'w')
  508. super
  509. end
  510. def push(line)
  511. if self.length >= @max_size
  512. @file.puts(self.shift) while (self.length >= @min_size)
  513. @file.flush
  514. end
  515. super
  516. end
  517. def history
  518. @file.flush
  519. @file.close
  520. @file = File.open(@filename, 'r')
  521. h = @file.readlines
  522. @file.close
  523. @file = File.open(@filename, 'a')
  524. h
  525. end
  526. end
  527. module StringFormatting
  528. def as_time
  529. sprintf("%d:%02d:%02d", (self / 60).truncate, self.truncate % 60, ((self % 1) * 60).truncate)
  530. end
  531. end
  532. class Numeric
  533. include StringFormatting
  534. end
  535. class TrueClass
  536. def method_missing(*usersave)
  537. true
  538. end
  539. def to_ary
  540. nil
  541. end
  542. end
  543. class FalseClass
  544. def method_missing(*usersave)
  545. nil
  546. end
  547. end
  548. class String
  549. # causes an error in File.exists? for Ruby 1.9.2
  550. # def method_missing(*usersave)
  551. # ""
  552. # end
  553. def silent
  554. false
  555. end
  556. def to_s
  557. self.dup
  558. end
  559. def split_as_list
  560. string = self
  561. string.sub!(/^You (?:also see|notice) |^In the .+ you see /, ',')
  562. string.sub('.','').sub(/ and (an?|some|the)/, ', \1').split(',').reject { |str| str.strip.empty? }.collect { |str| str.lstrip }
  563. end
  564. end
  565. class XMLParser
  566. 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
  567. attr_accessor :send_fake_tags
  568. @@warned_depreciated_spellfront = false
  569. include StreamListener
  570. def initialize
  571. @buffer = String.new
  572. @unescape = { 'lt' => '<', 'gt' => '>', 'quot' => '"', 'apos' => "'", 'amp' => '&' }
  573. @bold = false
  574. @active_tags = Array.new
  575. @active_ids = Array.new
  576. @last_tag = String.new
  577. @last_id = String.new
  578. @current_stream = String.new
  579. @current_style = String.new
  580. @stow_container_id = nil
  581. @obj_location = nil
  582. @obj_exist = nil
  583. @obj_noun = nil
  584. @obj_before_name = nil
  585. @obj_name = nil
  586. @obj_after_name = nil
  587. @pc = nil
  588. @last_obj = nil
  589. @in_stream = false
  590. @player_status = nil
  591. @fam_mode = String.new
  592. @room_window_disabled = false
  593. @wound_gsl = String.new
  594. @scar_gsl = String.new
  595. @send_fake_tags = false
  596. #@prompt = String.new
  597. @nerve_tracker_num = 0
  598. @nerve_tracker_active = 'no'
  599. @server_time = Time.now.to_i
  600. @server_time_offset = 0
  601. @roundtime_end = 0
  602. @cast_roundtime_end = 0
  603. @last_pulse = Time.now.to_i
  604. @level = 0
  605. @next_level_value = 0
  606. @next_level_text = String.new
  607. @room_count = 0
  608. @room_title = String.new
  609. @room_description = String.new
  610. @room_exits = Array.new
  611. @room_exits_string = String.new
  612. @room_changing = true
  613. @familiar_room_title = String.new
  614. @familiar_room_description = String.new
  615. @familiar_room_exits = Array.new
  616. @bounty_task = String.new
  617. @society_task = String.new
  618. @name = String.new
  619. @game = String.new
  620. @player_id = String.new
  621. @mana = 0
  622. @max_mana = 0
  623. @health = 0
  624. @max_health = 0
  625. @spirit = 0
  626. @max_spirit = 0
  627. @last_spirit = nil
  628. @stamina = 0
  629. @max_stamina = 0
  630. @stance_text = String.new
  631. @stance_value = 0
  632. @mind_text = String.new
  633. @mind_value = 0
  634. @prepared_spell = 'None'
  635. @encumbrance_text = String.new
  636. @encumbrance_full_text = String.new
  637. @encumbrance_value = 0
  638. @indicator = Hash.new
  639. @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}}
  640. @injury_mode = 0
  641. @active_spells = Hash.new
  642. end
  643. def reset
  644. @active_tags = Array.new
  645. @active_ids = Array.new
  646. @current_stream = String.new
  647. @current_style = String.new
  648. end
  649. def make_wound_gsl
  650. @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'])
  651. end
  652. def make_scar_gsl
  653. @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'])
  654. end
  655. def parse(line)
  656. @buffer.concat(line)
  657. loop {
  658. if str = @buffer.slice!(/^[^<]+/)
  659. text(str.gsub(/&(lt|gt|quot|apos|amp)/) { @unescape[$1] })
  660. elsif str = @buffer.slice!(/^<\/[^<]+>/)
  661. element = /^<\/([^\s>\/]+)/.match(str).captures.first
  662. tag_end(element)
  663. elsif str = @buffer.slice!(/^<[^<]+>/)
  664. element = /^<([^\s>\/]+)/.match(str).captures.first
  665. attributes = Hash.new
  666. str.scan(/([A-z][A-z0-9_\-]*)=(["'])(.*?)\2/).each { |attr| attributes[attr[0]] = attr[2] }
  667. tag_start(element, attributes)
  668. tag_end(element) if str =~ /\/>$/
  669. else
  670. break
  671. end
  672. }
  673. end
  674. def tag_start(name, attributes)
  675. begin
  676. @active_tags.push(name)
  677. @active_ids.push(attributes['id'].to_s)
  678. if name == 'dialogData' and attributes['id'] == 'ActiveSpells' and attributes['clear'] == 't'
  679. @active_spells.clear
  680. elsif name == 'resource' or name == 'nav'
  681. nil
  682. elsif name == 'pushStream'
  683. @in_stream = true
  684. @current_stream = attributes['id'].to_s
  685. GameObj.clear_inv if attributes['id'].to_s == 'inv'
  686. elsif name == 'popStream'
  687. @in_stream = false
  688. if attributes['id'] == 'bounty'
  689. @bounty_task.strip!
  690. end
  691. @current_stream = String.new
  692. elsif name == 'pushBold'
  693. @bold = true
  694. elsif name == 'popBold'
  695. @bold = false
  696. elsif (name == 'streamWindow')
  697. if attributes['id'] == 'room'
  698. @room_changing = true
  699. elsif (attributes['id'] == 'main') and attributes['subtitle']
  700. @room_title = '[' + attributes['subtitle'][3..-1] + ']'
  701. end
  702. elsif name == 'style'
  703. @current_style = attributes['id']
  704. if @room_changing and attributes['id'] == 'roomName'
  705. @room_count += 1
  706. $room_count += 1
  707. @room_changing = false
  708. end
  709. elsif name == 'prompt'
  710. @server_time = attributes['time'].to_i
  711. @server_time_offset = (Time.now.to_i - @server_time)
  712. $_CLIENT_.puts "\034GSq#{sprintf('%010d', @server_time)}\r\n" if @send_fake_tags
  713. elsif (name == 'compDef') or (name == 'component')
  714. if attributes['id'] == 'room objs'
  715. GameObj.clear_loot
  716. GameObj.clear_npcs
  717. elsif attributes['id'] == 'room players'
  718. GameObj.clear_pcs
  719. elsif attributes['id'] == 'room exits'
  720. @room_exits = Array.new
  721. @room_exits_string = String.new
  722. elsif attributes['id'] == 'room desc'
  723. @room_description = String.new
  724. GameObj.clear_room_desc
  725. # elsif attributes['id'] == 'sprite'
  726. end
  727. elsif name =~ /^(?:a|right|left)$/
  728. @obj_exist = attributes['exist']
  729. @obj_noun = attributes['noun']
  730. elsif name == 'clearContainer'
  731. if attributes['id'] == 'stow'
  732. GameObj.clear_container(@stow_container_id)
  733. else
  734. GameObj.clear_container(attributes['id'])
  735. end
  736. elsif name == 'deleteContainer'
  737. GameObj.delete_container(attributes['id'])
  738. elsif name == 'inv'
  739. if attributes['id'] == 'stow'
  740. @obj_location = @stow_container_id
  741. else
  742. @obj_location = attributes['id']
  743. end
  744. @obj_exist = nil
  745. @obj_noun = nil
  746. @obj_name = nil
  747. @obj_before_name = nil
  748. @obj_after_name = nil
  749. elsif name == 'progressBar'
  750. if attributes['id'] == 'pbarStance'
  751. @stance_text = attributes['text'].split.first
  752. @stance_value = attributes['value'].to_i
  753. $_CLIENT_.puts "\034GSg#{sprintf('%010d', @stance_value)}\r\n" if @send_fake_tags
  754. elsif attributes['id'] == 'mana'
  755. last_mana = @mana
  756. @mana, @max_mana = attributes['text'].scan(/-?\d+/).collect { |num| num.to_i }
  757. difference = @mana - last_mana
  758. # fixme: enhancives screw this up
  759. if (difference == noded_pulse) or (difference == unnoded_pulse) or ( (@mana == @max_mana) and (last_mana + noded_pulse > @max_mana) )
  760. @last_pulse = Time.now.to_i
  761. if @send_fake_tags
  762. $_CLIENT_.puts "\034GSZ#{sprintf('%010d',(@mana+1))}\n"
  763. $_CLIENT_.puts "\034GSZ#{sprintf('%010d',@mana)}\n"
  764. end
  765. end
  766. if @send_fake_tags
  767. $_CLIENT_.puts "\034GSV#{sprintf('%010d%010d%010d%010d%010d%010d%010d%010d', @max_health, @health, @max_spirit, @spirit, @max_mana, @mana, @wound_gsl, @scar_gsl)}\r\n"
  768. end
  769. elsif attributes['id'] == 'stamina'
  770. @stamina, @max_stamina = attributes['text'].scan(/-?\d+/).collect { |num| num.to_i }
  771. elsif attributes['id'] == 'mindState'
  772. @mind_text = attributes['text']
  773. @mind_value = attributes['value'].to_i
  774. $_CLIENT_.puts "\034GSr#{MINDMAP[@mind_text]}\r\n" if @send_fake_tags
  775. elsif attributes['id'] == 'health'
  776. @health, @max_health = attributes['text'].scan(/-?\d+/).collect { |num| num.to_i }
  777. $_CLIENT_.puts "\034GSV#{sprintf('%010d%010d%010d%010d%010d%010d%010d%010d', @max_health, @health, @max_spirit, @spirit, @max_mana, @mana, @wound_gsl, @scar_gsl)}\r\n" if @send_fake_tags
  778. elsif attributes['id'] == 'spirit'
  779. @last_spirit = @spirit if @last_spirit
  780. @spirit, @max_spirit = attributes['text'].scan(/-?\d+/).collect { |num| num.to_i }
  781. @last_spirit = @spirit unless @last_spirit
  782. $_CLIENT_.puts "\034GSV#{sprintf('%010d%010d%010d%010d%010d%010d%010d%010d', @max_health, @health, @max_spirit, @spirit, @max_mana, @mana, @wound_gsl, @scar_gsl)}\r\n" if @send_fake_tags
  783. elsif attributes['id'] == 'nextLvlPB'
  784. Gift.pulse unless @next_level_text == attributes['text']
  785. @next_level_value = attributes['value'].to_i
  786. @next_level_text = attributes['text']
  787. elsif attributes['id'] == 'encumlevel'
  788. @encumbrance_value = attributes['value'].to_i
  789. @encumbrance_text = attributes['text']
  790. end
  791. elsif name == 'roundTime'
  792. @roundtime_end = attributes['value'].to_i
  793. $_CLIENT_.puts "\034GSQ#{sprintf('%010d', @roundtime_end)}\r\n" if @send_fake_tags
  794. elsif name == 'castTime'
  795. @cast_roundtime_end = attributes['value'].to_i
  796. elsif name == 'indicator'
  797. @indicator[attributes['id']] = attributes['visible']
  798. if @send_fake_tags
  799. if attributes['id'] == 'IconPOISONED'
  800. if attributes['visible'] == 'y'
  801. $_CLIENT_.puts "\034GSJ0000000000000000000100000000001\r\n"
  802. else
  803. $_CLIENT_.puts "\034GSJ0000000000000000000000000000000\r\n"
  804. end
  805. elsif attributes['id'] == 'IconDISEASED'
  806. if attributes['visible'] == 'y'
  807. $_CLIENT_.puts "\034GSK0000000000000000000100000000001\r\n"
  808. else
  809. $_CLIENT_.puts "\034GSK0000000000000000000000000000000\r\n"
  810. end
  811. else
  812. gsl_prompt = String.new; ICONMAP.keys.each { |icon| gsl_prompt += ICONMAP[icon] if @indicator[icon] == 'y' }
  813. $_CLIENT_.puts "\034GSP#{sprintf('%-30s', gsl_prompt)}\r\n"
  814. end
  815. end
  816. elsif (name == 'image') and @active_ids.include?('injuries')
  817. if @injuries.keys.include?(attributes['id'])
  818. if attributes['name'] =~ /Injury/i
  819. @injuries[attributes['id']]['wound'] = attributes['name'].slice(/\d/).to_i
  820. elsif attributes['name'] =~ /Scar/i
  821. @injuries[attributes['id']]['wound'] = 0
  822. @injuries[attributes['id']]['scar'] = attributes['name'].slice(/\d/).to_i
  823. elsif attributes['name'] =~ /Nsys/i
  824. rank = attributes['name'].slice(/\d/).to_i
  825. if rank == 0
  826. @injuries['nsys']['wound'] = 0
  827. @injuries['nsys']['scar'] = 0
  828. else
  829. Thread.new {
  830. wait_while { dead? }
  831. action = proc { |server_string|
  832. if (@nerve_tracker_active == 'maybe')
  833. if @nerve_tracker_active == 'maybe'
  834. if server_string =~ /^You/
  835. @nerve_tracker_active = 'yes'
  836. @injuries['nsys']['wound'] = 0
  837. @injuries['nsys']['scar'] = 0
  838. else
  839. @nerve_tracker_active = 'no'
  840. end
  841. end
  842. end
  843. if @nerve_tracker_active == 'yes'
  844. if server_string =~ /<output class=['"]['"]\/>/
  845. @nerve_tracker_active = 'no'
  846. @nerve_tracker_num -= 1
  847. DownstreamHook.remove('nerve_tracker') if @nerve_tracker_num < 1
  848. $_CLIENT_.puts "\034GSV#{sprintf('%010d%010d%010d%010d%010d%010d%010d%010d', @max_health, @health, @max_spirit, @spirit, @max_mana, @mana, make_wound_gsl, make_scar_gsl)}\r\n" if @send_fake_tags
  849. server_string
  850. elsif server_string =~ /a case of uncontrollable convulsions/
  851. @injuries['nsys']['wound'] = 3
  852. nil
  853. elsif server_string =~ /a case of sporadic convulsions/
  854. @injuries['nsys']['wound'] = 2
  855. nil
  856. elsif server_string =~ /a strange case of muscle twitching/
  857. @injuries['nsys']['wound'] = 1
  858. nil
  859. elsif server_string =~ /a very difficult time with muscle control/
  860. @injuries['nsys']['scar'] = 3
  861. nil
  862. elsif server_string =~ /constant muscle spasms/
  863. @injuries['nsys']['scar'] = 2
  864. nil
  865. elsif server_string =~ /developed slurred speech/
  866. @injuries['nsys']['scar'] = 1
  867. nil
  868. end
  869. else
  870. if server_string =~ /<output class=['"]mono['"]\/>/
  871. @nerve_tracker_active = 'maybe'
  872. end
  873. server_string
  874. end
  875. }
  876. @nerve_tracker_num += 1
  877. DownstreamHook.add('nerve_tracker', action)
  878. $_SERVER_.puts "#{$cmd_prefix}health\n"
  879. }
  880. end
  881. else
  882. @injuries[attributes['id']]['wound'] = 0
  883. @injuries[attributes['id']]['scar'] = 0
  884. end
  885. end
  886. $_CLIENT_.puts "\034GSV#{sprintf('%010d%010d%010d%010d%010d%010d%010d%010d', @max_health, @health, @max_spirit, @spirit, @max_mana, @mana, make_wound_gsl, make_scar_gsl)}\r\n" if @send_fake_tags
  887. elsif name == 'compass'
  888. if @current_stream == 'familiar'
  889. @fam_mode = String.new
  890. elsif @room_window_disabled
  891. @room_exits = Array.new
  892. end
  893. elsif @room_window_disabled and (name == 'dir') and @active_tags.include?('compass')
  894. @room_exits.push(LONGDIR[attributes['value']])
  895. elsif name == 'radio'
  896. if attributes['id'] == 'injrRad'
  897. @injury_mode = 0 if attributes['value'] == '1'
  898. elsif attributes['id'] == 'scarRad'
  899. @injury_mode = 1 if attributes['value'] == '1'
  900. elsif attributes['id'] == 'bothRad'
  901. @injury_mode = 2 if attributes['value'] == '1'
  902. end
  903. elsif name == 'label'
  904. if attributes['id'] == 'yourLvl'
  905. @level = Stats.level = attributes['value'].slice(/\d+/).to_i
  906. elsif attributes['id'] == 'encumblurb'
  907. @encumbrance_full_text = attributes['value']
  908. elsif @active_tags[-2] == 'dialogData' and @active_ids[-2] == 'ActiveSpells'
  909. if (name = /^lbl(.+)$/.match(attributes['id']).captures.first) and (value = /^\s*([0-9\:]+)\s*$/.match(attributes['value']).captures.first)
  910. hour, minute = value.split(':')
  911. @active_spells[name] = Time.now + (hour.to_i * 3600) + (minute.to_i * 60)
  912. end
  913. end
  914. elsif (name == 'container') and (attributes['id'] == 'stow')
  915. @stow_container_id = attributes['target'].sub('#', '')
  916. elsif (name == 'clearStream')
  917. if attributes['id'] == 'bounty'
  918. @bounty_task = String.new
  919. end
  920. elsif (name == 'playerID')
  921. @player_id = attributes['id']
  922. unless $frontend =~ /^(?:wizard|avalon)$/
  923. LichSettings[@player_id] ||= Hash.new
  924. if LichSettings[@player_id]['enable_inventory_boxes']
  925. DownstreamHook.remove('inventory_boxes_off')
  926. end
  927. end
  928. elsif (name == 'app') and (@name = attributes['char'])
  929. @game = attributes['game']
  930. if @game.nil? or @game.empty?
  931. @game = 'unknown'
  932. end
  933. unless File.exists?("#{$data_dir}#{@game}")
  934. Dir.mkdir("#{$data_dir}#{@game}")
  935. end
  936. unless File.exists?("#{$data_dir}#{@game}/#{@name}")
  937. Dir.mkdir("#{$data_dir}#{@game}/#{@name}")
  938. end
  939. if $frontend =~ /^(?:wizard|avalon)$/
  940. $_SERVER_.puts "#{$cmd_prefix}_flag Display Dialog Boxes 0"
  941. sleep "0.05".to_f
  942. $_SERVER_.puts "#{$cmd_prefix}_injury 2"
  943. sleep "0.05".to_f
  944. # fixme: game name hardcoded as Gemstone IV; maybe doesn't make any difference to the client
  945. $_CLIENT_.puts "\034GSB0000000000#{attributes['char']}\r\n\034GSA#{Time.now.to_i.to_s}GemStone IV\034GSD\r\n"
  946. # 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
  947. @send_fake_tags = true
  948. # Send all the tags we missed out on
  949. $_CLIENT_.puts "\034GSV#{sprintf('%010d%010d%010d%010d%010d%010d%010d%010d', @max_health, @health, @max_spirit, @spirit, @max_mana, @mana, make_wound_gsl, make_scar_gsl)}\r\n"
  950. $_CLIENT_.puts "\034GSg#{sprintf('%010d', @stance_value)}\r\n"
  951. $_CLIENT_.puts "\034GSr#{MINDMAP[@mind_text]}\r\n"
  952. gsl_prompt = String.new
  953. @indicator.keys.each { |icon| gsl_prompt += ICONMAP[icon] if @indicator[icon] == 'y' }
  954. $_CLIENT_.puts "\034GSP#{sprintf('%-30s', gsl_prompt)}\r\n"
  955. gsl_prompt = nil
  956. gsl_exits = String.new
  957. @room_exits.each { |exit| gsl_exits.concat(DIRMAP[SHORTDIR[exit]].to_s) }
  958. $_CLIENT_.puts "\034GSj#{sprintf('%-20s', gsl_exits)}\r\n"
  959. gsl_exits = nil
  960. $_CLIENT_.puts "\034GSn#{sprintf('%-14s', @prepared_spell)}\r\n"
  961. $_CLIENT_.puts "\034GSm#{sprintf('%-45s', GameObj.right_hand.name)}\r\n"
  962. $_CLIENT_.puts "\034GSl#{sprintf('%-45s', GameObj.left_hand.name)}\r\n"
  963. $_CLIENT_.puts "\034GSq#{sprintf('%010d', @server_time)}\r\n"
  964. $_CLIENT_.puts "\034GSQ#{sprintf('%010d', @roundtime_end)}\r\n" if @roundtime_end > 0
  965. end
  966. $_SERVER_.puts("#{$cmd_prefix}_flag Display Inventory Boxes 1")
  967. UserVariables.init
  968. Alias.init
  969. Favorites.init
  970. if arg = ARGV.find { |a| a=~ /^\-\-start\-scripts=/ }
  971. for script_name in arg.sub('--start-scripts=', '').split(',')
  972. start_script(script_name)
  973. end
  974. end
  975. end
  976. rescue
  977. $stdout.puts "--- error: XMLParser.tag_start: #{$!}"
  978. $stderr.puts "error: XMLParser.tag_start: #{$!}"
  979. $stderr.puts $!.backtrace
  980. sleep "0.1".to_f
  981. reset
  982. end
  983. end
  984. def text(text_string)
  985. begin
  986. # fixme: /<stream id="Spells">.*?<\/stream>/m
  987. # $_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)$/))
  988. if @active_tags.last == 'prompt'
  989. #@prompt = text_string
  990. nil
  991. elsif @active_tags.include?('right')
  992. GameObj.new_right_hand(@obj_exist, @obj_noun, text_string)
  993. $_CLIENT_.puts "\034GSm#{sprintf('%-45s', text_string)}\r\n" if @send_fake_tags
  994. elsif @active_tags.include?('left')
  995. GameObj.new_left_hand(@obj_exist, @obj_noun, text_string)
  996. $_CLIENT_.puts "\034GSl#{sprintf('%-45s', text_string)}\r\n" if @send_fake_tags
  997. elsif @active_tags.include?('spell')
  998. @prepared_spell = text_string
  999. $_CLIENT_.puts "\034GSn#{sprintf('%-14s', text_string)}\r\n" if @send_fake_tags
  1000. elsif @active_tags.include?('compDef') or @active_tags.include?('component')
  1001. if @active_ids.include?('room objs')
  1002. if @active_tags.include?('a')
  1003. if @bold
  1004. GameObj.new_npc(@obj_exist, @obj_noun, text_string)
  1005. else
  1006. GameObj.new_loot(@obj_exist, @obj_noun, text_string)
  1007. end
  1008. elsif (text_string =~ /that (?:is|appears) ([\w\s]+)(?:,| and|\.)/) or (text_string =~ / \(([^\(]+)\)/)
  1009. GameObj.npcs[-1].status = $1
  1010. end
  1011. elsif @active_ids.include?('room players')
  1012. if @active_tags.include?('a')
  1013. @pc = GameObj.new_pc(@obj_exist, @obj_noun, "#{@player_title}#{text_string}", @player_status)
  1014. @player_status = nil
  1015. else
  1016. if @game =~ /^DR/
  1017. GameObj.clear_pcs
  1018. text_string.sub(/^Also here\: /, '').sub(/ and ([^,]+)\./) { ", #{$1}" }.split(', ').each { |player|
  1019. if player =~ / who is (.+)/
  1020. status = $1
  1021. player.sub!(/ who is .+/, '')
  1022. elsif player =~ / \((.+)\)/
  1023. status = $1
  1024. player.sub!(/ \(.+\)/, '')
  1025. else
  1026. status = nil
  1027. end
  1028. noun = player.slice(/\b[A-Z][a-z]+$/)
  1029. if player =~ /the body of /
  1030. player.sub!('the body of ', '')
  1031. if status
  1032. status.concat ' dead'
  1033. else
  1034. status = 'dead'
  1035. end
  1036. end
  1037. if player =~ /a stunned /
  1038. player.sub!('a stunned ', '')
  1039. if status
  1040. status.concat ' stunned'
  1041. else
  1042. status = 'stunned'
  1043. end
  1044. end
  1045. GameObj.new_pc(nil, noun, player, status)
  1046. }
  1047. else
  1048. if (text_string =~ /^ who (?:is|appears) ([\w\s]+)(?:,| and|\.|$)/) or (text_string =~ / \(([\w\s]+)\)(?: \(([\w\s]+)\))?/)
  1049. if @pc.status
  1050. @pc.status.concat " #{$1}"
  1051. else
  1052. @pc.status = $1
  1053. end
  1054. @pc.status.concat " #{$2}" if $2
  1055. end
  1056. if text_string =~ /(?:^Also here: |, )(?:a )?([a-z\s]+)?([\w\s\-!\?',]+)?$/
  1057. @player_status = ($1.strip.gsub('the body of', 'dead')) if $1
  1058. @player_title = $2
  1059. end
  1060. end
  1061. end
  1062. elsif @active_ids.include?('room desc')
  1063. if text_string == '[Room window disabled at this location.]'
  1064. @room_window_disabled = true
  1065. else
  1066. @room_window_disabled = false
  1067. @room_description.concat(text_string)
  1068. if @active_tags.include?('a')
  1069. GameObj.new_room_desc(@obj_exist, @obj_noun, text_string)
  1070. end
  1071. end
  1072. elsif @active_ids.include?('room exits')
  1073. @room_exits_string.concat(text_string)
  1074. @room_exits.push(text_string) if @active_tags.include?('d')
  1075. end
  1076. elsif @active_tags.include?('inv')
  1077. if @active_tags[-1] == 'a'
  1078. @obj_name = text_string
  1079. elsif @obj_name.nil?
  1080. @obj_before_name = text_string.strip
  1081. else
  1082. @obj_after_name = text_string.strip
  1083. end
  1084. elsif @current_stream == 'bounty'
  1085. @bounty_task += text_string
  1086. elsif @current_stream == 'society'
  1087. @society_task = text_string
  1088. elsif (@current_stream == 'inv') and @active_tags.include?('a')
  1089. GameObj.new_inv(@obj_exist, @obj_noun, text_string, nil)
  1090. elsif @current_stream == 'familiar'
  1091. # 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
  1092. if @current_style == 'roomName'
  1093. @familiar_room_title = text_string
  1094. @familiar_room_description = String.new
  1095. @familiar_room_exits = Array.new
  1096. GameObj.clear_fam_room_desc
  1097. GameObj.clear_fam_loot
  1098. GameObj.clear_fam_npcs
  1099. GameObj.clear_fam_pcs
  1100. @fam_mode = String.new
  1101. elsif @current_style == 'roomDesc'
  1102. @familiar_room_description.concat(text_string)
  1103. if @active_tags.include?('a')
  1104. GameObj.new_fam_room_desc(@obj_exist, @obj_noun, text_string)
  1105. end
  1106. elsif text_string =~ /^You also see/
  1107. @fam_mode = 'things'
  1108. elsif text_string =~ /^Also here/
  1109. @fam_mode = 'people'
  1110. elsif text_string =~ /Obvious (?:paths|exits)/
  1111. @fam_mode = 'paths'
  1112. elsif @fam_mode == 'things'
  1113. if @active_tags.include?('a')
  1114. if @bold
  1115. GameObj.new_fam_npc(@obj_exist, @obj_noun, text_string)
  1116. else
  1117. GameObj.new_fam_loot(@obj_exist, @obj_noun, text_string)
  1118. end
  1119. end
  1120. # puts 'things: ' + text_string
  1121. elsif @fam_mode == 'people' and @active_tags.include?('a')
  1122. GameObj.new_fam_pc(@obj_exist, @obj_noun, text_string)
  1123. # puts 'people: ' + text_string
  1124. elsif (@fam_mode == 'paths') and @active_tags.include?('a')
  1125. @familiar_room_exits.push(text_string)
  1126. end
  1127. elsif @room_window_disabled
  1128. if @current_style == 'roomDesc'
  1129. @room_description.concat(text_string)
  1130. if @active_tags.include?('a')
  1131. GameObj.new_room_desc(@obj_exist, @obj_noun, text_string)
  1132. end
  1133. elsif text_string =~ /^Obvious (?:paths|exits): (?:none)?$/
  1134. @room_exits_string = text_string.strip
  1135. end
  1136. end
  1137. rescue
  1138. $stdout.puts "--- error: XMLParser.text: #{$!}"
  1139. $stderr.puts "error: XMLParser.text: #{$!}"
  1140. $stderr.puts $!.backtrace
  1141. sleep "0.1".to_f
  1142. reset
  1143. end
  1144. end
  1145. def tag_end(name)
  1146. begin
  1147. if name == 'inv'
  1148. if @obj_exist == @obj_location
  1149. if @obj_after_name == 'is closed.'
  1150. GameObj.delete_container(@stow_container_id)
  1151. end
  1152. elsif @obj_exist
  1153. GameObj.new_inv(@obj_exist, @obj_noun, @obj_name, @obj_location, @obj_before_name, @obj_after_name)
  1154. end
  1155. elsif @send_fake_tags and (@active_ids.last == 'room exits')
  1156. gsl_exits = String.new
  1157. @room_exits.each { |exit| gsl_exits.concat(DIRMAP[SHORTDIR[exit]].to_s) }
  1158. $_CLIENT_.puts "\034GSj#{sprintf('%-20s', gsl_exits)}\r\n"
  1159. gsl_exits = nil
  1160. elsif @room_window_disabled and (name == 'compass')
  1161. @room_window_disabled = false
  1162. @room_description = @room_description.strip
  1163. @room_exits_string.concat " #{@room_exits.join(', ')}" unless @room_exits.empty?
  1164. gsl_exits = String.new
  1165. @room_exits.each { |exit| gsl_exits.concat(DIRMAP[SHORTDIR[exit]].to_s) }
  1166. $_CLIENT_.puts "\034GSj#{sprintf('%-20s', gsl_exits)}\r\n"
  1167. gsl_exits = nil
  1168. end
  1169. @last_tag = @active_tags.pop
  1170. @last_id = @active_ids.pop
  1171. rescue
  1172. $stdout.puts "--- error: XMLParser.tag_end: #{$!}"
  1173. $stderr.puts "error: XMLParser.tag_end: #{$!}"
  1174. $stderr.puts $!.backtrace
  1175. sleep "0.1".to_f
  1176. reset
  1177. end
  1178. end
  1179. # here for backwards compatibility, but spellfront xml isn't sent by the game anymore
  1180. def spellfront
  1181. unless @@warned_depreciated_spellfront
  1182. @@warned_depreciated_spellfront = true
  1183. unless script_name = Script.self.name
  1184. script_name = 'unknown script'
  1185. end
  1186. respond "--- warning: #{script_name} is using depreciated method XMLData.spellfront"
  1187. end
  1188. @active_spells.keys
  1189. end
  1190. end
  1191. XMLData = XMLParser.new
  1192. class UpstreamHook
  1193. @@upstream_hooks ||= Hash.new
  1194. def UpstreamHook.add(name, action)
  1195. unless action.class == Proc
  1196. echo "UpstreamHook: not a Proc (#{action})"
  1197. return false
  1198. end
  1199. @@upstream_hooks[name] = action
  1200. end
  1201. def UpstreamHook.run(client_string)
  1202. for key in @@upstream_hooks.keys
  1203. begin
  1204. client_string = @@upstream_hooks[key].call(client_string)
  1205. rescue
  1206. @@upstream_hooks.delete(key)
  1207. respond "--- Lich: UpstreamHook: #{$!}"
  1208. respond $!.backtrace.first
  1209. end
  1210. return nil if client_string.nil?
  1211. end
  1212. return client_string
  1213. end
  1214. def UpstreamHook.remove(name)
  1215. @@upstream_hooks.delete(name)
  1216. end
  1217. def UpstreamHook.list
  1218. @@upstream_hooks.keys.dup
  1219. end
  1220. end
  1221. class DownstreamHook
  1222. @@downstream_hooks ||= Hash.new
  1223. def DownstreamHook.add(name, action)
  1224. unless action.class == Proc
  1225. echo "DownstreamHook: not a Proc (#{action})"
  1226. return false
  1227. end
  1228. @@downstream_hooks[name] = action
  1229. end
  1230. def DownstreamHook.run(server_string)
  1231. for key in @@downstream_hooks.keys
  1232. begin
  1233. server_string = @@downstream_hooks[key].call(server_string.dup)
  1234. rescue
  1235. @@downstream_hooks.delete(key)
  1236. respond "--- Lich: DownstreamHook: #{$!}"
  1237. respond $!.backtrace.first
  1238. end
  1239. return nil if server_string.nil?
  1240. end
  1241. return server_string
  1242. end
  1243. def DownstreamHook.remove(name)
  1244. @@downstream_hooks.delete(name)
  1245. end
  1246. def DownstreamHook.list
  1247. @@downstream_hooks.keys.dup
  1248. end
  1249. end
  1250. class LichSettings
  1251. @@settings ||= Hash.new
  1252. def LichSettings.load
  1253. if File.exists?("#{$data_dir}lich.sav")
  1254. begin
  1255. File.open("#{$data_dir}lich.sav", 'rb') { |f|
  1256. @@settings = Marshal.load(f.read)['lichsettings']
  1257. }
  1258. rescue
  1259. respond "--- error: LichSettings.load: #{$!}"
  1260. $stderr.puts "error: LichSettings.load: #{$!}"
  1261. $stderr.puts $!.backtrace
  1262. end
  1263. end
  1264. @@settings ||= Hash.new
  1265. end
  1266. def LichSettings.save
  1267. begin
  1268. all_settings = Hash.new
  1269. if File.exists?("#{$data_dir}lich.sav")
  1270. File.open("#{$data_dir}lich.sav", 'rb') { |f| all_settings = Marshal.load(f.read) }
  1271. end
  1272. all_settings['lichsettings'] = @@settings
  1273. File.open("#{$data_dir}lich.sav", 'wb') { |f| f.write(Marshal.dump(all_settings)) }
  1274. true
  1275. rescue
  1276. false
  1277. end
  1278. end
  1279. def LichSettings.list
  1280. settings = @@settings.dup
  1281. if caller.any? { |line| line =~ /start_script|start_exec_script|main_with_queue/ }
  1282. settings.delete('quick_game_entry')
  1283. settings.delete('trusted_scripts')
  1284. settings.delete('untrusted_scripts')
  1285. end
  1286. settings
  1287. end
  1288. def LichSettings.clear
  1289. @@settings = Hash.new
  1290. end
  1291. def LichSettings.[](setting_name)
  1292. if (setting_name == 'quick_game_entry') and (($SAFE != 0) or caller.any? { |line| line =~ /start_script|start_exec_script|main_with_queue/})
  1293. nil
  1294. elsif (setting_name == 'trusted_scripts' or setting_name == 'untrusted_scripts') and (($SAFE != 0) or (caller.any? { |line| line =~ /start_script|start_exec_script|main_with_queue/} and Script.self.name !~ /^updater$|^repository$/))
  1295. @@settings[setting_name].dup
  1296. else
  1297. @@settings[setting_name]
  1298. end
  1299. end
  1300. def LichSettings.[]=(setting_name, setting_value)
  1301. if (setting_name == 'quick_game_entry') and (($SAFE != 0) or caller.any? { |line| line =~ /start_script|start_exec_script|main_with_queue/})
  1302. nil
  1303. elsif (setting_name == 'trusted_scripts' or setting_name == 'untrusted_scripts') and (($SAFE != 0) or (caller.any? { |line| line =~ /start_script|start_exec_script|main_with_queue/} and Script.self.name !~ /^updater$|^repository$/))
  1304. nil
  1305. else
  1306. @@settings[setting_name] = setting_value
  1307. end
  1308. end
  1309. end
  1310. class Settings
  1311. @@settings ||= Hash.new
  1312. @@timestamps ||= Hash.new
  1313. @@md5 ||= Hash.new
  1314. def Settings.load
  1315. if $SAFE == 0
  1316. if script = Script.self
  1317. if script.class == Script
  1318. if script.name =~ /^lich$/i
  1319. respond '--- Lich: If you insist, you may have a script named \'lich\', but it cannot use Settings, because it will conflict with Lich\'s settings.'
  1320. return false
  1321. end
  1322. filename = "#{$data_dir}#{script.name}.sav"
  1323. if File.exists?(filename) and (@@timestamps[script.name].nil? or (@@timestamps[script.name] < File.mtime(filename)))
  1324. begin
  1325. File.open(filename, 'rb') { |f| @@settings[script.name] = Marshal.load(f.read) }
  1326. @@timestamps[script] = File.mtime(filename)
  1327. @@md5[script.name] = Digest::MD5.hexdigest(@@settings[script.name].inspect)
  1328. rescue
  1329. respond "--- error: Settings.load: #{$!}"
  1330. $stderr.puts "error: Settings.load: #{$!}"
  1331. $stderr.puts $!.backtrace
  1332. begin
  1333. filename.concat '~'
  1334. if File.exists?(filename)
  1335. File.open(filename, 'rb') { |f| @@settings[script.name] = Marshal.load(f.read) }
  1336. @@timestamps[script] = File.mtime(filename)
  1337. @@md5[script.name] = Digest::MD5.hexdigest(@@settings[script.name].inspect)
  1338. end
  1339. rescue
  1340. respond "--- error: Settings.load: #{$!}"
  1341. $stderr.puts "error: Settings.load: #{$!}"
  1342. $stderr.puts $!.backtrace
  1343. end
  1344. end
  1345. end
  1346. end
  1347. else
  1348. respond '--- Lich: Settings: cannot identify calling script'
  1349. end
  1350. else
  1351. UNTRUSTED_SETTINGS_LOAD.call
  1352. end
  1353. nil
  1354. end
  1355. def Settings.save
  1356. if $SAFE == 0
  1357. if script = Script.self
  1358. if (script.class == Script) or (script.class == WizardScript)
  1359. if script.name =~ /^lich$/i
  1360. respond '--- Lich: If you insist, you may have a script named \'lich\', but it cannot use Settings, because it will conflict with Lich\'s settings.'
  1361. return false
  1362. end
  1363. filename = "#{$data_dir}#{script.name}.sav"
  1364. @@settings[script.name] ||= Hash.new
  1365. unless (@@settings[script.name].empty? and not File.exists?(filename))
  1366. md5 = Digest::MD5.hexdigest(@@settings[script.name].inspect)
  1367. unless @@md5[script.name] == md5
  1368. begin
  1369. if File.exists?(filename)
  1370. File.rename(filename, "#{filename}~")
  1371. end
  1372. File.open(filename, 'wb') { |f| f.write(Marshal.dump(@@settings[script.name])) }
  1373. @@timestamps[script.name] = File.mtime(filename)
  1374. @@md5[script.name] = md5
  1375. rescue
  1376. respond "--- error: Settings.save: #{$!}"
  1377. $stderr.puts "error: Settings.save: #{$!}"
  1378. $stderr.puts $!.backtrace
  1379. end
  1380. end
  1381. end
  1382. end
  1383. else
  1384. Settings.save_all
  1385. end
  1386. else
  1387. UNTRUSTED_SETTINGS_SAVE.call
  1388. end
  1389. nil
  1390. end
  1391. def Settings.save_all
  1392. if $SAFE == 0
  1393. for script_name in @@settings.keys
  1394. filename = "#{$data_dir}#{script_name}.sav"
  1395. @@settings[script_name] ||= Hash.new
  1396. unless (@@settings[script_name].empty? and not File.exists?(filename))
  1397. md5 = Digest::MD5.hexdigest(@@settings[script_name].inspect)
  1398. unless @@md5[script_name] == md5
  1399. begin
  1400. if File.exists?(filename)
  1401. File.rename(filename, "#{filename}~")
  1402. end
  1403. File.open(filename, 'wb') { |f| f.write(Marshal.dump(@@settings[script_name])) }
  1404. @@timestamps[script_name] = File.mtime(filename)
  1405. @@md5[script_name] = md5
  1406. rescue
  1407. respond "--- error: Settings.save_all: #{$!}"
  1408. $stderr.puts "error: Settings.save_all: #{$!}"
  1409. $stderr.puts $!.backtrace
  1410. end
  1411. end
  1412. end
  1413. end
  1414. else
  1415. UNTRUSTED_SETTINGS_SAVE_ALL.call
  1416. end
  1417. nil
  1418. end
  1419. def Settings.clear
  1420. if (script = Script.self) and (script.class == Script)
  1421. @@settings[script.name] ||= Hash.new
  1422. @@settings[script.name].clear
  1423. else
  1424. respond '--- Lich: Settings: cannot identify calling script'
  1425. nil
  1426. end
  1427. end
  1428. def Settings.[](val)
  1429. if (script = Script.self) and (script.class == Script)
  1430. @@settings[script.name] ||= Hash.new
  1431. @@settings[script.name][val]
  1432. else
  1433. respond '--- Lich: Settings: cannot identify calling script'
  1434. nil
  1435. end
  1436. end
  1437. def Settings.[]=(setting, val)
  1438. if (script = Script.self) and (script.class == Script)
  1439. @@settings[script.name] ||= Hash.new
  1440. @@settings[script.name][setting] = val
  1441. @@settings[script.name][setting]
  1442. else
  1443. respond '--- Lich: Settings: cannot identify calling script'
  1444. nil
  1445. end
  1446. end
  1447. def Settings.to_hash
  1448. if (script = Script.self) and (script.class == Script)
  1449. @@settings[script.name] ||= Hash.new
  1450. @@settings[script.name]
  1451. else
  1452. respond '--- Lich: Settings: cannot identify calling script'
  1453. nil
  1454. end
  1455. end
  1456. def Settings.auto=(val)
  1457. nil
  1458. end
  1459. def Settings.auto
  1460. nil
  1461. end
  1462. def Settings.autoload
  1463. nil
  1464. end
  1465. end
  1466. class GameSettings
  1467. @@settings ||= Hash.new
  1468. @@timestamps ||= Hash.new
  1469. @@md5 ||= Hash.new
  1470. def GameSettings.load
  1471. if $SAFE == 0
  1472. if script = Script.self
  1473. if script.class == Script
  1474. if script.name =~ /^lich$/i
  1475. respond '--- Lich: If you insist, you may have a script named \'lich\', but it cannot use GameSettings, because it will conflict with Lich\'s settings.'
  1476. return false
  1477. end
  1478. filename = "#{$data_dir}#{XMLData.game}/#{script.name}.sav"
  1479. if File.exists?(filename) and (@@timestamps[script.name].nil? or (@@timestamps[script.name] < File.mtime(filename)))
  1480. begin
  1481. File.open(filename, 'rb') { |f| @@settings[script.name] = Marshal.load(f.read) }
  1482. @@timestamps[script] = File.mtime(filename)
  1483. @@md5[script.name] = Digest::MD5.hexdigest(@@settings[script.name].inspect)
  1484. rescue
  1485. respond "--- error: GameSettings.load: #{$!}"
  1486. $stderr.puts "error: GameSettings.load: #{$!}"
  1487. $stderr.puts $!.backtrace
  1488. begin
  1489. filename.concat '~'
  1490. if File.exists?(filename)
  1491. File.open(filename, 'rb') { |f| @@settings[script.name] = Marshal.load(f.read) }
  1492. @@timestamps[script] = File.mtime(filename)
  1493. @@md5[script.name] = Digest::MD5.hexdigest(@@settings[script.name].inspect)
  1494. end
  1495. rescue
  1496. respond "--- error: GameSettings.load: #{$!}"
  1497. $stderr.puts "error: GameSettings.load: #{$!}"
  1498. $stderr.puts $!.backtrace
  1499. end
  1500. end
  1501. end
  1502. end
  1503. else
  1504. respond '--- Lich: GameSettings: cannot identify calling script'
  1505. end
  1506. else
  1507. UNTRUSTED_GAMESETTINGS_LOAD.call
  1508. end
  1509. nil
  1510. end
  1511. def GameSettings.save
  1512. if $SAFE == 0
  1513. if script = Script.self
  1514. if script.class == Script
  1515. if script.name =~ /^lich$/i
  1516. respond '--- Lich: If you insist, you may have a script named \'lich\', but it cannot use Settings, because it will conflict with Lich\'s settings.'
  1517. return false
  1518. end
  1519. filename = "#{$data_dir}#{XMLData.game}/#{script.name}.sav"
  1520. @@settings[script.name] ||= Hash.new
  1521. unless (@@settings[script.name].empty? and not File.exists?(filename))
  1522. md5 = Digest::MD5.hexdigest(@@settings[script.name].inspect)
  1523. unless @@md5[script.name] == md5
  1524. begin
  1525. if File.exists?(filename)
  1526. File.rename(filename, "#{filename}~")
  1527. end
  1528. File.open(filename, 'wb') { |f| f.write(Marshal.dump(@@settings[script.name])) }
  1529. @@timestamps[script.name] = File.mtime(filename)
  1530. @@md5[script.name] = md5
  1531. rescue
  1532. respond "--- error: GameSettings.save: #{$!}"
  1533. $stderr.puts "error: GameSettings.save: #{$!}"
  1534. $stderr.puts $!.backtrace
  1535. end
  1536. end
  1537. end
  1538. end
  1539. else
  1540. GameSettings.save_all
  1541. end
  1542. else
  1543. UNTRUSTED_GAMESETTINGS_SAVE.call
  1544. end
  1545. nil
  1546. end
  1547. def GameSettings.save_all
  1548. if $SAFE == 0
  1549. for script_name in @@settings.keys
  1550. filename = "#{$data_dir}#{XMLData.game}/#{script_name}.sav"
  1551. @@settings[script_name] ||= Hash.new
  1552. unless (@@settings[script_name].empty? and not File.exists?(filename))
  1553. md5 = Digest::MD5.hexdigest(@@settings[script_name].inspect)
  1554. unless @@md5[script_name] == md5
  1555. begin
  1556. if File.exists?(filename)
  1557. File.rename(filename, "#{filename}~")
  1558. end
  1559. File.open(filename, 'wb') { |f| f.write(Marshal.dump(@@settings[script_name])) }
  1560. @@timestamps[script_name] = File.mtime(filename)
  1561. @@md5[script_name] = md5
  1562. rescue
  1563. respond "--- error: GameSettings.save_all: #{$!}"
  1564. $stderr.puts "error: GameSettings.save_all: #{$!}"
  1565. $stderr.puts $!.backtrace
  1566. end
  1567. end
  1568. end
  1569. end
  1570. else
  1571. UNTRUSTED_GAMESETTINGS_SAVE_ALL.call
  1572. end
  1573. nil
  1574. end
  1575. def GameSettings.clear
  1576. if (script = Script.self) and (script.class == Script)
  1577. @@settings[script.name] ||= Hash.new
  1578. @@settings[script.name].clear
  1579. else
  1580. respond '--- Lich: GameSettings: cannot identify calling script'
  1581. nil
  1582. end
  1583. end
  1584. def GameSettings.[](val)
  1585. if (script = Script.self) and (script.class == Script)
  1586. @@settings[script.name] ||= Hash.new
  1587. @@settings[script.name][val]
  1588. else
  1589. respond '--- Lich: GameSettings: cannot identify calling script'
  1590. nil
  1591. end
  1592. end
  1593. def GameSettings.[]=(setting, val)
  1594. if (script = Script.self) and (script.class == Script)
  1595. @@settings[script.name] ||= Hash.new
  1596. @@settings[script.name][setting] = val
  1597. @@settings[script.name][setting]
  1598. else
  1599. respond '--- Lich: GameSettings: cannot identify calling script'
  1600. nil
  1601. end
  1602. end
  1603. def GameSettings.to_hash
  1604. if (script = Script.self) and (script.class == Script)
  1605. @@settings[script.name] ||= Hash.new
  1606. @@settings[script.name]
  1607. else
  1608. respond '--- Lich: GameSettings: cannot identify calling script'
  1609. nil
  1610. end
  1611. end
  1612. end
  1613. class CharSettings
  1614. @@settings ||= Hash.new
  1615. @@timestamps ||= Hash.new
  1616. @@md5 ||= Hash.new
  1617. def CharSettings.load
  1618. if $SAFE == 0
  1619. if script = Script.self
  1620. if script.class == Script
  1621. if script.name =~ /^lich$/i
  1622. respond '--- Lich: If you insist, you may have a script named \'lich\', but it cannot use CharSettings, because it will conflict with Lich\'s settings.'
  1623. return false
  1624. end
  1625. filename = "#{$data_dir}#{XMLData.game}/#{XMLData.name}/#{script.name}.sav"
  1626. if File.exists?(filename) and (@@timestamps[script.name].nil? or (@@timestamps[script.name] < File.mtime(filename)))
  1627. begin
  1628. File.open(filename, 'rb') { |f| @@settings[script.name] = Marshal.load(f.read) }
  1629. @@timestamps[script] = File.mtime(filename)
  1630. @@md5[script.name] = Digest::MD5.hexdigest(@@settings[script.name].inspect)
  1631. rescue
  1632. respond "--- error: CharSettings.load: #{$!}"
  1633. $stderr.puts "error: CharSettings.load: #{$!}"
  1634. $stderr.puts $!.backtrace
  1635. begin
  1636. filename.concat '~'
  1637. if File.exists?(filename)
  1638. File.open(filename, 'rb') { |f| @@settings[script.name] = Marshal.load(f.read) }
  1639. @@timestamps[script] = File.mtime(filename)
  1640. @@md5[script.name] = Digest::MD5.hexdigest(@@settings[script.name].inspect)
  1641. end
  1642. rescue
  1643. respond "--- error: CharSettings.load: #{$!}"
  1644. $stderr.puts "error: CharSettings.load: #{$!}"
  1645. $stderr.puts $!.backtrace
  1646. end
  1647. end
  1648. end
  1649. end
  1650. else
  1651. respond '--- Lich: CharSettings: cannot identify calling script'
  1652. end
  1653. else
  1654. UNTRUSTED_CHARSETTINGS_LOAD.call
  1655. end
  1656. nil
  1657. end
  1658. def CharSettings.save
  1659. if $SAFE == 0
  1660. if script = Script.self
  1661. if script.class == Script
  1662. if script.name =~ /^lich$/i
  1663. respond '--- Lich: If you insist, you may have a script named \'lich\', but it cannot use Settings, because it will conflict with Lich\'s settings.'
  1664. return false
  1665. end
  1666. filename = "#{$data_dir}#{XMLData.game}/#{XMLData.name}/#{script.name}.sav"
  1667. @@settings[script.name] ||= Hash.new
  1668. unless (@@settings[script.name].empty? and not File.exists?(filename))
  1669. md5 = Digest::MD5.hexdigest(@@settings[script.name].inspect)
  1670. unless @@md5[script.name] == md5
  1671. begin
  1672. if File.exists?(filename)
  1673. File.rename(filename, "#{filename}~")
  1674. end
  1675. File.open(filename, 'wb') { |f| f.write(Marshal.dump(@@settings[script.name])) }
  1676. @@timestamps[script.name] = File.mtime(filename)
  1677. @@md5[script.name] = md5
  1678. rescue
  1679. respond "--- error: CharSettings.save: #{$!}"
  1680. $stderr.puts "error: CharSettings.save: #{$!}"
  1681. $stderr.puts $!.backtrace
  1682. end
  1683. end
  1684. end
  1685. end
  1686. else
  1687. CharSettings.save_all
  1688. end
  1689. else
  1690. UNTRUSTED_CHARSETTINGS_SAVE.call
  1691. end
  1692. nil
  1693. end
  1694. def CharSettings.save_all
  1695. if $SAFE == 0
  1696. for script_name in @@settings.keys
  1697. filename = "#{$data_dir}#{XMLData.game}/#{XMLData.name}/#{script_name}.sav"
  1698. @@settings[script_name] ||= Hash.new
  1699. unless (@@settings[script_name].empty? and not File.exists?(filename))
  1700. md5 = Digest::MD5.hexdigest(@@settings[script_name].inspect)
  1701. unless @@md5[script_name] == md5
  1702. begin
  1703. if File.exists?(filename)
  1704. File.rename(filename, "#{filename}~")
  1705. end
  1706. File.open(filename, 'wb') { |f| f.write(Marshal.dump(@@settings[script_name])) }
  1707. @@timestamps[script_name] = File.mtime(filename)
  1708. @@md5[script_name] = md5
  1709. rescue
  1710. respond "--- error: CharSettings.save_all: #{$!}"
  1711. $stderr.puts "error: CharSettings.save_all: #{$!}"
  1712. $stderr.puts $!.backtrace
  1713. end
  1714. end
  1715. end
  1716. end
  1717. else
  1718. UNTRUSTED_CHARSETTINGS_SAVE_ALL.call
  1719. end
  1720. nil
  1721. end
  1722. def CharSettings.clear
  1723. if (script = Script.self) and (script.class == Script)
  1724. @@settings[script.name] ||= Hash.new
  1725. @@settings[script.name].clear
  1726. else
  1727. respond '--- Lich: CharSettings: cannot identify calling script'
  1728. nil
  1729. end
  1730. end
  1731. def CharSettings.[](val)
  1732. if (script = Script.self) and (script.class == Script)
  1733. @@settings[script.name] ||= Hash.new
  1734. @@settings[script.name][val]
  1735. else
  1736. respond '--- Lich: CharSettings: cannot identify calling script'
  1737. nil
  1738. end
  1739. end
  1740. def CharSettings.[]=(setting, val)
  1741. if (script = Script.self) and (script.class == Script)
  1742. @@settings[script.name] ||= Hash.new
  1743. @@settings[script.name][setting] = val
  1744. @@settings[script.name][setting]
  1745. else
  1746. respond '--- Lich: CharSettings: cannot identify calling script'
  1747. nil
  1748. end
  1749. end
  1750. def CharSettings.to_hash
  1751. if (script = Script.self) and (script.class == Script)
  1752. @@settings[script.name] ||= Hash.new
  1753. @@settings[script.name]
  1754. else
  1755. respond '--- Lich: CharSettings: cannot identify calling script'
  1756. nil
  1757. end
  1758. end
  1759. end
  1760. class Favorites
  1761. @@settings ||= Hash.new
  1762. def Favorites.init
  1763. Favorites.load if @@settings.empty?
  1764. begin
  1765. @@settings['global'].each_pair { |scr,vars| start_script(scr, vars) }
  1766. @@settings[XMLData.name].each_pair { |scr,vars| start_script(scr, vars) }
  1767. rescue
  1768. respond "--- Lich: error starting favorite scripts: (#{$!})"
  1769. end
  1770. end
  1771. def Favorites.load
  1772. if File.exists?("#{$data_dir}lich.sav")
  1773. begin
  1774. File.open("#{$data_dir}lich.sav", 'rb') { |f|
  1775. @@settings = Marshal.load(f.read)['favorites']
  1776. }
  1777. rescue
  1778. respond "--- error: Favorites.load: #{$!}"
  1779. $stderr.puts "error: Favorites.load: #{$!}"
  1780. $stderr.puts $!.backtrace
  1781. end
  1782. end
  1783. if File.exists?("#{$data_dir}favs.sav")
  1784. File.open("#{$data_dir}favs.sav", 'rb') { |f|
  1785. @@settings = Marshal.load(f.read)
  1786. }
  1787. @@settings['global'].delete('alias')
  1788. @@settings['global'].delete('setting')
  1789. File.rename("#{$data_dir}favs.sav", "#{$temp_dir}favs.sav")
  1790. Favorites.save
  1791. end
  1792. @@settings ||= Hash.new
  1793. @@settings['global'] ||= { 'updater' => ['update'], 'infomon' => [], 'lnet' => [] }
  1794. @@settings[XMLData.name] ||= Hash.new
  1795. end
  1796. def Favorites.save
  1797. all_settings = Hash.new
  1798. if File.exists?("#{$data_dir}lich.sav")
  1799. File.open("#{$data_dir}lich.sav", 'rb') { |f| all_settings = Marshal.load(f.read) }
  1800. end
  1801. all_settings['favorites'] = @@settings
  1802. File.open("#{$data_dir}lich.sav", 'wb') { |f| f.write(Marshal.dump(all_settings)) }
  1803. end
  1804. def Favorites.list
  1805. @@settings.dup
  1806. end
  1807. def Favorites.add(script_name, script_vars = Array.new, type = :char)
  1808. if type == :char
  1809. @@settings[XMLData.name] ||= Hash.new
  1810. @@settings[XMLData.name][script_name] = script_vars
  1811. Favorites.save
  1812. true
  1813. elsif type == :global
  1814. @@settings['global'] ||= Hash.new
  1815. @@settings['global'][script_name] = script_vars
  1816. Favorites.save
  1817. true
  1818. else
  1819. echo 'Favs.add: invalid type given, use :char or :global'
  1820. false
  1821. end
  1822. end
  1823. def Favorites.delete(script_name, type = :char)
  1824. if type == :char
  1825. if @@settings[XMLData.name].delete(script_name)
  1826. Favorites.save
  1827. true
  1828. else
  1829. false
  1830. end
  1831. elsif type == :global
  1832. if @@settings['global'].delete(script_name)
  1833. Favorites.save
  1834. true
  1835. else
  1836. false
  1837. end
  1838. else
  1839. false
  1840. end
  1841. end
  1842. end
  1843. class Favs < Favorites
  1844. end
  1845. class Alias
  1846. @@char_regex_string ||= String.new
  1847. @@global_regex_string ||= String.new
  1848. @@settings ||= Hash.new
  1849. def Alias.init
  1850. if File.exists?("#{$data_dir}lich.sav")
  1851. begin
  1852. File.open("#{$data_dir}lich.sav", 'rb') { |f|
  1853. @@settings = Marshal.load(f.read)['alias']
  1854. }
  1855. rescue
  1856. respond "--- error: Alias.init: #{$!}"
  1857. $stderr.puts "error: Alias.init: #{$!}"
  1858. $stderr.puts $!.backtrace
  1859. end
  1860. end
  1861. if File.exists?("#{$data_dir}alias.sav")
  1862. File.open("#{$data_dir}alias.sav", 'rb') { |f|
  1863. @@settings = Marshal.load(f.read)
  1864. }
  1865. File.rename("#{$data_dir}alias.sav", "#{$temp_dir}alias.sav")
  1866. Alias.save
  1867. end
  1868. @@settings ||= Hash.new
  1869. @@settings['global'] ||= { 'repo' => ';repo' }
  1870. @@settings[XMLData.name] ||= Hash.new
  1871. @@char_regex_string = @@settings[XMLData.name].keys.join('|')
  1872. @@global_regex_string = @@settings['global'].keys.join('|')
  1873. end
  1874. def Alias.save
  1875. all_settings = Hash.new
  1876. if File.exists?("#{$data_dir}lich.sav")
  1877. File.open("#{$data_dir}lich.sav", 'rb') { |f| all_settings = Marshal.load(f.read) }
  1878. end
  1879. all_settings['alias'] = @@settings
  1880. File.open("#{$data_dir}lich.sav", 'wb') { |f| f.write(Marshal.dump(all_settings)) }
  1881. true
  1882. end
  1883. def Alias.add(trigger, target, type = :char)
  1884. trigger = Regexp.escape(trigger.strip)
  1885. if type == :char
  1886. @@settings[XMLData.name][trigger.downcase] = target
  1887. @@char_regex_string = @@settings[XMLData.name].keys.join('|')
  1888. Alias.save
  1889. true
  1890. elsif type == :global
  1891. @@settings['global'][trigger.downcase] = target
  1892. @@global_regex_string = @@settings['global'].keys.join('|')
  1893. Alias.save
  1894. true
  1895. else
  1896. echo 'Alias.add: invalid type given, use :char or :global'
  1897. false
  1898. end
  1899. end
  1900. def Alias.delete(trigger, type = :char)
  1901. trigger = Regexp.escape(trigger)
  1902. if type == :char
  1903. which = @@settings[XMLData.name].keys.find { |key| key == trigger.downcase }
  1904. if which and @@settings[XMLData.name].delete(which)
  1905. @@char_regex_string = @@settings[XMLData.name].keys.join('|')
  1906. Alias.save
  1907. true
  1908. else
  1909. false
  1910. end
  1911. elsif type == :global
  1912. which = @@settings['global'].keys.find { |key| key == trigger.downcase }
  1913. if which and @@settings['global'].delete(which)
  1914. @@global_regex_string = @@settings['global'].keys.join('|')
  1915. Alias.save
  1916. true
  1917. else
  1918. false
  1919. end
  1920. else
  1921. echo 'Alias.delete: invalid type given, use :char or :global'
  1922. false
  1923. end
  1924. end
  1925. def Alias.find(trigger)
  1926. return false if trigger.nil? or trigger.empty?
  1927. if not @@char_regex_string.empty? and /^(?:<c>)?(#{@@char_regex_string})(?:\s|$)/i.match(trigger.strip).captures.first
  1928. true
  1929. elsif not @@global_regex_string.empty? and /^(?:<c>)?(#{@@global_regex_string})(?:\s|$)/i.match(trigger.strip).captures.first
  1930. true
  1931. else
  1932. false
  1933. end
  1934. end
  1935. def Alias.list
  1936. @@settings.dup
  1937. end
  1938. def Alias.run(trig)
  1939. if trig.strip =~ /^(?:<c>)?(#{@@char_regex_string})(?:\s+|$)(.*)/i
  1940. trigger, extra = $1, $2
  1941. elsif trig.strip =~ /^(?:<c>)?(#{@@global_regex_string})(?:\s+|$)(.*)/i
  1942. trigger, extra = $1, $2
  1943. end
  1944. unless target = @@settings[XMLData.name][Regexp.escape(trigger.downcase)].dup || target = @@settings['global'][Regexp.escape(trigger.downcase)].dup
  1945. respond '--- Lich: tried to run unkown alias (' + trig.to_s.strip + ')'
  1946. return false
  1947. end
  1948. target = target.split('\\r')
  1949. if extra.empty?
  1950. target.collect! { |line| line.gsub('\\?', '') }
  1951. elsif target.any? { |line| line.include?('\\?') }
  1952. target.collect! { |line|
  1953. if line =~ /^;e.*"\\\?"/
  1954. line.gsub('"\\?"', extra.inspect)
  1955. elsif line.include?('\\?')
  1956. line.gsub('\\?', extra)
  1957. else
  1958. line
  1959. end
  1960. }
  1961. elsif target.length == 1
  1962. target.first.concat(" #{extra}")
  1963. end
  1964. target.each { |line| do_client("#{line.chomp}\n") }
  1965. end
  1966. end
  1967. class UserVariables
  1968. @@settings ||= Hash.new
  1969. @@settings_mtime = nil
  1970. @@char_settings ||= Hash.new
  1971. def UserVariables.init
  1972. if File.exists?("#{$data_dir}uservars.dat")
  1973. begin
  1974. File.open("#{$data_dir}uservars.dat", 'rb') { |f|
  1975. @@settings = Marshal.load(f.read)
  1976. }
  1977. @@settings_mtime = File.mtime("#{$data_dir}uservars.dat")
  1978. rescue
  1979. respond "--- error: UserVariables.init: #{$!}"
  1980. $stderr.puts "error: UserVariables.init: #{$!}"
  1981. $stderr.puts $!.backtrace
  1982. end
  1983. elsif File.exists?("#{$data_dir}lich.sav")
  1984. begin
  1985. File.open("#{$data_dir}lich.sav", 'rb') { |f|
  1986. @@settings = Marshal.load(f.read)['uservariables']['global'] || Hash.new
  1987. }
  1988. File.open("#{$data_dir}uservars.dat", 'wb') { |f| f.write(Marshal.dump(@@settings)) }
  1989. @@settings_mtime = File.mtime("#{$data_dir}uservars.dat")
  1990. rescue
  1991. respond "--- error: UserVariables.init: #{$!}"
  1992. $stderr.puts "error: UserVariables.init: #{$!}"
  1993. $stderr.puts $!.backtrace
  1994. end
  1995. end
  1996. if File.exists?("#{$data_dir}#{XMLData.game}/#{XMLData.name}/uservars.dat")
  1997. begin
  1998. File.open("#{$data_dir}#{XMLData.game}/#{XMLData.name}/uservars.dat", 'rb') { |f|
  1999. @@char_settings = Marshal.load(f.read)
  2000. }
  2001. rescue
  2002. respond "--- error: UserVariables.init: #{$!}"
  2003. $stderr.puts "error: UserVariables.init: #{$!}"
  2004. $stderr.puts $!.backtrace
  2005. end
  2006. elsif File.exists?("#{$data_dir}lich.sav")
  2007. begin
  2008. File.open("#{$data_dir}lich.sav", 'rb') { |f|
  2009. @@char_settings = Marshal.load(f.read)['uservariables'][XMLData.name] || Hash.new
  2010. }
  2011. rescue
  2012. respond "--- error: UserVariables.init: #{$!}"
  2013. $stderr.puts "error: UserVariables.init: #{$!}"
  2014. $stderr.puts $!.backtrace
  2015. end
  2016. end
  2017. end
  2018. def UserVariables.save
  2019. if $SAFE == 0
  2020. File.open("#{$data_dir}uservars.dat", 'wb') { |f| f.write(Marshal.dump(@@settings)) }
  2021. @@settings_mtime = File.mtime("#{$data_dir}uservars.dat")
  2022. File.open("#{$data_dir}#{XMLData.game}/#{XMLData.name}/uservars.dat", 'wb') { |f| f.write(Marshal.dump(@@char_settings)) }
  2023. else
  2024. UNTRUSTED_USERVARIABLES_SAVE.call
  2025. end
  2026. end
  2027. def UserVariables.change(var_name, value, type = :char)
  2028. if $SAFE == 0
  2029. if type == :char
  2030. @@char_settings[var_name] = value
  2031. File.open("#{$data_dir}#{XMLData.game}/#{XMLData.name}/uservars.dat", 'wb') { |f| f.write(Marshal.dump(@@char_settings)) }
  2032. true
  2033. elsif type == :global
  2034. if @@settings_mtime < File.mtime("#{$data_dir}uservars.dat")
  2035. File.open("#{$data_dir}uservars.dat", 'rb') { |f| @@settings = Marshal.load(f.read) }
  2036. end
  2037. @@settings[var_name] = value
  2038. File.open("#{$data_dir}uservars.dat", 'wb') { |f| f.write(Marshal.dump(@@settings)) }
  2039. @@settings_mtime = File.mtime("#{$data_dir}uservars.dat")
  2040. true
  2041. else
  2042. echo 'UserVariables.change: invalid type given, use :char or :global.'
  2043. false
  2044. end
  2045. else
  2046. UNTRUSTED_USERVARIABLES_CHANGE.call(var_name, value, type)
  2047. end
  2048. end
  2049. def UserVariables.add(var_name, value, type = :char)
  2050. if $SAFE == 0
  2051. if type == :char
  2052. @@char_settings[var_name] = @@char_settings[var_name].split(', ').push(value.strip).join(', ')
  2053. File.open("#{$data_dir}#{XMLData.game}/#{XMLData.name}/uservars.dat", 'wb') { |f| f.write(Marshal.dump(@@char_settings)) }
  2054. true
  2055. elsif type == :global
  2056. if @@settings_mtime < File.mtime("#{$data_dir}uservars.dat")
  2057. File.open("#{$data_dir}uservars.dat", 'rb') { |f| @@settings = Marshal.load(f.read) }
  2058. end
  2059. @@settings[var_name] = @@settings[var_name].split(', ').push(value.strip).join(', ')
  2060. File.open("#{$data_dir}uservars.dat", 'wb') { |f| f.write(Marshal.dump(@@settings)) }
  2061. @@settings_mtime = File.mtime("#{$data_dir}uservars.dat")
  2062. true
  2063. else
  2064. echo 'UserVariables.add: invalid type given, use :char or :global.'
  2065. nil
  2066. end
  2067. else
  2068. UNTRUSTED_USERVARIABLES_ADD.call(var_name, value, type)
  2069. end
  2070. end
  2071. def UserVariables.delete(var_name, type = :char)
  2072. if $SAFE == 0
  2073. if type == :char
  2074. if @@char_settings.delete(var_name)
  2075. File.open("#{$data_dir}#{XMLData.game}/#{XMLData.name}/uservars.dat", 'wb') { |f| f.write(Marshal.dump(@@char_settings)) }
  2076. true
  2077. else
  2078. false
  2079. end
  2080. elsif type == :global
  2081. if @@settings_mtime < File.mtime("#{$data_dir}uservars.dat")
  2082. File.open("#{$data_dir}uservars.dat", 'rb') { |f| @@settings = Marshal.load(f.read) }
  2083. @@settings_mtime = File.mtime("#{$data_dir}uservars.dat")
  2084. end
  2085. if @@settings.delete(var_name)
  2086. File.open("#{$data_dir}uservars.dat", 'wb') { |f| f.write(Marshal.dump(@@settings)) }
  2087. @@settings_mtime = File.mtime("#{$data_dir}uservars.dat")
  2088. true
  2089. else
  2090. false
  2091. end
  2092. else
  2093. false
  2094. end
  2095. else
  2096. UNTRUSTED_USERVARIABLES_DELETE.call(var_name, type)
  2097. end
  2098. end
  2099. def UserVariables.list_global
  2100. @@settings.dup
  2101. end
  2102. def UserVariables.list_char
  2103. @@char_settings.dup
  2104. end
  2105. def UserVariables.method_missing(arg1, arg2='')
  2106. if $SAFE == 0
  2107. if arg1.to_s.split('')[-1] == '='
  2108. @@char_settings[arg1.to_s.chop] = arg2
  2109. File.open("#{$data_dir}#{XMLData.game}/#{XMLData.name}/uservars.dat", 'wb') { |f| f.write(Marshal.dump(@@char_settings)) }
  2110. elsif @@char_settings[arg1.to_s]
  2111. @@char_settings[arg1.to_s]
  2112. else
  2113. if @@settings_mtime < File.mtime("#{$data_dir}uservars.dat")
  2114. File.open("#{$data_dir}uservars.dat", 'rb') { |f| @@settings = Marshal.load(f.read) }
  2115. @@settings_mtime = File.mtime("#{$data_dir}uservars.dat")
  2116. end
  2117. @@settings[arg1.to_s]
  2118. end
  2119. else
  2120. UNTRUSTED_USERVARIABLES_METHOD_MISSING.call(arg1, arg2)
  2121. end
  2122. end
  2123. end
  2124. class UserVars < UserVariables
  2125. end
  2126. class Lich < UserVariables
  2127. def Lich.list_settings
  2128. @@settings
  2129. end
  2130. def Lich.fetchloot
  2131. if items = checkloot.find_all { |item| item =~ /#{@@treasure.join('|')}/ }
  2132. take(items)
  2133. else
  2134. return false
  2135. end
  2136. end
  2137. end
  2138. class Script
  2139. @@running ||= Array.new
  2140. attr_reader :name, :vars, :safe, :labels, :file_name, :label_order, :thread_group
  2141. attr_accessor :quiet, :no_echo, :jump_label, :current_label, :want_downstream, :want_downstream_xml, :want_upstream, :want_script_output, :dying_procs, :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
  2142. def Script.self
  2143. if script = @@running.find { |scr| scr.thread_group == Thread.current.group }
  2144. sleep "0.2".to_f while script.paused
  2145. script
  2146. else
  2147. nil
  2148. end
  2149. end
  2150. def Script.running
  2151. list = Array.new
  2152. for script in @@running
  2153. list.push(script) unless script.hidden
  2154. end
  2155. return list
  2156. end
  2157. def Script.index
  2158. Script.running
  2159. end
  2160. def Script.hidden
  2161. list = Array.new
  2162. for script in @@running
  2163. list.push(script) if script.hidden
  2164. end
  2165. return list
  2166. end
  2167. def Script.exists?(scriptname)
  2168. scriptname += '.lic' unless (scriptname =~ /\.lic$/i)
  2169. if $SAFE == 0
  2170. if File.exists?("#{$script_dir}#{scriptname}") and (File.dirname("#{$script_dir}#{scriptname}") == $script_dir.sub(/\/$/,''))
  2171. true
  2172. else
  2173. false
  2174. end
  2175. else
  2176. UNTRUSTED_SCRIPT_EXISTS.call(scriptname)
  2177. end
  2178. end
  2179. def Script.new_downstream_xml(line)
  2180. for script in @@running
  2181. script.downstream_buffer.push(line.chomp) if script.want_downstream_xml
  2182. end
  2183. end
  2184. def Script.new_upstream(line)
  2185. for script in @@running
  2186. script.upstream_buffer.push(line.chomp) if script.want_upstream
  2187. end
  2188. end
  2189. def Script.new_downstream(line)
  2190. @@running.each { |script|
  2191. script.downstream_buffer.push(line.chomp) if script.want_downstream
  2192. unless script.watchfor.empty?
  2193. script.watchfor.each_pair { |trigger,action|
  2194. if line =~ trigger
  2195. new_thread = Thread.new {
  2196. sleep "0.011".to_f until Script.self
  2197. begin
  2198. action.call
  2199. rescue
  2200. echo "watchfor error: #{$!}"
  2201. end
  2202. }
  2203. script.thread_group.add(new_thread)
  2204. end
  2205. }
  2206. end
  2207. }
  2208. end
  2209. def Script.new_script_output(line)
  2210. for script in @@running
  2211. script.downstream_buffer.push(line.chomp) if script.want_script_output
  2212. end
  2213. end
  2214. def Script.log(data)
  2215. if $SAFE == 0
  2216. if script = Script.self
  2217. begin
  2218. unless File.exists?("#{$lich_dir}logs")
  2219. Dir.mkdir("#{$lich_dir}logs")
  2220. end
  2221. File.open("#{$lich_dir}logs/#{script.name}.log", 'a') { |file| file.puts data }
  2222. true
  2223. rescue
  2224. respond "--- error: Script.log: #{$!}"
  2225. false
  2226. end
  2227. else
  2228. respond '--- Script.log: unable to identify calling script'
  2229. false
  2230. end
  2231. else
  2232. UNTRUSTED_SCRIPT_LOG.call(data)
  2233. end
  2234. end
  2235. def initialize(file_name, cli_vars=[], cmd_line=nil)
  2236. @name = /.*[\/\\]+([^\.]+)\./.match(file_name).captures.first
  2237. @file_name = file_name
  2238. @command_line = cmd_line || cli_vars.join(' ')
  2239. @vars = Array.new
  2240. unless cli_vars.empty?
  2241. cli_vars.each_index { |idx| @vars[idx+1] = cli_vars[idx] }
  2242. @vars[0] = @vars[1..-1].join(' ')
  2243. cli_vars = nil
  2244. end
  2245. if @vars.first =~ /^quiet$/i
  2246. @quiet = true
  2247. @vars.shift
  2248. else
  2249. @quiet = false
  2250. end
  2251. @downstream_buffer = LimitedArray.new
  2252. @want_downstream = true
  2253. @want_downstream_xml = false
  2254. @want_script_output = false
  2255. @upstream_buffer = LimitedArray.new
  2256. @want_upstream = false
  2257. @unique_buffer = LimitedArray.new
  2258. @watchfor = Hash.new
  2259. @dying_procs = Array.new
  2260. @die_with = Array.new
  2261. @paused = false
  2262. @hidden = false
  2263. @no_pause_all = false
  2264. @no_kill_all = false
  2265. @silent = false
  2266. @safe = false
  2267. @no_echo = false
  2268. @match_stack_labels = Array.new
  2269. @match_stack_strings = Array.new
  2270. @label_order = Array.new
  2271. @labels = Hash.new
  2272. data = nil
  2273. begin
  2274. Zlib::GzipReader.open(file_name) { |f| data = f.readlines.collect { |line| line.chomp } }
  2275. rescue
  2276. begin
  2277. File.open(file_name) { |f| data = f.readlines.collect { |line| line.chomp } }
  2278. rescue
  2279. respond "--- Lich: error reading script file (#{file_name}): #{$!}"
  2280. return nil
  2281. end
  2282. end
  2283. @quiet = true if data[0] =~ /^[\t\s]*#?[\t\s]*(?:quiet|hush)$/i
  2284. @current_label = '~start'
  2285. @labels[@current_label] = String.new
  2286. @label_order.push(@current_label)
  2287. for line in data
  2288. if line =~ /^([\d_\w]+):$/
  2289. @current_label = $1
  2290. @label_order.push(@current_label)
  2291. @labels[@current_label] = String.new
  2292. else
  2293. @labels[@current_label] += "#{line}\n"
  2294. end
  2295. end
  2296. data = nil
  2297. @current_label = @label_order[0]
  2298. @thread_group = ThreadGroup.new
  2299. @@running.push(self)
  2300. return self
  2301. end
  2302. def kill
  2303. Thread.new {
  2304. begin
  2305. die_with, dying_procs = @die_with.dup, @dying_procs.dup
  2306. @die_with, @dying_procs = nil, nil
  2307. @thread_group.list.dup.each { |thr|
  2308. unless thr == Thread.current
  2309. thr.kill rescue()
  2310. end
  2311. }
  2312. @thread_group.add(Thread.current)
  2313. die_with.each { |script_name| stop_script script_name }
  2314. @paused = false
  2315. dying_procs.each { |runme|
  2316. begin
  2317. runme.call
  2318. rescue SyntaxError
  2319. echo("Syntax error in dying code block: #{$!}")
  2320. rescue SystemExit
  2321. nil
  2322. rescue Exception
  2323. if $! == JUMP or $! == JUMP_ERROR
  2324. echo('Cannot execute jumps in before_dying code blocks...!')
  2325. else
  2326. echo("--- error in dying code block: #{$!}")
  2327. end
  2328. rescue
  2329. echo("--- error in dying code block: #{$!}")
  2330. end
  2331. }
  2332. @downstream_buffer = @upstream_buffer = @match_stack_labels = @match_stack_strings = nil
  2333. Settings.save
  2334. GameSettings.save
  2335. CharSettings.save
  2336. @@running.delete(self)
  2337. respond("--- Lich: #{@name} has exited.") unless @quiet
  2338. GC.start
  2339. rescue
  2340. puts $!
  2341. puts $!.backtrace[0..2]
  2342. end
  2343. }
  2344. @name
  2345. end
  2346. def pause
  2347. respond "--- Lich: #{@name} paused."
  2348. @paused = true
  2349. end
  2350. def unpause
  2351. respond "--- Lich: #{@name} unpaused."
  2352. @paused = false
  2353. end
  2354. def get_next_label
  2355. if !@jump_label
  2356. @current_label = @label_order[@label_order.index(@current_label)+1]
  2357. else
  2358. if label = @labels.keys.find { |val| val =~ /^#{@jump_label}$/ }
  2359. @current_label = label
  2360. elsif label = @labels.keys.find { |val| val =~ /^#{@jump_label}$/i }
  2361. @current_label = label
  2362. elsif label = @labels.keys.find { |val| val =~ /^labelerror$/i }
  2363. @current_label = label
  2364. else
  2365. @current_label = nil
  2366. return JUMP_ERROR
  2367. end
  2368. @jump_label = nil
  2369. @current_label
  2370. end
  2371. end
  2372. def clear
  2373. to_return = @downstream_buffer.dup
  2374. @downstream_buffer.clear
  2375. to_return
  2376. end
  2377. def to_s
  2378. @name
  2379. end
  2380. def gets
  2381. # fixme: no xml gets
  2382. if @want_downstream or @want_downstream_xml or @want_script_output
  2383. sleep "0.05".to_f while @downstream_buffer.empty?
  2384. @downstream_buffer.shift
  2385. else
  2386. echo 'this script is set as unique but is waiting for game data...'
  2387. sleep 2
  2388. false
  2389. end
  2390. end
  2391. def gets?
  2392. if @want_downstream or @want_downstream_xml or @want_script_output
  2393. if @downstream_buffer.empty?
  2394. nil
  2395. else
  2396. @downstream_buffer.shift
  2397. end
  2398. else
  2399. echo 'this script is set as unique but is waiting for game data...'
  2400. sleep 2
  2401. false
  2402. end
  2403. end
  2404. def upstream_gets
  2405. sleep "0.05".to_f while @upstream_buffer.empty?
  2406. @upstream_buffer.shift
  2407. end
  2408. def upstream_gets?
  2409. if @upstream_buffer.empty?
  2410. nil
  2411. else
  2412. @upstream_buffer.shift
  2413. end
  2414. end
  2415. def unique_gets
  2416. sleep "0.05".to_f while @unique_buffer.empty?
  2417. @unique_buffer.shift
  2418. end
  2419. def unique_gets?
  2420. if @unique_buffer.empty?
  2421. nil
  2422. else
  2423. @unique_buffer.shift
  2424. end
  2425. end
  2426. def safe?
  2427. @safe
  2428. end
  2429. def feedme_upstream
  2430. @want_upstream = !@want_upstream
  2431. end
  2432. def match_stack_add(label,string)
  2433. @match_stack_labels.push(label)
  2434. @match_stack_strings.push(string)
  2435. end
  2436. def match_stack_clear
  2437. @match_stack_labels.clear
  2438. @match_stack_strings.clear
  2439. end
  2440. # for backwards compatability
  2441. def Script.namescript_incoming(line)
  2442. Script.new_downstream(line)
  2443. end
  2444. end
  2445. class ExecScript<Script
  2446. attr_reader :cmd_data
  2447. def initialize(cmd_data, flags=Hash.new)
  2448. num = '1'
  2449. while @@running.find { |script| script.name == "exec%s" % num }
  2450. num.succ!
  2451. end
  2452. @name = "exec#{num}"
  2453. @cmd_data = cmd_data
  2454. @vars = Array.new
  2455. @downstream_buffer = LimitedArray.new
  2456. @want_downstream = true
  2457. @want_downstream_xml = false
  2458. @upstream_buffer = LimitedArray.new
  2459. @want_upstream = false
  2460. @dying_procs = Array.new
  2461. @watchfor = Hash.new
  2462. @hidden = false
  2463. @paused = false
  2464. @silent = false
  2465. if flags[:quiet].nil?
  2466. @quiet = false
  2467. else
  2468. @quiet = flags[:quiet]
  2469. end
  2470. @safe = false
  2471. @no_echo = false
  2472. @thread_group = ThreadGroup.new
  2473. @unique_buffer = LimitedArray.new
  2474. @die_with = Array.new
  2475. @no_pause_all = false
  2476. @no_kill_all = false
  2477. @match_stack_labels = Array.new
  2478. @match_stack_strings = Array.new
  2479. @@running.push(self)
  2480. self
  2481. end
  2482. def get_next_label
  2483. echo 'goto labels are not available in exec scripts.'
  2484. nil
  2485. end
  2486. end
  2487. class WizardScript<Script
  2488. def initialize(file_name, cli_vars=[])
  2489. @name = /.*[\/\\]+([^\.]+)\./.match(file_name).captures.first
  2490. @file_name = file_name
  2491. @vars = Array.new
  2492. unless cli_vars.empty?
  2493. cli_vars.each_index { |idx| @vars[idx+1] = cli_vars[idx] }
  2494. @vars[0] = @vars[1..-1].join(' ')
  2495. cli_vars = nil
  2496. end
  2497. if @vars.first =~ /^quiet$/i
  2498. @quiet = true
  2499. @vars.shift
  2500. else
  2501. @quiet = false
  2502. end
  2503. @downstream_buffer = LimitedArray.new
  2504. @want_downstream = true
  2505. @want_downstream_xml = false
  2506. @upstream_buffer = LimitedArray.new
  2507. @want_upstream = false
  2508. @unique_buffer = LimitedArray.new
  2509. @dying_procs = Array.new
  2510. @patchfor = Hash.new
  2511. @die_with = Array.new
  2512. @paused = false
  2513. @hidden = false
  2514. @no_pause_all = false
  2515. @no_kill_all = false
  2516. @silent = false
  2517. @safe = false
  2518. @no_echo = false
  2519. @match_stack_labels = Array.new
  2520. @match_stack_strings = Array.new
  2521. @label_order = Array.new
  2522. @labels = Hash.new
  2523. data = nil
  2524. begin
  2525. Zlib::GzipReader.open(file_name) { |f| data = f.readlines.collect { |line| line.chomp } }
  2526. rescue
  2527. begin
  2528. File.open(file_name) { |f| data = f.readlines.collect { |line| line.chomp } }
  2529. rescue
  2530. respond "--- Lich: error reading script file (#{file_name}): #{$!}"
  2531. return nil
  2532. end
  2533. end
  2534. @quiet = true if data[0] =~ /^[\t\s]*#?[\t\s]*(?:quiet|hush)$/i
  2535. counter_action = {
  2536. 'add' => '+',
  2537. 'sub' => '-',
  2538. 'subtract' => '-',
  2539. 'multiply' => '*',
  2540. 'divide' => '/',
  2541. 'set' => ''
  2542. }
  2543. setvars = Array.new
  2544. data.each { |line| setvars.push($1) if line =~ /[\s\t]*setvariable\s+([^\s\t]+)[\s\t]/i and not setvars.include?($1) }
  2545. has_counter = data.find { |line| line =~ /%c/i }
  2546. has_save = data.find { |line| line =~ /%s/i }
  2547. has_nextroom = data.find { |line| line =~ /nextroom/i }
  2548. fixstring = proc { |str|
  2549. while not setvars.empty? and str =~ /%(#{setvars.join('|')})%/io
  2550. str.gsub!('%' + $1 + '%', '#{' + $1.downcase + '}')
  2551. end
  2552. str.gsub!(/%c(?:%)?/i, '#{c}')
  2553. str.gsub!(/%s(?:%)?/i, '#{sav}')
  2554. while str =~ /%([0-9])(?:%)?/
  2555. str.gsub!(/%#{$1}(?:%)?/, '#{script.vars[' + $1 + ']}')
  2556. end
  2557. str
  2558. }
  2559. fixline = proc { |line|
  2560. if line =~ /^[\s\t]*[A-Za-z0-9_\-']+:/i
  2561. line = line.downcase.strip
  2562. elsif line =~ /^([\s\t]*)counter\s+(add|sub|subtract|divide|multiply|set)\s+([0-9]+)/i
  2563. line = "#{$1}c #{counter_action[$2]}= #{$3}"
  2564. elsif line =~ /^([\s\t]*)counter\s+(add|sub|subtract|divide|multiply|set)\s+(.*)/i
  2565. indent, action, arg = $1, $2, $3
  2566. line = "#{indent}c #{counter_action[action]}= #{fixstring.call(arg.inspect)}.to_i"
  2567. elsif line =~ /^([\s\t]*)save[\s\t]+"?(.*?)"?[\s\t]*$/i
  2568. indent, arg = $1, $2
  2569. line = "#{indent}sav = #{fixstring.call(arg.inspect)}"
  2570. elsif line =~ /^([\s\t]*)echo[\s\t]+(.+)/i
  2571. indent, arg = $1, $2
  2572. line = "#{indent}echo #{fixstring.call(arg.inspect)}"
  2573. elsif line =~ /^([\s\t]*)waitfor[\s\t]+(.+)/i
  2574. indent, arg = $1, $2
  2575. line = "#{indent}waitfor #{fixstring.call(Regexp.escape(arg).inspect.gsub("\\\\ ", ' '))}"
  2576. elsif line =~ /^([\s\t]*)put[\s\t]+(?:\.|;)(.+)$/i
  2577. indent, arg = $1, $2
  2578. if arg.include?(' ')
  2579. 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"
  2580. else
  2581. line = "#{indent}start_script(#{Regexp.escape(fixstring.call(arg.inspect))})\n#{indent}exit"
  2582. end
  2583. elsif line =~ /^([\s\t]*)(put|move)[\s\t]+(.+)/i
  2584. indent, cmd, arg = $1, $2, $3
  2585. line = "#{indent}waitrt?\n#{indent}clear\n#{indent}#{cmd.downcase} #{fixstring.call(arg.inspect)}"
  2586. elsif line =~ /^([\s\t]*)goto[\s\t]+(.+)/i
  2587. indent, arg = $1, $2
  2588. line = "#{indent}goto #{fixstring.call(arg.inspect).downcase}"
  2589. elsif line =~ /^([\s\t]*)waitforre[\s\t]+(.+)/i
  2590. indent, arg = $1, $2
  2591. line = "#{indent}waitforre #{arg}"
  2592. elsif line =~ /^([\s\t]*)pause[\s\t]*(.*)/i
  2593. indent, arg = $1, $2
  2594. arg = '1' if arg.empty?
  2595. arg = '0'+arg.strip if arg.strip =~ /^\.[0-9]+$/
  2596. line = "#{indent}pause #{arg}"
  2597. elsif line =~ /^([\s\t]*)match[\s\t]+([^\s\t]+)[\s\t]+(.+)/i
  2598. indent, label, arg = $1, $2, $3
  2599. line = "#{indent}match #{fixstring.call(label.inspect).downcase}, #{fixstring.call(Regexp.escape(arg).inspect.gsub("\\\\ ", ' '))}"
  2600. elsif line =~ /^([\s\t]*)matchre[\s\t]+([^\s\t]+)[\s\t]+(.+)/i
  2601. indent, label, regex = $1, $2, $3
  2602. line = "#{indent}matchre #{fixstring.call(label.inspect).downcase}, #{regex}"
  2603. elsif line =~ /^([\s\t]*)setvariable[\s\t]+([^\s\t]+)[\s\t]+(.+)/i
  2604. indent, var, arg = $1, $2, $3
  2605. line = "#{indent}#{var.downcase} = #{fixstring.call(arg.inspect)}"
  2606. elsif line =~ /^([\s\t]*)deletevariable[\s\t]+(.+)/i
  2607. line = "#{$1}#{$2.downcase} = nil"
  2608. elsif line =~ /^([\s\t]*)(wait|nextroom|exit|echo)\b/i
  2609. line = "#{$1}#{$2.downcase}"
  2610. elsif line =~ /^([\s\t]*)matchwait\b/i
  2611. line = "#{$1}matchwait"
  2612. elsif line =~ /^([\s\t]*)if_([0-9])[\s\t]+(.*)/i
  2613. indent, num, stuff = $1, $2, $3
  2614. line = "#{indent}if script.vars[#{num}]\n#{indent}\t#{fixline.call($3)}\n#{indent}end"
  2615. elsif line =~ /^([\s\t]*)shift\b/i
  2616. line = "#{$1}script.vars.shift"
  2617. else
  2618. respond "--- Lich: unknown line: #{line}"
  2619. line = '#' + line
  2620. end
  2621. }
  2622. lich_block = false
  2623. data.each_index { |idx|
  2624. if lich_block
  2625. if data[idx] =~ /\}[\s\t]*LICH[\s\t]*$/
  2626. data[idx] = data[idx].sub(/\}[\s\t]*LICH[\s\t]*$/, '')
  2627. lich_block = false
  2628. else
  2629. next
  2630. end
  2631. elsif data[idx] =~ /^[\s\t]*#|^[\s\t]*$/
  2632. next
  2633. elsif data[idx] =~ /^[\s\t]*LICH[\s\t]*\{/
  2634. data[idx] = data[idx].sub(/LICH[\s\t]*\{/, '')
  2635. if data[idx] =~ /\}[\s\t]*LICH[\s\t]*$/
  2636. data[idx] = data[idx].sub(/\}[\s\t]*LICH[\s\t]*$/, '')
  2637. else
  2638. lich_block = true
  2639. end
  2640. else
  2641. data[idx] = fixline.call(data[idx])
  2642. end
  2643. }
  2644. if has_counter or has_save or has_nextroom
  2645. data.each_index { |idx|
  2646. next if data[idx] =~ /^[\s\t]*#/
  2647. data.insert(idx, '')
  2648. data.insert(idx, 'c = 0') if has_counter
  2649. data.insert(idx, "Settings.load\nsav = Settings['sav'] || String.new\nbefore_dying { Settings['sav'] = sav; Settings.save }") if has_save
  2650. data.insert(idx, "def nextroom\n\troom_count = XMLData.room_count\n\twait_while { room_count == XMLData.room_count }\nend") if has_nextroom
  2651. data.insert(idx, '')
  2652. break
  2653. }
  2654. end
  2655. @current_label = '~start'
  2656. @labels[@current_label] = String.new
  2657. @label_order.push(@current_label)
  2658. for line in data
  2659. if line =~ /^([\d_\w]+):$/
  2660. @current_label = $1
  2661. @label_order.push(@current_label)
  2662. @labels[@current_label] = String.new
  2663. else
  2664. @labels[@current_label] += "#{line}\n"
  2665. end
  2666. end
  2667. data = nil
  2668. @current_label = @label_order[0]
  2669. @thread_group = ThreadGroup.new
  2670. @@running.push(self)
  2671. return self
  2672. end
  2673. end
  2674. class ScriptBinder
  2675. def create_block
  2676. Proc.new { }
  2677. end
  2678. end
  2679. class UntrustedScriptBinder
  2680. def create_block
  2681. Proc.new { }
  2682. end
  2683. end
  2684. class Char
  2685. @@cha ||= nil
  2686. @@name ||= nil
  2687. @@citizenship ||= nil
  2688. private_class_method :new
  2689. def Char.init(blah)
  2690. echo 'Char.init is no longer used. Update or fix your script.'
  2691. end
  2692. def Char.name
  2693. XMLData.name
  2694. end
  2695. def Char.name=(name)
  2696. nil
  2697. end
  2698. def Char.health(*args)
  2699. health(*args)
  2700. end
  2701. def Char.mana(*args)
  2702. checkmana(*args)
  2703. end
  2704. def Char.spirit(*args)
  2705. checkspirit(*args)
  2706. end
  2707. def Char.maxhealth
  2708. Object.module_eval { maxhealth }
  2709. end
  2710. def Char.maxmana
  2711. Object.module_eval { maxmana }
  2712. end
  2713. def Char.maxspirit
  2714. Object.module_eval { maxspirit }
  2715. end
  2716. def Char.stamina(*args)
  2717. checkstamina(*args)
  2718. end
  2719. def Char.maxstamina
  2720. Object.module_eval { maxstamina }
  2721. end
  2722. def Char.cha(val=nil)
  2723. val == nil ? @@cha : @@cha = val
  2724. end
  2725. def Char.dump_info
  2726. Marshal.dump([
  2727. Spell.detailed?,
  2728. Spell.serialize,
  2729. Spellsong.serialize,
  2730. Stats.serialize,
  2731. Skills.serialize,
  2732. Spells.serialize,
  2733. Gift.serialize,
  2734. Society.serialize,
  2735. ])
  2736. end
  2737. def Char.load_info(string)
  2738. save = Char.dump_info
  2739. begin
  2740. Spell.load_detailed,
  2741. Spell.load_active,
  2742. Spellsong.load_serialized,
  2743. Stats.load_serialized,
  2744. Skills.load_serialized,
  2745. Spells.load_serialized,
  2746. Gift.load_serialized,
  2747. Society.load_serialized = Marshal.load(string)
  2748. rescue
  2749. raise $! if string == save
  2750. string = save
  2751. retry
  2752. end
  2753. end
  2754. def Char.method_missing(meth, *args)
  2755. [ Stats, Skills, Spellsong, Society ].each { |klass|
  2756. begin
  2757. result = klass.__send__(meth, *args)
  2758. return result
  2759. rescue
  2760. end
  2761. }
  2762. raise NoMethodError
  2763. end
  2764. def Char.info
  2765. ary = []
  2766. ary.push sprintf("Name: %s Race: %s Profession: %s", XMLData.name, Stats.race, Stats.prof)
  2767. ary.push sprintf("Gender: %s Age: %d Expr: %d Level: %d", Stats.gender, Stats.age, Stats.exp, Stats.level)
  2768. ary.push sprintf("%017.17s Normal (Bonus) ... Enhanced (Bonus)", "")
  2769. %w[ Strength Constitution Dexterity Agility Discipline Aura Logic Intuition Wisdom Influence ].each { |stat|
  2770. val, bon = Stats.send(stat[0..2].downcase)
  2771. spc = " " * (4 - bon.to_s.length)
  2772. ary.push sprintf("%012s (%s): %05s (%d) %s ... %05s (%d)", stat, stat[0..2].upcase, val, bon, spc, val, bon)
  2773. }
  2774. ary.push sprintf("Mana: %04s", mana)
  2775. ary
  2776. end
  2777. def Char.skills
  2778. ary = []
  2779. ary.push sprintf("%s (at level %d), your current skill bonuses and ranks (including all modifiers) are:", XMLData.name, Stats.level)
  2780. ary.push sprintf(" %-035s| Current Current", 'Skill Name')
  2781. ary.push sprintf(" %-035s|%08s%08s", '', 'Bonus', 'Ranks')
  2782. 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' ] ]
  2783. 0.upto(fmt.first.length - 1) { |n|
  2784. dots = '.' * (35 - fmt[0][n].length)
  2785. rnk = Skills.send(fmt[1][n])
  2786. ary.push sprintf(" %s%s|%08s%08s", fmt[0][n], dots, Skills.to_bonus(rnk), rnk) unless rnk.zero?
  2787. }
  2788. %[Minor Elemental,Major Elemental,Minor Spirit,Major Spirit,Minor Mental,Bard,Cleric,Empath,Paladin,Ranger,Sorcerer,Wizard].split(',').each { |circ|
  2789. rnk = Spells.send(circ.gsub(" ", '').downcase)
  2790. if rnk.nonzero?
  2791. ary.push ''
  2792. ary.push "Spell Lists"
  2793. dots = '.' * (35 - circ.length)
  2794. ary.push sprintf(" %s%s|%016s", circ, dots, rnk)
  2795. end
  2796. }
  2797. ary
  2798. end
  2799. def Char.citizenship
  2800. @@citizenship
  2801. end
  2802. def Char.citizenship=(val)
  2803. @@citizenship = val.to_s
  2804. end
  2805. end
  2806. class Society
  2807. @@status ||= String.new
  2808. @@rank ||= 0
  2809. def Society.serialize
  2810. [@@status,@@rank]
  2811. end
  2812. def Society.load_serialized=(val)
  2813. @@status,@@rank = val
  2814. end
  2815. def Society.status=(val)
  2816. @@status = val
  2817. end
  2818. def Society.status
  2819. @@status.dup
  2820. end
  2821. def Society.rank=(val)
  2822. if val =~ /Master/
  2823. if @@status =~ /Voln/
  2824. @@rank = 26
  2825. elsif @@status =~ /Council of Light|Guardians of Sunfist/
  2826. @@rank = 20
  2827. else
  2828. @@rank = val.to_i
  2829. end
  2830. else
  2831. @@rank = val.slice(/[0-9]+/).to_i
  2832. end
  2833. end
  2834. def Society.step
  2835. @@rank
  2836. end
  2837. def Society.member
  2838. @@status.dup
  2839. end
  2840. def Society.rank
  2841. @@rank
  2842. end
  2843. def Society.task
  2844. XMLData.society_task
  2845. end
  2846. end
  2847. class Spellsong
  2848. @@renewed ||= Time.at(Time.now.to_i - 1200)
  2849. def Spellsong.renewed
  2850. @@renewed = Time.now
  2851. end
  2852. def Spellsong.renewed=(val)
  2853. @@renewed = val
  2854. end
  2855. def Spellsong.renewed_at
  2856. @@renewed
  2857. end
  2858. def Spellsong.timeleft
  2859. (Spellsong.duration - ((Time.now - @@renewed) % Spellsong.duration)) / 60.to_f
  2860. end
  2861. def Spellsong.serialize
  2862. Spellsong.timeleft
  2863. end
  2864. def Spellsong.load_serialized=(old)
  2865. Thread.new {
  2866. n = 0
  2867. while Stats.level == 0
  2868. sleep "0.25".to_f
  2869. n += 1
  2870. break if n >= 4
  2871. end
  2872. unless n >= 4
  2873. @@renewed = Time.at(Time.now.to_f - (Spellsong.duration - old * 60.to_f))
  2874. else
  2875. @@renewed = Time.now
  2876. end
  2877. }
  2878. nil
  2879. end
  2880. def Spellsong.duration
  2881. total = 120
  2882. 1.upto(Stats.level.to_i) { |n|
  2883. if n < 26
  2884. total += 4
  2885. elsif n < 51
  2886. total += 3
  2887. elsif n < 76
  2888. total += 2
  2889. else
  2890. total += 1
  2891. end
  2892. }
  2893. total + Stats.log[1].to_i + (Stats.inf[1].to_i * 3) + (Skills.mltelepathy.to_i * 2)
  2894. end
  2895. def Spellsong.renew_cost
  2896. # fixme: multi-spell penalty?
  2897. total = num_active = 0
  2898. [ 1003, 1006, 1009, 1010, 1012, 1014, 1018, 1019, 1025 ].each { |song_num|
  2899. if song = Spell[song_num]
  2900. if song.active?
  2901. total += song.renew_cost
  2902. num_active += 1
  2903. end
  2904. else
  2905. echo "Spellsong.renew_cost: warning: can't find song number #{song_num}"
  2906. end
  2907. }
  2908. return total
  2909. end
  2910. def Spellsong.sonicarmordurability
  2911. 210 + (Stats.level / 2).round + Skills.to_bonus(Skills.elair)
  2912. end
  2913. def Spellsong.sonicbladedurability
  2914. 160 + (Stats.level / 2).round + Skills.to_bonus(Skills.elair)
  2915. end
  2916. def Spellsong.sonicweapondurability
  2917. Spellsong.sonicbladedurability
  2918. end
  2919. def Spellsong.sonicshielddurability
  2920. 125 + (Stats.level / 2).round + Skills.to_bonus(Skills.elair)
  2921. end
  2922. def Spellsong.tonishastebonus
  2923. bonus = -1
  2924. thresholds = [30,75]
  2925. thresholds.each { |val| if Skills.elair >= val then bonus -= 1 end }
  2926. bonus
  2927. end
  2928. def Spellsong.depressionpushdown
  2929. 20 + Skills.mltelepathy
  2930. end
  2931. def Spellsong.depressionslow
  2932. thresholds = [10,25,45,70,100]
  2933. bonus = -2
  2934. thresholds.each { |val| if Skills.mltelepathy >= val then bonus -= 1 end }
  2935. bonus
  2936. end
  2937. def Spellsong.holdingtargets
  2938. 1 + ((Spells.bard - 1) / 7).truncate
  2939. end
  2940. #
  2941. # depreciated
  2942. #
  2943. def Spellsong.cost
  2944. Spellsong.renew_cost
  2945. end
  2946. def Spellsong.tonisdodgebonus
  2947. thresholds = [1,2,3,5,8,10,14,17,21,26,31,36,42,49,55,63,70,78,87,96]
  2948. bonus = 20
  2949. thresholds.each { |val| if Skills.elair >= val then bonus += 1 end }
  2950. bonus
  2951. end
  2952. def Spellsong.mirrorsdodgebonus
  2953. 20 + ((Spells.bard - 19) / 2).round
  2954. end
  2955. def Spellsong.mirrorscost
  2956. [19 + ((Spells.bard - 19) / 5).truncate, 8 + ((Spells.bard - 19) / 10).truncate]
  2957. end
  2958. def Spellsong.sonicbonus
  2959. (Spells.bard / 2).round
  2960. end
  2961. def Spellsong.sonicarmorbonus
  2962. Spellsong.sonicbonus + 15
  2963. end
  2964. def Spellsong.sonicbladebonus
  2965. Spellsong.sonicbonus + 10
  2966. end
  2967. def Spellsong.sonicweaponbonus
  2968. Spellsong.sonicbladebonus
  2969. end
  2970. def Spellsong.sonicshieldbonus
  2971. Spellsong.sonicbonus + 10
  2972. end
  2973. def Spellsong.valorbonus
  2974. 10 + (([Spells.bard, Stats.level].min - 10) / 2).round
  2975. end
  2976. def Spellsong.valorcost
  2977. [10 + (Spellsong.valorbonus / 2), 3 + (Spellsong.valorbonus / 5)]
  2978. end
  2979. def Spellsong.luckcost
  2980. [6 + ((Spells.bard - 6) / 4),(6 + ((Spells.bard - 6) / 4) / 2).round]
  2981. end
  2982. def Spellsong.manacost
  2983. [18,15]
  2984. end
  2985. def Spellsong.fortcost
  2986. [3,1]
  2987. end
  2988. def Spellsong.shieldcost
  2989. [9,4]
  2990. end
  2991. def Spellsong.weaponcost
  2992. [12,4]
  2993. end
  2994. def Spellsong.armorcost
  2995. [14,5]
  2996. end
  2997. def Spellsong.swordcost
  2998. [25,15]
  2999. end
  3000. end
  3001. class Skills
  3002. @@twoweaponcombat ||= 0
  3003. @@armoruse ||= 0
  3004. @@shielduse ||= 0
  3005. @@combatmaneuvers ||= 0
  3006. @@edgedweapons ||= 0
  3007. @@bluntweapons ||= 0
  3008. @@twohandedweapons ||= 0
  3009. @@rangedweapons ||= 0
  3010. @@thrownweapons ||= 0
  3011. @@polearmweapons ||= 0
  3012. @@brawling ||= 0
  3013. @@ambush ||= 0
  3014. @@multiopponentcombat ||= 0
  3015. @@combatleadership ||= 0
  3016. @@physicalfitness ||= 0
  3017. @@dodging ||= 0
  3018. @@arcanesymbols ||= 0
  3019. @@magicitemuse ||= 0
  3020. @@spellaiming ||= 0
  3021. @@harnesspower ||= 0
  3022. @@emc ||= 0
  3023. @@mmc ||= 0
  3024. @@smc ||= 0
  3025. @@elair ||= 0
  3026. @@elearth ||= 0
  3027. @@elfire ||= 0
  3028. @@elwater ||= 0
  3029. @@slblessings ||= 0
  3030. @@slreligion ||= 0
  3031. @@slsummoning ||= 0
  3032. @@sldemonology ||= 0
  3033. @@slnecromancy ||= 0
  3034. @@mldivination ||= 0
  3035. @@mlmanipulation ||= 0
  3036. @@mltelepathy ||= 0
  3037. @@mltransference ||= 0
  3038. @@mltransformation ||= 0
  3039. @@survival ||= 0
  3040. @@disarmingtraps ||= 0
  3041. @@pickinglocks ||= 0
  3042. @@stalkingandhiding ||= 0
  3043. @@perception ||= 0
  3044. @@climbing ||= 0
  3045. @@swimming ||= 0
  3046. @@firstaid ||= 0
  3047. @@trading ||= 0
  3048. @@pickpocketing ||= 0
  3049. def Skills.twoweaponcombat; @@twoweaponcombat; end
  3050. def Skills.twoweaponcombat=(val); @@twoweaponcombat=val; end
  3051. def Skills.armoruse; @@armoruse; end
  3052. def Skills.armoruse=(val); @@armoruse=val; end
  3053. def Skills.shielduse; @@shielduse; end
  3054. def Skills.shielduse=(val); @@shielduse=val; end
  3055. def Skills.combatmaneuvers; @@combatmaneuvers; end
  3056. def Skills.combatmaneuvers=(val); @@combatmaneuvers=val; end
  3057. def Skills.edgedweapons; @@edgedweapons; end
  3058. def Skills.edgedweapons=(val); @@edgedweapons=val; end
  3059. def Skills.bluntweapons; @@bluntweapons; end
  3060. def Skills.bluntweapons=(val); @@bluntweapons=val; end
  3061. def Skills.twohandedweapons; @@twohandedweapons; end
  3062. def Skills.twohandedweapons=(val); @@twohandedweapons=val; end
  3063. def Skills.rangedweapons; @@rangedweapons; end
  3064. def Skills.rangedweapons=(val); @@rangedweapons=val; end
  3065. def Skills.thrownweapons; @@thrownweapons; end
  3066. def Skills.thrownweapons=(val); @@thrownweapons=val; end
  3067. def Skills.polearmweapons; @@polearmweapons; end
  3068. def Skills.polearmweapons=(val); @@polearmweapons=val; end
  3069. def Skills.brawling; @@brawling; end
  3070. def Skills.brawling=(val); @@brawling=val; end
  3071. def Skills.ambush; @@ambush; end
  3072. def Skills.ambush=(val); @@ambush=val; end
  3073. def Skills.multiopponentcombat; @@multiopponentcombat; end
  3074. def Skills.multiopponentcombat=(val); @@multiopponentcombat=val; end
  3075. def Skills.combatleadership; @@combatleadership; end
  3076. def Skills.combatleadership=(val); @@combatleadership=val; end
  3077. def Skills.physicalfitness; @@physicalfitness; end
  3078. def Skills.physicalfitness=(val); @@physicalfitness=val; end
  3079. def Skills.dodging; @@dodging; end
  3080. def Skills.dodging=(val); @@dodging=val; end
  3081. def Skills.arcanesymbols; @@arcanesymbols; end
  3082. def Skills.arcanesymbols=(val); @@arcanesymbols=val; end
  3083. def Skills.magicitemuse; @@magicitemuse; end
  3084. def Skills.magicitemuse=(val); @@magicitemuse=val; end
  3085. def Skills.spellaiming; @@spellaiming; end
  3086. def Skills.spellaiming=(val); @@spellaiming=val; end
  3087. def Skills.harnesspower; @@harnesspower; end
  3088. def Skills.harnesspower=(val); @@harnesspower=val; end
  3089. def Skills.emc; @@emc; end
  3090. def Skills.emc=(val); @@emc=val; end
  3091. def Skills.mmc; @@mmc; end
  3092. def Skills.mmc=(val); @@mmc=val; end
  3093. def Skills.smc; @@smc; end
  3094. def Skills.smc=(val); @@smc=val; end
  3095. def Skills.elair; @@elair; end
  3096. def Skills.elair=(val); @@elair=val; end
  3097. def Skills.elearth; @@elearth; end
  3098. def Skills.elearth=(val); @@elearth=val; end
  3099. def Skills.elfire; @@elfire; end
  3100. def Skills.elfire=(val); @@elfire=val; end
  3101. def Skills.elwater; @@elwater; end
  3102. def Skills.elwater=(val); @@elwater=val; end
  3103. def Skills.slblessings; @@slblessings; end
  3104. def Skills.slblessings=(val); @@slblessings=val; end
  3105. def Skills.slreligion; @@slreligion; end
  3106. def Skills.slreligion=(val); @@slreligion=val; end
  3107. def Skills.slsummoning; @@slsummoning; end
  3108. def Skills.slsummoning=(val); @@slsummoning=val; end
  3109. def Skills.sldemonology; @@sldemonology; end
  3110. def Skills.sldemonology=(val); @@sldemonology=val; end
  3111. def Skills.slnecromancy; @@slnecromancy; end
  3112. def Skills.slnecromancy=(val); @@slnecromancy=val; end
  3113. def Skills.mldivination; @@mldivination; end
  3114. def Skills.mldivination=(val); @@mldivination=val; end
  3115. def Skills.mlmanipulation; @@mlmanipulation; end
  3116. def Skills.mlmanipulation=(val); @@mlmanipulation=val; end
  3117. def Skills.mltelepathy; @@mltelepathy; end
  3118. def Skills.mltelepathy=(val); @@mltelepathy=val; end
  3119. def Skills.mltransference; @@mltransference; end
  3120. def Skills.mltransference=(val); @@mltransference=val; end
  3121. def Skills.mltransformation; @@mltransformation; end
  3122. def Skills.mltransformation=(val); @@mltransformation=val; end
  3123. def Skills.survival; @@survival; end
  3124. def Skills.survival=(val); @@survival=val; end
  3125. def Skills.disarmingtraps; @@disarmingtraps; end
  3126. def Skills.disarmingtraps=(val); @@disarmingtraps=val; end
  3127. def Skills.pickinglocks; @@pickinglocks; end
  3128. def Skills.pickinglocks=(val); @@pickinglocks=val; end
  3129. def Skills.stalkingandhiding; @@stalkingandhiding; end
  3130. def Skills.stalkingandhiding=(val); @@stalkingandhiding=val; end
  3131. def Skills.perception; @@perception; end
  3132. def Skills.perception=(val); @@perception=val; end
  3133. def Skills.climbing; @@climbing; end
  3134. def Skills.climbing=(val); @@climbing=val; end
  3135. def Skills.swimming; @@swimming; end
  3136. def Skills.swimming=(val); @@swimming=val; end
  3137. def Skills.firstaid; @@firstaid; end
  3138. def Skills.firstaid=(val); @@firstaid=val; end
  3139. def Skills.trading; @@trading; end
  3140. def Skills.trading=(val); @@trading=val; end
  3141. def Skills.pickpocketing; @@pickpocketing; end
  3142. def Skills.pickpocketing=(val); @@pickpocketing=val; end
  3143. def Skills.serialize
  3144. [@@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]
  3145. end
  3146. def Skills.load_serialized=(array)
  3147. @@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
  3148. end
  3149. def Skills.to_bonus(ranks)
  3150. bonus = 0
  3151. while ranks > 0
  3152. if ranks > 40
  3153. bonus += (ranks - 40)
  3154. ranks = 40
  3155. elsif ranks > 30
  3156. bonus += (ranks - 30) * 2
  3157. ranks = 30
  3158. elsif ranks > 20
  3159. bonus += (ranks - 20) * 3
  3160. ranks = 20
  3161. elsif ranks > 10
  3162. bonus += (ranks - 10) * 4
  3163. ranks = 10
  3164. else
  3165. bonus += (ranks * 5)
  3166. ranks = 0
  3167. end
  3168. end
  3169. bonus
  3170. end
  3171. end
  3172. class Spells
  3173. @@minorelemental ||= 0
  3174. @@minormental ||= 0
  3175. @@majorelemental ||= 0
  3176. @@minorspiritual ||= 0
  3177. @@majorspiritual ||= 0
  3178. @@wizard ||= 0
  3179. @@sorcerer ||= 0
  3180. @@ranger ||= 0
  3181. @@paladin ||= 0
  3182. @@empath ||= 0
  3183. @@cleric ||= 0
  3184. @@bard ||= 0
  3185. def Spells.minorelemental=(val); @@minorelemental = val; end
  3186. def Spells.minorelemental; @@minorelemental; end
  3187. def Spells.minormental=(val); @@minormental = val; end
  3188. def Spells.minormental; @@minormental; end
  3189. def Spells.majorelemental=(val); @@majorelemental = val; end
  3190. def Spells.majorelemental; @@majorelemental; end
  3191. def Spells.minorspiritual=(val); @@minorspiritual = val; end
  3192. def Spells.minorspiritual; @@minorspiritual; end
  3193. def Spells.minorspirit=(val); @@minorspiritual = val; end
  3194. def Spells.minorspirit; @@minorspiritual; end
  3195. def Spells.majorspiritual=(val); @@majorspiritual = val; end
  3196. def Spells.majorspiritual; @@majorspiritual; end
  3197. def Spells.majorspirit=(val); @@majorspiritual = val; end
  3198. def Spells.majorspirit; @@majorspiritual; end
  3199. def Spells.wizard=(val); @@wizard = val; end
  3200. def Spells.wizard; @@wizard; end
  3201. def Spells.sorcerer=(val); @@sorcerer = val; end
  3202. def Spells.sorcerer; @@sorcerer; end
  3203. def Spells.ranger=(val); @@ranger = val; end
  3204. def Spells.ranger; @@ranger; end
  3205. def Spells.paladin=(val); @@paladin = val; end
  3206. def Spells.paladin; @@paladin; end
  3207. def Spells.empath=(val); @@empath = val; end
  3208. def Spells.empath; @@empath; end
  3209. def Spells.cleric=(val); @@cleric = val; end
  3210. def Spells.cleric; @@cleric; end
  3211. def Spells.bard=(val); @@bard = val; end
  3212. def Spells.bard; @@bard; end
  3213. def Spells.get_circle_name(num)
  3214. val = num.to_s
  3215. if val == '1'
  3216. 'Minor Spirit'
  3217. elsif val == '2'
  3218. 'Major Spirit'
  3219. elsif val == '3'
  3220. 'Cleric'
  3221. elsif val == '4'
  3222. 'Minor Elemental'
  3223. elsif val == '5'
  3224. 'Major Elemental'
  3225. elsif val == '6'
  3226. 'Ranger'
  3227. elsif val == '7'
  3228. 'Sorcerer'
  3229. elsif val == '9'
  3230. 'Wizard'
  3231. elsif val == '10'
  3232. 'Bard'
  3233. elsif val == '11'
  3234. 'Empath'
  3235. elsif val == '12'
  3236. 'Minor Mental'
  3237. elsif val == '16'
  3238. 'Paladin'
  3239. elsif val == '17'
  3240. 'Arcane'
  3241. elsif val == '66'
  3242. 'Death'
  3243. elsif val == '65'
  3244. 'Imbedded Enchantment'
  3245. elsif val == '90'
  3246. 'Miscellaneous'
  3247. elsif val == '95'
  3248. 'Armor Specialization'
  3249. elsif val == '96'
  3250. 'Combat Maneuvers'
  3251. elsif val == '97'
  3252. 'Guardians of Sunfist'
  3253. elsif val == '98'
  3254. 'Order of Voln'
  3255. elsif val == '99'
  3256. 'Council of Light'
  3257. else
  3258. 'Unknown Circle'
  3259. end
  3260. end
  3261. def Spells.active
  3262. Spell.active
  3263. end
  3264. def Spells.known
  3265. known_spells = Array.new
  3266. Spell.list.each { |spell| known_spells.push(spell) if spell.known? }
  3267. return known_spells
  3268. end
  3269. def Spells.serialize
  3270. [@@minorelemental,@@majorelemental,@@minorspiritual,@@majorspiritual,@@wizard,@@sorcerer,@@ranger,@@paladin,@@empath,@@cleric,@@bard,@@minormental]
  3271. end
  3272. def Spells.load_serialized=(val)
  3273. @@minorelemental,@@majorelemental,@@minorspiritual,@@majorspiritual,@@wizard,@@sorcerer,@@ranger,@@paladin,@@empath,@@cleric,@@bard,@@minormental = val
  3274. # new spell circle added 2012-07-18; old data files will make @@minormental nil
  3275. @@minormental ||= 0
  3276. end
  3277. end
  3278. class SpellRanks
  3279. @@list ||= Array.new
  3280. @@timestamp ||= 0
  3281. @@loaded ||= false
  3282. attr_reader :name
  3283. attr_accessor :minorspiritual, :majorspiritual, :cleric, :minorelemental, :majorelemental, :minormental, :ranger, :sorcerer, :wizard, :bard, :empath, :paladin, :arcanesymbols, :magicitemuse, :monk
  3284. def SpellRanks.load
  3285. if $SAFE == 0
  3286. if File.exists?("#{$data_dir}#{XMLData.game}/spell-ranks.dat")
  3287. begin
  3288. File.open("#{$data_dir}#{XMLData.game}/spell-ranks.dat", 'rb') { |f|
  3289. @@timestamp, @@list = Marshal.load(f.read)
  3290. }
  3291. # minor mental circle added 2012-07-18; old data files will have @minormental as nil
  3292. @@list.each { |rank_info| rank_info.minormental ||= 0 }
  3293. # monk circle added 2013-01-15; old data files will have @minormental as nil
  3294. @@list.each { |rank_info| rank_info.monk ||= 0 }
  3295. @@loaded = true
  3296. rescue
  3297. respond "--- error: SpellRanks.load: #{$!}"
  3298. $stderr.puts "error: SpellRanks.load: #{$!}"
  3299. $stderr.puts $!.backtrace
  3300. @@list = Array.new
  3301. @@timestamp = 0
  3302. @@loaded = true
  3303. end
  3304. else
  3305. @@loaded = true
  3306. end
  3307. else
  3308. UNTRUSTED_SPELL_RANKS_LOAD.call
  3309. end
  3310. end
  3311. def SpellRanks.save
  3312. if $SAFE == 0
  3313. begin
  3314. File.open("#{$data_dir}#{XMLData.game}/spell-ranks.dat", 'wb') { |f|
  3315. f.write(Marshal.dump([@@timestamp, @@list]))
  3316. }
  3317. rescue
  3318. respond "--- error: SpellRanks.save: #{$!}"
  3319. $stderr.puts "error: SpellRanks.save: #{$!}"
  3320. $stderr.puts $!.backtrace
  3321. end
  3322. else
  3323. UNTRUSTED_SPELL_RANKS_SAVE.call
  3324. end
  3325. end
  3326. def SpellRanks.timestamp
  3327. SpellRanks.load unless @@loaded
  3328. @@timestamp
  3329. end
  3330. def SpellRanks.timestamp=(val)
  3331. SpellRanks.load unless @@loaded
  3332. @@timestamp = val
  3333. end
  3334. def SpellRanks.[](name)
  3335. SpellRanks.load unless @@loaded
  3336. @@list.find { |n| n.name == name }
  3337. end
  3338. def SpellRanks.list
  3339. SpellRanks.load unless @@loaded
  3340. @@list
  3341. end
  3342. def SpellRanks.method_missing(arg=nil)
  3343. echo "error: unknown method #{arg} for class SpellRanks"
  3344. respond caller[0..1]
  3345. end
  3346. def initialize(name)
  3347. SpellRanks.load unless @@loaded
  3348. @name = name
  3349. @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
  3350. @@list.push(self)
  3351. end
  3352. end
  3353. class Spell
  3354. @@list ||= Array.new
  3355. @@loaded ||= false
  3356. @@load_lock = Array.new
  3357. @@cast_lock ||= Array.new
  3358. @@bonus_list ||= Array.new
  3359. @@cost_list ||= Array.new
  3360. attr_reader :num, :name, :timestamp, :msgup, :msgdn, :circle, :active, :type, :cast_proc, :real_time, :persist_on_death, :availability
  3361. attr_accessor :stance, :channel
  3362. def initialize(xml_spell)
  3363. @num = xml_spell.attributes['number'].to_i
  3364. @name = xml_spell.attributes['name']
  3365. @type = xml_spell.attributes['type']
  3366. if xml_spell.attributes['availability'] == 'all'
  3367. @availability = 'all'
  3368. elsif xml_spell.attributes['availability'] == 'group'
  3369. @availability = 'group'
  3370. else
  3371. @availability = 'self-cast'
  3372. end
  3373. @bonus = Hash.new
  3374. xml_spell.elements.find_all { |e| e.name == 'bonus' }.each { |e|
  3375. @bonus[e.attributes['type']] = e.text
  3376. @bonus[e.attributes['type']].untaint
  3377. }
  3378. @msgup = xml_spell.elements.find_all { |e| (e.name == 'message') and (e.attributes['type'].downcase == 'start') }.collect { |e| e.text }.join('$|^')
  3379. @msgup = nil if @msgup.empty?
  3380. @msgdn = xml_spell.elements.find_all { |e| (e.name == 'message') and (e.attributes['type'].downcase == 'end') }.collect { |e| e.text }.join('$|^')
  3381. @msgdn = nil if @msgdn.empty?
  3382. @stance = (xml_spell.attributes['stance'] =~ /^(yes|true)$/i)
  3383. @channel = (xml_spell.attributes['channel'] =~ /^(yes|true)$/i)
  3384. @cost = Hash.new
  3385. xml_spell.elements.find_all { |e| e.name == 'cost' }.each { |xml_cost|
  3386. @cost[xml_cost.attributes['type'].downcase] ||= Hash.new
  3387. if xml_cost.attributes['cast-type'].downcase == 'target'
  3388. @cost[xml_cost.attributes['type'].downcase]['target'] = xml_cost.text
  3389. else
  3390. @cost[xml_cost.attributes['type'].downcase]['self'] = xml_cost.text
  3391. end
  3392. }
  3393. @duration = Hash.new
  3394. xml_spell.elements.find_all { |e| e.name == 'duration' }.each { |xml_duration|
  3395. if xml_duration.attributes['cast-type'].downcase == 'target'
  3396. cast_type = 'target'
  3397. else
  3398. cast_type = 'self'
  3399. if xml_duration.attributes['real-time'] =~ /^(yes|true)$/i
  3400. @real_time = true
  3401. else
  3402. @real_time = false
  3403. end
  3404. end
  3405. @duration[cast_type] = Hash.new
  3406. @duration[cast_type][:duration] = xml_duration.text
  3407. @duration[cast_type][:stackable] = (xml_duration.attributes['span'].downcase == 'stackable')
  3408. @duration[cast_type][:refreshable] = (xml_duration.attributes['span'].downcase == 'refreshable')
  3409. if xml_duration.attributes['multicastable'] =~ /^(yes|true)$/i
  3410. @duration[cast_type][:multicastable] = true
  3411. else
  3412. @duration[cast_type][:multicastable] = false
  3413. end
  3414. if xml_duration.attributes['persist-on-death'] =~ /^(yes|true)$/i
  3415. @persist_on_death = true
  3416. else
  3417. @persist_on_death = false
  3418. end
  3419. if xml_duration.attributes['max-duration']
  3420. @duration[cast_type][:max_duration] = xml_duration.attributes['max-duration'].to_f
  3421. else
  3422. @duration[cast_type][:max_duration] = 250.0
  3423. end
  3424. }
  3425. @cast_proc = xml_spell.elements['cast-proc'].text
  3426. @cast_proc.untaint
  3427. @timestamp = Time.now
  3428. @timeleft = 0
  3429. @active = false
  3430. @circle = (num.to_s.length == 3 ? num.to_s[0..0] : num.to_s[0..1])
  3431. @@list.push(self) unless @@list.find { |spell| spell.num == @num }
  3432. self
  3433. end
  3434. def Spell.load(filename="#{$script_dir}spell-list.xml")
  3435. if $SAFE == 0
  3436. script = Script.self
  3437. begin
  3438. @@load_lock.push(script)
  3439. unless @@load_lock.first == script
  3440. sleep "0.1".to_f until @loaded or (@@load_lock.first == script) or @@load_lock.empty?
  3441. return true if @loaded
  3442. end
  3443. @@loaded = false
  3444. begin
  3445. spell_times = Hash.new
  3446. # reloading spell data should not reset spell tracking...
  3447. unless @@list.empty?
  3448. @@list.each { |spell| spell_times[spell.num] = spell.timeleft if spell.active? }
  3449. @@list.clear
  3450. end
  3451. File.open(filename) { |file|
  3452. xml_doc = REXML::Document.new(file)
  3453. xml_root = xml_doc.root
  3454. xml_root.elements.each { |xml_spell| Spell.new(xml_spell) }
  3455. }
  3456. @@list.each { |spell|
  3457. if spell_times[spell.num]
  3458. spell.timeleft = spell_times[spell.num]
  3459. spell.active = true
  3460. end
  3461. }
  3462. @@loaded = true
  3463. @@bonus_list = @@list.collect { |spell| spell._bonus.keys }.flatten
  3464. @@bonus_list = @@bonus_list | @@bonus_list
  3465. @@cost_list = @@list.collect { |spell| spell._cost.keys }.flatten
  3466. @@cost_list = @@cost_list | @@cost_list
  3467. return true
  3468. rescue
  3469. respond "--- error: Spell.load: #{$!}"
  3470. $stderr.puts "error: Spell.load: #{$!}"
  3471. $stderr.puts $!.backtrace
  3472. @@loaded = false
  3473. return false
  3474. end
  3475. ensure
  3476. @@load_lock.delete(script)
  3477. end
  3478. else
  3479. UNTRUSTED_SPELL_LOAD.call
  3480. end
  3481. end
  3482. def Spell.[](val)
  3483. Spell.load unless @@loaded
  3484. if val.class == Spell
  3485. val
  3486. elsif (val.class == Fixnum) or (val.class == String and val =~ /^[0-9]+$/)
  3487. @@list.find { |spell| spell.num == val.to_i }
  3488. else
  3489. if (ret = @@list.find { |spell| spell.name =~ /^#{val}$/i })
  3490. ret
  3491. elsif (ret = @@list.find { |spell| spell.name =~ /^#{val}/i })
  3492. ret
  3493. else
  3494. @@list.find { |spell| spell.msgup =~ /#{val}/i or spell.msgdn =~ /#{val}/i }
  3495. end
  3496. end
  3497. end
  3498. def Spell.active
  3499. Spell.load unless @@loaded
  3500. active = Array.new
  3501. @@list.each { |spell| active.push(spell) if spell.active? }
  3502. active
  3503. end
  3504. def Spell.active?(val)
  3505. Spell.load unless @@loaded
  3506. Spell[val].active?
  3507. end
  3508. def Spell.list
  3509. Spell.load unless @@loaded
  3510. @@list
  3511. end
  3512. def Spell.upmsgs
  3513. Spell.load unless @@loaded
  3514. @@list.collect { |spell| spell.msgup }.compact
  3515. end
  3516. def Spell.dnmsgs
  3517. Spell.load unless @@loaded
  3518. @@list.collect { |spell| spell.msgdn }.compact
  3519. end
  3520. def time_per_formula(options={})
  3521. activator_modifier = { 'tap' => 0.5, 'rub' => 1, 'wave' => 1, 'raise' => 1.33, 'drink' => 0, 'bite' => 0, 'eat' => 0, 'gobble' => 0 }
  3522. can_haz_spell_ranks = /Spells\.(?:minorelemental|majorelemental|minorspiritual|majorspiritual|wizard|sorcerer|ranger|paladin|empath|cleric|bard|minormental)/
  3523. 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' ]
  3524. if options[:caster] and (options[:caster] !~ /^(?:self|#{XMLData.name})$/i)
  3525. if options[:target] and (options[:target].downcase == options[:caster].downcase)
  3526. formula = @duration['self'][:duration].to_s.dup
  3527. else
  3528. formula = @duration['target'][:duration].dup || @duration['self'][:duration].to_s.dup
  3529. end
  3530. if options[:activator] =~ /^(#{activator_modifier.keys.join('|')})$/i
  3531. if formula =~ can_haz_spell_ranks
  3532. skills.each { |skill_name| formula.gsub!(skill_name, "(SpellRanks['#{options[:caster]}'].magicitemuse * #{activator_modifier[options[:activator]]}).to_i") }
  3533. formula = "(#{formula})/2.0"
  3534. elsif formula =~ /Skills\.(?:magicitemuse|arancesymbols)/
  3535. skills.each { |skill_name| formula.gsub!(skill_name, "(SpellRanks['#{options[:caster]}'].magicitemuse * #{activator_modifier[options[:activator]]}).to_i") }
  3536. end
  3537. elsif options[:activator] =~ /^(invoke|scroll)$/i
  3538. if formula =~ can_haz_spell_ranks
  3539. skills.each { |skill_name| formula.gsub!(skill_name, "SpellRanks['#{options[:caster]}'].arcanesymbols.to_i") }
  3540. formula = "(#{formula})/2.0"
  3541. elsif formula =~ /Skills\.(?:magicitemuse|arancesymbols)/
  3542. skills.each { |skill_name| formula.gsub!(skill_name, "SpellRanks['#{options[:caster]}'].arcanesymbols.to_i") }
  3543. end
  3544. else
  3545. skills.each { |skill_name| formula.gsub!(skill_name, "SpellRanks[#{options[:caster].to_s.inspect}].#{skill_name.sub(/^(?:Spells|Skills)\./, '')}.to_i") }
  3546. end
  3547. else
  3548. if options[:target] and (options[:target] !~ /^(?:self|#{XMLData.name})$/i)
  3549. formula = @duration['target'][:duration].dup || @duration['self'][:duration].to_s.dup
  3550. else
  3551. formula = @duration['self'][:duration].to_s.dup
  3552. end
  3553. if options[:activator] =~ /^(#{activator_modifier.keys.join('|')})$/i
  3554. if formula =~ can_haz_spell_ranks
  3555. skills.each { |skill_name| formula.gsub!(skill_name, "(Skills.magicitemuse * #{activator_modifier[options[:activator]]}).to_i") }
  3556. formula = "(#{formula})/2.0"
  3557. elsif formula =~ /Skills\.(?:magicitemuse|arancesymbols)/
  3558. skills.each { |skill_name| formula.gsub!(skill_name, "(Skills.magicitemuse * #{activator_modifier[options[:activator]]}).to_i") }
  3559. end
  3560. elsif options[:activator] =~ /^(invoke|scroll)$/i
  3561. if formula =~ can_haz_spell_ranks
  3562. skills.each { |skill_name| formula.gsub!(skill_name, "Skills.arcanesymbols.to_i") }
  3563. formula = "(#{formula})/2.0"
  3564. elsif formula =~ /Skills\.(?:magicitemuse|arancesymbols)/
  3565. skills.each { |skill_name| formula.gsub!(skill_name, "Skills.arcanesymbols.to_i") }
  3566. end
  3567. end
  3568. end
  3569. UNTRUSTED_UNTAINT.call(formula)
  3570. formula
  3571. end
  3572. def time_per(options={})
  3573. formula = self.time_per_formula(options)
  3574. if options[:line]
  3575. line = options[:line]
  3576. end
  3577. if $SAFE < 3
  3578. proc { $SAFE = 3; eval(formula) }.call.to_f
  3579. else
  3580. eval(formula).to_f
  3581. end
  3582. end
  3583. def timeleft=(val)
  3584. @timeleft = val
  3585. @timestamp = Time.now
  3586. end
  3587. def timeleft
  3588. if self.time_per_formula.to_s == 'Spellsong.timeleft'
  3589. @timeleft = Spellsong.timeleft
  3590. else
  3591. @timeleft = @timeleft - ((Time.now - @timestamp) / 60.to_f)
  3592. if @timeleft <= 0
  3593. self.putdown
  3594. return 0.to_f
  3595. end
  3596. end
  3597. @timestamp = Time.now
  3598. @timeleft
  3599. end
  3600. def minsleft
  3601. self.timeleft
  3602. end
  3603. def secsleft
  3604. self.timeleft * 60
  3605. end
  3606. def active=(val)
  3607. @active = val
  3608. end
  3609. def active?
  3610. (self.timeleft > 0) and @active
  3611. end
  3612. def stackable?(options={})
  3613. if options[:caster] and (options[:caster] !~ /^(?:self|#{XMLData.name})$/i)
  3614. if options[:target] and (options[:target].downcase == options[:caster].downcase)
  3615. @duration['self'][:stackable]
  3616. else
  3617. if @duration['target'][:stackable].nil?
  3618. @duration['self'][:stackable]
  3619. else
  3620. @duration['target'][:stackable]
  3621. end
  3622. end
  3623. else
  3624. if options[:target] and (options[:target] !~ /^(?:self|#{XMLData.name})$/i)
  3625. if @duration['target'][:stackable].nil?
  3626. @duration['self'][:stackable]
  3627. else
  3628. @duration['target'][:stackable]
  3629. end
  3630. else
  3631. @duration['self'][:stackable]
  3632. end
  3633. end
  3634. end
  3635. def refreshable?(options={})
  3636. if options[:caster] and (options[:caster] !~ /^(?:self|#{XMLData.name})$/i)
  3637. if options[:target] and (options[:target].downcase == options[:caster].downcase)
  3638. @duration['self'][:refreshable]
  3639. else
  3640. if @duration['target'][:refreshable].nil?
  3641. @duration['self'][:refreshable]
  3642. else
  3643. @duration['target'][:refreshable]
  3644. end
  3645. end
  3646. else
  3647. if options[:target] and (options[:target] !~ /^(?:self|#{XMLData.name})$/i)
  3648. if @duration['target'][:refreshable].nil?
  3649. @duration['self'][:refreshable]
  3650. else
  3651. @duration['target'][:refreshable]
  3652. end
  3653. else
  3654. @duration['self'][:refreshable]
  3655. end
  3656. end
  3657. end
  3658. def multicastable?(options={})
  3659. if options[:caster] and (options[:caster] !~ /^(?:self|#{XMLData.name})$/i)
  3660. if options[:target] and (options[:target].downcase == options[:caster].downcase)
  3661. @duration['self'][:multicastable]
  3662. else
  3663. if @duration['target'][:multicastable].nil?
  3664. @duration['self'][:multicastable]
  3665. else
  3666. @duration['target'][:multicastable]
  3667. end
  3668. end
  3669. else
  3670. if options[:target] and (options[:target] !~ /^(?:self|#{XMLData.name})$/i)
  3671. if @duration['target'][:multicastable].nil?
  3672. @duration['self'][:multicastable]
  3673. else
  3674. @duration['target'][:multicastable]
  3675. end
  3676. else
  3677. @duration['self'][:multicastable]
  3678. end
  3679. end
  3680. end
  3681. def known?
  3682. if @num.to_s.length == 3
  3683. circle_num = @num.to_s[0..0].to_i
  3684. elsif @num.to_s.length == 4
  3685. circle_num = @num.to_s[0..1].to_i
  3686. else
  3687. return false
  3688. end
  3689. if circle_num == 1
  3690. ranks = [ Spells.minorspiritual, XMLData.level ].min
  3691. elsif circle_num == 2
  3692. ranks = [ Spells.majorspiritual, XMLData.level ].min
  3693. elsif circle_num == 3
  3694. ranks = [ Spells.cleric, XMLData.level ].min
  3695. elsif circle_num == 4
  3696. ranks = [ Spells.minorelemental, XMLData.level ].min
  3697. elsif circle_num == 5
  3698. ranks = [ Spells.majorelemental, XMLData.level ].min
  3699. elsif circle_num == 6
  3700. ranks = [ Spells.ranger, XMLData.level ].min
  3701. elsif circle_num == 7
  3702. ranks = [ Spells.sorcerer, XMLData.level ].min
  3703. elsif circle_num == 9
  3704. ranks = [ Spells.wizard, XMLData.level ].min
  3705. elsif circle_num == 10
  3706. ranks = [ Spells.bard, XMLData.level ].min
  3707. elsif circle_num == 11
  3708. ranks = [ Spells.empath, XMLData.level ].min
  3709. elsif circle_num == 12
  3710. ranks = [ Spells.minormental, XMLData.level ].min
  3711. elsif circle_num == 16
  3712. ranks = [ Spells.paladin, XMLData.level ].min
  3713. elsif (circle_num == 97) and (Society.status == 'Guardians of Sunfist')
  3714. ranks = Society.rank
  3715. elsif (circle_num == 98) and (Society.status == 'Order of Voln')
  3716. ranks = Society.rank
  3717. elsif (circle_num == 99) and (Society.status == 'Council of Light')
  3718. ranks = Society.rank
  3719. elsif (circle_num == 96)
  3720. if CMan[@name].to_i > 0
  3721. return true
  3722. else
  3723. return false
  3724. end
  3725. else
  3726. return false
  3727. end
  3728. if (@num % 100) <= ranks
  3729. return true
  3730. else
  3731. return false
  3732. end
  3733. end
  3734. def available?(options={})
  3735. if self.known?
  3736. if options[:caster] and (options[:caster] !~ /^(?:self|#{XMLData.name})$/i)
  3737. if options[:target] and (options[:target].downcase == options[:caster].downcase)
  3738. true
  3739. else
  3740. @availability == 'all'
  3741. end
  3742. else
  3743. if options[:target] and (options[:target] !~ /^(?:self|#{XMLData.name})$/i)
  3744. @availability == 'all'
  3745. else
  3746. true
  3747. end
  3748. end
  3749. else
  3750. false
  3751. end
  3752. end
  3753. def to_s
  3754. @name.to_s
  3755. end
  3756. def max_duration(options={})
  3757. if options[:caster] and (options[:caster] !~ /^(?:self|#{XMLData.name})$/i)
  3758. if options[:target] and (options[:target].downcase == options[:caster].downcase)
  3759. @duration['self'][:max_duration]
  3760. else
  3761. @duration['target'][:max_duration] || @duration['self'][:max_duration]
  3762. end
  3763. else
  3764. if options[:target] and (options[:target] !~ /^(?:self|#{XMLData.name})$/i)
  3765. @duration['target'][:max_duration] || @duration['self'][:max_duration]
  3766. else
  3767. @duration['self'][:max_duration]
  3768. end
  3769. end
  3770. end
  3771. def putup(options={})
  3772. if stackable?(options)
  3773. self.timeleft = [ self.timeleft + self.time_per(options), self.max_duration(options) ].min
  3774. else
  3775. self.timeleft = [ self.time_per(options), self.max_duration(options) ].min
  3776. end
  3777. @active = true
  3778. end
  3779. def putdown
  3780. self.timeleft = 0
  3781. @active = false
  3782. end
  3783. def remaining
  3784. self.timeleft.as_time
  3785. end
  3786. def affordable?(options={})
  3787. # fixme: deal with them dirty bards!
  3788. release_options = options.dup
  3789. release_options[:multicast] = nil
  3790. 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)) )
  3791. false
  3792. elsif (self.stamina_cost(options) > 0) and (Spell[9699].active? or not checkstamina(self.stamina_cost(options)))
  3793. false
  3794. 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)
  3795. false
  3796. else
  3797. true
  3798. end
  3799. end
  3800. def Spell.lock_cast
  3801. script = Script.self
  3802. @@cast_lock.push(script)
  3803. until (@@cast_lock.first == script) or @@cast_lock.empty?
  3804. sleep "0.1".to_f
  3805. Script.self # allows this loop to be paused
  3806. @@cast_lock.delete_if { |s| s.paused or not (Script.running + Script.hidden).include?(s) }
  3807. end
  3808. end
  3809. def Spell.unlock_cast
  3810. @@cast_lock.delete(Script.self)
  3811. end
  3812. def cast(target=nil, results_of_interest=nil)
  3813. script = Script.self
  3814. if @type.nil?
  3815. echo "cast: spell missing type (#{@name})"
  3816. sleep "0.1".to_f
  3817. return false
  3818. end
  3819. unless (self.mana_cost <= 0) or checkmana(self.mana_cost)
  3820. echo 'cast: not enough mana'
  3821. sleep "0.1".to_f
  3822. return false
  3823. end
  3824. unless (self.spirit_cost > 0) or checkspirit(self.spirit_cost + 1 + [ 9912, 9913, 9914, 9916, 9916, 9916 ].delete_if { |num| !Spell[num].active? }.length)
  3825. echo 'cast: not enough spirit'
  3826. sleep "0.1".to_f
  3827. return false
  3828. end
  3829. unless (self.stamina_cost <= 0) or checkstamina(self.stamina_cost)
  3830. echo 'cast: not enough stamina'
  3831. sleep "0.1".to_f
  3832. return false
  3833. end
  3834. begin
  3835. save_want_downstream = script.want_downstream
  3836. save_want_downstream_xml = script.want_downstream_xml
  3837. script.want_downstream = true
  3838. script.want_downstream_xml = false
  3839. @@cast_lock.push(script)
  3840. until (@@cast_lock.first == script) or @@cast_lock.empty?
  3841. sleep "0.1".to_f
  3842. Script.self # allows this loop to be paused
  3843. @@cast_lock.delete_if { |s| s.paused or not (Script.running + Script.hidden).include?(s) }
  3844. end
  3845. unless (self.mana_cost <= 0) or checkmana(self.mana_cost)
  3846. echo 'cast: not enough mana'
  3847. sleep "0.1".to_f
  3848. return false
  3849. end
  3850. unless (self.spirit_cost > 0) or checkspirit(self.spirit_cost + 1 + [ 9912, 9913, 9914, 9916, 9916, 9916 ].delete_if { |num| !Spell[num].active? }.length)
  3851. echo 'cast: not enough spirit'
  3852. sleep "0.1".to_f
  3853. return false
  3854. end
  3855. unless (self.stamina_cost <= 0) or checkstamina(self.stamina_cost)
  3856. echo 'cast: not enough stamina'
  3857. sleep "0.1".to_f
  3858. return false
  3859. end
  3860. if @cast_proc
  3861. waitrt?
  3862. waitcastrt?
  3863. unless (self.mana_cost <= 0) or checkmana(self.mana_cost)
  3864. echo 'cast: not enough mana'
  3865. sleep "0.1".to_f
  3866. return false
  3867. end
  3868. unless (self.spirit_cost > 0) or checkspirit(self.spirit_cost + 1 + [ 9912, 9913, 9914, 9916, 9916, 9916 ].delete_if { |num| !Spell[num].active? }.length)
  3869. echo 'cast: not enough spirit'
  3870. sleep "0.1".to_f
  3871. return false
  3872. end
  3873. unless (self.stamina_cost <= 0) or checkstamina(self.stamina_cost)
  3874. echo 'cast: not enough stamina'
  3875. sleep "0.1".to_f
  3876. return false
  3877. end
  3878. begin
  3879. if $SAFE < 3
  3880. proc { $SAFE = 3; eval(@cast_proc) }.call
  3881. else
  3882. eval(@cast_proc)
  3883. end
  3884. rescue
  3885. echo "cast: error: #{$!}"
  3886. respond $!.backtrace[0..2]
  3887. return false
  3888. end
  3889. else
  3890. if @channel
  3891. cast_cmd = 'channel'
  3892. else
  3893. cast_cmd = 'cast'
  3894. end
  3895. if (target.nil? or target.to_s.empty?) and (@type =~ /attack/i) and not [410,435,525,912,909,609].include?(@num)
  3896. cast_cmd += ' target'
  3897. elsif target.class == GameObj
  3898. cast_cmd += " ##{target.id}"
  3899. elsif target.class == Fixnum
  3900. cast_cmd += " ##{target}"
  3901. else
  3902. cast_cmd += " #{target}"
  3903. end
  3904. cast_result = nil
  3905. loop {
  3906. waitrt?
  3907. unless checkprep == @name
  3908. waitcastrt? unless Spell[515].active?
  3909. unless checkprep == 'None'
  3910. dothistimeout 'release', 5, /^You feel the magic of your spell rush away from you\.$|^You don't have a prepared spell to release!$/
  3911. unless (self.mana_cost <= 0) or checkmana(self.mana_cost)
  3912. echo 'cast: not enough mana'
  3913. sleep "0.1".to_f
  3914. return false
  3915. end
  3916. 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))
  3917. echo 'cast: not enough spirit'
  3918. sleep "0.1".to_f
  3919. return false
  3920. end
  3921. unless (self.stamina_cost <= 0) or checkstamina(self.stamina_cost)
  3922. echo 'cast: not enough stamina'
  3923. sleep "0.1".to_f
  3924. return false
  3925. end
  3926. end
  3927. loop {
  3928. waitrt?
  3929. waitcastrt?
  3930. 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!$/
  3931. if prepare_result =~ /^Your spell(?:song)? is ready\./
  3932. break
  3933. elsif prepare_result == 'You already have a spell readied! You must RELEASE it if you wish to prepare another!'
  3934. dothistimeout 'release', 5, /^You feel the magic of your spell rush away from you\.$|^You don't have a prepared spell to release!$/
  3935. unless (self.mana_cost <= 0) or checkmana(self.mana_cost)
  3936. echo 'cast: not enough mana'
  3937. sleep "0.1".to_f
  3938. return false
  3939. end
  3940. 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!$/
  3941. sleep "0.1".to_f
  3942. return prepare_result
  3943. end
  3944. }
  3945. end
  3946. if @stance and checkstance != 'offensive'
  3947. put 'stance offensive'
  3948. # dothistimeout 'stance offensive', 5, /^You (?:are now in|move into) an? offensive stance|^You are unable to change your stance\.$/
  3949. end
  3950. if results_of_interest.class == Regexp
  3951. 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!$|#{results_of_interest.to_s}/
  3952. else
  3953. 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!$/
  3954. end
  3955. cast_result = dothistimeout cast_cmd, 5, results_regex
  3956. if cast_result == "You don't seem to be able to move to do that."
  3957. 100.times { break if clear.any? { |line| line =~ /^You regain control of your senses!$/ }; sleep "0.1".to_f }
  3958. cast_result = dothistimeout cast_cmd, 5, results_regex
  3959. end
  3960. if @stance and checkstance !~ /^guarded$|^defensive$/
  3961. dothistimeout 'stance guarded', 5, /^You (?:are now in|move into) an? \w+ stance|^You are unable to change your stance\.$/
  3962. end
  3963. 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\.$/
  3964. dothistimeout 'release', 5, /^You feel the magic of your spell rush away from you\.$|^You don't have a prepared spell to release!$/
  3965. end
  3966. break unless (@circle.to_i == 10) and (cast_result =~ /^\[Spell Hindrance for/)
  3967. }
  3968. cast_result
  3969. end
  3970. ensure
  3971. script.want_downstream = save_want_downstream
  3972. script.want_downstream_xml = save_want_downstream_xml
  3973. @@cast_lock.delete(script)
  3974. end
  3975. end
  3976. def _bonus
  3977. @bonus.dup
  3978. end
  3979. def _cost
  3980. @cost.dup
  3981. end
  3982. def method_missing(*args)
  3983. if @@bonus_list.include?(args[0].to_s.gsub('_', '-'))
  3984. if @bonus[args[0].to_s.gsub('_', '-')]
  3985. if $SAFE < 3
  3986. proc { $SAFE = 3; eval(@bonus[args[0].to_s.gsub('_', '-')]) }.call.to_i
  3987. else
  3988. eval(@bonus[args[0].to_s.gsub('_', '-')]).to_i
  3989. end
  3990. else
  3991. 0
  3992. end
  3993. elsif @@bonus_list.include?(args[0].to_s.sub(/_formula$/, '').gsub('_', '-'))
  3994. @bonus[args[0].to_s.sub(/_formula$/, '').gsub('_', '-')].dup
  3995. elsif (args[0].to_s =~ /_cost(?:_formula)?$/) and @@cost_list.include?(args[0].to_s.sub(/_formula$/, '').sub(/_cost$/, ''))
  3996. options = args[1].to_hash
  3997. if options[:caster] and (options[:caster] !~ /^(?:self|#{XMLData.name})$/i)
  3998. if options[:target] and (options[:target].downcase == options[:caster].downcase)
  3999. formula = @cost[args[0].to_s.sub(/_formula$/, '').sub(/_cost$/, '')]['self'].dup
  4000. else
  4001. formula = @cost[args[0].to_s.sub(/_formula$/, '').sub(/_cost$/, '')]['target'].dup || @cost[args[0].to_s.gsub('_', '-')]['self'].dup
  4002. end
  4003. 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' }
  4004. skills.each_pair { |a, b| formula.gsub!(a, b) }
  4005. else
  4006. if options[:target] and (options[:target] !~ /^(?:self|#{XMLData.name})$/i)
  4007. formula = @cost[args[0].to_s.sub(/_formula$/, '').sub(/_cost$/, '')]['target'].dup || @cost[args[0].to_s.gsub('_', '-')]['self'].dup
  4008. else
  4009. formula = @cost[args[0].to_s.sub(/_formula$/, '').sub(/_cost$/, '')]['self'].dup
  4010. end
  4011. end
  4012. if options[:multicast].to_i > 1
  4013. formula = "(#{formula})*#{options[:multicast].to_i}"
  4014. end
  4015. if args[0].to_s =~ /_formula$/
  4016. formula.dup
  4017. else
  4018. if formula
  4019. if $SAFE < 3
  4020. formula.untaint
  4021. proc { $SAFE = 3; eval(formula) }.call.to_i
  4022. else
  4023. UNTRUSTED_UNTAINT.call(formula)
  4024. eval(formula).to_i
  4025. end
  4026. else
  4027. 0
  4028. end
  4029. end
  4030. else
  4031. raise NoMethodError
  4032. end
  4033. end
  4034. def circle_name
  4035. Spells.get_circle_name(@circle)
  4036. end
  4037. def clear_on_death
  4038. !@persist_on_death
  4039. end
  4040. # for backwards compatiblity
  4041. def duration; self.time_per_formula; end
  4042. def cost; self.mana_cost_formula || '0'; end
  4043. def manaCost; self.mana_cost_formula || '0'; end
  4044. def spiritCost; self.spirit_cost_formula || '0'; end
  4045. def staminaCost; self.stamina_cost_formula || '0'; end
  4046. def boltAS; self.bolt_as_formula; end
  4047. def physicalAS; self.physical_as_formula; end
  4048. def boltDS; self.bolt_ds_formula; end
  4049. def physicalDS; self.physical_ds_formula; end
  4050. def elementalCS; self.elemental_cs_formula; end
  4051. def mentalCS; self.mental_cs_formula; end
  4052. def spiritCS; self.spirit_cs_formula; end
  4053. def sorcererCS; self.sorcerer_cs_formula; end
  4054. def elementalTD; self.elemental_td_formula; end
  4055. def mentalTD; self.mental_td_formula; end
  4056. def spiritTD; self.spirit_td_formula; end
  4057. def sorcererTD; self.sorcerer_td_formula; end
  4058. def castProc; @cast_proc; end
  4059. def stacks; self.stackable? end
  4060. def command; nil; end
  4061. def circlename; self.circle_name; end
  4062. def selfonly; @availability != 'all'; end
  4063. end
  4064. class CMan
  4065. @@bearhug ||= 0
  4066. @@berserk ||= 0
  4067. @@block_mastery ||= 0
  4068. @@bull_rush ||= 0
  4069. @@charge ||= 0
  4070. @@cheapshots ||= 0
  4071. @@combat_focus ||= 0
  4072. @@combat_mastery ||= 0
  4073. @@combat_mobility ||= 0
  4074. @@combat_movement ||= 0
  4075. @@combat_toughness ||= 0
  4076. @@coup_de_grace ||= 0
  4077. @@crowd_press ||= 0
  4078. @@cunning_defense ||= 0
  4079. @@cutthroat ||= 0
  4080. @@dirtkick ||= 0
  4081. @@disarm_weapon ||= 0
  4082. @@divert ||= 0
  4083. @@dust_shroud ||= 0
  4084. @@evade_mastery ||= 0
  4085. @@feint ||= 0
  4086. @@garrote ||= 0
  4087. @@groin_kick ||= 0
  4088. @@hamstring ||= 0
  4089. @@haymaker ||= 0
  4090. @@headbutt ||= 0
  4091. @@mighty_blow ||= 0
  4092. @@multi_fire ||= 0
  4093. @@parry_mastery ||= 0
  4094. @@precision ||= 0
  4095. @@quickstrike ||= 0
  4096. @@shadow_mastery ||= 0
  4097. @@shield_bash ||= 0
  4098. @@shield_charge ||= 0
  4099. @@side_by_side ||= 0
  4100. @@silent_strike ||= 0
  4101. @@specialization_i ||= 0
  4102. @@specialization_ii ||= 0
  4103. @@specialization_iii ||= 0
  4104. @@spin_attack ||= 0
  4105. @@staggering_blow ||= 0
  4106. @@stun_maneuvers ||= 0
  4107. @@subdual_strike ||= 0
  4108. @@subdue ||= 0
  4109. @@sucker_punch ||= 0
  4110. @@sunder_shield ||= 0
  4111. @@surge_of_strength ||= 0
  4112. @@sweep ||= 0
  4113. @@tackle ||= 0
  4114. @@trip ||= 0
  4115. @@truehand ||= 0
  4116. @@twin_hammerfists ||= 0
  4117. @@weapon_bonding ||= 0
  4118. @@vanish ||= 0
  4119. @@duck_and_weave ||= 0
  4120. @@slipery_mind ||= 0
  4121. @@predators_eye ||= 0
  4122. @@rolling_krynch_stance ||= 0
  4123. @@stance_of_the_mongoose ||= 0
  4124. @@slippery_mind ||= 0
  4125. @@flurry_of_blows ||= 0
  4126. @@inner_harmony ||= 0
  4127. def CMan.bearhug; @@bearhug; end
  4128. def CMan.berserk; @@berserk; end
  4129. def CMan.block_mastery; @@block_mastery; end
  4130. def CMan.bull_rush; @@bull_rush; end
  4131. def CMan.charge; @@charge; end
  4132. def CMan.cheapshots; @@cheapshots; end
  4133. def CMan.combat_focus; @@combat_focus; end
  4134. def CMan.combat_mastery; @@combat_mastery; end
  4135. def CMan.combat_mobility; @@combat_mobility; end
  4136. def CMan.combat_movement; @@combat_movement; end
  4137. def CMan.combat_toughness; @@combat_toughness; end
  4138. def CMan.coup_de_grace; @@coup_de_grace; end
  4139. def CMan.crowd_press; @@crowd_press; end
  4140. def CMan.cunning_defense; @@cunning_defense; end
  4141. def CMan.cutthroat; @@cutthroat; end
  4142. def CMan.dirtkick; @@dirtkick; end
  4143. def CMan.disarm_weapon; @@disarm_weapon; end
  4144. def CMan.divert; @@divert; end
  4145. def CMan.dust_shroud; @@dust_shroud; end
  4146. def CMan.evade_mastery; @@evade_mastery; end
  4147. def CMan.feint; @@feint; end
  4148. def CMan.garrote; @@garrote; end
  4149. def CMan.groin_kick; @@groin_kick; end
  4150. def CMan.hamstring; @@hamstring; end
  4151. def CMan.haymaker; @@haymaker; end
  4152. def CMan.headbutt; @@headbutt; end
  4153. def CMan.mighty_blow; @@mighty_blow; end
  4154. def CMan.multi_fire; @@multi_fire; end
  4155. def CMan.parry_mastery; @@parry_mastery; end
  4156. def CMan.precision; @@precision; end
  4157. def CMan.quickstrike; @@quickstrike; end
  4158. def CMan.shadow_mastery; @@shadow_mastery; end
  4159. def CMan.shield_bash; @@shield_bash; end
  4160. def CMan.shield_charge; @@shield_charge; end
  4161. def CMan.side_by_side; @@side_by_side; end
  4162. def CMan.silent_strike; @@silent_strike; end
  4163. def CMan.specialization_i; @@specialization_i; end
  4164. def CMan.specialization_ii; @@specialization_ii; end
  4165. def CMan.specialization_iii; @@specialization_iii; end
  4166. def CMan.spin_attack; @@spin_attack; end
  4167. def CMan.staggering_blow; @@staggering_blow; end
  4168. def CMan.stun_maneuvers; @@stun_maneuvers; end
  4169. def CMan.subdual_strike; @@subdual_strike; end
  4170. def CMan.subdue; @@subdue; end
  4171. def CMan.sucker_punch; @@sucker_punch; end
  4172. def CMan.sunder_shield; @@sunder_shield; end
  4173. def CMan.surge_of_strength; @@surge_of_strength; end
  4174. def CMan.sweep; @@sweep; end
  4175. def CMan.tackle; @@tackle; end
  4176. def CMan.trip; @@trip; end
  4177. def CMan.truehand; @@truehand; end
  4178. def CMan.twin_hammerfists; @@twin_hammerfists; end
  4179. def CMan.weapon_bonding; @@weapon_bonding; end
  4180. def CMan.vanish; @@vanish; end
  4181. def CMan.duck_and_weave; @@duck_and_weave; end
  4182. def CMan.slipery_mind; @@slipery_mind; end
  4183. def CMan.predators_eye; @@predators_eye; end
  4184. def CMan.bearhug=(val); @@bearhug=val; end
  4185. def CMan.berserk=(val); @@berserk=val; end
  4186. def CMan.block_mastery=(val); @@block_mastery=val; end
  4187. def CMan.bull_rush=(val); @@bull_rush=val; end
  4188. def CMan.charge=(val); @@charge=val; end
  4189. def CMan.cheapshots=(val); @@cheapshots=val; end
  4190. def CMan.combat_focus=(val); @@combat_focus=val; end
  4191. def CMan.combat_mastery=(val); @@combat_mastery=val; end
  4192. def CMan.combat_mobility=(val); @@combat_mobility=val; end
  4193. def CMan.combat_movement=(val); @@combat_movement=val; end
  4194. def CMan.combat_toughness=(val); @@combat_toughness=val; end
  4195. def CMan.coup_de_grace=(val); @@coup_de_grace=val; end
  4196. def CMan.crowd_press=(val); @@crowd_press=val; end
  4197. def CMan.cunning_defense=(val); @@cunning_defense=val; end
  4198. def CMan.cutthroat=(val); @@cutthroat=val; end
  4199. def CMan.dirtkick=(val); @@dirtkick=val; end
  4200. def CMan.disarm_weapon=(val); @@disarm_weapon=val; end
  4201. def CMan.divert=(val); @@divert=val; end
  4202. def CMan.dust_shroud=(val); @@dust_shroud=val; end
  4203. def CMan.evade_mastery=(val); @@evade_mastery=val; end
  4204. def CMan.feint=(val); @@feint=val; end
  4205. def CMan.garrote=(val); @@garrote=val; end
  4206. def CMan.groin_kick=(val); @@groin_kick=val; end
  4207. def CMan.hamstring=(val); @@hamstring=val; end
  4208. def CMan.haymaker=(val); @@haymaker=val; end
  4209. def CMan.headbutt=(val); @@headbutt=val; end
  4210. def CMan.mighty_blow=(val); @@mighty_blow=val; end
  4211. def CMan.multi_fire=(val); @@multi_fire=val; end
  4212. def CMan.parry_mastery=(val); @@parry_mastery=val; end
  4213. def CMan.precision=(val); @@precision=val; end
  4214. def CMan.quickstrike=(val); @@quickstrike=val; end
  4215. def CMan.shadow_mastery=(val); @@shadow_mastery=val; end
  4216. def CMan.shield_bash=(val); @@shield_bash=val; end
  4217. def CMan.shield_charge=(val); @@shield_charge=val; end
  4218. def CMan.side_by_side=(val); @@side_by_side=val; end
  4219. def CMan.silent_strike=(val); @@silent_strike=val; end
  4220. def CMan.specialization_i=(val); @@specialization_i=val; end
  4221. def CMan.specialization_ii=(val); @@specialization_ii=val; end
  4222. def CMan.specialization_iii=(val); @@specialization_iii=val; end
  4223. def CMan.spin_attack=(val); @@spin_attack=val; end
  4224. def CMan.staggering_blow=(val); @@staggering_blow=val; end
  4225. def CMan.stun_maneuvers=(val); @@stun_maneuvers=val; end
  4226. def CMan.subdual_strike=(val); @@subdual_strike=val; end
  4227. def CMan.subdue=(val); @@subdue=val; end
  4228. def CMan.sucker_punch=(val); @@sucker_punch=val; end
  4229. def CMan.sunder_shield=(val); @@sunder_shield=val; end
  4230. def CMan.surge_of_strength=(val); @@surge_of_strength=val; end
  4231. def CMan.sweep=(val); @@sweep=val; end
  4232. def CMan.tackle=(val); @@tackle=val; end
  4233. def CMan.trip=(val); @@trip=val; end
  4234. def CMan.truehand=(val); @@truehand=val; end
  4235. def CMan.twin_hammerfists=(val); @@twin_hammerfists=val; end
  4236. def CMan.weapon_bonding=(val); @@weapon_bonding=val; end
  4237. def CMan.vanish=(val); @@vanish=val; end
  4238. def CMan.duck_and_weave=(val); @@duck_and_weave=val; end
  4239. def CMan.slipery_mind=(val); @@slipery_mind=val; end
  4240. def CMan.predators_eye=(val); @@predators_eye=val; end
  4241. def CMan.method_missing(arg1, arg2=nil)
  4242. nil
  4243. end
  4244. def CMan.[](name)
  4245. CMan.send(name.gsub(/[\s\-]/, '_').gsub("'", "").downcase)
  4246. end
  4247. def CMan.[]=(name,val)
  4248. CMan.send("#{name.gsub(/[\s\-]/, '_').gsub("'", "").downcase}=", val.to_i)
  4249. end
  4250. end
  4251. class Stats
  4252. @@race ||= 'unknown'
  4253. @@prof ||= 'unknown'
  4254. @@gender ||= 'unknown'
  4255. @@age ||= 0
  4256. @@level ||= 0
  4257. @@str ||= [0,0]
  4258. @@con ||= [0,0]
  4259. @@dex ||= [0,0]
  4260. @@agi ||= [0,0]
  4261. @@dis ||= [0,0]
  4262. @@aur ||= [0,0]
  4263. @@log ||= [0,0]
  4264. @@int ||= [0,0]
  4265. @@wis ||= [0,0]
  4266. @@inf ||= [0,0]
  4267. def Stats.race; @@race; end
  4268. def Stats.race=(val); @@race=val; end
  4269. def Stats.prof; @@prof; end
  4270. def Stats.prof=(val); @@prof=val; end
  4271. def Stats.gender; @@gender; end
  4272. def Stats.gender=(val); @@gender=val; end
  4273. def Stats.age; @@age; end
  4274. def Stats.age=(val); @@age=val; end
  4275. def Stats.level; @@level; end
  4276. def Stats.level=(val); @@level=val; end
  4277. def Stats.str; @@str; end
  4278. def Stats.str=(val); @@str=val; end
  4279. def Stats.con; @@con; end
  4280. def Stats.con=(val); @@con=val; end
  4281. def Stats.dex; @@dex; end
  4282. def Stats.dex=(val); @@dex=val; end
  4283. def Stats.agi; @@agi; end
  4284. def Stats.agi=(val); @@agi=val; end
  4285. def Stats.dis; @@dis; end
  4286. def Stats.dis=(val); @@dis=val; end
  4287. def Stats.aur; @@aur; end
  4288. def Stats.aur=(val); @@aur=val; end
  4289. def Stats.log; @@log; end
  4290. def Stats.log=(val); @@log=val; end
  4291. def Stats.int; @@int; end
  4292. def Stats.int=(val); @@int=val; end
  4293. def Stats.wis; @@wis; end
  4294. def Stats.wis=(val); @@wis=val; end
  4295. def Stats.inf; @@inf; end
  4296. def Stats.inf=(val); @@inf=val; end
  4297. def Stats.exp
  4298. if XMLData.next_level_text =~ /until next level/
  4299. 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 ]
  4300. exp_threshold[XMLData.level] - XMLData.next_level_text.slice(/[0-9]+/).to_i
  4301. else
  4302. XMLData.next_level_text.slice(/[0-9]+/).to_i
  4303. end
  4304. end
  4305. def Stats.exp=(val); nil; end
  4306. def Stats.serialize
  4307. [@@race,@@prof,@@gender,@@age,Stats.exp,@@level,@@str,@@con,@@dex,@@agi,@@dis,@@aur,@@log,@@int,@@wis,@@inf]
  4308. end
  4309. def Stats.load_serialized=(array)
  4310. @@race,@@prof,@@gender,@@age = array[0..3]
  4311. @@level,@@str,@@con,@@dex,@@agi,@@dis,@@aur,@@log,@@int,@@wis,@@inf = array[5..15]
  4312. end
  4313. end
  4314. class Gift
  4315. @@gift_start ||= Time.now
  4316. @@pulse_count ||= 0
  4317. def Gift.started
  4318. @@gift_start = Time.now
  4319. @@pulse_count = 0
  4320. end
  4321. def Gift.pulse
  4322. @@pulse_count += 1
  4323. end
  4324. def Gift.remaining
  4325. ([360 - @@pulse_count, 0].max * 60).to_f
  4326. end
  4327. def Gift.restarts_on
  4328. if XMLData.game == 'GSF'
  4329. @@gift_start + 597600
  4330. else
  4331. @@gift_start + 604800
  4332. end
  4333. end
  4334. def Gift.serialize
  4335. [@@gift_start, @@pulse_count]
  4336. end
  4337. def Gift.load_serialized=(array)
  4338. @@gift_start = array[0]
  4339. @@pulse_count = array[1].to_i
  4340. end
  4341. def Gift.ended
  4342. @@pulse_count = 360
  4343. end
  4344. def Gift.stopwatch
  4345. nil
  4346. end
  4347. end
  4348. class Wounds
  4349. def Wounds.leftEye; fix_injury_mode; XMLData.injuries['leftEye']['wound']; end
  4350. def Wounds.leye; fix_injury_mode; XMLData.injuries['leftEye']['wound']; end
  4351. def Wounds.rightEye; fix_injury_mode; XMLData.injuries['rightEye']['wound']; end
  4352. def Wounds.reye; fix_injury_mode; XMLData.injuries['rightEye']['wound']; end
  4353. def Wounds.head; fix_injury_mode; XMLData.injuries['head']['wound']; end
  4354. def Wounds.neck; fix_injury_mode; XMLData.injuries['neck']['wound']; end
  4355. def Wounds.back; fix_injury_mode; XMLData.injuries['back']['wound']; end
  4356. def Wounds.chest; fix_injury_mode; XMLData.injuries['chest']['wound']; end
  4357. def Wounds.abdomen; fix_injury_mode; XMLData.injuries['abdomen']['wound']; end
  4358. def Wounds.abs; fix_injury_mode; XMLData.injuries['abdomen']['wound']; end
  4359. def Wounds.leftArm; fix_injury_mode; XMLData.injuries['leftArm']['wound']; end
  4360. def Wounds.larm; fix_injury_mode; XMLData.injuries['leftArm']['wound']; end
  4361. def Wounds.rightArm; fix_injury_mode; XMLData.injuries['rightArm']['wound']; end
  4362. def Wounds.rarm; fix_injury_mode; XMLData.injuries['rightArm']['wound']; end
  4363. def Wounds.rightHand; fix_injury_mode; XMLData.injuries['rightHand']['wound']; end
  4364. def Wounds.rhand; fix_injury_mode; XMLData.injuries['rightHand']['wound']; end
  4365. def Wounds.leftHand; fix_injury_mode; XMLData.injuries['leftHand']['wound']; end
  4366. def Wounds.lhand; fix_injury_mode; XMLData.injuries['leftHand']['wound']; end
  4367. def Wounds.leftLeg; fix_injury_mode; XMLData.injuries['leftLeg']['wound']; end
  4368. def Wounds.lleg; fix_injury_mode; XMLData.injuries['leftLeg']['wound']; end
  4369. def Wounds.rightLeg; fix_injury_mode; XMLData.injuries['rightLeg']['wound']; end
  4370. def Wounds.rleg; fix_injury_mode; XMLData.injuries['rightLeg']['wound']; end
  4371. def Wounds.leftFoot; fix_injury_mode; XMLData.injuries['leftFoot']['wound']; end
  4372. def Wounds.rightFoot; fix_injury_mode; XMLData.injuries['rightFoot']['wound']; end
  4373. def Wounds.nsys; fix_injury_mode; XMLData.injuries['nsys']['wound']; end
  4374. def Wounds.nerves; fix_injury_mode; XMLData.injuries['nsys']['wound']; end
  4375. def Wounds.arms
  4376. fix_injury_mode
  4377. [XMLData.injuries['leftArm']['wound'],XMLData.injuries['rightArm']['wound'],XMLData.injuries['leftHand']['wound'],XMLData.injuries['rightHand']['wound']].max
  4378. end
  4379. def Wounds.limbs
  4380. fix_injury_mode
  4381. [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
  4382. end
  4383. def Wounds.torso
  4384. fix_injury_mode
  4385. [XMLData.injuries['rightEye']['wound'],XMLData.injuries['leftEye']['wound'],XMLData.injuries['chest']['wound'],XMLData.injuries['abdomen']['wound'],XMLData.injuries['back']['wound']].max
  4386. end
  4387. def Wounds.method_missing(arg=nil)
  4388. echo "Wounds: Invalid area, try one of these: arms, limbs, torso, #{XMLData.injuries.keys.join(', ')}"
  4389. nil
  4390. end
  4391. end
  4392. class Scars
  4393. def Scars.leftEye; fix_injury_mode; XMLData.injuries['leftEye']['scar']; end
  4394. def Scars.leye; fix_injury_mode; XMLData.injuries['leftEye']['scar']; end
  4395. def Scars.rightEye; fix_injury_mode; XMLData.injuries['rightEye']['scar']; end
  4396. def Scars.reye; fix_injury_mode; XMLData.injuries['rightEye']['scar']; end
  4397. def Scars.head; fix_injury_mode; XMLData.injuries['head']['scar']; end
  4398. def Scars.neck; fix_injury_mode; XMLData.injuries['neck']['scar']; end
  4399. def Scars.back; fix_injury_mode; XMLData.injuries['back']['scar']; end
  4400. def Scars.chest; fix_injury_mode; XMLData.injuries['chest']['scar']; end
  4401. def Scars.abdomen; fix_injury_mode; XMLData.injuries['abdomen']['scar']; end
  4402. def Scars.abs; fix_injury_mode; XMLData.injuries['abdomen']['scar']; end
  4403. def Scars.leftArm; fix_injury_mode; XMLData.injuries['leftArm']['scar']; end
  4404. def Scars.larm; fix_injury_mode; XMLData.injuries['leftArm']['scar']; end
  4405. def Scars.rightArm; fix_injury_mode; XMLData.injuries['rightArm']['scar']; end
  4406. def Scars.rarm; fix_injury_mode; XMLData.injuries['rightArm']['scar']; end
  4407. def Scars.rightHand; fix_injury_mode; XMLData.injuries['rightHand']['scar']; end
  4408. def Scars.rhand; fix_injury_mode; XMLData.injuries['rightHand']['scar']; end
  4409. def Scars.leftHand; fix_injury_mode; XMLData.injuries['leftHand']['scar']; end
  4410. def Scars.lhand; fix_injury_mode; XMLData.injuries['leftHand']['scar']; end
  4411. def Scars.leftLeg; fix_injury_mode; XMLData.injuries['leftLeg']['scar']; end
  4412. def Scars.lleg; fix_injury_mode; XMLData.injuries['leftLeg']['scar']; end
  4413. def Scars.rightLeg; fix_injury_mode; XMLData.injuries['rightLeg']['scar']; end
  4414. def Scars.rleg; fix_injury_mode; XMLData.injuries['rightLeg']['scar']; end
  4415. def Scars.leftFoot; fix_injury_mode; XMLData.injuries['leftFoot']['scar']; end
  4416. def Scars.rightFoot; fix_injury_mode; XMLData.injuries['rightFoot']['scar']; end
  4417. def Scars.nsys; fix_injury_mode; XMLData.injuries['nsys']['scar']; end
  4418. def Scars.nerves; fix_injury_mode; XMLData.injuries['nsys']['scar']; end
  4419. def Scars.arms
  4420. fix_injury_mode
  4421. [XMLData.injuries['leftArm']['scar'],XMLData.injuries['rightArm']['scar'],XMLData.injuries['leftHand']['scar'],XMLData.injuries['rightHand']['scar']].max
  4422. end
  4423. def Scars.limbs
  4424. fix_injury_mode
  4425. [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
  4426. end
  4427. def Scars.torso
  4428. fix_injury_mode
  4429. [XMLData.injuries['rightEye']['scar'],XMLData.injuries['leftEye']['scar'],XMLData.injuries['chest']['scar'],XMLData.injuries['abdomen']['scar'],XMLData.injuries['back']['scar']].max
  4430. end
  4431. def Scars.method_missing(arg=nil)
  4432. echo "Scars: Invalid area, try one of these: arms, limbs, torso, #{XMLData.injuries.keys.join(', ')}"
  4433. nil
  4434. end
  4435. end
  4436. class Watchfor
  4437. def initialize(line, theproc=nil, &block)
  4438. return nil unless script = Script.self
  4439. if line.class == String
  4440. line = Regexp.new(Regexp.escape(line))
  4441. elsif line.class != Regexp
  4442. echo 'watchfor: no string or regexp given'
  4443. return nil
  4444. end
  4445. if block.nil?
  4446. if theproc.respond_to? :call
  4447. block = theproc
  4448. else
  4449. echo 'watchfor: no block or proc given'
  4450. return nil
  4451. end
  4452. end
  4453. script.watchfor[line] = block
  4454. end
  4455. def Watchfor.clear
  4456. script.watchfor = Hash.new
  4457. end
  4458. end
  4459. class GameObj
  4460. @@loot ||= Array.new
  4461. @@npcs ||= Array.new
  4462. @@npc_status ||= Hash.new
  4463. @@pcs ||= Array.new
  4464. @@pc_status ||= Hash.new
  4465. @@inv ||= Array.new
  4466. @@contents ||= Hash.new
  4467. @@right_hand ||= nil
  4468. @@left_hand ||= nil
  4469. @@room_desc ||= Array.new
  4470. @@fam_loot ||= Array.new
  4471. @@fam_npcs ||= Array.new
  4472. @@fam_pcs ||= Array.new
  4473. @@fam_room_desc ||= Array.new
  4474. @@type_data ||= Hash.new
  4475. @@sellable_data ||= Hash.new
  4476. attr_reader :id
  4477. attr_accessor :noun, :name, :before_name, :after_name
  4478. def initialize(id, noun, name, before=nil, after=nil)
  4479. @id = id
  4480. @noun = noun
  4481. @noun = 'lapis' if @noun == 'lapis lazuli'
  4482. @noun = 'hammer' if @noun == "Hammer of Kai"
  4483. @noun = 'mother-of-pearl' if (@noun == 'pearl') and (@name =~ /mother\-of\-pearl/)
  4484. @name = name
  4485. @before_name = before
  4486. @after_name = after
  4487. end
  4488. def type
  4489. GameObj.load_data if @@type_data.empty?
  4490. 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]) }
  4491. if list.empty?
  4492. nil
  4493. else
  4494. list.join(',')
  4495. end
  4496. end
  4497. def sellable
  4498. GameObj.load_data if @@sellable_data.empty?
  4499. 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]) }
  4500. if list.empty?
  4501. nil
  4502. else
  4503. list.join(',')
  4504. end
  4505. end
  4506. def status
  4507. if @@npc_status.keys.include?(@id)
  4508. @@npc_status[@id]
  4509. elsif @@pc_status.keys.include?(@id)
  4510. @@pc_status[@id]
  4511. 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 } }
  4512. nil
  4513. else
  4514. 'gone'
  4515. end
  4516. end
  4517. def status=(val)
  4518. if @@npcs.any? { |npc| npc.id == @id }
  4519. @@npc_status[@id] = val
  4520. elsif @@pcs.any? { |pc| pc.id == @id }
  4521. @@pc_status[@id] = val
  4522. else
  4523. nil
  4524. end
  4525. end
  4526. def to_s
  4527. @noun
  4528. end
  4529. def empty?
  4530. false
  4531. end
  4532. def contents
  4533. @@contents[@id].dup
  4534. end
  4535. def GameObj.[](val)
  4536. if val.class == String
  4537. if val =~ /^\-?[0-9]+$/
  4538. 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 }
  4539. elsif val.split(' ').length == 1
  4540. 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 }
  4541. else
  4542. 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 }
  4543. end
  4544. elsif val.class == Regexp
  4545. 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 }
  4546. end
  4547. end
  4548. def GameObj
  4549. @noun
  4550. end
  4551. def full_name
  4552. "#{@before_name}#{' ' unless @before_name.nil? or @before_name.empty?}#{name}#{' ' unless @after_name.nil? or @after_name.empty?}#{@after_name}"
  4553. end
  4554. def GameObj.new_npc(id, noun, name, status=nil)
  4555. obj = GameObj.new(id, noun, name)
  4556. @@npcs.push(obj)
  4557. @@npc_status[id] = status
  4558. obj
  4559. end
  4560. def GameObj.new_loot(id, noun, name)
  4561. obj = GameObj.new(id, noun, name)
  4562. @@loot.push(obj)
  4563. obj
  4564. end
  4565. def GameObj.new_pc(id, noun, name, status=nil)
  4566. obj = GameObj.new(id, noun, name)
  4567. @@pcs.push(obj)
  4568. @@pc_status[id] = status
  4569. obj
  4570. end
  4571. def GameObj.new_inv(id, noun, name, container=nil, before=nil, after=nil)
  4572. obj = GameObj.new(id, noun, name, before, after)
  4573. if container
  4574. @@contents[container].push(obj)
  4575. else
  4576. @@inv.push(obj)
  4577. end
  4578. obj
  4579. end
  4580. def GameObj.new_room_desc(id, noun, name)
  4581. obj = GameObj.new(id, noun, name)
  4582. @@room_desc.push(obj)
  4583. obj
  4584. end
  4585. def GameObj.new_fam_room_desc(id, noun, name)
  4586. obj = GameObj.new(id, noun, name)
  4587. @@fam_room_desc.push(obj)
  4588. obj
  4589. end
  4590. def GameObj.new_fam_loot(id, noun, name)
  4591. obj = GameObj.new(id, noun, name)
  4592. @@fam_loot.push(obj)
  4593. obj
  4594. end
  4595. def GameObj.new_fam_npc(id, noun, name)
  4596. obj = GameObj.new(id, noun, name)
  4597. @@fam_npcs.push(obj)
  4598. obj
  4599. end
  4600. def GameObj.new_fam_pc(id, noun, name)
  4601. obj = GameObj.new(id, noun, name)
  4602. @@fam_pcs.push(obj)
  4603. obj
  4604. end
  4605. def GameObj.new_right_hand(id, noun, name)
  4606. @@right_hand = GameObj.new(id, noun, name)
  4607. end
  4608. def GameObj.right_hand
  4609. @@right_hand.dup
  4610. end
  4611. def GameObj.new_left_hand(id, noun, name)
  4612. @@left_hand = GameObj.new(id, noun, name)
  4613. end
  4614. def GameObj.left_hand
  4615. @@left_hand.dup
  4616. end
  4617. def GameObj.clear_loot
  4618. @@loot.clear
  4619. end
  4620. def GameObj.clear_npcs
  4621. @@npcs.clear
  4622. @@npc_status.clear
  4623. end
  4624. def GameObj.clear_pcs
  4625. @@pcs.clear
  4626. @@pc_status.clear
  4627. end
  4628. def GameObj.clear_inv
  4629. @@inv.clear
  4630. end
  4631. def GameObj.clear_room_desc
  4632. @@room_desc.clear
  4633. end
  4634. def GameObj.clear_fam_room_desc
  4635. @@fam_room_desc.clear
  4636. end
  4637. def GameObj.clear_fam_loot
  4638. @@fam_loot.clear
  4639. end
  4640. def GameObj.clear_fam_npcs
  4641. @@fam_npcs.clear
  4642. end
  4643. def GameObj.clear_fam_pcs
  4644. @@fam_pcs.clear
  4645. end
  4646. def GameObj.npcs
  4647. if @@npcs.empty?
  4648. nil
  4649. else
  4650. @@npcs.dup
  4651. end
  4652. end
  4653. def GameObj.loot
  4654. if @@loot.empty?
  4655. nil
  4656. else
  4657. @@loot.dup
  4658. end
  4659. end
  4660. def GameObj.pcs
  4661. if @@pcs.empty?
  4662. nil
  4663. else
  4664. @@pcs.dup
  4665. end
  4666. end
  4667. def GameObj.inv
  4668. if @@inv.empty?
  4669. nil
  4670. else
  4671. @@inv.dup
  4672. end
  4673. end
  4674. def GameObj.room_desc
  4675. if @@room_desc.empty?
  4676. nil
  4677. else
  4678. @@room_desc.dup
  4679. end
  4680. end
  4681. def GameObj.fam_room_desc
  4682. if @@fam_room_desc.empty?
  4683. nil
  4684. else
  4685. @@fam_room_desc.dup
  4686. end
  4687. end
  4688. def GameObj.fam_loot
  4689. if @@fam_loot.empty?
  4690. nil
  4691. else
  4692. @@fam_loot.dup
  4693. end
  4694. end
  4695. def GameObj.fam_npcs
  4696. if @@fam_npcs.empty?
  4697. nil
  4698. else
  4699. @@fam_npcs.dup
  4700. end
  4701. end
  4702. def GameObj.fam_pcs
  4703. if @@fam_pcs.empty?
  4704. nil
  4705. else
  4706. @@fam_pcs.dup
  4707. end
  4708. end
  4709. def GameObj.clear_container(container_id)
  4710. @@contents[container_id] = Array.new
  4711. end
  4712. def GameObj.delete_container(container_id)
  4713. @@contents.delete(container_id)
  4714. end
  4715. def GameObj.dead
  4716. dead_list = Array.new
  4717. for obj in @@npcs
  4718. dead_list.push(obj) if obj.status == "dead"
  4719. end
  4720. return nil if dead_list.empty?
  4721. return dead_list
  4722. end
  4723. def GameObj.containers
  4724. @@contents.dup
  4725. end
  4726. def GameObj.load_data(filename="#{$script_dir}gameobj-data.xml")
  4727. if $SAFE == 0
  4728. if File.exists?(filename)
  4729. begin
  4730. @@type_data = Hash.new
  4731. @@sellable_data = Hash.new
  4732. File.open(filename) { |file|
  4733. doc = REXML::Document.new(file.read)
  4734. doc.elements.each('data/type') { |e|
  4735. if type = e.attributes['name']
  4736. @@type_data[type] = Hash.new
  4737. @@type_data[type][:name] = Regexp.new(e.elements['name'].text) unless e.elements['name'].text.nil? or e.elements['name'].text.empty?
  4738. @@type_data[type][:noun] = Regexp.new(e.elements['noun'].text) unless e.elements['noun'].text.nil? or e.elements['noun'].text.empty?
  4739. @@type_data[type][:exclude] = Regexp.new(e.elements['exclude'].text) unless e.elements['exclude'].text.nil? or e.elements['exclude'].text.empty?
  4740. end
  4741. }
  4742. doc.elements.each('data/sellable') { |e|
  4743. if sellable = e.attributes['name']
  4744. @@sellable_data[sellable] = Hash.new
  4745. @@sellable_data[sellable][:name] = Regexp.new(e.elements['name'].text) unless e.elements['name'].text.nil? or e.elements['name'].text.empty?
  4746. @@sellable_data[sellable][:noun] = Regexp.new(e.elements['noun'].text) unless e.elements['noun'].text.nil? or e.elements['noun'].text.empty?
  4747. @@sellable_data[sellable][:exclude] = Regexp.new(e.elements['exclude'].text) unless e.elements['exclude'].text.nil? or e.elements['exclude'].text.empty?
  4748. end
  4749. }
  4750. }
  4751. true
  4752. rescue
  4753. @@type_data = nil
  4754. @@sellable_data = nil
  4755. echo "error: GameObj.load_data: #{$!}"
  4756. respond $!.backtrace[0..1]
  4757. false
  4758. end
  4759. else
  4760. @@type_data = nil
  4761. @@sellable_data = nil
  4762. echo "error: GameObj.load_data: file does not exist: #{filename}"
  4763. false
  4764. end
  4765. else
  4766. UNTRUSTED_GAMEOBJ_LOAD_DATA.call
  4767. end
  4768. end
  4769. def GameObj.type_data
  4770. @@type_data
  4771. end
  4772. def GameObj.sellable_data
  4773. @@sellable_data
  4774. end
  4775. end
  4776. class RoomObj < GameObj
  4777. end
  4778. class Map
  4779. @@list ||= Array.new
  4780. @@tags ||= Array.new
  4781. @@warned_depreciated_guaranteed_shortest_pathfind = false
  4782. @@warned_depreciated_estimation_pathfind = false
  4783. @@current_room_id ||= 0
  4784. @@current_room_count ||= -1
  4785. @@current_location ||= nil
  4786. @@current_location_count ||= -1
  4787. attr_reader :id
  4788. attr_accessor :title, :description, :paths, :location, :climate, :terrain, :wayto, :timeto, :image, :image_coords, :tags, :check_location, :unique_loot
  4789. 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)
  4790. @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
  4791. @@list[@id] = self
  4792. end
  4793. def outside?
  4794. @paths.first =~ /Obvious paths:/
  4795. end
  4796. def to_i
  4797. @id
  4798. end
  4799. def to_s
  4800. "##{@id}:\n#{@title[-1]}\n#{@description[-1]}\n#{@paths[-1]}"
  4801. end
  4802. def inspect
  4803. self.instance_variables.collect { |var| var.to_s + "=" + self.instance_variable_get(var).inspect }.join("\n")
  4804. end
  4805. def Map.get_free_id
  4806. Map.load if @@list.empty?
  4807. free_id = 0
  4808. until @@list[free_id].nil?
  4809. free_id += 1
  4810. end
  4811. free_id
  4812. end
  4813. def Map.list
  4814. Map.load if @@list.empty?
  4815. @@list
  4816. end
  4817. def Map.[](val)
  4818. Map.load if @@list.empty?
  4819. if (val.class == Fixnum) or (val.class == Bignum) or val =~ /^[0-9]+$/
  4820. @@list[val.to_i]
  4821. else
  4822. chkre = /#{val.strip.sub(/\.$/, '').gsub(/\.(?:\.\.)?/, '|')}/i
  4823. chk = /#{Regexp.escape(val.strip)}/i
  4824. @@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 } }
  4825. end
  4826. end
  4827. def Map.get_location
  4828. unless XMLData.room_count == @@current_location_count
  4829. script = Script.self
  4830. save_want_downstream = script.want_downstream
  4831. script.want_downstream = true
  4832. waitrt?
  4833. 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\.$/
  4834. script.want_downstream = save_want_downstream
  4835. @@current_location_count = XMLData.room_count
  4836. 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\.$/
  4837. @@current_location = false
  4838. else
  4839. @@current_location = /^You carefully survey your surroundings and guess that your current location is (.*?) or somewhere close to it\.$/.match(location_result).captures.first
  4840. end
  4841. end
  4842. @@current_location
  4843. end
  4844. def Map.current
  4845. Map.load if @@list.empty?
  4846. 1.times {
  4847. count = XMLData.room_count
  4848. if (XMLData.room_count == @@current_room_count) and (room = @@list[@@current_room_id])
  4849. return room
  4850. else
  4851. room = @@list.find { |r| r.title.include?(XMLData.room_title) and r.description.include?(XMLData.room_description.strip) and r.paths.include?(XMLData.room_exits_string.strip) and (r.unique_loot.nil? or (r.unique_loot.to_a - GameObj.loot.to_a.collect { |obj| obj.name }).empty?) }
  4852. unless room
  4853. desc_regex = /#{Regexp.escape(XMLData.room_description.strip).gsub(/\\\.(?:\\\.\\\.)?/, '|')}/
  4854. room = @@list.find { |r| r.title.include?(XMLData.room_title) and r.paths.include?(XMLData.room_exits_string.strip) and r.description.find { |desc| desc =~ desc_regex } and (r.unique_loot.nil? or (r.unique_loot.to_a - GameObj.loot.to_a.collect { |obj| obj.name }).empty?) }
  4855. end
  4856. if room.check_location
  4857. current_location = Map.get_location
  4858. 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.paths.include?(XMLData.room_exits_string.strip) and (r.unique_loot.nil? or (r.unique_loot.to_a - GameObj.loot.to_a.collect { |obj| obj.name }).empty?) }
  4859. unless room
  4860. desc_regex = /#{Regexp.escape(XMLData.room_description.strip).gsub(/\\\.(?:\\\.\\\.)?/, '|')}/
  4861. room = @@list.find { |r| (r.location == current_location) and r.title.include?(XMLData.room_title) and r.paths.include?(XMLData.room_exits_string.strip) and r.description.find { |desc| desc =~ desc_regex } and (r.unique_loot.nil? or (r.unique_loot.to_a - GameObj.loot.to_a.collect { |obj| obj.name }).empty?) }
  4862. end
  4863. end
  4864. if peer_tag = room.tags.find { |tag| tag =~ /^(set desc on; )?peer [a-z]+ =~ \/.+\/$/ }
  4865. need_desc, peer_direction, peer_requirement = /^(set desc on; )?peer ([a-z]+) =~ \/(.+)\/$/.match(peer_tag).captures
  4866. if need_desc
  4867. unless last_roomdesc = $_SERVERBUFFER_.reverse.find { |line| line =~ /<style id="roomDesc"\/>/ } and (last_roomdesc =~ /<style id="roomDesc"\/>[^<]/)
  4868. put 'set description on'
  4869. end
  4870. end
  4871. script = Script.self
  4872. save_want_downstream = script.want_downstream
  4873. script.want_downstream = true
  4874. squelch_started = false
  4875. squelch_proc = proc { |server_string|
  4876. if squelch_started
  4877. if server_string =~ /<prompt/
  4878. DownstreamHook.remove('squelch-peer')
  4879. end
  4880. nil
  4881. elsif server_string =~ /^You peer/
  4882. squelch_started = true
  4883. nil
  4884. else
  4885. server_string
  4886. end
  4887. }
  4888. DownstreamHook.add('squelch-peer', squelch_proc)
  4889. redo unless count == XMLData.room_count
  4890. result = dothistimeout "peer #{peer_direction}", 3, /^You peer|^\[Usage: PEER/
  4891. redo unless count == XMLData.room_count
  4892. if result =~ /^You peer/
  4893. peer_results = Array.new
  4894. 5.times {
  4895. if line = get?
  4896. peer_results.push line
  4897. break if line =~ /^Obvious/
  4898. end
  4899. }
  4900. unless peer_results.any? { |line| line =~ /#{peer_requirement}/ }
  4901. room = @@list[(room.id+1)..-1].find { |r| r.title.include?(XMLData.room_title) and r.description.include?(XMLData.room_description.strip) and r.paths.include?(XMLData.room_exits_string.strip) and (r.unique_loot.nil? or (r.unique_loot.to_a - GameObj.loot.to_a.collect { |obj| obj.name }).empty?) }
  4902. if peer_tag = room.tags.find { |tag| tag =~ /^(set desc on; )?peer [a-z]+ =~ \/.+\/$/ }
  4903. need_desc, peer_direction, peer_requirement = /^(set desc on; )?peer ([a-z]+) =~ \/(.+)\/$/.match(peer_tag).captures
  4904. unless peer_results.any? { |line| line =~ /#{peer_requirement}/ }
  4905. room = nil
  4906. end
  4907. end
  4908. end
  4909. end
  4910. script.want_downstream = save_want_downstream
  4911. end
  4912. if room
  4913. @@current_room_count = count
  4914. @@current_room_id = room.id
  4915. end
  4916. return room
  4917. end
  4918. }
  4919. end
  4920. def Map.current_or_new
  4921. if XMLData.game =~ /DR/
  4922. Map.current || Map.new(Map.get_free_id, [ XMLData.room_title ], [ XMLData.room_description.strip ], [ XMLData.room_exits_string.strip ])
  4923. else
  4924. peer_tag_good = proc { |r|
  4925. if peer_tag = r.tags.find { |tag| tag =~ /^(set desc on; )?peer [a-z]+ =~ \/.+\/$/ }
  4926. good = false
  4927. need_desc, peer_direction, peer_requirement = /^(set desc on; )?peer ([a-z]+) =~ \/(.+)\/$/.match(peer_tag).captures
  4928. if need_desc
  4929. unless last_roomdesc = $_SERVERBUFFER_.reverse.find { |line| line =~ /<style id="roomDesc"\/>/ } and (last_roomdesc =~ /<style id="roomDesc"\/>[^<]/)
  4930. put 'set description on'
  4931. end
  4932. end
  4933. script = Script.self
  4934. save_want_downstream = script.want_downstream
  4935. script.want_downstream = true
  4936. squelch_started = false
  4937. squelch_proc = proc { |server_string|
  4938. if squelch_started
  4939. if server_string =~ /<prompt/
  4940. DownstreamHook.remove('squelch-peer')
  4941. end
  4942. nil
  4943. elsif server_string =~ /^You peer/
  4944. squelch_started = true
  4945. nil
  4946. else
  4947. server_string
  4948. end
  4949. }
  4950. DownstreamHook.add('squelch-peer', squelch_proc)
  4951. result = dothistimeout "peer #{peer_direction}", 3, /^You peer|^\[Usage: PEER/
  4952. if result =~ /^You peer/
  4953. peer_results = Array.new
  4954. 5.times {
  4955. if line = get?
  4956. peer_results.push line
  4957. break if line =~ /^Obvious/
  4958. end
  4959. }
  4960. if peer_results.any? { |line| line =~ /#{peer_requirement}/ }
  4961. good = true
  4962. end
  4963. end
  4964. script.want_downstream = save_want_downstream
  4965. else
  4966. good = true
  4967. end
  4968. good
  4969. }
  4970. current_location = Map.get_location
  4971. 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.paths.include?(XMLData.room_exits_string.strip) and (r.unique_loot.nil? or (r.unique_loot.to_a - GameObj.loot.to_a.collect { |obj| obj.name }).empty?) and peer_tag_good.call(r) }
  4972. return room
  4973. 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.paths.include?(XMLData.room_exits_string.strip) and (r.unique_loot.nil? or (r.unique_loot.to_a - GameObj.loot.to_a.collect { |obj| obj.name }).empty?) and peer_tag_good.call(r) }
  4974. room.location = current_location
  4975. return room
  4976. else
  4977. title = [ XMLData.room_title ]
  4978. description = [ XMLData.room_description.strip ]
  4979. paths = [ XMLData.room_exits_string.strip ]
  4980. room = Map.new(Map.get_free_id, title, description, paths, current_location)
  4981. 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.paths.include?(XMLData.room_exits_string.strip) and (r.unique_loot.nil? or (r.unique_loot.to_a - GameObj.loot.to_a.collect { |obj| obj.name }).empty?) }
  4982. if identical_rooms.length > 0
  4983. room.check_location = true
  4984. identical_rooms.each { |r| r.check_location = true }
  4985. end
  4986. return room
  4987. end
  4988. end
  4989. end
  4990. def Map.tags
  4991. @@tags.dup
  4992. end
  4993. def Map.reload
  4994. @@list.clear
  4995. Map.load
  4996. GC.start
  4997. end
  4998. def Map.load(filename=nil)
  4999. if $SAFE == 0
  5000. if filename.nil?
  5001. 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
  5002. else
  5003. file_list = [ filename ]
  5004. end
  5005. if file_list.empty?
  5006. respond "--- error: no map database found"
  5007. return false
  5008. end
  5009. while filename = file_list.shift
  5010. if filename =~ /\.xml$/
  5011. if Map.load_xml(filename)
  5012. return true
  5013. end
  5014. else
  5015. if Map.load_dat(filename)
  5016. return true
  5017. end
  5018. end
  5019. end
  5020. return false
  5021. else
  5022. UNTRUSTED_MAP_LOAD.call
  5023. end
  5024. end
  5025. def Map.load_dat(filename=nil)
  5026. if $SAFE == 0
  5027. if filename.nil?
  5028. 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
  5029. else
  5030. file_list = [ filename ]
  5031. end
  5032. if file_list.empty?
  5033. respond "--- error: no map database found"
  5034. return false
  5035. end
  5036. error = false
  5037. while filename = file_list.shift
  5038. begin
  5039. File.open(filename, 'rb') { |file| @@list = Marshal.load(file.read) }
  5040. if @@list[0].instance_variables.include?('@pause') or @@list[0].instance_variables.include?(:@pause)
  5041. respond "--- converting #{filename}"
  5042. temp_list = @@list
  5043. @@list = Array.new
  5044. temp_list.each { |room|
  5045. next if room.nil?
  5046. if room.instance_variable_get('@map_x') and room.instance_variable_get('@map_y') and room.instance_variable_get('@map_roomsize')
  5047. image_coords = [ (room.instance_variable_get('@map_x').to_i - (room.instance_variable_get('@map_roomsize')/2.0).round), (room.instance_variable_get('@map_y').to_i - (room.instance_variable_get('@map_roomsize')/2.0).round), (room.instance_variable_get('@map_x').to_i + (room.instance_variable_get('@map_roomsize')/2.0).round), (room.instance_variable_get('@map_y').to_i + (room.instance_variable_get('@map_roomsize')/2.0).round) ]
  5048. else
  5049. image_coords = nil
  5050. end
  5051. Map.new(room.id, room.title, room.instance_variable_get('@desc'), room.paths, nil, nil, nil, room.wayto, room.timeto, room.instance_variable_get('@map_name'), image_coords, Array.new, nil)
  5052. }
  5053. temp_list = nil
  5054. Map.save
  5055. end
  5056. GC.start
  5057. @@tags.clear
  5058. @@list.each { |room| @@tags = @@tags | room.tags unless room.tags.nil? }
  5059. respond "--- loaded #{filename}" if error
  5060. return true
  5061. rescue
  5062. error = true
  5063. if file_list.empty?
  5064. respond "--- error: failed to load #{filename}: #{$!}"
  5065. else
  5066. respond "--- warning: failed to load #{filename}: #{$!}"
  5067. end
  5068. end
  5069. end
  5070. return false
  5071. else
  5072. UNTRUSTED_MAP_LOAD_DAT.call
  5073. end
  5074. end
  5075. def Map.load_xml(filename="#{$data_dir}#{XMLData.game}/map.xml")
  5076. if $SAFE == 0
  5077. unless File.exists?(filename)
  5078. raise Exception.exception("MapDatabaseError"), "Fatal error: file `#{filename}' does not exist!"
  5079. end
  5080. missing_end = false
  5081. current_tag = nil
  5082. current_attributes = nil
  5083. room = nil
  5084. buffer = String.new
  5085. unescape = { 'lt' => '<', 'gt' => '>', 'quot' => '"', 'apos' => "'", 'amp' => '&' }
  5086. tag_start = proc { |element,attributes|
  5087. current_tag = element
  5088. current_attributes = attributes
  5089. if element == 'room'
  5090. room = Hash.new
  5091. room['id'] = attributes['id'].to_i
  5092. room['location'] = attributes['location']
  5093. room['climate'] = attributes['climate']
  5094. room['terrain'] = attributes['terrain']
  5095. room['wayto'] = Hash.new
  5096. room['timeto'] = Hash.new
  5097. room['title'] = Array.new
  5098. room['description'] = Array.new
  5099. room['paths'] = Array.new
  5100. room['tags'] = Array.new
  5101. room['unique_loot'] = Array.new
  5102. elsif element =~ /^(?:image|tsoran)$/ and attributes['name'] and attributes['x'] and attributes['y'] and attributes['size']
  5103. room['image'] = attributes['name']
  5104. 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) ]
  5105. elsif (element == 'image') and attributes['name'] and attributes['coords'] and (attributes['coords'] =~ /[0-9]+,[0-9]+,[0-9]+,[0-9]+/)
  5106. room['image'] = attributes['name']
  5107. room['image_coords'] = attributes['coords'].split(',').collect { |num| num.to_i }
  5108. elsif element == 'map'
  5109. missing_end = true
  5110. end
  5111. }
  5112. text = proc { |text_string|
  5113. if current_tag == 'tag'
  5114. room['tags'].push(text_string)
  5115. elsif current_tag =~ /^(?:title|description|paths|tag|unique_loot)$/
  5116. room[current_tag].push(text_string)
  5117. elsif current_tag == 'exit' and current_attributes['target']
  5118. if current_attributes['type'].downcase == 'string'
  5119. room['wayto'][current_attributes['target']] = text_string
  5120. elsif
  5121. room['wayto'][current_attributes['target']] = StringProc.new(text_string)
  5122. end
  5123. if current_attributes['cost'] =~ /^[0-9\.]+$/
  5124. room['timeto'][current_attributes['target']] = current_attributes['cost'].to_f
  5125. elsif current_attributes['cost'].length > 0
  5126. room['timeto'][current_attributes['target']] = StringProc.new(current_attributes['cost'])
  5127. else
  5128. room['timeto'][current_attributes['target']] = 0.2
  5129. end
  5130. end
  5131. }
  5132. tag_end = proc { |element|
  5133. if element == 'room'
  5134. room['unique_loot'] = nil if room['unique_loot'].empty?
  5135. 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'])
  5136. elsif element == 'map'
  5137. missing_end = false
  5138. end
  5139. current_tag = nil
  5140. }
  5141. begin
  5142. File.open(filename) { |file|
  5143. while line = file.gets
  5144. buffer.concat(line)
  5145. # fixme: remove (?=<) ?
  5146. while str = buffer.slice!(/^<([^>]+)><\/\1>|^[^<]+(?=<)|^<[^<]+>/)
  5147. if str[0,1] == '<'
  5148. if str[1,1] == '/'
  5149. element = /^<\/([^\s>\/]+)/.match(str).captures.first
  5150. tag_end.call(element)
  5151. else
  5152. if str =~ /^<([^>]+)><\/\1>/
  5153. element = $1
  5154. tag_start.call(element)
  5155. text.call('')
  5156. tag_end.call(element)
  5157. else
  5158. element = /^<([^\s>\/]+)/.match(str).captures.first
  5159. attributes = Hash.new
  5160. str.scan(/([A-z][A-z0-9_\-]*)=(["'])(.*?)\2/).each { |attr| attributes[attr[0]] = attr[2].gsub(/&(#{unescape.keys.join('|')});/) { unescape[$1] } }
  5161. tag_start.call(element, attributes)
  5162. tag_end.call(element) if str[-2,1] == '/'
  5163. end
  5164. end
  5165. else
  5166. text.call(str.gsub(/&(#{unescape.keys.join('|')});/) { unescape[$1] })
  5167. end
  5168. end
  5169. end
  5170. }
  5171. if missing_end
  5172. respond "--- error: failed to load #{filename}: unexpected end of file"
  5173. return false
  5174. end
  5175. @@tags.clear
  5176. @@list.each { |room| (@@tags = @@tags | room.tags) unless room.tags.nil? }
  5177. true
  5178. rescue
  5179. respond "--- error: failed to load #{filename}: #{$!}"
  5180. return false
  5181. end
  5182. else
  5183. UNTRUSTED_MAP_LOAD_XML.call
  5184. end
  5185. end
  5186. def Map.save(filename="#{$data_dir}#{XMLData.game}/map-#{Time.now.to_i}.dat")
  5187. if $SAFE == 0
  5188. if File.exists?(filename)
  5189. respond "--- Backing up map database"
  5190. begin
  5191. File.open(filename, 'rb') { |infile|
  5192. File.open("#{filename}.bak", 'wb') { |outfile|
  5193. outfile.write(infile.read)
  5194. }
  5195. }
  5196. rescue
  5197. respond "--- error: #{$!}"
  5198. end
  5199. end
  5200. begin
  5201. File.open(filename, 'wb') { |file|
  5202. file.write(Marshal.dump(@@list))
  5203. }
  5204. @@tags.clear
  5205. @@list.each { |room| @@tags = @@tags | room.tags unless room.tags.nil? }
  5206. respond "--- Map database saved"
  5207. rescue
  5208. respond "--- error: #{$!}"
  5209. end
  5210. GC.start
  5211. else
  5212. UNTRUSTED_MAP_SAVE.call
  5213. end
  5214. end
  5215. def Map.save_xml(filename="#{$data_dir}#{XMLData.game}/map-#{Time.now.to_i}.xml")
  5216. if $SAFE == 0
  5217. if File.exists?(filename)
  5218. respond "File exists! Backing it up before proceeding..."
  5219. begin
  5220. file = nil
  5221. bakfile = nil
  5222. file = File.open(filename, 'rb')
  5223. bakfile = File.open(filename + ".bak", "wb")
  5224. bakfile.write(file.read)
  5225. rescue
  5226. respond $!
  5227. ensure
  5228. file ? file.close : nil
  5229. bakfile ? bakfile.close : nil
  5230. end
  5231. end
  5232. begin
  5233. escape = { '<' => '&lt;', '>' => '&gt;', '"' => '&quot;', "'" => "&apos;", '&' => '&amp;' }
  5234. File.open(filename, 'w') { |file|
  5235. file.write "<map>\n"
  5236. @@list.each { |room|
  5237. next if room == nil
  5238. if room.location
  5239. location = " location=#{(room.location.gsub(/(<|>|"|'|&)/) { escape[$1] }).inspect}"
  5240. else
  5241. location = ''
  5242. end
  5243. if room.climate
  5244. climate = " climate=#{(room.climate.gsub(/(<|>|"|'|&)/) { escape[$1] }).inspect}"
  5245. else
  5246. climate = ''
  5247. end
  5248. if room.terrain
  5249. terrain = " terrain=#{(room.terrain.gsub(/(<|>|"|'|&)/) { escape[$1] }).inspect}"
  5250. else
  5251. terrain = ''
  5252. end
  5253. file.write " <room id=\"#{room.id}\"#{location}#{climate}#{terrain}>\n"
  5254. room.title.each { |title| file.write " <title>#{title.gsub(/(<|>|"|'|&)/) { escape[$1] }}</title>\n" }
  5255. room.description.each { |desc| file.write " <description>#{desc.gsub(/(<|>|"|'|&)/) { escape[$1] }}</description>\n" }
  5256. room.paths.each { |paths| file.write " <paths>#{paths.gsub(/(<|>|"|'|&)/) { escape[$1] }}</paths>\n" }
  5257. room.tags.each { |tag| file.write " <tag>#{tag.gsub(/(<|>|"|'|&)/) { escape[$1] }}</tag>\n" }
  5258. room.unique_loot.to_a.each { |loot| file.write " <unique_loot>#{loot.gsub(/(<|>|"|'|&)/) { escape[$1] }}</unique_loot>\n" }
  5259. file.write " <image name=\"#{room.image.gsub(/(<|>|"|'|&)/) { escape[$1] }}\" coords=\"#{room.image_coords.join(',')}\" />\n" if room.image and room.image_coords
  5260. room.wayto.keys.each { |target|
  5261. if room.timeto[target].class == Proc
  5262. cost = " cost=\"#{room.timeto[target]._dump.gsub(/(<|>|"|'|&)/) { escape[$1] }}\""
  5263. elsif room.timeto[target]
  5264. cost = " cost=\"#{room.timeto[target]}\""
  5265. else
  5266. cost = ''
  5267. end
  5268. if room.wayto[target].class == Proc
  5269. file.write " <exit target=\"#{target}\" type=\"Proc\"#{cost}>#{room.wayto[target]._dump.gsub(/(<|>|"|'|&)/) { escape[$1] }}</exit>\n"
  5270. else
  5271. file.write " <exit target=\"#{target}\" type=\"#{room.wayto[target].class}\"#{cost}>#{room.wayto[target].gsub(/(<|>|"|'|&)/) { escape[$1] }}</exit>\n"
  5272. end
  5273. }
  5274. file.write " </room>\n"
  5275. }
  5276. file.write "</map>\n"
  5277. }
  5278. @@tags.clear
  5279. @@list.each { |room| @@tags = @@tags | room.tags unless room.tags.nil? }
  5280. respond "--- map database saved to: #{filename}"
  5281. rescue
  5282. respond $!
  5283. end
  5284. GC.start
  5285. else
  5286. UNTRUSTED_MAP_SAVE_XML.call
  5287. end
  5288. end
  5289. def Map.estimate_time(array)
  5290. Map.load if @@list.empty?
  5291. unless array.class == Array
  5292. raise Exception.exception("MapError"), "Map.estimate_time was given something not an array!"
  5293. end
  5294. time = 0.to_f
  5295. until array.length < 2
  5296. room = array.shift
  5297. if t = Map[room].timeto[array.first.to_s]
  5298. if t.class == Proc
  5299. time += t.call.to_f
  5300. else
  5301. time += t.to_f
  5302. end
  5303. else
  5304. time += "0.2".to_f
  5305. end
  5306. end
  5307. time
  5308. end
  5309. def Map.dijkstra(source, destination=nil)
  5310. if source.class == Map
  5311. source.dijkstra(destination)
  5312. elsif room = Map[source]
  5313. room.dijkstra(destination)
  5314. else
  5315. echo "Map.dijkstra: error: invalid source room"
  5316. nil
  5317. end
  5318. end
  5319. def dijkstra(destination=nil)
  5320. begin
  5321. Map.load if @@list.empty?
  5322. source = @id
  5323. visited = Array.new
  5324. shortest_distances = Array.new
  5325. previous = Array.new
  5326. pq = [ source ]
  5327. pq_push = proc { |val|
  5328. for i in 0...pq.size
  5329. if shortest_distances[val] <= shortest_distances[pq[i]]
  5330. pq.insert(i, val)
  5331. break
  5332. end
  5333. end
  5334. pq.push(val) if i.nil? or (i == pq.size-1)
  5335. }
  5336. visited[source] = true
  5337. shortest_distances[source] = 0
  5338. if destination.nil?
  5339. until pq.size == 0
  5340. v = pq.shift
  5341. visited[v] = true
  5342. @@list[v].wayto.keys.each { |adj_room|
  5343. adj_room_i = adj_room.to_i
  5344. unless visited[adj_room_i]
  5345. if @@list[v].timeto[adj_room].class == Proc
  5346. nd = @@list[v].timeto[adj_room].call
  5347. else
  5348. nd = @@list[v].timeto[adj_room]
  5349. end
  5350. if nd
  5351. nd += shortest_distances[v]
  5352. if shortest_distances[adj_room_i].nil? or (shortest_distances[adj_room_i] > nd)
  5353. shortest_distances[adj_room_i] = nd
  5354. previous[adj_room_i] = v
  5355. pq_push.call(adj_room_i)
  5356. end
  5357. end
  5358. end
  5359. }
  5360. end
  5361. elsif destination.class == Fixnum
  5362. until pq.size == 0
  5363. v = pq.shift
  5364. break if v == destination
  5365. visited[v] = true
  5366. @@list[v].wayto.keys.each { |adj_room|
  5367. adj_room_i = adj_room.to_i
  5368. unless visited[adj_room_i]
  5369. if @@list[v].timeto[adj_room].class == Proc
  5370. nd = @@list[v].timeto[adj_room].call
  5371. else
  5372. nd = @@list[v].timeto[adj_room]
  5373. end
  5374. if nd
  5375. nd += shortest_distances[v]
  5376. if shortest_distances[adj_room_i].nil? or (shortest_distances[adj_room_i] > nd)
  5377. shortest_distances[adj_room_i] = nd
  5378. previous[adj_room_i] = v
  5379. pq_push.call(adj_room_i)
  5380. end
  5381. end
  5382. end
  5383. }
  5384. end
  5385. elsif destination.class == Array
  5386. dest_list = destination.collect { |dest| dest.to_i }
  5387. until pq.size == 0
  5388. v = pq.shift
  5389. break if dest_list.include?(v) and (shortest_distances[v] < 20)
  5390. visited[v] = true
  5391. @@list[v].wayto.keys.each { |adj_room|
  5392. adj_room_i = adj_room.to_i
  5393. unless visited[adj_room_i]
  5394. if @@list[v].timeto[adj_room].class == Proc
  5395. nd = @@list[v].timeto[adj_room].call
  5396. else
  5397. nd = @@list[v].timeto[adj_room]
  5398. end
  5399. if nd
  5400. nd += shortest_distances[v]
  5401. if shortest_distances[adj_room_i].nil? or (shortest_distances[adj_room_i] > nd)
  5402. shortest_distances[adj_room_i] = nd
  5403. previous[adj_room_i] = v
  5404. pq_push.call(adj_room_i)
  5405. end
  5406. end
  5407. end
  5408. }
  5409. end
  5410. end
  5411. return previous, shortest_distances
  5412. rescue
  5413. echo "Map.dijkstra: error: #{$!}"
  5414. respond $!.backtrace
  5415. nil
  5416. end
  5417. end
  5418. def Map.findpath(source, destination)
  5419. if source.class == Map
  5420. source.path_to(destination)
  5421. elsif room = Map[source]
  5422. room.path_to(destination)
  5423. else
  5424. echo "Map.findpath: error: invalid source room"
  5425. nil
  5426. end
  5427. end
  5428. def path_to(destination)
  5429. Map.load if @@list.empty?
  5430. destination = destination.to_i
  5431. previous, shortest_distances = dijkstra(destination)
  5432. return nil unless previous[destination]
  5433. path = [ destination ]
  5434. path.push(previous[path[-1]]) until previous[path[-1]] == @id
  5435. path.reverse!
  5436. path.pop
  5437. return path
  5438. end
  5439. def find_nearest_by_tag(tag_name)
  5440. target_list = Array.new
  5441. @@list.each { |room| target_list.push(room.id) if room.tags.include?(tag_name) }
  5442. previous, shortest_distances = Map.dijkstra(@id, target_list)
  5443. if target_list.include?(@id)
  5444. @id
  5445. else
  5446. target_list.delete_if { |room_num| shortest_distances[room_num].nil? }
  5447. target_list.sort { |a,b| shortest_distances[a] <=> shortest_distances[b] }.first
  5448. end
  5449. end
  5450. def find_all_nearest_by_tag(tag_name)
  5451. target_list = Array.new
  5452. @@list.each { |room| target_list.push(room.id) if room.tags.include?(tag_name) }
  5453. previous, shortest_distances = Map.dijkstra(@id)
  5454. target_list.delete_if { |room_num| shortest_distances[room_num].nil? }
  5455. target_list.sort { |a,b| shortest_distances[a] <=> shortest_distances[b] }
  5456. end
  5457. def find_nearest(target_list)
  5458. target_list = target_list.collect { |num| num.to_i }
  5459. if target_list.include?(@id)
  5460. @id
  5461. else
  5462. previous, shortest_distances = Map.dijkstra(@id, target_list)
  5463. target_list.delete_if { |room_num| shortest_distances[room_num].nil? }
  5464. target_list.sort { |a,b| shortest_distances[a] <=> shortest_distances[b] }.first
  5465. end
  5466. end
  5467. # depreciated
  5468. def desc
  5469. @description
  5470. end
  5471. def map_name
  5472. @image
  5473. end
  5474. def map_x
  5475. if @image_coords.nil?
  5476. nil
  5477. else
  5478. ((image_coords[0] + image_coords[2])/2.0).round
  5479. end
  5480. end
  5481. def map_y
  5482. if @image_coords.nil?
  5483. nil
  5484. else
  5485. ((image_coords[1] + image_coords[3])/2.0).round
  5486. end
  5487. end
  5488. def map_roomsize
  5489. if @image_coords.nil?
  5490. nil
  5491. else
  5492. image_coords[2] - image_coords[0]
  5493. end
  5494. end
  5495. def geo
  5496. nil
  5497. end
  5498. def guaranteed_shortest_pathfind(target)
  5499. unless @@warned_depreciated_guaranteed_shortest_pathfind
  5500. unless script_name = Script.self.name
  5501. script_name = 'unknown script'
  5502. end
  5503. respond "--- warning: #{script_name} is using depreciated method guaranteed_shortest_pathfind"
  5504. @@warned_depreciated_guaranteed_shortest_pathfind = true
  5505. end
  5506. path_to(target)
  5507. end
  5508. def estimation_pathfind(target)
  5509. unless @@warned_depreciated_estimation_pathfind
  5510. unless script_name = Script.self.name
  5511. script_name = 'unknown script'
  5512. end
  5513. respond "--- warning: #{script_name} is using depreciated method estimation_pathfind"
  5514. @@warned_depreciated_estimation_pathfind = true
  5515. end
  5516. path_to(target)
  5517. end
  5518. end
  5519. class Room < Map
  5520. # private_class_method :new
  5521. def Room.method_missing(*args)
  5522. super(*args)
  5523. end
  5524. end
  5525. # backward compatability
  5526. class Pathfind
  5527. def Pathfind.reassoc_nodes
  5528. nil
  5529. end
  5530. def Pathfind.trace_field_positions
  5531. nil
  5532. end
  5533. def Pathfind.find_node(target_id)
  5534. Room[target_id.to_i]
  5535. end
  5536. end
  5537. # proc objects can't be dumped, since an intrinsic part of what they are is the 'binding' environment... this is just a quick fix so that a proc object can be saved; it's identical to a proc except that it also carries around the string that created the proc, so when it's loaded from a Marshal dump the proc object is recreated from the original string. Basically it's a way for each room to carry around a mini-script they can save and load with the rest of the map database info
  5538. class StringProc
  5539. def initialize(string)
  5540. @string = string
  5541. if $SAFE == 0
  5542. @string.untaint
  5543. else
  5544. UNTRUSTED_UNTAINT.call(@string)
  5545. end
  5546. end
  5547. def kind_of?(type)
  5548. Proc.new {}.kind_of? type
  5549. end
  5550. def class
  5551. Proc
  5552. end
  5553. def call(*args)
  5554. if $SAFE < 3
  5555. proc { $SAFE = 3; eval(@string) }.call
  5556. else
  5557. eval(@string)
  5558. end
  5559. end
  5560. def _dump(depth = nil)
  5561. @string
  5562. end
  5563. def StringProc._load(string)
  5564. StringProc.new(string)
  5565. end
  5566. def inspect
  5567. "StringProc.new(#{@string.inspect})"
  5568. end
  5569. end
  5570. class Critter
  5571. unless defined?(LIST)
  5572. LIST = []
  5573. end
  5574. attr_reader :id, :name, :level, :race, :type, :undead, :geo
  5575. attr_accessor :as, :ds, :cs, :td, :attacks, :mb
  5576. def initialize(id,name,level,race,type,undead=false,geo=nil)
  5577. @id,@name,@level,@race,@type,@undead,@geo = id,name,level.to_i,race,type,undead,geo
  5578. LIST.push(self) unless LIST.find { |critter| critter.name == @name }
  5579. end
  5580. end
  5581. module Enumerable
  5582. def qfind(obj)
  5583. find { |el| el.match obj }
  5584. end
  5585. end
  5586. def hide_me
  5587. Script.self.hidden = !Script.self.hidden
  5588. end
  5589. def no_kill_all
  5590. script = Script.self
  5591. script.no_kill_all = !script.no_kill_all
  5592. end
  5593. def no_pause_all
  5594. script = Script.self
  5595. script.no_pause_all = !script.no_pause_all
  5596. end
  5597. def toggle_upstream
  5598. unless script = Script.self then echo 'toggle_upstream: cannot identify calling script.'; return nil; end
  5599. script.want_upstream = !script.want_upstream
  5600. end
  5601. def silence_me
  5602. unless script = Script.self then echo 'silence_me: cannot identify calling script.'; return nil; end
  5603. if script.safe? then echo("WARNING: 'safe' script attempted to silence itself. Ignoring the request.")
  5604. sleep 1
  5605. return true
  5606. end
  5607. script.silent = !script.silent
  5608. end
  5609. def toggle_echo
  5610. unless script = Script.self then respond('--- toggle_echo: Unable to identify calling script.'); return nil; end
  5611. script.no_echo = !script.no_echo
  5612. end
  5613. def echo_on
  5614. unless script = Script.self then respond('--- echo_on: Unable to identify calling script.'); return nil; end
  5615. script.no_echo = false
  5616. end
  5617. def echo_off
  5618. unless script = Script.self then respond('--- echo_off: Unable to identify calling script.'); return nil; end
  5619. script.no_echo = true
  5620. end
  5621. def upstream_get
  5622. unless script = Script.self then echo 'upstream_get: cannot identify calling script.'; return nil; end
  5623. unless script.want_upstream
  5624. 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)")
  5625. sleep "0.3".to_f
  5626. return false
  5627. end
  5628. script.upstream_gets
  5629. end
  5630. def upstream_get?
  5631. unless script = Script.self then echo 'upstream_get: cannot identify calling script.'; return nil; end
  5632. unless script.want_upstream
  5633. 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)")
  5634. return false
  5635. end
  5636. script.upstream_gets?
  5637. end
  5638. def echo(*messages)
  5639. if script = Script.self
  5640. unless script.no_echo
  5641. if messages.class == Array
  5642. messages = messages.flatten.compact
  5643. else
  5644. messages = [ messages.to_s ]
  5645. end
  5646. respond if messages.empty?
  5647. messages.each { |message| respond("[#{script.name}: #{message.to_s.chomp}]") }
  5648. end
  5649. else
  5650. messages = messages.flatten.compact
  5651. respond if messages.empty?
  5652. messages.each { |message| respond("[(unknown script): #{message.to_s.chomp}]") }
  5653. end
  5654. nil
  5655. end
  5656. def goto(label)
  5657. Script.self.jump_label = label.to_s
  5658. raise JUMP
  5659. end
  5660. def start_script(script_name, cli_vars=[], flags=Hash.new)
  5661. # fixme: look in wizard script directory
  5662. if $SAFE == 0
  5663. file_name = nil
  5664. if File.exists?("#{$script_dir}#{script_name}.lic")
  5665. file_name = "#{script_name}.lic"
  5666. elsif File.exists?("#{$script_dir}#{script_name}.cmd")
  5667. file_name = "#{script_name}.cmd"
  5668. elsif File.exists?("#{$script_dir}#{script_name}.wiz")
  5669. file_name = "#{script_name}.wiz"
  5670. else
  5671. file_list = Dir.entries($script_dir).delete_if { |fn| (fn == '.') or (fn == '..') }.sort
  5672. file_name = (file_list.find { |val| val =~ /^#{script_name}\.(?:lic|rbw?|cmd|wiz)(?:\.gz|\.Z)?$/i } || file_list.find { |val| val =~ /^#{script_name}[^.]+\.(?i:lic|rbw?|cmd|wiz)(?:\.gz|\.Z)?$/ } || file_list.find { |val| val =~ /^#{script_name}[^.]+\.(?:lic|rbw?|cmd|wiz)(?:\.gz|\.Z)?$/i } || file_list.find { |val| val =~ /^#{script_name}$/i })
  5673. file_list = nil
  5674. end
  5675. unless file_name
  5676. respond("--- Lich: could not find script `#{script_name}' in directory #{$script_dir}!")
  5677. return false
  5678. end
  5679. if (flags[:force] != true) and (Script.running + Script.hidden).find { |scr| scr.name == file_name.sub(/\..{1,3}$/, '') }
  5680. respond("--- Lich: #{script_name} is already running (use #{$clean_lich_char}force [scriptname] if desired).")
  5681. return false
  5682. end
  5683. begin
  5684. if file_name =~ /cmd$|wiz$/i
  5685. trusted = false
  5686. new_script = WizardScript.new("#{$script_dir}#{file_name}", cli_vars)
  5687. else
  5688. trusted = LichSettings['trusted_scripts'].include?(file_name.sub(/\.lic$/,''))
  5689. new_script = Script.new("#{$script_dir}#{file_name}", cli_vars, flags[:command_line])
  5690. end
  5691. if not trusted
  5692. script_binding = UntrustedScriptBinder.new.create_block.binding
  5693. elsif new_script.labels.length > 1
  5694. script_binding = ScriptBinder.new.create_block.binding
  5695. else
  5696. script_binding = nil
  5697. end
  5698. rescue
  5699. respond "--- Lich: error starting script (#{script_name}): #{$!}"
  5700. return false
  5701. end
  5702. unless new_script
  5703. respond "--- Lich: failed to start script (#{script_name})"
  5704. return false
  5705. end
  5706. new_script.quiet = true if flags[:quiet]
  5707. new_thread = Thread.new {
  5708. 100.times { break if Script.self == new_script; sleep "0.01".to_f }
  5709. if script = Script.self
  5710. eval('script = Script.self', script_binding, script.name) if script_binding
  5711. Thread.current.priority = 1
  5712. respond("--- Lich: #{script.name} active.") unless script.quiet
  5713. if trusted
  5714. begin
  5715. Settings.load
  5716. GameSettings.load
  5717. CharSettings.load
  5718. while (script = Script.self) and script.current_label
  5719. eval(script.labels[script.current_label].to_s, script_binding, script.name)
  5720. Script.self.get_next_label
  5721. end
  5722. Script.self.kill
  5723. rescue SystemExit
  5724. Script.self.kill
  5725. rescue SyntaxError
  5726. respond "--- SyntaxError: #{$!}"
  5727. respond $!.backtrace.first
  5728. $stderr.puts "--- SyntaxError: #{$!}"
  5729. $stderr.puts $!.backtrace
  5730. respond "--- Lich: cannot execute #{Script.self.name}, aborting."
  5731. Script.self.kill
  5732. rescue ScriptError
  5733. respond "--- ScriptError: #{$!}"
  5734. respond $!.backtrace.first
  5735. $stderr.puts "--- ScriptError: #{$!}"
  5736. $stderr.puts $!.backtrace
  5737. Script.self.kill
  5738. rescue NoMemoryError
  5739. respond "--- NoMemoryError: #{$!}"
  5740. respond $!.backtrace.first
  5741. $stderr.puts "--- NoMemoryError: #{$!}"
  5742. $stderr.puts $!.backtrace
  5743. Script.self.kill
  5744. rescue LoadError
  5745. respond "--- LoadError: #{$!}"
  5746. respond $!.backtrace.first
  5747. $stderr.puts "--- LoadError: #{$!}"
  5748. $stderr.puts $!.backtrace
  5749. Script.self.kill
  5750. rescue SecurityError
  5751. respond "--- Review this script (#{Script.self.name}) to make sure it isn't malicious, and type ;trust #{Script.self.name}"
  5752. respond "--- SecurityError: #{$!}"
  5753. respond $!.backtrace[0..1]
  5754. $stderr.puts "--- SecurityError: #{$!}"
  5755. $stderr.puts $!.backtrace
  5756. Script.self.kill
  5757. rescue ThreadError
  5758. respond "--- ThreadError: #{$!}"
  5759. respond $!.backtrace.first
  5760. $stderr.puts "--- ThreadError: #{$!}"
  5761. $stderr.puts $!.backtrace
  5762. Script.self.kill
  5763. rescue SystemStackError
  5764. respond "--- SystemStackError: #{$!}"
  5765. respond $!.backtrace.first
  5766. $stderr.puts "--- SystemStackError: #{$!}"
  5767. $stderr.puts $!.backtrace
  5768. Script.self.kill
  5769. rescue Exception
  5770. if $! == JUMP
  5771. retry if Script.self.get_next_label != JUMP_ERROR
  5772. respond "--- Label Error: `#{Script.self.jump_label}' was not found, and no `LabelError' label was found!"
  5773. respond $!.backtrace.first
  5774. $stderr.puts "--- Label Error: `#{Script.self.jump_label}' was not found, and no `LabelError' label was found!"
  5775. $stderr.puts $!.backtrace
  5776. Script.self.kill
  5777. else
  5778. respond "--- Exception: #{$!}"
  5779. respond $!.backtrace.first
  5780. $stderr.puts "--- Exception: #{$!}"
  5781. $stderr.puts $!.backtrace
  5782. Script.self.kill
  5783. end
  5784. rescue
  5785. respond "--- Error: #{Script.self.name}: #{$!}"
  5786. respond $!.backtrace.first
  5787. $stderr.puts "--- Error: #{Script.self.name}: #{$!}"
  5788. $stderr.puts $!.backtrace
  5789. Script.self.kill
  5790. end
  5791. else
  5792. begin
  5793. Settings.load
  5794. GameSettings.load
  5795. CharSettings.load
  5796. while (script = Script.self) and script.current_label
  5797. proc { script.labels[script.current_label].untaint; $SAFE = 3; eval(script.labels[script.current_label], script_binding, script.name, 1) }.call
  5798. Script.self.get_next_label
  5799. end
  5800. Script.self.kill
  5801. rescue SystemExit
  5802. Script.self.kill
  5803. rescue SyntaxError
  5804. respond "--- SyntaxError: #{$!}"
  5805. respond $!.backtrace.first
  5806. $stderr.puts "--- SyntaxError: #{$!}"
  5807. $stderr.puts $!.backtrace
  5808. respond "--- Lich: cannot execute #{Script.self.name}, aborting."
  5809. Script.self.kill
  5810. rescue ScriptError
  5811. respond "--- ScriptError: #{$!}"
  5812. respond $!.backtrace.first
  5813. $stderr.puts "--- ScriptError: #{$!}"
  5814. $stderr.puts $!.backtrace
  5815. Script.self.kill
  5816. rescue NoMemoryError
  5817. respond "--- NoMemoryError: #{$!}"
  5818. respond $!.backtrace.first
  5819. $stderr.puts "--- NoMemoryError: #{$!}"
  5820. $stderr.puts $!.backtrace
  5821. Script.self.kill
  5822. rescue LoadError
  5823. respond "--- LoadError: #{$!}"
  5824. respond $!.backtrace.first
  5825. $stderr.puts "--- LoadError: #{$!}"
  5826. $stderr.puts $!.backtrace
  5827. Script.self.kill
  5828. rescue SecurityError
  5829. respond "--- Review this script (#{Script.self.name}) to make sure it isn't malicious, and type ;trust #{Script.self.name}"
  5830. respond "--- SecurityError: #{$!}"
  5831. respond $!.backtrace[0..1]
  5832. $stderr.puts "--- SecurityError: #{$!}"
  5833. $stderr.puts $!.backtrace
  5834. Script.self.kill
  5835. rescue ThreadError
  5836. respond "--- ThreadError: #{$!}"
  5837. respond $!.backtrace.first
  5838. $stderr.puts "--- ThreadError: #{$!}"
  5839. $stderr.puts $!.backtrace
  5840. Script.self.kill
  5841. rescue SystemStackError
  5842. respond "--- SystemStackError: #{$!}"
  5843. respond $!.backtrace.first
  5844. $stderr.puts "--- SystemStackError: #{$!}"
  5845. $stderr.puts $!.backtrace
  5846. Script.self.kill
  5847. rescue Exception
  5848. if $! == JUMP
  5849. retry if Script.self.get_next_label != JUMP_ERROR
  5850. respond "--- Label Error: `#{Script.self.jump_label}' was not found, and no `LabelError' label was found!"
  5851. respond $!.backtrace.first
  5852. $stderr.puts "--- Label Error: `#{Script.self.jump_label}' was not found, and no `LabelError' label was found!"
  5853. $stderr.puts $!.backtrace
  5854. Script.self.kill
  5855. else
  5856. respond "--- Exception: #{$!}"
  5857. respond $!.backtrace.first
  5858. $stderr.puts "--- Exception: #{$!}"
  5859. $stderr.puts $!.backtrace
  5860. Script.self.kill
  5861. end
  5862. rescue
  5863. respond "--- Error: #{Script.self.name}: #{$!}"
  5864. respond $!.backtrace.first
  5865. $stderr.puts "--- Error: #{Script.self.name}: #{$!}"
  5866. $stderr.puts $!.backtrace
  5867. Script.self.kill
  5868. end
  5869. end
  5870. else
  5871. respond 'start_script screwed up...'
  5872. end
  5873. }
  5874. new_script.thread_group.add(new_thread)
  5875. true
  5876. else
  5877. UNTRUSTED_START_SCRIPT.call(script_name, cli_vars, flags)
  5878. end
  5879. end
  5880. def start_scripts(*script_names)
  5881. script_names.flatten.each { |script_name|
  5882. start_script(script_name)
  5883. sleep "0.02".to_f
  5884. }
  5885. end
  5886. def force_start_script(script_name,cli_vars=[], flags={})
  5887. flags = Hash.new unless flags.class == Hash
  5888. flags[:force] = true
  5889. start_script(script_name,cli_vars,flags)
  5890. end
  5891. def start_exec_script(cmd_data, flags=Hash.new)
  5892. flags = { :quiet => true } if flags == true
  5893. if $SAFE == 0
  5894. trusted = flags[:trusted]
  5895. unless new_script = ExecScript.new(cmd_data, flags)
  5896. respond '--- Lich: failed to start exec script'
  5897. return false
  5898. end
  5899. new_thread = Thread.new {
  5900. 100.times { break if Script.self == new_script; sleep "0.01".to_f }
  5901. if script = Script.self
  5902. Thread.current.priority = 1
  5903. respond("--- Lich: #{script.name} active.") unless script.quiet
  5904. begin
  5905. if trusted
  5906. eval(cmd_data, nil, script.name.to_s)
  5907. else
  5908. proc { cmd_data.untaint; $SAFE = 3; eval(cmd_data, nil, script.name.to_s) }.call
  5909. end
  5910. Script.self.kill
  5911. rescue SystemExit
  5912. Script.self.kill
  5913. rescue SyntaxError
  5914. respond "--- SyntaxError: #{$!}"
  5915. respond $!.backtrace.first
  5916. $stderr.puts "--- SyntaxError: #{$!}"
  5917. $stderr.puts $!.backtrace
  5918. Script.self.kill
  5919. rescue ScriptError
  5920. respond "--- ScriptError: #{$!}"
  5921. respond $!.backtrace.first
  5922. $stderr.puts "--- ScriptError: #{$!}"
  5923. $stderr.puts $!.backtrace
  5924. Script.self.kill
  5925. rescue NoMemoryError
  5926. respond "--- NoMemoryError: #{$!}"
  5927. respond $!.backtrace.first
  5928. $stderr.puts "--- NoMemoryError: #{$!}"
  5929. $stderr.puts $!.backtrace
  5930. Script.self.kill
  5931. rescue LoadError
  5932. respond("--- LoadError: #{$!}")
  5933. respond "--- LoadError: #{$!}"
  5934. respond $!.backtrace.first
  5935. $stderr.puts "--- LoadError: #{$!}"
  5936. $stderr.puts $!.backtrace
  5937. Script.self.kill
  5938. rescue SecurityError
  5939. respond "--- SecurityError: #{$!}"
  5940. respond $!.backtrace[0..1]
  5941. $stderr.puts "--- SecurityError: #{$!}"
  5942. $stderr.puts $!.backtrace
  5943. Script.self.kill
  5944. rescue ThreadError
  5945. respond "--- ThreadError: #{$!}"
  5946. respond $!.backtrace.first
  5947. $stderr.puts "--- ThreadError: #{$!}"
  5948. $stderr.puts $!.backtrace
  5949. Script.self.kill
  5950. rescue SystemStackError
  5951. respond "--- SystemStackError: #{$!}"
  5952. respond $!.backtrace.first
  5953. $stderr.puts "--- SystemStackError: #{$!}"
  5954. $stderr.puts $!.backtrace
  5955. Script.self.kill
  5956. rescue Exception
  5957. respond "--- Exception: #{$!}"
  5958. respond $!.backtrace.first
  5959. $stderr.puts "--- Exception: #{$!}"
  5960. $stderr.puts $!.backtrace
  5961. Script.self.kill
  5962. rescue
  5963. respond "--- Error: #{$!}"
  5964. respond $!.backtrace.first
  5965. $stderr.puts "--- Error: #{$!}"
  5966. $stderr.puts $!.backtrace
  5967. Script.self.kill
  5968. end
  5969. else
  5970. respond 'start_exec_script screwed up...'
  5971. end
  5972. }
  5973. new_script.thread_group.add(new_thread)
  5974. true
  5975. else
  5976. UNTRUSTED_START_EXEC_SCRIPT.call(cmd_data, flags)
  5977. end
  5978. end
  5979. def pause_script(*names)
  5980. names.flatten!
  5981. if names.empty?
  5982. Script.self.pause
  5983. Script.self
  5984. else
  5985. names.each { |scr|
  5986. fnd = (Script.running + Script.hidden).find { |nm| nm.name =~ /^#{scr}/i }
  5987. fnd.pause unless (fnd.paused || fnd.nil?)
  5988. }
  5989. end
  5990. end
  5991. def unpause_script(*names)
  5992. names.flatten!
  5993. names.each { |scr|
  5994. fnd = (Script.running + Script.hidden).find { |nm| nm.name =~ /^#{scr}/i }
  5995. fnd.unpause if (fnd.paused and not fnd.nil?)
  5996. }
  5997. end
  5998. def fix_injury_mode
  5999. unless XMLData.injury_mode == 2
  6000. $_SERVER_.puts '_injury 2'
  6001. 150.times { sleep "0.05".to_f; break if XMLData.injury_mode == 2 }
  6002. end
  6003. end
  6004. def hide_script(*args)
  6005. args.flatten!
  6006. args.each { |name|
  6007. if script = Script.running.find { |scr| scr.name == name }
  6008. script.hidden = !script.hidden
  6009. end
  6010. }
  6011. end
  6012. def parse_list(string)
  6013. string.split_as_list
  6014. end
  6015. def waitrt
  6016. wait_until { (XMLData.roundtime_end.to_f - Time.now.to_f + XMLData.server_time_offset.to_f) > 0 }
  6017. sleep((XMLData.roundtime_end.to_f - Time.now.to_f + XMLData.server_time_offset.to_f + "0.6".to_f).abs)
  6018. end
  6019. def waitrt?
  6020. rt = XMLData.roundtime_end.to_f - Time.now.to_f + XMLData.server_time_offset.to_f + "0.6".to_f
  6021. if rt > 0
  6022. sleep rt
  6023. end
  6024. end
  6025. def waitcastrt
  6026. wait_until { (XMLData.cast_roundtime_end.to_f - Time.now.to_f + XMLData.server_time_offset.to_f) > 0 }
  6027. sleep((XMLData.cast_roundtime_end.to_f - Time.now.to_f + XMLData.server_time_offset.to_f + "0.6".to_f).abs)
  6028. end
  6029. def waitcastrt?
  6030. rt = XMLData.cast_roundtime_end.to_f - Time.now.to_f + XMLData.server_time_offset.to_f + "0.6".to_f
  6031. if rt > 0
  6032. sleep rt
  6033. end
  6034. end
  6035. def checkrt
  6036. [XMLData.roundtime_end.to_f - Time.now.to_f + XMLData.server_time_offset.to_f + "0.6".to_f, 0].max
  6037. end
  6038. def checkcastrt
  6039. [XMLData.cast_roundtime_end.to_f - Time.now.to_f + XMLData.server_time_offset.to_f + "0.6".to_f, 0].max
  6040. end
  6041. def checkpoison
  6042. XMLData.indicator['IconPOISONED'] == 'y'
  6043. end
  6044. def checkdisease
  6045. XMLData.indicator['IconDISEASED'] == 'y'
  6046. end
  6047. def checksitting
  6048. XMLData.indicator['IconSITTING'] == 'y'
  6049. end
  6050. def checkkneeling
  6051. XMLData.indicator['IconKNEELING'] == 'y'
  6052. end
  6053. def checkstunned
  6054. XMLData.indicator['IconSTUNNED'] == 'y'
  6055. end
  6056. def checkbleeding
  6057. XMLData.indicator['IconBLEEDING'] == 'y'
  6058. end
  6059. def checkgrouped
  6060. XMLData.indicator['IconJOINED'] == 'y'
  6061. end
  6062. def checkdead
  6063. XMLData.indicator['IconDEAD'] == 'y'
  6064. end
  6065. def checkreallybleeding
  6066. # fixme: What the hell does W stand for?
  6067. # checkbleeding and !$_TAGHASH_['GSP'].include?('W')
  6068. checkbleeding and !(Spell[9909].active? or Spell[9905].active?)
  6069. end
  6070. def muckled?
  6071. muckled = checkwebbed or checkdead or checkstunned
  6072. if defined?(checksleeping)
  6073. muckled = muckled or checksleeping
  6074. end
  6075. if defined?(checkbound)
  6076. muckled = muckled or checkbound
  6077. end
  6078. return muckled
  6079. end
  6080. def checkhidden
  6081. XMLData.indicator['IconHIDDEN'] == 'y'
  6082. end
  6083. def checkinvisible
  6084. XMLData.indicator['IconINVISIBLE'] == 'y'
  6085. end
  6086. def checkwebbed
  6087. XMLData.indicator['IconWEBBED'] == 'y'
  6088. end
  6089. def checkprone
  6090. XMLData.indicator['IconPRONE'] == 'y'
  6091. end
  6092. def checknotstanding
  6093. XMLData.indicator['IconSTANDING'] == 'n'
  6094. end
  6095. def checkstanding
  6096. XMLData.indicator['IconSTANDING'] == 'y'
  6097. end
  6098. def checkname(*strings)
  6099. strings.flatten!
  6100. if strings.empty?
  6101. XMLData.name
  6102. else
  6103. XMLData.name =~ /^(?:#{strings.join('|')})/i
  6104. end
  6105. end
  6106. def checkloot
  6107. GameObj.loot.collect { |item| item.noun }
  6108. end
  6109. def i_stand_alone
  6110. unless script = Script.self then echo 'i_stand_alone: cannot identify calling script.'; return nil; end
  6111. script.want_downstream = !script.want_downstream
  6112. return !script.want_downstream
  6113. end
  6114. def debug(*args)
  6115. if $LICH_DEBUG
  6116. if block_given?
  6117. yield(*args)
  6118. else
  6119. echo(*args)
  6120. end
  6121. end
  6122. end
  6123. def timetest(*contestants)
  6124. contestants.collect { |code| start = Time.now; 5000.times { code.call }; Time.now - start }
  6125. end
  6126. def dec2bin(n)
  6127. "0" + [n].pack("N").unpack("B32")[0].sub(/^0+(?=\d)/, '')
  6128. end
  6129. def bin2dec(n)
  6130. [("0"*32+n.to_s)[-32..-1]].pack("B32").unpack("N")[0]
  6131. end
  6132. def idle?(time = 60)
  6133. Time.now - $_IDLETIMESTAMP_ >= time
  6134. end
  6135. def selectput(string, success, failure, timeout = nil)
  6136. timeout = timeout.to_f if timeout and !timeout.kind_of?(Numeric)
  6137. success = [ success ] if success.kind_of? String
  6138. failure = [ failure ] if failure.kind_of? String
  6139. if !string.kind_of?(String) or !success.kind_of?(Array) or !failure.kind_of?(Array) or timeout && !timeout.kind_of?(Numeric)
  6140. raise ArgumentError, "usage is: selectput(game_command,success_array,failure_array[,timeout_in_secs])"
  6141. end
  6142. success.flatten!
  6143. failure.flatten!
  6144. regex = /#{(success + failure).join('|')}/i
  6145. successre = /#{success.join('|')}/i
  6146. failurere = /#{failure.join('|')}/i
  6147. thr = Thread.current
  6148. timethr = Thread.new {
  6149. timeout -= sleep("0.1".to_f) until timeout <= 0
  6150. thr.raise(StandardError)
  6151. } if timeout
  6152. begin
  6153. loop {
  6154. fput(string)
  6155. response = waitforre(regex)
  6156. if successre.match(response.to_s)
  6157. timethr.kill if timethr.alive?
  6158. break(response.string)
  6159. end
  6160. yield(response.string) if block_given?
  6161. }
  6162. rescue
  6163. nil
  6164. end
  6165. end
  6166. def toggle_unique
  6167. unless script = Script.self then echo 'toggle_unique: cannot identify calling script.'; return nil; end
  6168. script.want_downstream = !script.want_downstream
  6169. end
  6170. def die_with_me(*vals)
  6171. unless script = Script.self then echo 'die_with_me: cannot identify calling script.'; return nil; end
  6172. script.die_with.push vals
  6173. script.die_with.flatten!
  6174. echo("The following script(s) will now die when I do: #{script.die_with.join(', ')}") unless script.die_with.empty?
  6175. end
  6176. def upstream_waitfor(*strings)
  6177. strings.flatten!
  6178. script = Script.self
  6179. 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
  6180. regexpstr = strings.join('|')
  6181. while line = script.upstream_gets
  6182. if line =~ /#{regexpstr}/i
  6183. return line
  6184. end
  6185. end
  6186. end
  6187. def survivepoison?
  6188. # fixme
  6189. echo 'survivepoison? called, but there is no XML for poison rate'
  6190. return true
  6191. end
  6192. def survivedisease?
  6193. # fixme
  6194. echo 'survivepoison? called, but there is no XML for disease rate'
  6195. return true
  6196. end
  6197. def before_dying(&code)
  6198. unless script = Script.self then echo 'before_dying: cannot identify calling script.'; return nil; end
  6199. if code.nil?
  6200. echo "No code block was given to the `before_dying' command! (a \"code block\" is the stuff inside squiggly-brackets); cannot register a block of code to run when this script dies unless it provides the code block."
  6201. sleep 1
  6202. return nil
  6203. end
  6204. script.dying_procs.push(code)
  6205. true
  6206. end
  6207. def undo_before_dying
  6208. unless script = Script.self then echo 'undo_before_dying: cannot identify calling script.'; return nil; end
  6209. script.dying_procs.clear
  6210. nil
  6211. end
  6212. def abort!
  6213. unless script = Script.self then echo 'abort!: cannot identify calling script.'; return nil; end
  6214. script.dying_procs.clear
  6215. exit
  6216. end
  6217. def send_to_script(*values)
  6218. values.flatten!
  6219. if script = (Script.running + Script.hidden).find { |val| val.name =~ /^#{values.first}/i }
  6220. if script.want_downstream
  6221. values[1..-1].each { |val| script.downstream_buffer.push(val) }
  6222. else
  6223. values[1..-1].each { |val| script.unique_buffer.push(val) }
  6224. end
  6225. echo("Sent to #{script.name} -- '#{values[1..-1].join(' ; ')}'")
  6226. return true
  6227. else
  6228. echo("'#{values.first}' does not match any active scripts!")
  6229. return false
  6230. end
  6231. end
  6232. def unique_send_to_script(*values)
  6233. values.flatten!
  6234. if script = (Script.running + Script.hidden).find { |val| val.name =~ /^#{values.first}/i }
  6235. values[1..-1].each { |val| script.unique_buffer.push(val) }
  6236. echo("sent to #{script}: #{values[1..-1].join(' ; ')}")
  6237. return true
  6238. else
  6239. echo("'#{values.first}' does not match any active scripts!")
  6240. return false
  6241. end
  6242. end
  6243. def unique_waitfor(*strings)
  6244. unless script = Script.self then echo 'unique_waitfor: cannot identify calling script.'; return nil; end
  6245. strings.flatten!
  6246. regexp = /#{strings.join('|')}/
  6247. while true
  6248. str = script.unique_gets
  6249. if str =~ regexp
  6250. return str
  6251. end
  6252. end
  6253. end
  6254. def unique_get
  6255. unless script = Script.self then echo 'unique_get: cannot identify calling script.'; return nil; end
  6256. script.unique_gets
  6257. end
  6258. def unique_get?
  6259. unless script = Script.self then echo 'unique_get: cannot identify calling script.'; return nil; end
  6260. script.unique_gets?
  6261. end
  6262. def multimove(*dirs)
  6263. dirs.flatten.each { |dir| move(dir) }
  6264. end
  6265. def n; 'north'; end
  6266. def ne; 'northeast'; end
  6267. def e; 'east'; end
  6268. def se; 'southeast'; end
  6269. def s; 'south'; end
  6270. def sw; 'southwest'; end
  6271. def w; 'west'; end
  6272. def nw; 'northwest'; end
  6273. def u; 'up'; end
  6274. def up; 'up'; end
  6275. def down; 'down'; end
  6276. def d; 'down'; end
  6277. def o; 'out'; end
  6278. def out; 'out'; end
  6279. def move(dir='none', giveup_seconds=30, giveup_lines=30)
  6280. #[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)
  6281. #[LNet]-[Private]-Casis: "You smack into the water with a splash and sink far below the surface." (20:35:50)
  6282. # 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."
  6283. if dir == 'none'
  6284. echo 'move: no direction given'
  6285. return false
  6286. end
  6287. need_full_hands = false
  6288. tried_open = false
  6289. tried_fix_drag = false
  6290. line_count = 0
  6291. room_count = XMLData.room_count
  6292. giveup_time = Time.now.to_i + giveup_seconds.to_i
  6293. save_stream = Array.new
  6294. put_dir = proc {
  6295. if XMLData.room_count > room_count
  6296. fill_hands if need_full_hands
  6297. Script.self.downstream_buffer.unshift(save_stream)
  6298. Script.self.downstream_buffer.flatten!
  6299. return true
  6300. end
  6301. waitrt?
  6302. wait_while { stunned? }
  6303. giveup_time = Time.now.to_i + giveup_seconds.to_i
  6304. line_count = 0
  6305. save_stream.push(clear)
  6306. put dir
  6307. }
  6308. put_dir.call
  6309. loop {
  6310. line = get?
  6311. unless line.nil?
  6312. save_stream.push(line)
  6313. line_count += 1
  6314. end
  6315. if line.nil?
  6316. sleep "0.1".to_f
  6317. 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\.$/
  6318. fput 'unhide'
  6319. put_dir.call
  6320. elsif (line == 'You take a few steps towards a rusty doorknob.') and (dir =~ /door/)
  6321. which = [ 'first', 'second', 'third', 'fourth', 'fifth', 'sixth', 'seventh', 'eight', 'ninth', 'tenth', 'eleventh', 'twelfth' ]
  6322. if dir =~ /\b#{which.join('|')}\b/
  6323. dir.sub!(/\b(#{which.join('|')})\b/) { "#{which[which.index($1)+1]}" }
  6324. else
  6325. dir.sub!('door', 'second door')
  6326. end
  6327. put_dir.call
  6328. elsif line =~ /^You can't go there|^You can't 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\.$/
  6329. echo 'move: failed'
  6330. fill_hands if need_full_hands
  6331. Script.self.downstream_buffer.unshift(save_stream)
  6332. Script.self.downstream_buffer.flatten!
  6333. return false
  6334. 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!/
  6335. echo 'move: failed'
  6336. fill_hands if need_full_hands
  6337. Script.self.downstream_buffer.unshift(save_stream)
  6338. Script.self.downstream_buffer.flatten!
  6339. # return nil instead of false to show the direction shouldn't be removed from the map database
  6340. return nil
  6341. 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\.$/
  6342. sleep 1
  6343. waitrt?
  6344. put_dir.call
  6345. 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\./
  6346. sleep 1
  6347. waitrt?
  6348. fput 'stand' unless standing?
  6349. waitrt?
  6350. put_dir.call
  6351. elsif line =~ /^You begin to climb up the silvery thread.* you tumble to the ground/
  6352. sleep 0.5
  6353. waitrt?
  6354. fput 'stand' unless standing?
  6355. waitrt?
  6356. if checkleft or checkright
  6357. need_full_hands = true
  6358. empty_hands
  6359. end
  6360. put_dir.call
  6361. elsif line == "You can't do that while engaged!"
  6362. # DragonRealms
  6363. fput 'retreat'
  6364. fput 'retreat'
  6365. put_dir.call
  6366. elsif line == 'You are too injured to be doing any climbing!'
  6367. if (resolve = Spell[9704]) and resolve.known?
  6368. wait_until { resolve.affordable? }
  6369. resove.cast
  6370. put_dir.call
  6371. else
  6372. return nil
  6373. end
  6374. elsif line =~ /^You(?:'re going to| will) have to climb that\./
  6375. dir.gsub!('go', 'climb')
  6376. put_dir.call
  6377. elsif line =~ /^You can't climb that\./
  6378. dir.gsub!('climb', 'go')
  6379. put_dir.call
  6380. elsif line =~ /^You can't drag/
  6381. if tried_fix_drag
  6382. fill_hands if need_full_hands
  6383. Script.self.downstream_buffer.unshift(save_stream)
  6384. Script.self.downstream_buffer.flatten!
  6385. return false
  6386. 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/ })
  6387. tried_fix_drag = true
  6388. 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)
  6389. target = /^(?:go|climb) (.+)$/.match(dir).captures.first
  6390. fput "drag #{name}"
  6391. dir = "drag #{name} #{target}"
  6392. put_dir.call
  6393. else
  6394. tried_fix_drag = true
  6395. dir.sub!(/^climb /, 'go ')
  6396. put_dir.call
  6397. end
  6398. 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\./
  6399. need_full_hands = true
  6400. empty_hands
  6401. put_dir.call
  6402. elsif line =~ /(?:appears|seems) to be closed\.$|^You cannot quite manage to squeeze between the stone doors\.$/
  6403. if tried_open
  6404. fill_hands if need_full_hands
  6405. Script.self.downstream_buffer.unshift(save_stream)
  6406. Script.self.downstream_buffer.flatten!
  6407. return false
  6408. else
  6409. tried_open = true
  6410. fput dir.sub(/go|climb/, 'open')
  6411. put_dir.call
  6412. end
  6413. elsif line =~ /^(\.\.\.w|W)ait ([0-9]+) sec(onds)?\.$/
  6414. if $2.to_i > 1
  6415. sleep ($2.to_i - "0.2".to_f)
  6416. else
  6417. sleep "0.3".to_f
  6418. end
  6419. put_dir.call
  6420. 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/
  6421. fput 'stand'
  6422. waitrt?
  6423. put_dir.call
  6424. elsif line =~ /^Sorry, you may only type ahead/
  6425. sleep 1
  6426. put_dir.call
  6427. elsif line == 'You are still stunned.'
  6428. wait_while { stunned? }
  6429. put_dir.call
  6430. 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!$/
  6431. waitrt?
  6432. fput 'stand' unless standing?
  6433. waitrt?
  6434. put_dir.call
  6435. elsif line =~ /^You flick your hand (?:up|down)wards and focus your aura on your disk, but your disk only wobbles briefly\.$/
  6436. put_dir.call
  6437. elsif line =~ /^You dive into the fast-moving river, but the current catches you and whips you back to shore, wet and battered\.$/
  6438. waitrt?
  6439. put_dir.call
  6440. elsif line == "You don't seem to be able to move to do that."
  6441. 30.times {
  6442. break if clear.include?('You regain control of your senses!')
  6443. sleep "0.1".to_f
  6444. }
  6445. put_dir.call
  6446. end
  6447. if XMLData.room_count > room_count
  6448. fill_hands if need_full_hands
  6449. Script.self.downstream_buffer.unshift(save_stream)
  6450. Script.self.downstream_buffer.flatten!
  6451. return true
  6452. end
  6453. if Time.now.to_i >= giveup_time
  6454. echo "move: no recognized response in #{giveup_seconds} seconds. giving up."
  6455. fill_hands if need_full_hands
  6456. Script.self.downstream_buffer.unshift(save_stream)
  6457. Script.self.downstream_buffer.flatten!
  6458. return nil
  6459. end
  6460. if line_count >= giveup_lines
  6461. echo "move: no recognized response after #{line_count} lines. giving up."
  6462. fill_hands if need_full_hands
  6463. Script.self.downstream_buffer.unshift(save_stream)
  6464. Script.self.downstream_buffer.flatten!
  6465. return nil
  6466. end
  6467. }
  6468. end
  6469. def fetchloot(userbagchoice=Lich.lootsack)
  6470. if GameObj.loot.empty?
  6471. return false
  6472. end
  6473. if Lich.excludeloot.empty?
  6474. regexpstr = nil
  6475. else
  6476. regexpstr = Lich.excludeloot.split(', ').join('|')
  6477. end
  6478. if checkright and checkleft
  6479. stowed = GameObj.right_hand.noun
  6480. fput "put my #{stowed} in my #{Lich.lootsack}"
  6481. else
  6482. stowed = nil
  6483. end
  6484. GameObj.loot.each { |loot|
  6485. unless not regexpstr.nil? and loot.name =~ /#{regexpstr}/
  6486. fput "get #{loot.noun}"
  6487. fput("put my #{loot.noun} in my #{userbagchoice}") if (checkright || checkleft)
  6488. end
  6489. }
  6490. if stowed
  6491. fput "take my #{stowed} from my #{Lich.lootsack}"
  6492. end
  6493. end
  6494. def take(*items)
  6495. items.flatten!
  6496. if (righthand? && lefthand?)
  6497. weap = checkright
  6498. fput "put my #{checkright} in my #{Lich.lootsack}"
  6499. unsh = true
  6500. else
  6501. unsh = false
  6502. end
  6503. items.each { |trinket|
  6504. fput "take #{trinket}"
  6505. fput("put my #{trinket} in my #{Lich.lootsack}") if (righthand? || lefthand?)
  6506. }
  6507. if unsh then fput("take my #{weap} from my #{Lich.lootsack}") end
  6508. end
  6509. def watchhealth(value, theproc=nil, &block)
  6510. value = value.to_i
  6511. if block.nil?
  6512. if !theproc.respond_to? :call
  6513. respond "`watchhealth' was not given a block or a proc to execute!"
  6514. return nil
  6515. else
  6516. block = theproc
  6517. end
  6518. end
  6519. Thread.new {
  6520. wait_while { health(value) }
  6521. block.call
  6522. }
  6523. end
  6524. def wait_until(announce=nil)
  6525. priosave = Thread.current.priority
  6526. Thread.current.priority = 0
  6527. unless announce.nil? or yield
  6528. respond(announce)
  6529. end
  6530. until yield
  6531. sleep "0.25".to_f
  6532. end
  6533. Thread.current.priority = priosave
  6534. end
  6535. def wait_while(announce=nil)
  6536. priosave = Thread.current.priority
  6537. Thread.current.priority = 0
  6538. unless announce.nil? or !yield
  6539. respond(announce)
  6540. end
  6541. while yield
  6542. sleep "0.25".to_f
  6543. end
  6544. Thread.current.priority = priosave
  6545. end
  6546. def checkpaths(dir="none")
  6547. if dir == "none"
  6548. if XMLData.room_exits.empty?
  6549. return false
  6550. else
  6551. return XMLData.room_exits.collect { |dir| dir = SHORTDIR[dir] }
  6552. end
  6553. else
  6554. XMLData.room_exits.include?(dir) || XMLData.room_exits.include?(SHORTDIR[dir])
  6555. end
  6556. end
  6557. def reverse_direction(dir)
  6558. if dir == "n" then 's'
  6559. elsif dir == "ne" then 'sw'
  6560. elsif dir == "e" then 'w'
  6561. elsif dir == "se" then 'nw'
  6562. elsif dir == "s" then 'n'
  6563. elsif dir == "sw" then 'ne'
  6564. elsif dir == "w" then 'e'
  6565. elsif dir == "nw" then 'se'
  6566. elsif dir == "up" then 'down'
  6567. elsif dir == "down" then 'up'
  6568. elsif dir == "out" then 'out'
  6569. elsif dir == 'o' then out
  6570. elsif dir == 'u' then 'down'
  6571. elsif dir == 'd' then up
  6572. elsif dir == n then s
  6573. elsif dir == ne then sw
  6574. elsif dir == e then w
  6575. elsif dir == se then nw
  6576. elsif dir == s then n
  6577. elsif dir == sw then ne
  6578. elsif dir == w then e
  6579. elsif dir == nw then se
  6580. elsif dir == u then d
  6581. elsif dir == d then u
  6582. else echo("Cannot recognize direction to properly reverse it!"); false
  6583. end
  6584. end
  6585. def walk(*boundaries, &block)
  6586. boundaries.flatten!
  6587. unless block.nil?
  6588. until val = yield
  6589. walk(*boundaries)
  6590. end
  6591. return val
  6592. end
  6593. if $last_dir and !boundaries.empty? and checkroomdescrip =~ /#{boundaries.join('|')}/i
  6594. move($last_dir)
  6595. $last_dir = reverse_direction($last_dir)
  6596. return checknpcs
  6597. end
  6598. dirs = checkpaths
  6599. dirs.delete($last_dir) unless dirs.length < 2
  6600. this_time = rand(dirs.length)
  6601. $last_dir = reverse_direction(dirs[this_time])
  6602. move(dirs[this_time])
  6603. checknpcs
  6604. end
  6605. def run
  6606. loop { break unless walk }
  6607. end
  6608. def check_mind(string=nil)
  6609. if string.nil?
  6610. return XMLData.mind_text
  6611. elsif (string.class == String) and (string.to_i == 0)
  6612. if string =~ /#{XMLData.mind_text}/i
  6613. return true
  6614. else
  6615. return false
  6616. end
  6617. elsif string.to_i.between?(0,100)
  6618. return string.to_i <= XMLData.mind_value.to_i
  6619. else
  6620. 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
  6621. return false
  6622. end
  6623. end
  6624. def checkmind(string=nil)
  6625. if string.nil?
  6626. return XMLData.mind_text
  6627. elsif string.class == String and string.to_i == 0
  6628. if string =~ /#{XMLData.mind_text}/i
  6629. return true
  6630. else
  6631. return false
  6632. end
  6633. elsif string.to_i.between?(1,8)
  6634. mind_state = ['clear as a bell','fresh and clear','clear','muddled','becoming numbed','numbed','must rest','saturated']
  6635. if mind_state.index(XMLData.mind_text)
  6636. mind = mind_state.index(XMLData.mind_text) + 1
  6637. return string.to_i <= mind
  6638. else
  6639. echo "Bad string in checkmind: mind_state"
  6640. nil
  6641. end
  6642. else
  6643. 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
  6644. return false
  6645. end
  6646. end
  6647. def percentmind(num=nil)
  6648. if num.nil?
  6649. XMLData.mind_value
  6650. else
  6651. XMLData.mind_value >= num.to_i
  6652. end
  6653. end
  6654. def checkfried
  6655. if XMLData.mind_text =~ /must rest|saturated/
  6656. true
  6657. else
  6658. false
  6659. end
  6660. end
  6661. def checksaturated
  6662. if XMLData.mind_text =~ /saturated/
  6663. true
  6664. else
  6665. false
  6666. end
  6667. end
  6668. def checkmana(num=nil)
  6669. if num.nil?
  6670. XMLData.mana
  6671. else
  6672. XMLData.mana >= num.to_i
  6673. end
  6674. end
  6675. def maxmana
  6676. XMLData.max_mana
  6677. end
  6678. def percentmana(num=nil)
  6679. if XMLData.max_mana == 0
  6680. percent = 100
  6681. else
  6682. percent = ((XMLData.mana.to_f / XMLData.max_mana.to_f) * 100).to_i
  6683. end
  6684. if num.nil?
  6685. percent
  6686. else
  6687. percent >= num.to_i
  6688. end
  6689. end
  6690. def checkhealth(num=nil)
  6691. if num.nil?
  6692. XMLData.health
  6693. else
  6694. XMLData.health >= num.to_i
  6695. end
  6696. end
  6697. def maxhealth
  6698. XMLData.max_health
  6699. end
  6700. def percenthealth(num=nil)
  6701. if num.nil?
  6702. ((XMLData.health.to_f / XMLData.max_health.to_f) * 100).to_i
  6703. else
  6704. ((XMLData.health.to_f / XMLData.max_health.to_f) * 100).to_i >= num.to_i
  6705. end
  6706. end
  6707. def checkspirit(num=nil)
  6708. if num.nil?
  6709. XMLData.spirit
  6710. else
  6711. XMLData.spirit >= num.to_i
  6712. end
  6713. end
  6714. def maxspirit
  6715. XMLData.max_spirit
  6716. end
  6717. def percentspirit(num=nil)
  6718. if num.nil?
  6719. ((XMLData.spirit.to_f / XMLData.max_spirit.to_f) * 100).to_i
  6720. else
  6721. ((XMLData.spirit.to_f / XMLData.max_spirit.to_f) * 100).to_i >= num.to_i
  6722. end
  6723. end
  6724. def checkstamina(num=nil)
  6725. if num.nil?
  6726. XMLData.stamina
  6727. else
  6728. XMLData.stamina >= num.to_i
  6729. end
  6730. end
  6731. def maxstamina()
  6732. XMLData.max_stamina
  6733. end
  6734. def percentstamina(num=nil)
  6735. if XMLData.max_stamina == 0
  6736. percent = 100
  6737. else
  6738. percent = ((XMLData.stamina.to_f / XMLData.max_stamina.to_f) * 100).to_i
  6739. end
  6740. if num.nil?
  6741. percent
  6742. else
  6743. percent >= num.to_i
  6744. end
  6745. end
  6746. def checkstance(num=nil)
  6747. if num.nil?
  6748. XMLData.stance_text
  6749. elsif (num.class == String) and (num.to_i == 0)
  6750. if num =~ /off/i
  6751. XMLData.stance_value == 0
  6752. elsif num =~ /adv/i
  6753. XMLData.stance_value.between?(01, 20)
  6754. elsif num =~ /for/i
  6755. XMLData.stance_value.between?(21, 40)
  6756. elsif num =~ /neu/i
  6757. XMLData.stance_value.between?(41, 60)
  6758. elsif num =~ /gua/i
  6759. XMLData.stance_value.between?(61, 80)
  6760. elsif num =~ /def/i
  6761. XMLData.stance_value == 100
  6762. else
  6763. echo "checkstance: invalid argument (#{num}). Must be off/adv/for/neu/gua/def or 0-100"
  6764. nil
  6765. end
  6766. elsif (num.class == Fixnum) or (num =~ /^[0-9]+$/ and num = num.to_i)
  6767. XMLData.stance_value == num.to_i
  6768. else
  6769. echo "checkstance: invalid argument (#{num}). Must be off/adv/for/neu/gua/def or 0-100"
  6770. nil
  6771. end
  6772. end
  6773. def percentstance(num=nil)
  6774. if num.nil?
  6775. XMLData.stance_value
  6776. else
  6777. XMLData.stance_value >= num.to_i
  6778. end
  6779. end
  6780. def checkencumbrance(string=nil)
  6781. if string.nil?
  6782. XMLData.encumbrance_text
  6783. elsif (string.class == Fixnum) or (string =~ /^[0-9]+$/ and string = string.to_i)
  6784. string <= XMLData.encumbrance_value
  6785. else
  6786. # fixme
  6787. if string =~ /#{XMLData.encumbrance_text}/i
  6788. true
  6789. else
  6790. false
  6791. end
  6792. end
  6793. end
  6794. def percentencumbrance(num=nil)
  6795. if num.nil?
  6796. XMLData.encumbrance_value
  6797. else
  6798. num.to_i <= XMLData.encumbrance_value
  6799. end
  6800. end
  6801. def checkarea(*strings)
  6802. strings.flatten!
  6803. if strings.empty?
  6804. XMLData.room_title.split(',').first.sub('[','')
  6805. else
  6806. XMLData.room_title.split(',').first =~ /#{strings.join('|')}/i
  6807. end
  6808. end
  6809. def checkroom(*strings)
  6810. strings.flatten!
  6811. if strings.empty?
  6812. XMLData.room_title.chomp
  6813. else
  6814. XMLData.room_title =~ /#{strings.join('|')}/i
  6815. end
  6816. end
  6817. def outside?
  6818. if XMLData.room_exits_string =~ /Obvious paths:/
  6819. true
  6820. else
  6821. false
  6822. end
  6823. end
  6824. def checkfamarea(*strings)
  6825. strings.flatten!
  6826. if strings.empty? then return XMLData.familiar_room_title.split(',').first.sub('[','') end
  6827. XMLData.familiar_room_title.split(',').first =~ /#{strings.join('|')}/i
  6828. end
  6829. def checkfampaths(dir="none")
  6830. if dir == "none"
  6831. if XMLData.familiar_room_exits.empty?
  6832. return false
  6833. else
  6834. return XMLData.familiar_room_exits
  6835. end
  6836. else
  6837. XMLData.familiar_room_exits.include?(dir)
  6838. end
  6839. end
  6840. def checkfamroom(*strings)
  6841. strings.flatten! ; if strings.empty? then return XMLData.familiar_room_title.chomp end
  6842. XMLData.familiar_room_title =~ /#{strings.join('|')}/i
  6843. end
  6844. def checkfamnpcs(*strings)
  6845. parsed = Array.new
  6846. XMLData.familiar_npcs.each { |val| parsed.push(val.split.last) }
  6847. if strings.empty?
  6848. if parsed.empty?
  6849. return false
  6850. else
  6851. return parsed
  6852. end
  6853. else
  6854. if mtch = strings.find { |lookfor| parsed.find { |critter| critter =~ /#{lookfor}/ } }
  6855. return mtch
  6856. else
  6857. return false
  6858. end
  6859. end
  6860. end
  6861. def checkfampcs(*strings)
  6862. familiar_pcs = Array.new
  6863. 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]+/)) }
  6864. if familiar_pcs.empty?
  6865. return false
  6866. elsif strings.empty?
  6867. return familiar_pcs
  6868. else
  6869. regexpstr = strings.join('|\b')
  6870. peeps = familiar_pcs.find_all { |val| val =~ /\b#{regexpstr}/i }
  6871. if peeps.empty?
  6872. return false
  6873. else
  6874. return peeps
  6875. end
  6876. end
  6877. end
  6878. def checkpcs(*strings)
  6879. pcs = GameObj.pcs.collect { |pc| pc.noun }
  6880. if pcs.empty?
  6881. if strings.empty? then return nil else return false end
  6882. end
  6883. strings.flatten!
  6884. if strings.empty?
  6885. pcs
  6886. else
  6887. regexpstr = strings.join(' ')
  6888. pcs.find { |pc| regexpstr =~ /\b#{pc}/i }
  6889. end
  6890. end
  6891. def checknpcs(*strings)
  6892. npcs = GameObj.npcs.collect { |npc| npc.noun }
  6893. if npcs.empty?
  6894. if strings.empty? then return nil else return false end
  6895. end
  6896. strings.flatten!
  6897. if strings.empty?
  6898. npcs
  6899. else
  6900. regexpstr = strings.join(' ')
  6901. npcs.find { |npc| regexpstr =~ /\b#{npc}/i }
  6902. end
  6903. end
  6904. def count_npcs
  6905. checknpcs.length
  6906. end
  6907. def checkright(*hand)
  6908. if GameObj.right_hand.nil? then return nil end
  6909. hand.flatten!
  6910. if GameObj.right_hand.name == "Empty" or GameObj.right_hand.name.empty?
  6911. nil
  6912. elsif hand.empty?
  6913. GameObj.right_hand.noun
  6914. else
  6915. hand.find { |instance| GameObj.right_hand.name =~ /#{instance}/i }
  6916. end
  6917. end
  6918. def checkleft(*hand)
  6919. if GameObj.left_hand.nil? then return nil end
  6920. hand.flatten!
  6921. if GameObj.left_hand.name == "Empty" or GameObj.left_hand.name.empty?
  6922. nil
  6923. elsif hand.empty?
  6924. GameObj.left_hand.noun
  6925. else
  6926. hand.find { |instance| GameObj.left_hand.name =~ /#{instance}/i }
  6927. end
  6928. end
  6929. def checkroomdescrip(*val)
  6930. val.flatten!
  6931. if val.empty?
  6932. return XMLData.room_description
  6933. else
  6934. return XMLData.room_description =~ /#{val.join('|')}/i
  6935. end
  6936. end
  6937. def checkfamroomdescrip(*val)
  6938. val.flatten!
  6939. if val.empty?
  6940. return XMLData.familiar_room_description
  6941. else
  6942. return XMLData.familiar_room_description =~ /#{val.join('|')}/i
  6943. end
  6944. end
  6945. def checkspell(*spells)
  6946. spells.flatten!
  6947. return false if Spell.active.empty?
  6948. spells.each { |spell| return false unless Spell[spell].active? }
  6949. true
  6950. end
  6951. def checkprep(spell=nil)
  6952. if spell.nil?
  6953. XMLData.prepared_spell
  6954. elsif spell.class != String
  6955. echo("Checkprep error, spell # not implemented! You must use the spell name")
  6956. false
  6957. else
  6958. XMLData.prepared_spell =~ /^#{spell}/i
  6959. end
  6960. end
  6961. def setpriority(val=nil)
  6962. if val.nil? then return Thread.current.priority end
  6963. if val.to_i > 3
  6964. 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")
  6965. return Thread.current.priority
  6966. else
  6967. Thread.current.group.list.each { |thr| thr.priority = val.to_i }
  6968. return Thread.current.priority
  6969. end
  6970. end
  6971. def checkbounty
  6972. if XMLData.bounty_task
  6973. return XMLData.bounty_task
  6974. else
  6975. return nil
  6976. end
  6977. end
  6978. def variable
  6979. unless script = Script.self then echo 'variable: cannot identify calling script.'; return nil; end
  6980. script.vars
  6981. end
  6982. def pause(num=1)
  6983. if num =~ /m/
  6984. sleep((num.sub(/m/, '').to_f * 60))
  6985. elsif num =~ /h/
  6986. sleep((num.sub(/h/, '').to_f * 3600))
  6987. elsif num =~ /d/
  6988. sleep((num.sub(/d/, '').to_f * 86400))
  6989. else
  6990. sleep(num.to_f)
  6991. end
  6992. end
  6993. def cast(spell, target=nil, results_of_interest=nil)
  6994. if spell.class == Spell
  6995. spell.cast(target, results_of_interest)
  6996. elsif ( (spell.class == Fixnum) or (spell.to_s =~ /^[0-9]+$/) ) and (find_spell = Spell[spell.to_i])
  6997. find_spell.cast(target, results_of_interest)
  6998. elsif (spell.class == String) and (find_spell = Spell[spell])
  6999. find_spell.cast(target, results_of_interest)
  7000. else
  7001. echo "cast: invalid spell (#{spell})"
  7002. false
  7003. end
  7004. end
  7005. def clear(opt=0)
  7006. unless script = Script.self then respond('--- clear: Unable to identify calling script.'); return false; end
  7007. to_return = script.downstream_buffer.dup
  7008. script.downstream_buffer.clear
  7009. to_return
  7010. end
  7011. def match(label, string)
  7012. strings = [ label, string ]
  7013. strings.flatten!
  7014. unless script = Script.self 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
  7015. if strings.empty? then echo("Error! 'match' was given no strings to look for!") ; sleep 1 ; return false end
  7016. unless strings.length == 2
  7017. while line_in = script.gets
  7018. strings.each { |string|
  7019. if line_in =~ /#{string}/ then return $~.to_s end
  7020. }
  7021. end
  7022. else
  7023. if script.respond_to?(:match_stack_add)
  7024. script.match_stack_add(strings.first.to_s, strings.last)
  7025. else
  7026. script.match_stack_labels.push(strings[0].to_s)
  7027. script.match_stack_strings.push(strings[1])
  7028. end
  7029. end
  7030. end
  7031. def matchtimeout(secs, *strings)
  7032. unless script = Script.self 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
  7033. unless (secs.class == Float || secs.class == Fixnum)
  7034. echo('matchtimeout error! You appear to have given it a string, not a #! Syntax: matchtimeout(30, "You stand up")')
  7035. return false
  7036. end
  7037. strings.flatten!
  7038. if strings.empty?
  7039. echo("matchtimeout without any strings to wait for!")
  7040. sleep 1
  7041. return false
  7042. end
  7043. regexpstr = strings.join('|')
  7044. end_time = Time.now.to_f + secs
  7045. loop {
  7046. line = get?
  7047. if line.nil?
  7048. sleep "0.1".to_f
  7049. elsif line =~ /#{regexpstr}/i
  7050. return line
  7051. end
  7052. if (Time.now.to_f > end_time)
  7053. return false
  7054. end
  7055. }
  7056. end
  7057. def matchbefore(*strings)
  7058. strings.flatten!
  7059. unless script = Script.self 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
  7060. if strings.empty? then echo("matchbefore without any strings to wait for!") ; return false end
  7061. regexpstr = strings.join('|')
  7062. loop { if (line_in = script.gets) =~ /#{regexpstr}/ then return $`.to_s end }
  7063. end
  7064. def matchafter(*strings)
  7065. strings.flatten!
  7066. unless script = Script.self 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
  7067. if strings.empty? then echo("matchafter without any strings to wait for!") ; return end
  7068. regexpstr = strings.join('|')
  7069. loop { if (line_in = script.gets) =~ /#{regexpstr}/ then return $'.to_s end }
  7070. end
  7071. def matchboth(*strings)
  7072. strings.flatten!
  7073. unless script = Script.self 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
  7074. if strings.empty? then echo("matchboth without any strings to wait for!") ; return end
  7075. regexpstr = strings.join('|')
  7076. loop { if (line_in = script.gets) =~ /#{regexpstr}/ then break end }
  7077. return [ $`.to_s, $'.to_s ]
  7078. end
  7079. def matchwait(*strings)
  7080. unless script = Script.self then respond('--- matchwait: Unable to identify calling script.'); return false; end
  7081. strings.flatten!
  7082. unless strings.empty?
  7083. regexpstr = strings.collect { |str| str.kind_of?(Regexp) ? str.source : str }.join('|')
  7084. regexobj = /#{regexpstr}/
  7085. while line_in = script.gets
  7086. return line_in if line_in =~ regexobj
  7087. end
  7088. else
  7089. strings = script.match_stack_strings
  7090. labels = script.match_stack_labels
  7091. regexpstr = /#{strings.join('|')}/i
  7092. while line_in = script.gets
  7093. if mdata = regexpstr.match(line_in)
  7094. jmp = labels[strings.index(mdata.to_s) || strings.index(strings.find { |str| line_in =~ /#{str}/i })]
  7095. script.match_stack_clear
  7096. goto jmp
  7097. end
  7098. end
  7099. end
  7100. end
  7101. def waitforre(regexp)
  7102. unless script = Script.self then respond('--- waitforre: Unable to identify calling script.'); return false; end
  7103. 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
  7104. regobj = regexp.match(script.gets) until regobj
  7105. end
  7106. def waitfor(*strings)
  7107. unless script = Script.self then respond('--- waitfor: Unable to identify calling script.'); return false; end
  7108. strings.flatten!
  7109. if (script.class == WizardScript) and (strings.length == 1) and (strings.first.strip == '>')
  7110. return script.gets
  7111. end
  7112. if strings.empty?
  7113. echo 'waitfor: no string to wait for'
  7114. return false
  7115. end
  7116. regexpstr = strings.join('|')
  7117. while true
  7118. line_in = script.gets
  7119. if (line_in =~ /#{regexpstr}/i) then return line_in end
  7120. end
  7121. end
  7122. def wait
  7123. unless script = Script.self then respond('--- wait: unable to identify calling script.'); return false; end
  7124. script.clear
  7125. return script.gets
  7126. end
  7127. def get
  7128. Script.self.gets
  7129. end
  7130. def get?
  7131. Script.self.gets?
  7132. end
  7133. def reget(*lines)
  7134. unless script = Script.self then respond('--- reget: Unable to identify calling script.'); return false; end
  7135. lines.flatten!
  7136. if caller.find { |c| c =~ /regetall/ }
  7137. history = ($_SERVERBUFFER_.history + $_SERVERBUFFER_).join("\n")
  7138. else
  7139. history = $_SERVERBUFFER_.dup.join("\n")
  7140. end
  7141. unless script.want_downstream_xml
  7142. history.gsub!(/<pushStream id=["'](?:spellfront|inv|bounty|society)["'][^>]*\/>.*?<popStream[^>]*>/m, '')
  7143. history.gsub!(/<stream id="Spells">.*?<\/stream>/m, '')
  7144. history.gsub!(/<(compDef|inv|component|right|left|spell|prompt)[^>]*>.*?<\/\1>/m, '')
  7145. history.gsub!(/<[^>]+>/, '')
  7146. history.gsub!('&gt;', '>')
  7147. history.gsub!('&lt;', '<')
  7148. end
  7149. history = history.split("\n").delete_if { |line| line.nil? or line.empty? or line =~ /^[\r\n\s\t]*$/ }
  7150. if lines.first.kind_of?(Numeric) or lines.first.to_i.nonzero?
  7151. history = history[-([lines.shift.to_i,history.length].min)..-1]
  7152. end
  7153. unless lines.empty? or lines.nil?
  7154. regex = /#{lines.join('|')}/i
  7155. history = history.find_all { |line| line =~ regex }
  7156. end
  7157. if history.empty?
  7158. nil
  7159. else
  7160. history
  7161. end
  7162. end
  7163. def regetall(*lines)
  7164. reget(*lines)
  7165. end
  7166. def multifput(*cmds)
  7167. cmds.flatten.compact.each { |cmd| fput(cmd) }
  7168. end
  7169. def fput(message, *waitingfor)
  7170. unless script = Script.self then respond('--- waitfor: Unable to identify calling script.'); return false; end
  7171. waitingfor.flatten!
  7172. clear
  7173. put(message)
  7174. while string = get
  7175. if string =~ /(?:\.\.\.wait |Wait )[0-9]+/
  7176. hold_up = string.slice(/[0-9]+/).to_i
  7177. sleep(hold_up) unless hold_up.nil?
  7178. clear
  7179. put(message)
  7180. next
  7181. elsif string =~ /^You.+struggle.+stand/
  7182. clear
  7183. fput 'stand'
  7184. next
  7185. elsif string =~ /stunned|can't do that while|cannot seem|^(?!You rummage).*can't seem|don't seem|Sorry, you may only type ahead/
  7186. if dead?
  7187. echo "You're dead...! You can't do that!"
  7188. sleep 1
  7189. script.downstream_buffer.unshift(string)
  7190. return false
  7191. elsif checkstunned
  7192. while checkstunned
  7193. sleep("0.25".to_f)
  7194. end
  7195. elsif checkwebbed
  7196. while checkwebbed
  7197. sleep("0.25".to_f)
  7198. end
  7199. elsif string =~ /Sorry, you may only type ahead/
  7200. sleep 1
  7201. else
  7202. sleep "0.1".to_f
  7203. script.downstream_buffer.unshift(string)
  7204. return false
  7205. end
  7206. clear
  7207. put(message)
  7208. next
  7209. else
  7210. if waitingfor.empty?
  7211. script.downstream_buffer.unshift(string)
  7212. return string
  7213. else
  7214. if foundit = waitingfor.find { |val| string =~ /#{val}/i }
  7215. script.downstream_buffer.unshift(string)
  7216. return foundit
  7217. end
  7218. sleep 1
  7219. clear
  7220. put(message)
  7221. next
  7222. end
  7223. end
  7224. end
  7225. end
  7226. def put(*messages)
  7227. unless script = Script.self then script = "(script unknown)" end
  7228. $_SCRIPTIDLETIMESTAMP_ = Time.now
  7229. messages.each { |message|
  7230. message.chomp!
  7231. unless scr = Script.self then scr = "(script unknown)" end
  7232. $_CLIENTBUFFER_.push("[#{scr}]#{$SEND_CHARACTER}#{$cmd_prefix}#{message}\r\n")
  7233. respond("[#{scr}]#{$SEND_CHARACTER}#{message}\r\n") unless scr.silent
  7234. $_SERVER_.write("#{$cmd_prefix}#{message}\n")
  7235. $_LASTUPSTREAM_ = "[#{scr}]#{$SEND_CHARACTER}#{message}"
  7236. }
  7237. end
  7238. def quiet_exit
  7239. script = Script.self
  7240. script.quiet = !(script.quiet)
  7241. end
  7242. def matchfindexact(*strings)
  7243. strings.flatten!
  7244. unless script = Script.self 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
  7245. if strings.empty? then echo("error! 'matchfind' with no strings to look for!") ; sleep 1 ; return false end
  7246. looking = Array.new
  7247. strings.each { |str| looking.push(str.gsub('?', '(\b.+\b)')) }
  7248. if looking.empty? then echo("matchfind without any strings to wait for!") ; return false end
  7249. regexpstr = looking.join('|')
  7250. while line_in = script.gets
  7251. if gotit = line_in.slice(/#{regexpstr}/)
  7252. matches = Array.new
  7253. looking.each_with_index { |str,idx|
  7254. if gotit =~ /#{str}/i
  7255. strings[idx].count('?').times { |n| matches.push(eval("$#{n+1}")) }
  7256. end
  7257. }
  7258. break
  7259. end
  7260. end
  7261. if matches.length == 1
  7262. return matches.first
  7263. else
  7264. return matches.compact
  7265. end
  7266. end
  7267. def matchfind(*strings)
  7268. regex = /#{strings.flatten.join('|').gsub('?', '(.+)')}/i
  7269. unless script = Script.self
  7270. respond "Unknown script is asking to use matchfind! Cannot process request without identifying the calling script; killing this thread."
  7271. Thread.current.kill
  7272. end
  7273. while true
  7274. if reobj = regex.match(script.gets)
  7275. ret = reobj.captures.compact
  7276. if ret.length < 2
  7277. return ret.first
  7278. else
  7279. return ret
  7280. end
  7281. end
  7282. end
  7283. end
  7284. def matchfindword(*strings)
  7285. regex = /#{strings.flatten.join('|').gsub('?', '([\w\d]+)')}/i
  7286. unless script = Script.self
  7287. respond "Unknown script is asking to use matchfindword! Cannot process request without identifying the calling script; killing this thread."
  7288. Thread.current.kill
  7289. end
  7290. while true
  7291. if reobj = regex.match(script.gets)
  7292. ret = reobj.captures.compact
  7293. if ret.length < 2
  7294. return ret.first
  7295. else
  7296. return ret
  7297. end
  7298. end
  7299. end
  7300. end
  7301. def send_scripts(*messages)
  7302. messages.flatten!
  7303. messages.each { |message|
  7304. Script.new_downstream(message)
  7305. }
  7306. true
  7307. end
  7308. def status_tags(onoff="none")
  7309. script = Script.self
  7310. if onoff == "on"
  7311. script.want_downstream = false
  7312. script.want_downstream_xml = true
  7313. echo("Status tags will be sent to this script.")
  7314. elsif onoff == "off"
  7315. script.want_downstream = true
  7316. script.want_downstream_xml = false
  7317. echo("Status tags will no longer be sent to this script.")
  7318. elsif script.want_downstream_xml
  7319. script.want_downstream = true
  7320. script.want_downstream_xml = false
  7321. else
  7322. script.want_downstream = false
  7323. script.want_downstream_xml = true
  7324. end
  7325. end
  7326. def stop_script(*target_names)
  7327. numkilled = 0
  7328. target_names.each { |target_name|
  7329. condemned = (Script.running + Script.hidden).find { |s_sock| s_sock.name =~ /^#{target_name}/i }
  7330. if condemned.nil?
  7331. respond("--- Lich: '#{Script.self}' tried to stop '#{target_name}', but it isn't running!")
  7332. else
  7333. if condemned.name =~ /^#{Script.self.name}$/i
  7334. exit
  7335. end
  7336. condemned.kill
  7337. respond("--- Lich: '#{condemned}' has been stopped by #{Script.self}.")
  7338. numkilled += 1
  7339. end
  7340. }
  7341. if numkilled == 0
  7342. return false
  7343. else
  7344. return numkilled
  7345. end
  7346. end
  7347. def running?(*snames)
  7348. 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 }) }
  7349. true
  7350. end
  7351. def respond(first = "", *messages)
  7352. str = ''
  7353. begin
  7354. if first.class == Array
  7355. first.flatten.each { |ln| str += sprintf("%s\r\n", ln.to_s.chomp) }
  7356. else
  7357. str += sprintf("%s\r\n", first.to_s.chomp)
  7358. end
  7359. messages.flatten.each { |message| str += sprintf("%s\r\n", message.to_s.chomp) }
  7360. str.split(/\r?\n/).each { |line| Script.new_script_output(line) }
  7361. str = "<output class=\"mono\"/>\r\n#{str.gsub('&', '&amp;').gsub('<', '&lt;').gsub('>', '&gt;')}<output class=\"\"/>\r\n" if $frontend == 'stormfront'
  7362. wait_while { XMLData.in_stream }
  7363. $_CLIENT_.puts(str)
  7364. rescue
  7365. puts $!
  7366. puts $!.backtrace.first
  7367. end
  7368. end
  7369. def noded_pulse
  7370. if Stats.prof =~ /warrior|rogue|sorcerer/i
  7371. stats = [ Skills.smc.to_i, Skills.emc.to_i ]
  7372. elsif Stats.prof =~ /empath|bard/i
  7373. stats = [ Skills.smc.to_i, Skills.mmc.to_i ]
  7374. elsif Stats.prof =~ /wizard/i
  7375. stats = [ Skills.emc.to_i, 0 ]
  7376. elsif Stats.prof =~ /paladin|cleric|ranger/i
  7377. stats = [ Skills.smc.to_i, 0 ]
  7378. else
  7379. stats = [ 0, 0 ]
  7380. end
  7381. return (maxmana * 25 / 100) + (stats.max/10) + (stats.min/20)
  7382. end
  7383. def unnoded_pulse
  7384. if Stats.prof =~ /warrior|rogue|sorcerer/i
  7385. stats = [ Skills.smc.to_i, Skills.emc.to_i ]
  7386. elsif Stats.prof =~ /empath|bard/i
  7387. stats = [ Skills.smc.to_i, Skills.mmc.to_i ]
  7388. elsif Stats.prof =~ /wizard/i
  7389. stats = [ Skills.emc.to_i, 0 ]
  7390. elsif Stats.prof =~ /paladin|cleric|ranger/i
  7391. stats = [ Skills.smc.to_i, 0 ]
  7392. else
  7393. stats = [ 0, 0 ]
  7394. end
  7395. return (maxmana * 15 / 100) + (stats.max/10) + (stats.min/20)
  7396. end
  7397. def empty_hands
  7398. $fill_hands_actions ||= Array.new
  7399. actions = Array.new
  7400. right_hand = GameObj.right_hand
  7401. left_hand = GameObj.left_hand
  7402. if UserVars.lootsack.nil? or UserVars.lootsack.empty?
  7403. lootsack = nil
  7404. else
  7405. 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 }
  7406. end
  7407. if left_hand.id
  7408. waitrt?
  7409. 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\.$/)
  7410. actions.unshift proc {
  7411. dothistimeout "remove ##{left_hand.id}", 3, /^You|^Remove what\?/
  7412. 20.times { break if GameObj.left_hand.id == left_hand.id or GameObj.right_hand.id == left_hand.id; sleep "0.1".to_f }
  7413. if GameObj.right_hand.id == left_hand.id
  7414. dothistimeout 'swap', 3, /^You don't have anything to swap!|^You swap/
  7415. end
  7416. }
  7417. elsif lootsack
  7418. actions.unshift proc {
  7419. dothistimeout "get ##{left_hand.id}", 3, /^You remove|^You reach into|^Get what\?/
  7420. 20.times { break if GameObj.left_hand.id == left_hand.id or GameObj.right_hand.id == left_hand.id; sleep "0.1".to_f }
  7421. if GameObj.right_hand.id == left_hand.id
  7422. dothistimeout 'swap', 3, /^You don't have anything to swap!|^You swap/
  7423. end
  7424. }
  7425. result = dothistimeout "put ##{left_hand.id} in ##{lootsack.id}", 4, /^You put|^You slip .*? into|^You can't .+ It's closed!$|^I could not find what you were referring to\./
  7426. if result =~ /^You can't .+ It's closed!$/
  7427. actions.push proc { fput "close ##{lootsack.id}" }
  7428. dothistimeout "open ##{lootsack.id}", 3, /^You open|^That is already open\./
  7429. dothistimeout "put ##{left_hand.id} in ##{lootsack.id}", 3, /^You put|^You slip .*? into|^I could not find what you were referring to\./
  7430. end
  7431. else
  7432. actions.unshift proc {
  7433. dothistimeout "get ##{left_hand.id}", 3, /^You|^Get what\?/
  7434. 20.times { break if GameObj.left_hand.id == left_hand.id or GameObj.right_hand.id == left_hand.id; sleep "0.1".to_f }
  7435. if GameObj.right_hand.id == left_hand.id
  7436. dothistimeout 'swap', 3, /^You don't have anything to swap!|^You swap/
  7437. end
  7438. }
  7439. dothistimeout 'stow left', 3, /^You put|^You slip .*? into|^You are not holding anything/
  7440. end
  7441. end
  7442. if right_hand.id
  7443. waitrt?
  7444. if XMLData.active_spells.keys.include?('Sonic Weapon Song') or XMLData.active_spells.keys.include?('1012')
  7445. type = right_hand.noun
  7446. if (type == 'sword') and right_hand.name =~ /short/
  7447. type = 'short'
  7448. elsif (type.downcase == 'hammer') and right_hand.name =~ /Hammer of Kai/
  7449. type = 'hammer of kai'
  7450. end
  7451. actions.unshift proc {
  7452. if (sonic_weapon_song = Spell[1012]) and sonic_weapon_song.known? and sonic_weapon_song.affordable?
  7453. sonic_weapon_song.cast(type)
  7454. end
  7455. }
  7456. fput 'stop 1012'
  7457. elsif lootsack
  7458. actions.unshift proc {
  7459. dothistimeout "get ##{right_hand.id}", 3, /^You remove|^You reach into|^Get what\?/
  7460. 20.times { break if GameObj.left_hand.id == right_hand.id or GameObj.right_hand.id == right_hand.id; sleep "0.1".to_f }
  7461. if GameObj.left_hand.id == right_hand.id
  7462. dothistimeout 'swap', 3, /^You don't have anything to swap!|^You swap/
  7463. end
  7464. }
  7465. result = dothistimeout "put ##{right_hand.id} in ##{lootsack.id}", 4, /^You put|^You slip .*? into|^You can't .+ It's closed!$|^I could not find what you were referring to\./
  7466. if result =~ /^You can't .+ It's closed!$/
  7467. actions.push proc { fput "close ##{lootsack.id}" }
  7468. dothistimeout "open ##{lootsack.id}", 3, /^You open|^That is already open\./
  7469. dothistimeout "put ##{right_hand.id} in ##{lootsack.id}", 3, /^You put|^You slip .*? into|^I could not find what you were referring to\./
  7470. end
  7471. else
  7472. actions.unshift proc {
  7473. dothistimeout "get ##{right_hand.id}", 3, /^You|^Get what\?/
  7474. 20.times { break if GameObj.left_hand.id == right_hand.id or GameObj.right_hand.id == right_hand.id; sleep "0.1".to_f }
  7475. if GameObj.left_hand.id == right_hand.id
  7476. dothistimeout 'swap', 3, /^You don't have anything to swap!|^You swap/
  7477. end
  7478. }
  7479. dothistimeout 'stow right', 3, /^You put|^You slip .*? into|^You are not holding anything/
  7480. end
  7481. end
  7482. $fill_hands_actions.push(actions)
  7483. end
  7484. def fill_hands
  7485. $fill_hands_actions ||= Array.new
  7486. for action in $fill_hands_actions.pop
  7487. action.call
  7488. end
  7489. end
  7490. def empty_hand
  7491. $fill_hand_actions ||= Array.new
  7492. actions = Array.new
  7493. right_hand = GameObj.right_hand
  7494. left_hand = GameObj.left_hand
  7495. if UserVars.lootsack.nil? or UserVars.lootsack.empty?
  7496. lootsack = nil
  7497. else
  7498. 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 }
  7499. end
  7500. 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))
  7501. 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)
  7502. waitrt?
  7503. if XMLData.active_spells.keys.include?('Sonic Weapon Song') or XMLData.active_spells.keys.include?('1012')
  7504. type = right_hand.noun
  7505. if (type == 'sword') and right_hand.name =~ /short/
  7506. type = 'short'
  7507. elsif (type.downcase == 'hammer') and right_hand.name =~ /Hammer of Kai/
  7508. type = 'hammer of kai'
  7509. end
  7510. actions.unshift proc {
  7511. if (sonic_weapon_song = Spell[1012]) and sonic_weapon_song.known? and sonic_weapon_song.affordable?
  7512. sonic_weapon_song.cast(type)
  7513. end
  7514. }
  7515. fput 'stop 1012'
  7516. elsif lootsack
  7517. actions.unshift proc {
  7518. dothistimeout "get ##{right_hand.id}", 3, /^You|^Get what\?/
  7519. 20.times { break if GameObj.left_hand.id == right_hand.id or GameObj.right_hand.id == right_hand.id; sleep "0.1".to_f }
  7520. if GameObj.left_hand.id == right_hand.id
  7521. dothistimeout 'swap', 3, /^You don't have anything to swap!|^You swap/
  7522. end
  7523. }
  7524. result = dothistimeout "put ##{right_hand.id} in ##{lootsack.id}", 4, /^You put|^You slip .*? into|^You can't .+ It's closed!$|^I could not find what you were referring to\./
  7525. if result =~ /^You can't .+ It's closed!$/
  7526. actions.push proc { fput "close ##{lootsack.id}" }
  7527. dothistimeout "open ##{lootsack.id}", 3, /^You open|^That is already open\./
  7528. dothistimeout "put ##{right_hand.id} in ##{lootsack.id}", 3, /^You put|^You slip .*? into|^I could not find what you were referring to\./
  7529. end
  7530. else
  7531. actions.unshift proc {
  7532. dothistimeout "get ##{right_hand.id}", 3, /^You|^Get what\?/
  7533. 20.times { break if GameObj.left_hand.id == right_hand.id or GameObj.right_hand.id == right_hand.id; sleep "0.1".to_f }
  7534. if GameObj.left_hand.id == right_hand.id
  7535. dothistimeout 'swap', 3, /^You don't have anything to swap!|^You swap/
  7536. end
  7537. }
  7538. dothistimeout 'stow right', 3, /^You put|^You slip .*? into|^You are not holding anything/
  7539. end
  7540. else
  7541. waitrt?
  7542. 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\.$/)
  7543. actions.unshift proc {
  7544. dothistimeout "remove ##{left_hand.id}", 3, /^You|^Remove what\?/
  7545. 20.times { break if GameObj.left_hand.id == left_hand.id or GameObj.right_hand.id == left_hand.id; sleep "0.1".to_f }
  7546. if GameObj.right_hand.id == left_hand.id
  7547. dothistimeout 'swap', 3, /^You don't have anything to swap!|^You swap/
  7548. end
  7549. }
  7550. elsif lootsack
  7551. actions.unshift proc {
  7552. dothistimeout "get ##{left_hand.id}", 3, /^You|^Get what\?/
  7553. 20.times { break if GameObj.left_hand.id == left_hand.id or GameObj.right_hand.id == left_hand.id; sleep "0.1".to_f }
  7554. if GameObj.right_hand.id == left_hand.id
  7555. dothistimeout 'swap', 3, /^You don't have anything to swap!|^You swap/
  7556. end
  7557. }
  7558. result = dothistimeout "put ##{left_hand.id} in ##{lootsack.id}", 4, /^You put|^You slip .*? into|^You can't .+ It's closed!$|^I could not find what you were referring to\./
  7559. if result =~ /^You can't .+ It's closed!$/
  7560. actions.push proc { fput "close ##{lootsack.id}" }
  7561. dothistimeout "open ##{lootsack.id}", 3, /^You open|^That is already open\./
  7562. dothistimeout "put ##{left_hand.id} in ##{lootsack.id}", 3, /^You put|^You slip .*? into|^I could not find what you were referring to\./
  7563. end
  7564. else
  7565. actions.unshift proc {
  7566. dothistimeout "get ##{left_hand.id}", 3, /^You|^Get what\?/
  7567. 20.times { break if GameObj.left_hand.id == left_hand.id or GameObj.right_hand.id == left_hand.id; sleep "0.1".to_f }
  7568. if GameObj.right_hand.id == left_hand.id
  7569. dothistimeout 'swap', 3, /^You don't have anything to swap!|^You swap/
  7570. end
  7571. }
  7572. dothistimeout 'stow left', 3, /^You put|^You slip .*? into|^You are not holding anything/
  7573. end
  7574. end
  7575. end
  7576. $fill_hand_actions.push(actions)
  7577. end
  7578. def fill_hand
  7579. $fill_hand_actions ||= Array.new
  7580. for action in $fill_hand_actions.pop
  7581. action.call
  7582. end
  7583. end
  7584. def empty_right_hand
  7585. $fill_right_hand_actions ||= Array.new
  7586. actions = Array.new
  7587. right_hand = GameObj.right_hand
  7588. if UserVars.lootsack.nil? or UserVars.lootsack.empty?
  7589. lootsack = nil
  7590. else
  7591. 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 }
  7592. end
  7593. if right_hand.id
  7594. waitrt?
  7595. if XMLData.active_spells.keys.include?('Sonic Weapon Song') or XMLData.active_spells.keys.include?('1012')
  7596. type = right_hand.noun
  7597. if (type == 'sword') and right_hand.name =~ /short/
  7598. type = 'short'
  7599. elsif (type.downcase == 'hammer') and right_hand.name =~ /Hammer of Kai/
  7600. type = 'hammer of kai'
  7601. end
  7602. actions.unshift proc {
  7603. if (sonic_weapon_song = Spell[1012]) and sonic_weapon_song.known? and sonic_weapon_song.affordable?
  7604. sonic_weapon_song.cast(type)
  7605. end
  7606. }
  7607. fput 'stop 1012'
  7608. elsif lootsack
  7609. actions.unshift proc {
  7610. dothistimeout "get ##{right_hand.id}", 3, /^You|^Get what\?/
  7611. 20.times { break if GameObj.left_hand.id == right_hand.id or GameObj.right_hand.id == right_hand.id; sleep "0.1".to_f }
  7612. if GameObj.left_hand.id == right_hand.id
  7613. dothistimeout 'swap', 3, /^You don't have anything to swap!|^You swap/
  7614. end
  7615. }
  7616. result = dothistimeout "put ##{right_hand.id} in ##{lootsack.id}", 4, /^You put|^You slip .*? into|^You can't .+ It's closed!$|^I could not find what you were referring to\./
  7617. if result =~ /^You can't .+ It's closed!$/
  7618. actions.push proc { fput "close ##{lootsack.id}" }
  7619. dothistimeout "open ##{lootsack.id}", 3, /^You open|^That is already open\./
  7620. dothistimeout "put ##{right_hand.id} in ##{lootsack.id}", 3, /^You put|^You slip .*? into|^I could not find what you were referring to\./
  7621. end
  7622. else
  7623. actions.unshift proc {
  7624. dothistimeout "get ##{right_hand.id}", 3, /^You|^Get what\?/
  7625. 20.times { break if GameObj.left_hand.id == right_hand.id or GameObj.right_hand.id == right_hand.id; sleep "0.1".to_f }
  7626. if GameObj.left_hand.id == right_hand.id
  7627. dothistimeout 'swap', 3, /^You don't have anything to swap!|^You swap/
  7628. end
  7629. }
  7630. dothistimeout 'stow right', 3, /^You put|^You slip .*? into|^You are not holding anything/
  7631. end
  7632. end
  7633. $fill_right_hand_actions.push(actions)
  7634. end
  7635. def fill_right_hand
  7636. $fill_right_hand_actions ||= Array.new
  7637. for action in $fill_right_hand_actions.pop
  7638. action.call
  7639. end
  7640. end
  7641. def empty_left_hand
  7642. $fill_left_hand_actions ||= Array.new
  7643. actions = Array.new
  7644. left_hand = GameObj.left_hand
  7645. if UserVars.lootsack.nil? or UserVars.lootsack.empty?
  7646. lootsack = nil
  7647. else
  7648. 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 }
  7649. end
  7650. if left_hand.id
  7651. waitrt?
  7652. 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\.$/)
  7653. actions.unshift proc {
  7654. dothistimeout "remove ##{left_hand.id}", 3, /^You|^Remove what\?/
  7655. 20.times { break if GameObj.left_hand.id == left_hand.id or GameObj.right_hand.id == left_hand.id; sleep "0.1".to_f }
  7656. if GameObj.right_hand.id == left_hand.id
  7657. dothistimeout 'swap', 3, /^You don't have anything to swap!|^You swap/
  7658. end
  7659. }
  7660. elsif lootsack
  7661. actions.unshift proc {
  7662. dothistimeout "get ##{left_hand.id}", 3, /^You|^Get what\?/
  7663. 20.times { break if GameObj.left_hand.id == left_hand.id or GameObj.right_hand.id == left_hand.id; sleep "0.1".to_f }
  7664. if GameObj.right_hand.id == left_hand.id
  7665. dothistimeout 'swap', 3, /^You don't have anything to swap!|^You swap/
  7666. end
  7667. }
  7668. result = dothistimeout "put ##{left_hand.id} in ##{lootsack.id}", 4, /^You put|^You slip .*? into|^You can't .+ It's closed!$|^I could not find what you were referring to\./
  7669. if result =~ /^You can't .+ It's closed!$/
  7670. actions.push proc { fput "close ##{lootsack.id}" }
  7671. dothistimeout "open ##{lootsack.id}", 3, /^You open|^That is already open\./
  7672. dothistimeout "put ##{left_hand.id} in ##{lootsack.id}", 3, /^You put|^You slip .*? into|^I could not find what you were referring to\./
  7673. end
  7674. else
  7675. actions.unshift proc {
  7676. dothistimeout "get ##{left_hand.id}", 3, /^You|^Get what\?/
  7677. 20.times { break if GameObj.left_hand.id == left_hand.id or GameObj.right_hand.id == left_hand.id; sleep "0.1".to_f }
  7678. if GameObj.right_hand.id == left_hand.id
  7679. dothistimeout 'swap', 3, /^You don't have anything to swap!|^You swap/
  7680. end
  7681. }
  7682. dothistimeout 'stow left', 3, /^You put|^You slip .*? into|^You are not holding anything/
  7683. end
  7684. end
  7685. $fill_left_hand_actions.push(actions)
  7686. end
  7687. def fill_left_hand
  7688. $fill_left_hand_actions ||= Array.new
  7689. for action in $fill_left_hand_actions.pop
  7690. action.call
  7691. end
  7692. end
  7693. def dothis (action, success_line)
  7694. loop {
  7695. Script.self.clear
  7696. put action
  7697. loop {
  7698. line = get
  7699. if line =~ success_line
  7700. return line
  7701. elsif line =~ /^(\.\.\.w|W)ait ([0-9]+) sec(onds)?\.$/
  7702. if $2.to_i > 1
  7703. sleep ($2.to_i - "0.5".to_f)
  7704. else
  7705. sleep "0.3".to_f
  7706. end
  7707. break
  7708. elsif line == 'Sorry, you may only type ahead 1 command.'
  7709. sleep 1
  7710. break
  7711. elsif line == 'You are still stunned.'
  7712. wait_while { stunned? }
  7713. break
  7714. elsif line == 'That is impossible to do while unconscious!'
  7715. 100.times {
  7716. unless line = get?
  7717. sleep "0.1".to_f
  7718. else
  7719. 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\.$/
  7720. end
  7721. }
  7722. break
  7723. elsif line == "You don't seem to be able to move to do that."
  7724. 100.times {
  7725. unless line = get?
  7726. sleep "0.1".to_f
  7727. else
  7728. break if line == 'The restricting force that envelops you dissolves away.'
  7729. end
  7730. }
  7731. break
  7732. elsif line == "You can't do that while entangled in a web."
  7733. wait_while { checkwebbed }
  7734. break
  7735. elsif line == 'You find that impossible under the effects of the lullabye.'
  7736. 100.times {
  7737. unless line = get?
  7738. sleep "0.1".to_f
  7739. else
  7740. # fixme
  7741. break if line == 'You shake off the effects of the lullabye.'
  7742. end
  7743. }
  7744. break
  7745. end
  7746. }
  7747. }
  7748. end
  7749. def dothistimeout (action, timeout, success_line)
  7750. end_time = Time.now.to_f + timeout
  7751. line = nil
  7752. loop {
  7753. Script.self.clear
  7754. put action unless action.nil?
  7755. loop {
  7756. line = get?
  7757. if line.nil?
  7758. sleep "0.1".to_f
  7759. elsif line =~ success_line
  7760. return line
  7761. elsif line =~ /^(\.\.\.w|W)ait ([0-9]+) sec(onds)?\.$/
  7762. if $2.to_i > 1
  7763. sleep ($2.to_i - "0.5".to_f)
  7764. else
  7765. sleep "0.3".to_f
  7766. end
  7767. end_time = Time.now.to_f + timeout
  7768. break
  7769. elsif line == 'Sorry, you may only type ahead 1 command.'
  7770. sleep 1
  7771. end_time = Time.now.to_f + timeout
  7772. break
  7773. elsif line == 'You are still stunned.'
  7774. wait_while { stunned? }
  7775. end_time = Time.now.to_f + timeout
  7776. break
  7777. elsif line == 'That is impossible to do while unconscious!'
  7778. 100.times {
  7779. unless line = get?
  7780. sleep "0.1".to_f
  7781. else
  7782. 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\.$/
  7783. end
  7784. }
  7785. break
  7786. elsif line == "You don't seem to be able to move to do that."
  7787. 100.times {
  7788. unless line = get?
  7789. sleep "0.1".to_f
  7790. else
  7791. break if line == 'The restricting force that envelops you dissolves away.'
  7792. end
  7793. }
  7794. break
  7795. elsif line == "You can't do that while entangled in a web."
  7796. wait_while { checkwebbed }
  7797. break
  7798. elsif line == 'You find that impossible under the effects of the lullabye.'
  7799. 100.times {
  7800. unless line = get?
  7801. sleep "0.1".to_f
  7802. else
  7803. # fixme
  7804. break if line == 'You shake off the effects of the lullabye.'
  7805. end
  7806. }
  7807. break
  7808. end
  7809. if Time.now.to_f >= end_time
  7810. return nil
  7811. end
  7812. }
  7813. }
  7814. end
  7815. def registry_get(key)
  7816. if $SAFE == 0
  7817. if (RUBY_PLATFORM =~ /mingw|win/i) and (RUBY_PLATFORM !~ /darwin/i)
  7818. path = /^(.*)\\.*?$/.match(key).captures.first.split("\\")
  7819. value = /^.*\\(.*?)$/.match(key).captures.first
  7820. begin
  7821. advapi32 = DL.dlopen('advapi32.dll')
  7822. reg_open_key_ex = advapi32['RegOpenKeyEx', 'LLSLLP']
  7823. reg_query_value_ex = advapi32['RegQueryValueEx', 'LLSLPPP']
  7824. kernel32 = DL.dlopen('kernel32.dll')
  7825. close_handle = kernel32['CloseHandle', 'LL']
  7826. keys = Array.new
  7827. if path[0].downcase == 'hkey_classes_root'
  7828. keys.push -2147483648
  7829. elsif path[0].downcase == 'hkey_current_user'
  7830. keys.push -2147483647
  7831. elsif path[0].downcase == 'hkey_local_machine'
  7832. keys.push -2147483646
  7833. elsif path[0].downcase == 'hkey_users'
  7834. keys.push -2147483645
  7835. else
  7836. raise "bad top level key: #{key}"
  7837. end
  7838. path[1..-1].each { |key|
  7839. new_key = DL.malloc(DL.sizeof('L'))
  7840. new_key.struct!('L', :data)
  7841. result = reg_open_key_ex.call(keys[-1], key, 0, 131097, new_key) # 131097 = KEY_READ
  7842. unless result.first == 0
  7843. raise "failed to open key: #{key}"
  7844. end
  7845. keys.push(new_key[:data])
  7846. }
  7847. type = DL.malloc(DL.sizeof('L'))
  7848. buffer = DL.malloc(DL.sizeof('C256'))
  7849. buffer.struct!('C256', :data)
  7850. size = DL.malloc(DL.sizeof('L'))
  7851. size.struct!('L', :data)
  7852. size[:data] = 256
  7853. result = reg_query_value_ex.call(keys.last, value, 0, type, buffer, size)
  7854. unless result.first == 0
  7855. raise "failed to read value '#{value}' from key '#{path[-1]}'"
  7856. end
  7857. buffer[:data][0..size[:data]].collect { |char| char.chr }.join('').sub(/\0.*/, '')
  7858. rescue
  7859. $stderr.puts $!
  7860. $stderr.puts $!.backtrace[0..1]
  7861. nil
  7862. ensure
  7863. keys[1..-1].each { |key| close_handle.call(key) rescue() }
  7864. end
  7865. else
  7866. hkey, subkey, thingie = /(HKEY_LOCAL_MACHINE|HKEY_CURRENT_USER)\\(.+)\\([^\\]*)/.match(key).captures
  7867. if ENV['WINEPREFIX'] and File.exists?(ENV['WINEPREFIX'])
  7868. wine_dir = ENV['WINEPREFIX']
  7869. elsif ENV['HOME'] and File.exists?(ENV['HOME'] + '/.wine')
  7870. wine_dir = ENV['HOME'] + '/.wine'
  7871. else
  7872. return false
  7873. end
  7874. if File.exists?(wine_dir + '/system.reg')
  7875. if hkey == 'HKEY_LOCAL_MACHINE'
  7876. reg_file = File.open(wine_dir + '/system.reg')
  7877. reg_data = reg_file.readlines
  7878. reg_file.close
  7879. lookin = false
  7880. result = false
  7881. subkey = "[#{subkey.gsub('\\', '\\\\\\')}]"
  7882. if thingie.nil? or thingie.empty?
  7883. thingie = '@'
  7884. else
  7885. thingie = "\"#{thingie}\""
  7886. end
  7887. reg_data.each { |line|
  7888. if line[0...subkey.length] == subkey
  7889. lookin = true
  7890. elsif line =~ /^\[/
  7891. lookin = false
  7892. elsif lookin and line =~ /^#{thingie}="(.*)"$/i
  7893. result = $1.split('\\"').join('"').split('\\\\').join('\\')
  7894. break
  7895. end
  7896. }
  7897. return result
  7898. else
  7899. return false
  7900. end
  7901. else
  7902. return false
  7903. end
  7904. end
  7905. else
  7906. nil
  7907. end
  7908. end
  7909. def registry_put(key, value)
  7910. if $SAFE == 0
  7911. if (RUBY_PLATFORM =~ /mingw|win/i) and (RUBY_PLATFORM !~ /darwin/i)
  7912. path = key.split("\\")
  7913. path.push('') if key =~ /\\$/
  7914. value.concat("\0")
  7915. begin
  7916. advapi32 = DL.dlopen('advapi32.dll')
  7917. reg_open_key_ex = advapi32['RegOpenKeyEx', 'LLSLLP']
  7918. reg_set_value_ex = advapi32['RegSetValueEx', 'LLSLLPL']
  7919. kernel32 = DL.dlopen('kernel32.dll')
  7920. close_handle = kernel32['CloseHandle', 'LL']
  7921. keys = Array.new
  7922. if path[0].downcase == 'hkey_classes_root'
  7923. keys.push -2147483648
  7924. elsif path[0].downcase == 'hkey_current_user'
  7925. keys.push -2147483647
  7926. elsif path[0].downcase == 'hkey_local_machine'
  7927. keys.push -2147483646
  7928. elsif path[0].downcase == 'hkey_users'
  7929. keys.push -2147483645
  7930. else
  7931. raise "bad top level key: #{key}"
  7932. end
  7933. path[1..-2].each_with_index { |key,index|
  7934. new_key = DL.malloc(DL.sizeof('L'))
  7935. new_key.struct!('L', :data)
  7936. result = reg_open_key_ex.call(keys[-1], key, 0, 983103, new_key) # 983103 = KEY_ALL_ACCESS
  7937. unless result.first == 0
  7938. raise "failed to open key: #{key} (#{result.first})"
  7939. end
  7940. keys.push(new_key[:data])
  7941. }
  7942. result = reg_set_value_ex.call(keys.last, path[-1], 0, 1, value, DL.sizeof("C#{value.length+1}")) # 1 = REG_SZ
  7943. if result.first == 0
  7944. true
  7945. else
  7946. raise "failed to write to key '#{path[-1]}' (#{result.first})"
  7947. end
  7948. rescue
  7949. $stderr.puts $!
  7950. $stderr.puts $!.backtrace[0..1]
  7951. false
  7952. ensure
  7953. keys[1..-1].each { |key| close_handle.call(key) rescue() }
  7954. end
  7955. else
  7956. hkey, subkey, thingie = /(HKEY_LOCAL_MACHINE|HKEY_CURRENT_USER)\\(.+)\\([^\\]*)/.match(key).captures
  7957. if ENV['WINEPREFIX'] and File.exists?(ENV['WINEPREFIX'])
  7958. wine_dir = ENV['WINEPREFIX']
  7959. elsif ENV['HOME'] and File.exists?(ENV['HOME'] + '/.wine')
  7960. wine_dir = ENV['HOME'] + '/.wine'
  7961. else
  7962. return false
  7963. end
  7964. if File.exists?(wine_dir)
  7965. if thingie.nil? or thingie.empty?
  7966. thingie = '@'
  7967. else
  7968. thingie = "\"#{thingie}\""
  7969. end
  7970. # gsub sucks for this..
  7971. value = value.split('\\').join('\\\\')
  7972. value = value.split('"').join('\"')
  7973. begin
  7974. regedit_data = "REGEDIT4\n\n[#{hkey}\\#{subkey}]\n#{thingie}=\"#{value}\"\n\n"
  7975. File.open('wine.reg', 'w') { |f| f.write(regedit_data) }
  7976. system('wine regedit wine.reg')
  7977. sleep "0.2".to_f
  7978. File.delete('wine.reg')
  7979. rescue
  7980. return false
  7981. end
  7982. return true
  7983. end
  7984. end
  7985. else
  7986. false
  7987. end
  7988. end
  7989. def find_hosts_dir
  7990. if (RUBY_PLATFORM =~ /mingw|win/i) and (RUBY_PLATFORM !~ /darwin/i)
  7991. if hosts_dir = registry_get('HKEY_LOCAL_MACHINE\\System\\CurrentControlSet\\Services\\Tcpip\\Parameters\\DataBasePath')
  7992. heal_hosts
  7993. return hosts_dir.gsub(/%SystemRoot%/, windir)
  7994. elsif hosts_dir = registry_get('HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\DataBasePath')
  7995. heal_hosts
  7996. return hosts_dir.gsub(/%SystemRoot%/, windir)
  7997. else
  7998. test_dirs = Array.new
  7999. if windir = ENV['windir'] || ENV['SYSTEMROOT']
  8000. test_dirs.push "#{windir}\\system32\\drivers\\etc\\" # winxp
  8001. test_dirs.push "#{windir}\\" # win98
  8002. end
  8003. if prefix = /(C|D|E|F|G|H)/.match(Dir.pwd.to_s[0..1]).captures.first
  8004. test_dirs.push "#{prefix}:\\winnt\\system32\\drivers\\etc\\" # winxp_pro
  8005. test_dirs.push "#{prefix}:\\windows\\system32\\drivers\\etc\\" # winxp_home
  8006. test_dirs.push "#{prefix}:\\windows\\" # win98
  8007. if prefix != 'C:'
  8008. test_dirs.push "C:\\winnt\\system32\\drivers\\etc\\" # winxp_pro
  8009. test_dirs.push "C:\\windows\\system32\\drivers\\etc\\" # winxp_home
  8010. test_dirs.push "C:\\windows\\" # win98
  8011. end
  8012. end
  8013. end
  8014. else
  8015. test_dirs = [ '/etc/', '/private/etc/' ]
  8016. end
  8017. test_dirs.each { |dir|
  8018. if File.exists?("#{dir}hosts.bak") or File.exists?("#{dir}hosts")
  8019. heal_hosts(dir)
  8020. return dir
  8021. end
  8022. }
  8023. return nil
  8024. end
  8025. def hack_hosts(hosts_dir, game_host)
  8026. hosts_dir += File::Separator unless hosts_dir[-1..-1] =~ /\/\\/
  8027. at_exit { heal_hosts(hosts_dir) }
  8028. begin
  8029. begin
  8030. unless File.exists?("#{hosts_dir}hosts.bak")
  8031. File.open("#{hosts_dir}hosts") { |infile|
  8032. File.open("#{$temp_dir}hosts.sav", 'w') { |outfile|
  8033. outfile.write(infile.read)
  8034. }
  8035. }
  8036. end
  8037. rescue
  8038. File.unlink("#{$temp_dir}hosts.sav") if File.exists?("#{$temp_dir}hosts.sav")
  8039. end
  8040. if File.exists?("#{hosts_dir}hosts.bak")
  8041. sleep 1
  8042. if File.exists?("#{hosts_dir}hosts.bak")
  8043. heal_hosts(hosts_dir)
  8044. end
  8045. end
  8046. File.open("#{hosts_dir}hosts") { |file|
  8047. File.open("#{hosts_dir}hosts.bak", 'w') { |f|
  8048. f.write(file.read)
  8049. }
  8050. }
  8051. File.open("#{hosts_dir}hosts", 'w') { |file|
  8052. file.puts "127.0.0.1\t\tlocalhost\r\n127.0.0.1\t\t#{game_host}"
  8053. }
  8054. rescue SystemCallError
  8055. $stdout.puts "--- error: hack_hosts: #{$!}"
  8056. $stderr.puts "error: hack_hosts: #{$!}"
  8057. $stderr.puts $!.backtrace
  8058. exit(1)
  8059. end
  8060. end
  8061. def heal_hosts(hosts_dir)
  8062. hosts_dir += File::Separator unless hosts_dir[-1..-1] =~ /\/\\/
  8063. begin
  8064. if File.exists? "#{hosts_dir}hosts.bak"
  8065. File.open("#{hosts_dir}hosts.bak") { |infile|
  8066. File.open("#{hosts_dir}hosts", 'w') { |outfile|
  8067. outfile.write(infile.read)
  8068. }
  8069. }
  8070. File.unlink "#{hosts_dir}hosts.bak"
  8071. end
  8072. rescue
  8073. $stdout.puts "--- error: heal_hosts: #{$!}"
  8074. $stderr.puts "error: heal_hosts: #{$!}"
  8075. $stderr.puts $!.backtrace
  8076. exit(1)
  8077. end
  8078. end
  8079. $link_highlight_start = ''
  8080. $link_highlight_end = ''
  8081. $speech_highlight_start = ''
  8082. $speech_highlight_end = ''
  8083. def sf_to_wiz(line)
  8084. # fixme: voln thoughts
  8085. begin
  8086. return line if line == "\r\n"
  8087. if $sftowiz_multiline
  8088. $sftowiz_multiline = $sftowiz_multiline + line
  8089. line = $sftowiz_multiline
  8090. end
  8091. if (line.scan(/<pushStream[^>]*\/>/).length > line.scan(/<popStream[^>]*\/>/).length)
  8092. $sftowiz_multiline = line
  8093. return nil
  8094. end
  8095. if (line.scan(/<style id="\w+"[^>]*\/>/).length > line.scan(/<style id=""[^>]*\/>/).length)
  8096. $sftowiz_multiline = line
  8097. return nil
  8098. end
  8099. $sftowiz_multiline = nil
  8100. if line =~ /<LaunchURL src="(.*?)" \/>/
  8101. $_CLIENT_.puts "\034GSw00005\r\nhttps://www.play.net#{$1}\r\n"
  8102. end
  8103. if line =~ /<preset id='speech'>(.*?)<\/preset>/m
  8104. line = line.sub(/<preset id='speech'>.*?<\/preset>/m, "#{$speech_highlight_start}#{$1}#{$speech_highlight_end}")
  8105. end
  8106. if line =~ /<pushStream id="thoughts"[^>]*>(?:<a[^>]*>)?([A-Z][a-z]+)(?:<\/a>)?\s*([\s\[\]\(\)A-z]+)?:(.*?)<popStream\/>/m
  8107. 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}")
  8108. end
  8109. if line =~ /<pushStream id="voln"[^>]*>\[Voln \- (?:<a[^>]*>)?([A-Z][a-z]+)(?:<\/a>)?\]\s*(".*")[\r\n]*<popStream\/>/m
  8110. 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")
  8111. end
  8112. if line =~ /<stream id="thoughts"[^>]*>([^:]+): (.*?)<\/stream>/m
  8113. line = line.sub(/<stream id="thoughts"[^>]*>.*?<\/stream>/m, "You hear the faint thoughts of #{$1} echo in your mind:\r\n#{$2}")
  8114. end
  8115. if line =~ /<pushStream id="familiar"[^>]*>(.*)<popStream\/>/m
  8116. line = line.sub(/<pushStream id="familiar"[^>]*>.*<popStream\/>/m, "\034GSe\r\n#{$1}\034GSf\r\n")
  8117. end
  8118. if line =~ /<pushStream id="death"\/>(.*?)<popStream\/>/m
  8119. line = line.sub(/<pushStream id="death"\/>.*?<popStream\/>/m, "\034GSw00003\r\n#{$1}\034GSw00004\r\n")
  8120. end
  8121. if line =~ /<style id="roomName" \/>(.*?)<style id=""\/>/m
  8122. line = line.sub(/<style id="roomName" \/>.*?<style id=""\/>/m, "\034GSo\r\n#{$1}\034GSp\r\n")
  8123. end
  8124. line.gsub!(/<style id="roomDesc"\/><style id=""\/>\r?\n/, '')
  8125. if line =~ /<style id="roomDesc"\/>(.*?)<style id=""\/>/m
  8126. desc = $1.gsub(/<a[^>]*>/, $link_highlight_start).gsub("</a>", $link_highlight_end)
  8127. line = line.sub(/<style id="roomDesc"\/>.*?<style id=""\/>/m, "\034GSH\r\n#{desc}\034GSI\r\n")
  8128. end
  8129. line = line.gsub("</prompt>\r\n", "</prompt>")
  8130. line = line.gsub("<pushBold/>", "\034GSL\r\n")
  8131. line = line.gsub("<popBold/>", "\034GSM\r\n")
  8132. line = line.gsub(/<pushStream id=["'](?:spellfront|inv|bounty|society)["'][^>]*\/>.*?<popStream[^>]*>/m, '')
  8133. line = line.gsub(/<stream id="Spells">.*?<\/stream>/m, '')
  8134. line = line.gsub(/<(compDef|inv|component|right|left|spell|prompt)[^>]*>.*?<\/\1>/m, '')
  8135. line = line.gsub(/<[^>]+>/, '')
  8136. line = line.gsub('&gt;', '>')
  8137. line = line.gsub('&lt;', '<')
  8138. return nil if line.gsub("\r\n", '').length < 1
  8139. return line
  8140. rescue
  8141. $_CLIENT_.puts "--- Error: sf_to_wiz: #{$!}"
  8142. $_CLIENT_.puts '$_SERVERSTRING_: ' + $_SERVERSTRING_.to_s
  8143. end
  8144. end
  8145. def strip_xml(line)
  8146. return line if line == "\r\n"
  8147. if $strip_xml_multiline
  8148. $strip_xml_multiline = $strip_xml_multiline + line
  8149. line = $strip_xml_multiline
  8150. end
  8151. if (line.scan(/<pushStream[^>]*\/>/).length > line.scan(/<popStream[^>]*\/>/).length)
  8152. $strip_xml_multiline = line
  8153. return nil
  8154. end
  8155. $strip_xml_multiline = nil
  8156. line = line.gsub(/<pushStream id=["'](?:spellfront|inv|bounty|society)["'][^>]*\/>.*?<popStream[^>]*>/m, '')
  8157. line = line.gsub(/<stream id="Spells">.*?<\/stream>/m, '')
  8158. line = line.gsub(/<(compDef|inv|component|right|left|spell|prompt)[^>]*>.*?<\/\1>/m, '')
  8159. line = line.gsub(/<[^>]+>/, '')
  8160. line = line.gsub('&gt;', '>')
  8161. line = line.gsub('&lt;', '<')
  8162. return nil if line.gsub("\n", '').gsub("\r", '').gsub(' ', '').length < 1
  8163. return line
  8164. end
  8165. def monsterbold_start
  8166. if $frontend =~ /^(?:wizard|avalon)$/
  8167. "\034GSL\r\n"
  8168. elsif $frontend == 'stormfront'
  8169. '<pushBold/>'
  8170. else
  8171. ''
  8172. end
  8173. end
  8174. def monsterbold_end
  8175. if $frontend =~ /^(?:wizard|avalon)$/
  8176. "\034GSM\r\n"
  8177. elsif $frontend == 'stormfront'
  8178. '<popBold/>'
  8179. else
  8180. ''
  8181. end
  8182. end
  8183. def install_to_registry(psinet_compatible = false)
  8184. Dir.chdir(File.dirname($PROGRAM_NAME))
  8185. launch_cmd = registry_get('HKEY_LOCAL_MACHINE\\Software\\Classes\\Simutronics.Autolaunch\\Shell\\Open\\command\\')
  8186. launch_dir = registry_get('HKEY_LOCAL_MACHINE\\Software\\Simutronics\\Launcher\\Directory')
  8187. return false unless launch_cmd or launch_dir
  8188. if (RUBY_PLATFORM =~ /mingw|win/i) and (RUBY_PLATFORM !~ /darwin/i)
  8189. if ruby_dir = ($:).find { |path| File.exists?("#{path}/../../../bin/rubyw.exe") }
  8190. ruby_dir = "#{ruby_dir.scan(/[^\/]+/)[0..-4].join('\\')}\\bin\\"
  8191. elsif ruby_dir = ($:).find { |path| File.exists?("#{path}/../../../../bin/rubyw.exe") }
  8192. ruby_dir = "#{ruby_dir.scan(/[^\/]+/)[0..-5].join('\\')}\\bin\\"
  8193. else
  8194. ruby_dir = String.new
  8195. end
  8196. win_lich_dir = $lich_dir.tr('/', "\\")
  8197. if lich_exe = ENV['OCRA_EXECUTABLE']
  8198. lich_launch_cmd = "\"#{ENV['OCRA_EXECUTABLE']}\" %1"
  8199. lich_launch_dir = "\"#{ENV['OCRA_EXECUTABLE']}\" "
  8200. elsif psinet_compatible
  8201. File.open("#{$lich_dir}lich.bat", 'w') { |f| f.puts "start /D\"#{ruby_dir}\" rubyw.exe \"#{win_lich_dir}#{$PROGRAM_NAME.split(/\/|\\/).last}\" %1 %2 %3 %4 %5 %6 %7 %8 %9" }
  8202. lich_launch_cmd = "\"#{win_lich_dir}lich.bat\" %1"
  8203. lich_launch_dir = "\"#{win_lich_dir}lich.bat\" "
  8204. else
  8205. lich_launch_cmd = "\"#{ruby_dir}rubyw.exe\" \"#{win_lich_dir}#{$PROGRAM_NAME.split(/\/|\\/).last}\" %1"
  8206. lich_launch_dir = "\"#{ruby_dir}rubyw.exe\" \"#{win_lich_dir}#{$PROGRAM_NAME.split(/\/|\\/).last}\" "
  8207. end
  8208. else
  8209. lich_launch_cmd = "#{$lich_dir}#{$PROGRAM_NAME.split(/\/|\\/).last} %1"
  8210. lich_launch_dir = "#{$lich_dir}#{$PROGRAM_NAME.split(/\/|\\/).last} "
  8211. end
  8212. result = true
  8213. if launch_cmd
  8214. if launch_cmd =~ /lich/i
  8215. $stdout.puts "--- warning: Lich appears to already be installed to the registry"
  8216. $stderr.puts "warning: Lich appears to already be installed to the registry"
  8217. $stderr.puts 'info: launch_cmd: ' + launch_cmd
  8218. else
  8219. registry_put('HKEY_LOCAL_MACHINE\\Software\\Classes\\Simutronics.Autolaunch\\Shell\\Open\\command\\RealCommand', launch_cmd) || result = false
  8220. registry_put('HKEY_LOCAL_MACHINE\\Software\\Classes\\Simutronics.Autolaunch\\Shell\\Open\\command\\', lich_launch_cmd) || result = false
  8221. end
  8222. end
  8223. if launch_dir
  8224. if launch_dir =~ /lich/i
  8225. $stdout.puts "--- warning: Lich appears to already be installed to the registry"
  8226. $stderr.puts "warning: Lich appears to already be installed to the registry"
  8227. $stderr.puts 'info: launch_dir: ' + launch_dir
  8228. else
  8229. registry_put('HKEY_LOCAL_MACHINE\\Software\\Simutronics\\Launcher\\RealDirectory', launch_dir) || result = false
  8230. registry_put('HKEY_LOCAL_MACHINE\\Software\\Simutronics\\Launcher\\Directory', lich_launch_dir) || result = false
  8231. end
  8232. end
  8233. unless (RUBY_PLATFORM =~ /mingw|win/i) and (RUBY_PLATFORM !~ /darwin/i)
  8234. wine = `which wine`.strip
  8235. if File.exists?(wine)
  8236. registry_put('HKEY_LOCAL_MACHINE\\Software\\Simutronics\\Launcher\\Wine', wine)
  8237. end
  8238. end
  8239. return result
  8240. end
  8241. def uninstall_from_registry
  8242. real_launch_cmd = registry_get('HKEY_LOCAL_MACHINE\\Software\\Classes\\Simutronics.Autolaunch\\Shell\\Open\\command\\RealCommand')
  8243. real_launch_dir = registry_get('HKEY_LOCAL_MACHINE\\Software\\Simutronics\\Launcher\\RealDirectory')
  8244. unless (real_launch_cmd and not real_launch_cmd.empty?) or (real_launch_dir and not real_launch_dir.empty?)
  8245. $stdout.puts "--- warning: Lich does not appear to be installed to the registry"
  8246. $stderr.puts "warning: Lich does not appear to be installed to the registry"
  8247. return false
  8248. end
  8249. result = true
  8250. if real_launch_cmd and not real_launch_cmd.empty?
  8251. registry_put('HKEY_LOCAL_MACHINE\\Software\\Classes\\Simutronics.Autolaunch\\Shell\\Open\\command\\', real_launch_cmd) || result = false
  8252. registry_put('HKEY_LOCAL_MACHINE\\Software\\Classes\\Simutronics.Autolaunch\\Shell\\Open\\command\\RealCommand', '') || result = false
  8253. end
  8254. if real_launch_dir and not real_launch_dir.empty?
  8255. registry_put('HKEY_LOCAL_MACHINE\\Software\\Simutronics\\Launcher\\Directory', real_launch_dir) || result = false
  8256. registry_put('HKEY_LOCAL_MACHINE\\Software\\Simutronics\\Launcher\\RealDirectory', '') || result = false
  8257. end
  8258. return result
  8259. end
  8260. def do_client(client_string)
  8261. client_string = UpstreamHook.run(client_string)
  8262. return nil if client_string.nil?
  8263. if client_string =~ /^(?:<c>)?#{$lich_char}(.+)$/
  8264. cmd = $1
  8265. if cmd =~ /^k$|^kill$|^stop$/
  8266. if Script.running.empty?
  8267. respond('--- Lich: no scripts to kill')
  8268. else
  8269. Script.running.last.kill
  8270. end
  8271. elsif cmd =~ /^p$|^pause$/
  8272. script = Script.running.reverse.find { |scr| scr.paused == false }
  8273. unless script
  8274. respond('--- Lich: no scripts to pause')
  8275. else
  8276. script.pause
  8277. end
  8278. script = nil
  8279. elsif cmd =~ /^u$|^unpause$/
  8280. script = Script.running.reverse.find { |scr| scr.paused == true }
  8281. unless script
  8282. respond('--- Lich: no scripts to unpause')
  8283. else
  8284. script.unpause
  8285. end
  8286. script = nil
  8287. elsif cmd =~ /^ka$|^kill\s?all$|^stop\s?all$/
  8288. killed = false
  8289. Script.running.each { |script|
  8290. unless script.no_kill_all
  8291. script.kill
  8292. killed = true
  8293. end
  8294. }
  8295. respond('--- Lich: no scripts to kill') unless killed
  8296. elsif cmd =~ /^pa$|^pause\s?all$/
  8297. paused = false
  8298. Script.running.each { |script|
  8299. unless script.paused or script.no_pause_all
  8300. script.pause
  8301. paused = true
  8302. end
  8303. }
  8304. unless paused
  8305. respond('--- Lich: no scripts to pause')
  8306. end
  8307. paused_scripts = nil
  8308. elsif cmd =~ /^ua$|^unpause\s?all$/
  8309. unpaused = false
  8310. Script.running.each { |script|
  8311. if script.paused and not script.no_pause_all
  8312. script.unpause
  8313. unpaused = true
  8314. end
  8315. }
  8316. unless unpaused
  8317. respond('--- Lich: no scripts to unpause')
  8318. end
  8319. unpaused_scripts = nil
  8320. elsif cmd =~ /^(k|kill|stop|p|pause|u|unpause)\s(.+)/
  8321. action = $1
  8322. target = $2
  8323. script = Script.running.find { |scr| scr.name == target }
  8324. script = Script.hidden.find { |scr| scr.name == target } unless script
  8325. script = Script.running.find { |scr| scr.name =~ /^#{target}/i } unless script
  8326. script = Script.hidden.find { |scr| scr.name =~ /^#{target}/i } unless script
  8327. if script.nil?
  8328. respond("--- Lich: #{target}.lic does not appear to be running! Use ';list' or ';listall' to see what's active.")
  8329. elsif action =~ /^(?:k|kill|stop)$/
  8330. script.kill
  8331. begin
  8332. GC.start
  8333. rescue
  8334. respond('--- Lich: Error starting garbage collector. (3)')
  8335. end
  8336. elsif action =~/^(?:p|pause)$/
  8337. script.pause
  8338. elsif action =~/^(?:u|unpause)$/
  8339. script.unpause
  8340. end
  8341. action = target = script = nil
  8342. elsif cmd =~ /^list\s?(?:all)?$|^l(?:a)?$/i
  8343. if cmd =~ /a(?:ll)?/i
  8344. list = Script.running + Script.hidden
  8345. else
  8346. list = Script.running
  8347. end
  8348. unless list.empty?
  8349. list.each_index { |index| if list[index].paused then list[index] = list[index].name + ' (paused)' end }
  8350. respond("--- Lich: #{list.join(", ")}.")
  8351. else
  8352. respond("--- Lich: no active scripts.")
  8353. end
  8354. list = nil
  8355. elsif cmd =~ /^force\s+(?:.+)$/
  8356. script_name = Regexp.escape(cmd.split[1].chomp)
  8357. vars = cmd.split[2..-1].join(' ').scan(/"[^"]+"|[^"\s]+/).collect { |val| val.gsub(/(?!\\)?"/,'') }
  8358. force_start_script(script_name, vars, flags={:command_line => client_string})
  8359. elsif cmd =~ /^send |^s /
  8360. if cmd.split[1] == "to"
  8361. 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 }
  8362. if script
  8363. msg = cmd.split[3..-1].join(' ').chomp
  8364. if script.want_downstream
  8365. script.downstream_buffer.push(msg)
  8366. else
  8367. script.unique_buffer.push(msg)
  8368. end
  8369. respond("--- sent to '#{script.name}': #{msg}")
  8370. else
  8371. respond("--- Lich: '#{cmd.split[2].chomp.strip}' does not match any active script!")
  8372. return
  8373. end
  8374. script = nil
  8375. else
  8376. if Script.running.empty? and Script.hidden.empty?
  8377. respond('--- Lich: no active scripts to send to.')
  8378. else
  8379. msg = cmd.split[1..-1].join(' ').chomp
  8380. respond("--- sent: #{msg}")
  8381. Script.new_downstream(msg)
  8382. end
  8383. end
  8384. elsif eobj = /^(?:exec|e)(q)? (.+)$/.match(cmd)
  8385. cmd_data = cmd.sub(/^(?:exec|execq|e|eq) /i, '')
  8386. if eobj.captures.first.nil?
  8387. start_exec_script(cmd_data, flags={ :quiet => false, :trusted => true })
  8388. else
  8389. start_exec_script(cmd_data, flags={ :quiet => true, :trusted => true })
  8390. end
  8391. elsif cmd =~ /^favs?(?: |$)(.*)?/i
  8392. args = $1.split(' ')
  8393. if (args[0].downcase == 'add') and (args[1] =~ /^all$|^global$/i) and not args[2].nil?
  8394. Favs.add(args[2], args[3..-1], :global)
  8395. respond "--- Lich: added #{args[2]} to the global favs list."
  8396. elsif (args[0].downcase == 'add') and not args[1].nil?
  8397. Favs.add(args[1], args[2..-1], :char)
  8398. respond "--- Lich: added #{args[1]} to #{XMLData.name}'s favs list."
  8399. elsif (args[0] =~ /^rem(?:ove)$|^del(?:ete)?$/i) and (args[1] =~ /^all$|^global$/i) and not args[2].nil?
  8400. if Favs.delete(args[2], :global)
  8401. respond "--- Lich: removed #{args[2]} from the global favs list."
  8402. else
  8403. respond "--- Lich: #{args[2]} was not found in the global favs list."
  8404. end
  8405. elsif (args[0] =~ /^rem(?:ove)$|^del(?:ete)?$/i) and not args[1].nil?
  8406. if Favs.delete(args[1], :char)
  8407. respond "--- Lich: removed #{args[1]} from #{XMLData.name}'s favs list."
  8408. else
  8409. respond "--- Lich: #{args[1]} was not found in #{XMLData.name}'s favs list."
  8410. end
  8411. elsif args[0].downcase == 'list'
  8412. favs = Favs.list
  8413. if favs['global'].empty?
  8414. global_favs = '(none)'
  8415. else
  8416. global_favs = favs['global'].keys.join(', ')
  8417. end
  8418. if favs[XMLData.name].empty?
  8419. char_favs = '(none)'
  8420. else
  8421. char_favs = favs[XMLData.name].keys.join(', ')
  8422. end
  8423. respond "--- Lich: Global favs: #{global_favs}"
  8424. respond "--- Lich: #{XMLData.name}'s favs: #{char_favs}"
  8425. favs = global_favs = char_favs = nil
  8426. else
  8427. respond
  8428. respond 'Usage:'
  8429. respond " #{$clean_lich_char}favs add [global] <script name> <vars>"
  8430. respond " #{$clean_lich_char}favs delete [global] <script name>"
  8431. respond " #{$clean_lich_char}favs list"
  8432. respond
  8433. end
  8434. elsif cmd =~ /^alias(?: |$)(.*)?/i
  8435. args = $1.split(' ')
  8436. if (args[0] =~ /^add$|^set$/i) and (args[1] =~ /^all$|^global$/i) and (args[2..-1].join(' ') =~ /([^=]+)=(.+)/)
  8437. trigger, target = $1, $2
  8438. Alias.add(trigger, target, :global)
  8439. respond "--- Lich: added (#{trigger} => #{target}) to the global alias list."
  8440. elsif (args[0] =~ /^add$|^set$/i) and (args[1..-1].join(' ') =~ /([^=]+)=(.+)/)
  8441. trigger, target = $1, $2
  8442. Alias.add(trigger, target, :char)
  8443. respond "--- Lich: added (#{trigger} => #{target}) to #{XMLData.name}'s alias list."
  8444. elsif (args[0] =~ /^rem(?:ove)$|^del(?:ete)?$/i) and (args[1] =~ /^all$|^global$/i) and not args[2].nil?
  8445. if Alias.delete(args[2], :global)
  8446. respond "--- Lich: removed #{args[2]} from the global alias list."
  8447. else
  8448. respond "--- Lich: #{args[2]} was not found in the global alias list."
  8449. end
  8450. elsif (args[0] =~ /^rem(?:ove)$|^del(?:ete)?$/i) and not args[1].nil?
  8451. if Alias.delete(args[1], :char)
  8452. respond "--- Lich: removed #{args[1]} from #{XMLData.name}'s alias list."
  8453. else
  8454. respond "--- Lich: #{args[1]} was not found in #{XMLData.name}'s alias list."
  8455. end
  8456. elsif args[0].downcase == 'list'
  8457. alist = Alias.list
  8458. if alist['global'].empty? and alist[XMLData.name].empty?
  8459. respond "\n--- You currently have no Lich aliases.\n"
  8460. end
  8461. unless alist['global'].empty?
  8462. respond '--- Global aliases'
  8463. alist['global'].to_a.sort { |a,b| a[0] <=> b[0] }.each { |a| respond " #{a[0]} => #{a[1]}" }
  8464. end
  8465. unless alist[XMLData.name].empty?
  8466. respond "--- #{XMLData.name}'s aliases"
  8467. alist[XMLData.name].to_a.sort { |a,b| a[0] <=> b[0] }.each { |a| respond " #{a[0]} => #{a[1]}" }
  8468. end
  8469. alist = nil
  8470. else
  8471. respond
  8472. respond 'Usage:'
  8473. respond " #{$clean_lich_char}alias add [global] <trigger>=<alias>"
  8474. respond " #{$clean_lich_char}alias delete [global] <trigger>"
  8475. respond " #{$clean_lich_char}alias list"
  8476. respond
  8477. end
  8478. elsif cmd =~ /^set(?:ting|tings)?(?: |$)(.*)?/i
  8479. args = $1.split(' ')
  8480. if (args[0].downcase == 'change') and (args[1] =~ /^all$|^global$/i) and (var_name = args[2]) and (value = args[3..-1].join(' '))
  8481. UserVars.change(var_name, value, :global)
  8482. respond "--- Lich: global setting changed (#{var_name}: #{value})"
  8483. elsif (args[0].downcase == 'change') and (var_name = args[1]) and (value = args[2..-1].join(' '))
  8484. UserVars.change(var_name, value, :char)
  8485. respond "--- Lich: #{XMLData.name}'s setting changed (#{var_name}: #{value})"
  8486. elsif (args[0].downcase == 'add') and (args[1] =~ /^all$|^global$/i) and (var_name = args[2]) and (value = args[3..-1].join(' '))
  8487. UserVars.add(var_name, value, :global)
  8488. respond "--- Lich: added to global setting (#{var_name}: #{value})"
  8489. elsif (args[0].downcase == 'add') and (var_name = args[1]) and (value = args[2..-1].join(' '))
  8490. UserVars.add(var_name, value, :char)
  8491. respond "--- Lich: added to #{XMLData.name}'s setting (#{var_name}: #{value})"
  8492. elsif (args[0] =~ /^rem(?:ove)$|^del(?:ete)?$/i) and (args[1] =~ /^all$|^global$/i) and (var_name = args[2]) and args[3]
  8493. rem_value = args[3..-1].join(' ')
  8494. echo rem_value.inspect
  8495. value = UserVars.list_global[var_name].to_s.split(', ')
  8496. if value.delete(rem_value)
  8497. UserVars.change(var_name, value.join(', '), :global)
  8498. respond "--- Lich: removed '#{rem_value}' from global setting '#{var_name}'"
  8499. else
  8500. respond "--- Lich: could not find '#{rem_value}' in global setting '#{var_name}'"
  8501. end
  8502. elsif (args[0] =~ /^rem(?:ove)$|^del(?:ete)?$/i) and (args[1] =~ /^all$|^global$/i) and (var_name = args[2])
  8503. if UserVars.delete(var_name, :global)
  8504. respond "--- Lich: removed global setting '#{var_name}'"
  8505. else
  8506. respond "--- Lich: could not find global setting '#{var_name}'"
  8507. end
  8508. elsif (args[0] =~ /^rem(?:ove)$|^del(?:ete)?$/i) and (var_name = args[1]) and args[2]
  8509. rem_value = args[2..-1].join(' ')
  8510. respond rem_value.inspect
  8511. value = UserVars.list_char[var_name].to_s.split(', ')
  8512. if value.delete(rem_value)
  8513. UserVars.change(var_name, value.join(', '), :char)
  8514. respond "--- Lich: removed '#{rem_value}' from #{XMLData.name}'s setting '#{var_name}'"
  8515. else
  8516. respond "--- Lich: could not find '#{rem_value}' in #{XMLData.name}'s setting '#{var_name}'"
  8517. end
  8518. elsif (args[0] =~ /^rem(?:ove)$|^del(?:ete)?$/i) and (var_name = args[1])
  8519. if UserVars.delete(var_name, :char)
  8520. respond "--- Lich: removed #{XMLData.name}'s setting '#{var_name}'"
  8521. else
  8522. respond "--- Lich: could not find #{XMLData.name}'s setting '#{var_name}'"
  8523. end
  8524. elsif args[0].downcase == 'list'
  8525. global_vars = UserVars.list_global
  8526. char_vars = UserVars.list_char
  8527. if global_vars.empty? and char_vars.empty?
  8528. respond "\n--- You currently have no Lich settings.\n"
  8529. end
  8530. unless global_vars.empty?
  8531. respond '--- Global settings'
  8532. global_vars.each_pair { |name,value|
  8533. respond " #{name}: #{if value.class == String; value; else; value.inspect; end}"
  8534. }
  8535. end
  8536. unless char_vars.empty?
  8537. respond "--- #{XMLData.name}'s settings"
  8538. char_vars.each_pair { |name,value|
  8539. respond " #{name}: #{if value.class == String; value; else; value.inspect; end}"
  8540. }
  8541. end
  8542. else
  8543. respond
  8544. respond "Usage:"
  8545. respond " #{$clean_lich_char}settings add [global] <setting name> <value>"
  8546. respond " #{$clean_lich_char}settings change [global] <setting name> <value>"
  8547. respond " #{$clean_lich_char}settings delete [global] <setting name> [value]"
  8548. respond " #{$clean_lich_char}settings list"
  8549. respond
  8550. end
  8551. elsif cmd =~ /^trust\s+(.*)/i
  8552. script_name = $1
  8553. if File.exists?("#{$script_dir}#{script_name}.lic")
  8554. LichSettings['trusted_scripts'] ||= Array.new
  8555. LichSettings['trusted_scripts'].push(script_name) unless LichSettings['trusted_scripts'].include?(script_name)
  8556. LichSettings.save
  8557. respond "--- Lich: '#{script_name}' is now a trusted script."
  8558. else
  8559. respond "--- Lich: could not find script: #{script_name}"
  8560. end
  8561. elsif cmd =~ /^distrust\s+(.*)/i
  8562. script_name = $1
  8563. if LichSettings['trusted_scripts'].delete(script_name)
  8564. LichSettings.save
  8565. respond "--- Lich: '#{script_name}' is no longer a trusted script."
  8566. else
  8567. respond "--- Lich: '#{script_name}' was not found in the trusted script list."
  8568. end
  8569. elsif cmd =~ /^list\s?(?:un)?trust(?:ed)?$|^lt$/i
  8570. if LichSettings['trusted_scripts'].nil? or LichSettings['untrusted_scripts'].empty?
  8571. respond "--- Lich: no scripts are trusted"
  8572. else
  8573. respond "--- Lich: trusted scripts: #{LichSettings['trusted_scripts'].join(', ')}"
  8574. end
  8575. elsif cmd =~ /^help$/i
  8576. respond
  8577. respond "Lich v#{$version}"
  8578. respond
  8579. respond 'built-in commands:'
  8580. respond " #{$clean_lich_char}<script name> start a script"
  8581. respond " #{$clean_lich_char}force <script name> start a script even if it's already running"
  8582. respond " #{$clean_lich_char}pause <script name> pause a script"
  8583. respond " #{$clean_lich_char}p <script name> ''"
  8584. respond " #{$clean_lich_char}unpause <script name> unpause a script"
  8585. respond " #{$clean_lich_char}u <script name> ''"
  8586. respond " #{$clean_lich_char}kill <script name> kill a script"
  8587. respond " #{$clean_lich_char}k <script name> ''"
  8588. respond " #{$clean_lich_char}pause pause the most recently started script that isn't aready paused"
  8589. respond " #{$clean_lich_char}p ''"
  8590. respond " #{$clean_lich_char}unpause unpause the most recently started script that is paused"
  8591. respond " #{$clean_lich_char}u ''"
  8592. respond " #{$clean_lich_char}kill kill the most recently started script"
  8593. respond " #{$clean_lich_char}k ''"
  8594. respond " #{$clean_lich_char}list show running scripts (except hidden ones)"
  8595. respond " #{$clean_lich_char}l ''"
  8596. respond " #{$clean_lich_char}pause all pause all scripts"
  8597. respond " #{$clean_lich_char}pa ''"
  8598. respond " #{$clean_lich_char}unpause all unpause all scripts"
  8599. respond " #{$clean_lich_char}ua ''"
  8600. respond " #{$clean_lich_char}kill all kill all scripts"
  8601. respond " #{$clean_lich_char}ka ''"
  8602. respond " #{$clean_lich_char}list all show all running scripts"
  8603. respond " #{$clean_lich_char}la ''"
  8604. respond
  8605. respond " #{$clean_lich_char}trust <script name> let the script do whatever it wants"
  8606. respond " #{$clean_lich_char}distrust <script name> restrict the script from doing things that might harm your computer"
  8607. respond " #{$clean_lich_char}list trusted show what scripts are trusted"
  8608. respond " #{$clean_lich_char}lt ''"
  8609. respond
  8610. respond " #{$clean_lich_char}send <line> send a line to all scripts as if it came from the game"
  8611. respond " #{$clean_lich_char}send to <script> <line> send a line to a specific script"
  8612. respond
  8613. respond " #{$clean_lich_char}favs add [global] <script name> [vars] automatically start a script start each time Lich starts"
  8614. respond " #{$clean_lich_char}favs delete [global] <script name> "
  8615. respond " #{$clean_lich_char}favs list "
  8616. respond
  8617. respond " #{$clean_lich_char}alias add [global] <trigger>=<alias> "
  8618. respond " #{$clean_lich_char}alias delete [global] <trigger> "
  8619. respond " #{$clean_lich_char}alias list "
  8620. respond
  8621. respond " #{$clean_lich_char}setting add [global] <setting name> <value>"
  8622. respond " #{$clean_lich_char}setting change [global] <setting name> <value>"
  8623. respond " #{$clean_lich_char}setting delete [global] <setting name> [value]"
  8624. respond " #{$clean_lich_char}setting list"
  8625. respond
  8626. respond 'If you liked this help message, you might also enjoy:'
  8627. respond " #{$clean_lich_char}lnet help"
  8628. respond " #{$clean_lich_char}magic help (infomon must be running)"
  8629. respond " #{$clean_lich_char}go2 help"
  8630. respond " #{$clean_lich_char}repository help"
  8631. respond " #{$clean_lich_char}updater help"
  8632. respond
  8633. else
  8634. script_name = Regexp.escape(cmd.split.first.chomp)
  8635. vars = cmd.split[1..-1].join(' ').scan(/"[^"]+"|[^"\s]+/).collect { |val| val.gsub(/(?!\\)?"/,'') }
  8636. start_script(script_name, vars, flags={:command_line => client_string})
  8637. end
  8638. else
  8639. if $offline_mode
  8640. respond "--- Lich: offline mode: ignoring #{client_string}"
  8641. else
  8642. client_string = "#{$cmd_prefix}bbs\n" if ($frontend =~ /^(?:wizard|avalon)$/) and (client_string == "#{$cmd_prefix}\egbbk\n") # launch forum
  8643. $_SERVER_.puts client_string
  8644. end
  8645. $_CLIENTBUFFER_.push client_string
  8646. end
  8647. Script.new_upstream(client_string)
  8648. end
  8649. def report_errors(&block)
  8650. begin
  8651. block.call
  8652. rescue
  8653. respond "--- error: #{$!}"
  8654. respond $!.backtrace[0..1]
  8655. $stderr.puts "--- error: #{$!}"
  8656. $stderr.puts $!.backtrace
  8657. rescue SyntaxError
  8658. respond "--- error: #{$!}"
  8659. respond $!.backtrace[0..1]
  8660. $stderr.puts "--- error: #{$!}"
  8661. $stderr.puts $!.backtrace
  8662. rescue SystemExit
  8663. nil
  8664. rescue SecurityError
  8665. respond "--- error: #{$!}"
  8666. respond $!.backtrace[0..1]
  8667. $stderr.puts "--- error: #{$!}"
  8668. $stderr.puts $!.backtrace
  8669. rescue ThreadError
  8670. respond "--- error: #{$!}"
  8671. respond $!.backtrace[0..1]
  8672. $stderr.puts "--- error: #{$!}"
  8673. $stderr.puts $!.backtrace
  8674. rescue SystemStackError
  8675. respond "--- error: #{$!}"
  8676. respond $!.backtrace[0..1]
  8677. $stderr.puts "--- error: #{$!}"
  8678. $stderr.puts $!.backtrace
  8679. rescue Exception
  8680. respond "--- error: #{$!}"
  8681. respond $!.backtrace[0..1]
  8682. $stderr.puts "--- error: #{$!}"
  8683. $stderr.puts $!.backtrace
  8684. rescue ScriptError
  8685. respond "--- error: #{$!}"
  8686. respond $!.backtrace[0..1]
  8687. $stderr.puts "--- error: #{$!}"
  8688. $stderr.puts $!.backtrace
  8689. rescue LoadError
  8690. respond "--- error: #{$!}"
  8691. respond $!.backtrace[0..1]
  8692. $stderr.puts "--- error: #{$!}"
  8693. $stderr.puts $!.backtrace
  8694. rescue NoMemoryError
  8695. respond "--- error: #{$!}"
  8696. respond $!.backtrace[0..1]
  8697. $stderr.puts "--- error: #{$!}"
  8698. $stderr.puts $!.backtrace
  8699. rescue
  8700. respond "--- error: #{$!}"
  8701. respond $!.backtrace[0..1]
  8702. $stderr.puts "--- error: #{$!}"
  8703. $stderr.puts $!.backtrace
  8704. end
  8705. end
  8706. sock_keepalive_proc = proc { |sock|
  8707. err_msg = proc { |err|
  8708. err ||= $!
  8709. $stdout.puts "--- error: sock_keepalive_proc: #{err}"
  8710. $stderr.puts "error: sock_keepalive_proc: #{err}"
  8711. $stderr.puts err.backtrace
  8712. }
  8713. begin
  8714. sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true)
  8715. rescue
  8716. err_msg.call($!)
  8717. rescue Exception
  8718. err_msg.call($!)
  8719. end
  8720. }
  8721. read_psinet_installstate = proc { |file_name|
  8722. psinet_installstate = Hash.new
  8723. File.open(file_name) { |f|
  8724. data = f.readlines
  8725. the_keys = Array.new
  8726. data.find_all { |line| line =~ /<Keys/i }.collect { |line| /ref-([0-9]+)/i.match(line).captures.first }.each { |ref|
  8727. data.join("\n").scan(/<SOAP-ENC:Array id="ref-#{ref}".*?<\/SOAP-ENC:Array>/m).each { |stupid|
  8728. stupid.scan(/<item.*?<\/item>|<item.*?\/>/).each { |whore|
  8729. whore =~ /<item .*?id="ref-([0-9]+).*?>(.*?)<\/item>/
  8730. the_keys.push($2)
  8731. }
  8732. }
  8733. }
  8734. the_values = Array.new
  8735. data.find_all { |line| line =~ /<Values/i }.collect { |line| /ref-([0-9]+)/i.match(line).captures.first }.each { |ref|
  8736. data.join("\n").scan(/<SOAP-ENC:Array id="ref-#{ref}".*?<\/SOAP-ENC:Array>/m).each { |stupid|
  8737. stupid.scan(/<item.*?<\/item>|<item.*?\/>/).each { |whore|
  8738. whore =~ /<item .*?id="ref-([0-9]+).*?>(.*?)<\/item>/
  8739. the_values.push($2)
  8740. }
  8741. }
  8742. }
  8743. the_keys.each_index { |index| psinet_installstate[the_keys[index]] = the_values[index] }
  8744. }
  8745. psinet_installstate
  8746. }
  8747. get_real_launcher_cmd = proc {
  8748. psinet_dir = nil
  8749. launcher_cmd = registry_get('HKEY_LOCAL_MACHINE\\Software\\Classes\\Simutronics.Autolaunch\\Shell\\Open\\command\\RealCommand')
  8750. psinet_dir = $1 if launcher_cmd =~ /^"?(.*?)PsiNet2.exe/i
  8751. unless (launcher_cmd =~ /launcher\.exe(?: |")/i)
  8752. launcher_cmd = registry_get('HKEY_LOCAL_MACHINE\\Software\\Classes\\Simutronics.Autolaunch\\Shell\\Open\\command\\')
  8753. psinet_dir = $1 if launcher_cmd =~ /^"?(.*?)PsiNet2.exe/i
  8754. end
  8755. unless (launcher_cmd =~ /launcher\.exe(?: |")/i)
  8756. if psinet_dir and File.exists?(psinet_dir)
  8757. Dir.entries(psinet_dir).each { |f|
  8758. if f =~ /^SageInstaller.*\.InstallState$/i
  8759. psinet_installstate = read_psinet_installstate.call("#{psinet_dir}#{f}")
  8760. launcher_cmd = psinet_installstate['UninstallSalCommand'].gsub(/&#([0-9]+);/) { $1.to_i.chr }
  8761. break
  8762. end
  8763. }
  8764. end
  8765. end
  8766. unless (launcher_cmd =~ /launcher\.exe(?: |")/i)
  8767. launcher_cmd = false
  8768. end
  8769. launcher_cmd
  8770. }
  8771. fix_game_host_port = proc { |gamehost,gameport|
  8772. if (gamehost == 'gs-plat.simutronics.net') and (gameport.to_i == 10121)
  8773. gamehost = 'storm.gs4.game.play.net'
  8774. gameport = 10124
  8775. elsif (gamehost == 'gs3.simutronics.net') and (gameport.to_i == 4900)
  8776. gamehost = 'storm.gs4.game.play.net'
  8777. gameport = 10024
  8778. elsif (gamehost == 'gs4.simutronics.net') and (gameport.to_i == 10321)
  8779. game_host = 'storm.gs4.game.play.net'
  8780. game_port = 10324
  8781. elsif (gamehost == 'prime.dr.game.play.net') and (gameport.to_i == 4901)
  8782. gamehost = 'dr.simutronics.net'
  8783. gameport = 11024
  8784. end
  8785. [ gamehost, gameport ]
  8786. }
  8787. break_game_host_port = proc { |gamehost,gameport|
  8788. if (gamehost == 'storm.gs4.game.play.net') and (gameport.to_i == 10324)
  8789. gamehost = 'gs4.simutronics.net'
  8790. gameport = 10321
  8791. elsif (gamehost == 'storm.gs4.game.play.net') and (gameport.to_i == 10124)
  8792. gamehost = 'gs-plat.simutronics.net'
  8793. gameport = 10121
  8794. elsif (gamehost == 'storm.gs4.game.play.net') and (gameport.to_i == 10024)
  8795. gamehost = 'gs3.simutronics.net'
  8796. gameport = 4900
  8797. elsif (gamehost == 'storm.gs4.game.play.net') and (gameport.to_i == 10324)
  8798. game_host = 'gs4.simutronics.net'
  8799. game_port = 10321
  8800. elsif (gamehost == 'dr.simutronics.net') and (gameport.to_i == 11024)
  8801. gamehost = 'prime.dr.game.play.net'
  8802. gameport = 4901
  8803. end
  8804. [ gamehost, gameport ]
  8805. }
  8806. get_process_list = proc {
  8807. begin
  8808. if (RUBY_PLATFORM =~ /mingw|win/i) and (RUBY_PLATFORM !~ /darwin/i)
  8809. process_query_information = 1024
  8810. process_vm_read = 16
  8811. max_list_size = 100
  8812. access = process_query_information | process_vm_read
  8813. kernel32 = DL.dlopen('kernel32.dll')
  8814. open_process = kernel32['OpenProcess', 'LLLL']
  8815. begin
  8816. psapi = DL.dlopen('psapi.dll')
  8817. enum_processes = psapi['EnumProcesses', 'LPLP']
  8818. get_module_filename_ex = psapi['GetModuleFileNameEx', 'LLLPL']
  8819. rescue
  8820. enum_processes = kernel32['EnumProcesses', 'LPLP']
  8821. get_module_filename_ex = kernel32['GetModuleFileNameEx', 'LLLPL']
  8822. end
  8823. process_ids = DL.malloc(DL.sizeof('L')*max_list_size)
  8824. size = DL.malloc(DL.sizeof('L'))
  8825. buffer = DL.malloc(DL.sizeof('C256'))
  8826. result = enum_processes.call(process_ids, DL.sizeof('L')*max_list_size, size)
  8827. process_list = Array.new
  8828. process_ids.to_a('L')[0...(size.to_a('L')[0]/DL.sizeof('L'))].each { |id|
  8829. handle = open_process.call(access, 0, id)
  8830. unless handle[0] == 0
  8831. result = get_module_filename_ex.call(handle[0], 0, buffer, 255)
  8832. process_list.push buffer.to_s
  8833. end
  8834. }
  8835. process_list
  8836. else
  8837. `ps xo command`.split("\n")[1..-1]
  8838. end
  8839. rescue
  8840. $stderr.puts "error: #{$!}"
  8841. Array.new
  8842. end
  8843. }
  8844. is_win8 = proc {
  8845. if (RUBY_PLATFORM =~ /mingw|win/i) and (RUBY_PLATFORM !~ /darwin/i)
  8846. begin
  8847. kernel32 = DL.dlopen('kernel32.dll')
  8848. get_version_ex = kernel32['GetVersionEx', 'LP']
  8849. os_version_info = DL.malloc(DL.sizeof('LLLLLC128'))
  8850. os_version_info.struct!('LLLLLC128', :dwOSVersionInfoSize, :dwMajorVersion, :dwMinorVersion, :dwBuildNumber, :dwPlatformId, :szCSDVersion)
  8851. os_version_info[:dwOSVersionInfoSize] = DL.sizeof('LLLLLC128')
  8852. get_version_ex.call(os_version_info)
  8853. if (os_version_info[:dwMajorVersion] == 6) and (os_version_info[:dwMinorVersion] >= 2)
  8854. true
  8855. else
  8856. false
  8857. end
  8858. rescue
  8859. false
  8860. end
  8861. else
  8862. false
  8863. end
  8864. }
  8865. system_win8fix = proc { |launcher_cmd|
  8866. # fixme: launcher_cmd may not have quotes
  8867. if is_win8.call and launcher_cmd =~ /^"(.*?)"\s*(.*)$/
  8868. dir_file = $1
  8869. param = $2
  8870. shell32 = DL.dlopen('shell32.dll')
  8871. shell_execute_ex = shell32['ShellExecuteEx', 'LP']
  8872. shell_execute_info = DL.malloc(DL.sizeof('LLLSSSSLLLSLLLL'))
  8873. shell_execute_info.struct!('LLLSSSSLLLSLLLL', :cbSize, :fMask, :hwnd, :lpVerb, :lpFile, :lpParameters, :lpDirectory, :nShow, :hInstApp, :lpIDList, :lpClass, :hkeyClass, :dwHotKey, :hIcon, :hProcess)
  8874. shell_execute_info[:cbSize] = DL.sizeof('LLLSSSSLLLSLLLL')
  8875. shell_execute_info[:fMask] = 0
  8876. shell_execute_info[:hwnd] = 0
  8877. shell_execute_info[:lpVerb] = "open"
  8878. shell_execute_info[:lpParameters] = param
  8879. shell_execute_info[:lpDirectory] = dir_file.slice(/^.*[\/\\]/)
  8880. shell_execute_info[:lpFile] = dir_file.sub(/^.*[\/\\]/, '')
  8881. shell_execute_info[:nShow] = 1
  8882. shell_execute_info[:hInstApp] = 0
  8883. shell_execute_info[:lpIDList] = 0
  8884. shell_execute_info[:lpClass] = ""
  8885. shell_execute_info[:hkeyClass] = 0
  8886. shell_execute_info[:dwHotKey] = 0
  8887. shell_execute_info[:hIcon] = 0
  8888. shell_execute_info[:hProcess] = 0
  8889. shell_execute_ex.call(shell_execute_info)
  8890. else
  8891. system(launcher_cmd)
  8892. end
  8893. }
  8894. reconnect_if_wanted = proc {
  8895. if ARGV.include?('--reconnect') and ARGV.include?('--login') and not $_CLIENTBUFFER_.any? { |cmd| cmd =~ /^(?:\[.*?\])?(?:<c>)?(?:quit|exit)/i }
  8896. $stderr.puts 'info: waiting 60 seconds to reconnect...'
  8897. sleep 60
  8898. $stderr.puts 'info: reconnecting...'
  8899. if (RUBY_PLATFORM =~ /mingw|win/i) and (RUBY_PLATFORM !~ /darwin/i)
  8900. args = [ 'rubyw.exe' ]
  8901. else
  8902. args = [ 'ruby' ]
  8903. end
  8904. args.push $PROGRAM_NAME.slice(/[^\/\\]+$/)
  8905. args.concat ARGV
  8906. args.push '--reconnected' unless args.include?('--reconnected')
  8907. exec args.join(' ')
  8908. end
  8909. }
  8910. begin
  8911. undef :abort
  8912. alias :mana :checkmana
  8913. alias :mana? :checkmana
  8914. alias :max_mana :maxmana
  8915. alias :health :checkhealth
  8916. alias :health? :checkhealth
  8917. alias :spirit :checkspirit
  8918. alias :spirit? :checkspirit
  8919. alias :stamina :checkstamina
  8920. alias :stamina? :checkstamina
  8921. alias :stunned? :checkstunned
  8922. alias :bleeding? :checkbleeding
  8923. alias :reallybleeding? :checkreallybleeding
  8924. alias :dead? :checkdead
  8925. alias :hiding? :checkhidden
  8926. alias :hidden? :checkhidden
  8927. alias :hidden :checkhidden
  8928. alias :checkhiding :checkhidden
  8929. alias :invisible? :checkinvisible
  8930. alias :standing? :checkstanding
  8931. alias :kneeling? :checkkneeling
  8932. alias :sitting? :checksitting
  8933. alias :stance? :checkstance
  8934. alias :stance :checkstance
  8935. alias :joined? :checkgrouped
  8936. alias :checkjoined :checkgrouped
  8937. alias :group? :checkgrouped
  8938. alias :myname? :checkname
  8939. alias :active? :checkspell
  8940. alias :righthand? :checkright
  8941. alias :lefthand? :checkleft
  8942. alias :righthand :checkright
  8943. alias :lefthand :checkleft
  8944. alias :mind? :checkmind
  8945. alias :checkactive :checkspell
  8946. alias :forceput :fput
  8947. alias :send_script :send_scripts
  8948. alias :stop_scripts :stop_script
  8949. alias :kill_scripts :stop_script
  8950. alias :kill_script :stop_script
  8951. alias :fried? :checkfried
  8952. alias :saturated? :checksaturated
  8953. alias :webbed? :checkwebbed
  8954. alias :pause_scripts :pause_script
  8955. alias :roomdescription? :checkroomdescrip
  8956. alias :prepped? :checkprep
  8957. alias :checkprepared :checkprep
  8958. alias :unpause_scripts :unpause_script
  8959. alias :priority? :setpriority
  8960. alias :checkoutside :outside?
  8961. alias :toggle_status :status_tags
  8962. alias :encumbrance? :checkencumbrance
  8963. alias :bounty? :checkbounty
  8964. alias $_PSINET_ $_CLIENT_
  8965. alias $_PSINETSTRING_ $_CLIENTSTRING_
  8966. alias $_PSINETBUFFER_ $_CLIENTBUFFER_
  8967. rescue
  8968. $stdout.puts "--- error: #{$!}"
  8969. $stderr.puts "error: #{$!}"
  8970. $stderr.puts $!.backtrace
  8971. end
  8972. # backward compatibility - this variable was most often used by scripts to tell if the game stream was XML
  8973. $stormfront = true
  8974. if (RUBY_PLATFORM =~ /mingw|win/i) and (RUBY_PLATFORM !~ /darwin/i)
  8975. wine_dir = wine_bin = nil
  8976. else
  8977. if ENV['WINEPREFIX'] and File.exists?(ENV['WINEPREFIX'].to_s)
  8978. wine_dir = ENV['WINEPREFIX']
  8979. elsif ENV['HOME'] and File.exists?(ENV['HOME'] + '/.wine')
  8980. wine_dir = ENV['HOME'] + '/.wine'
  8981. else
  8982. wine_dir = nil
  8983. end
  8984. begin
  8985. wine_bin = `which wine`.strip
  8986. rescue
  8987. wine_bin = nil
  8988. end
  8989. if wine_bin.nil? or wine_bin.empty?
  8990. wine_bin = registry_get('HKEY_LOCAL_MACHINE\\Software\\Simutronics\\Launcher\\Wine')
  8991. end
  8992. unless wine_bin and not wine_bin.empty? and File.exists?(wine_bin)
  8993. wine_bin = nil
  8994. end
  8995. end
  8996. if ARGV.include?('--install')
  8997. psinet_compatible = ARGV.any? { |arg| arg =~ /^--psinet-compat(?:ible)?/ }
  8998. if install_to_registry(psinet_compatible)
  8999. $stdout.puts 'Install was successful.'
  9000. $stderr.puts 'Install was successful.'
  9001. else
  9002. $stdout.puts 'Install failed.'
  9003. $stderr.puts 'Install failed.'
  9004. end
  9005. exit
  9006. elsif ARGV.include?('--uninstall')
  9007. if uninstall_from_registry
  9008. $stdout.puts 'Uninstall was successful.'
  9009. $stderr.puts 'Uninstall was successful.'
  9010. else
  9011. $stdout.puts 'Uninstall failed.'
  9012. $stderr.puts 'Uninstall failed.'
  9013. end
  9014. exit
  9015. end
  9016. if launch_file = ARGV.find { |arg| arg =~ /\.sal$|Gse\.~xt$/i }
  9017. unless File.exists?(launch_file)
  9018. $stderr.puts "warning: launch file does not exist: #{launch_file}"
  9019. launch_file = ARGV.join(' ').slice(/[A-Z]:\\.+\.(?:sal|~xt)/i)
  9020. unless File.exists?(launch_file)
  9021. $stderr.puts "warning: launch file does not exist: #{launch_file}"
  9022. if wine_dir
  9023. launch_file = "#{wine_dir}/drive_c/#{launch_file[3..-1].split('\\').join('/')}"
  9024. unless File.exists?(launch_file)
  9025. $stdout.puts "error: launch file does not exist: #{launch_file}"
  9026. $stderr.puts "error: launch file does not exist: #{launch_file}"
  9027. exit
  9028. end
  9029. end
  9030. end
  9031. end
  9032. $stderr.puts "info: launch file: #{launch_file}"
  9033. if launch_file =~ /SGE\.sal/i
  9034. unless launcher_cmd = get_real_launcher_cmd.call
  9035. $stdout.puts 'error: failed to find the Simutronics launcher'
  9036. $stderr.puts 'error: failed to find the Simutronics launcher'
  9037. exit(1)
  9038. end
  9039. launcher_cmd = "#{wine_bin} #{launcher_cmd}" if wine_bin
  9040. launcher_cmd.sub!('%1', launch_file)
  9041. $stderr.puts "info: launcher_cmd: #{launcher_cmd}"
  9042. system_win8fix.call(launcher_cmd)
  9043. exit
  9044. end
  9045. else
  9046. launch_file = nil
  9047. $stderr.puts "info: no launch file given"
  9048. end
  9049. if arg = ARGV.find { |a| (a == '-g') or (a == '--game') }
  9050. game_host, game_port = ARGV[ARGV.index(arg)+1].split(':')
  9051. game_port = game_port.to_i
  9052. if ARGV.any? { |arg| (arg == '-s') or (arg == '--stormfront') }
  9053. $frontend = 'stormfront'
  9054. elsif ARGV.any? { |arg| (arg == '-w') or (arg == '--wizard') }
  9055. $frontend = 'wizard'
  9056. elsif ARGV.any? { |arg| arg == '--avalon' }
  9057. $frontend = 'avalon'
  9058. else
  9059. $frontend = 'unknown'
  9060. end
  9061. elsif ARGV.include?('--gemstone')
  9062. if ARGV.include?('--platinum')
  9063. $platinum = true
  9064. if ARGV.any? { |arg| (arg == '-s') or (arg == '--stormfront') }
  9065. game_host = 'storm.gs4.game.play.net'
  9066. game_port = 10124
  9067. $frontend = 'stormfront'
  9068. else
  9069. game_host = 'gs-plat.simutronics.net'
  9070. game_port = 10121
  9071. if ARGV.any? { |arg| arg == '--avalon' }
  9072. $frontend = 'avalon'
  9073. else
  9074. $frontend = 'wizard'
  9075. end
  9076. end
  9077. else
  9078. $platinum = false
  9079. if ARGV.any? { |arg| (arg == '-s') or (arg == '--stormfront') }
  9080. game_host = 'storm.gs4.game.play.net'
  9081. game_port = 10024
  9082. $frontend = 'stormfront'
  9083. else
  9084. game_host = 'gs3.simutronics.net'
  9085. game_port = 4900
  9086. if ARGV.any? { |arg| arg == '--avalon' }
  9087. $frontend = 'avalon'
  9088. else
  9089. $frontend = 'wizard'
  9090. end
  9091. end
  9092. end
  9093. elsif ARGV.include?('--shattered')
  9094. $platinum = false
  9095. if ARGV.any? { |arg| (arg == '-s') or (arg == '--stormfront') }
  9096. game_host = 'storm.gs4.game.play.net'
  9097. game_port = 10324
  9098. $frontend = 'stormfront'
  9099. else
  9100. game_host = 'gs4.simutronics.net'
  9101. game_port = 10321
  9102. if ARGV.any? { |arg| arg == '--avalon' }
  9103. $frontend = 'avalon'
  9104. else
  9105. $frontend = 'wizard'
  9106. end
  9107. end
  9108. elsif ARGV.include?('--dragonrealms')
  9109. if ARGV.include?('--platinum')
  9110. $platinum = true
  9111. if ARGV.any? { |arg| (arg == '-s') or (arg == '--stormfront') }
  9112. $stdout.puts "fixme"
  9113. $stderr.puts "fixme"
  9114. exit
  9115. $frontend = 'stormfront'
  9116. else
  9117. $stdout.puts "fixme"
  9118. $stderr.puts "fixme"
  9119. exit
  9120. $frontend = 'wizard'
  9121. end
  9122. else
  9123. $platinum = false
  9124. if ARGV.any? { |arg| (arg == '-s') or (arg == '--stormfront') }
  9125. $frontend = 'stormfront'
  9126. $stdout.puts "fixme"
  9127. $stderr.puts "fixme"
  9128. exit
  9129. else
  9130. game_host = 'dr.simutronics.net'
  9131. game_port = 4901
  9132. if ARGV.any? { |arg| arg == '--avalon' }
  9133. $frontend = 'avalon'
  9134. else
  9135. $frontend = 'wizard'
  9136. end
  9137. end
  9138. end
  9139. else
  9140. game_host, game_port = nil, nil
  9141. $stderr.puts "info: no force-mode info given"
  9142. end
  9143. main_thread = Thread.new {
  9144. test_mode = false
  9145. sge = nil
  9146. $ZLIB_STREAM = false
  9147. $SEND_CHARACTER = '>'
  9148. $cmd_prefix = '<c>'
  9149. LichSettings.load
  9150. LichSettings['lich_char'] ||= ';'
  9151. LichSettings['cache_serverbuffer'] = false if LichSettings['cache_serverbuffer'].nil?
  9152. LichSettings['serverbuffer_max_size'] ||= 300
  9153. LichSettings['serverbuffer_min_size'] ||= 200
  9154. LichSettings['clientbuffer_max_size'] ||= 100
  9155. LichSettings['clientbuffer_min_size'] ||= 50
  9156. LichSettings['trusted_scripts'] ||= [ 'updater', 'infomon', 'lnet', 'narost', 'repository' ]
  9157. $clean_lich_char = LichSettings['lich_char']
  9158. $lich_char = Regexp.escape("#{$clean_lich_char}")
  9159. launch_data = nil
  9160. if ARGV.include?('--login')
  9161. if File.exists?("#{$data_dir}entry.dat")
  9162. entry_data = File.open("#{$data_dir}entry.dat", 'r') { |file|
  9163. begin
  9164. Marshal.load(file.read.unpack('m').first)
  9165. rescue
  9166. Array.new
  9167. end
  9168. }
  9169. else
  9170. entry_data = Array.new
  9171. end
  9172. char_name = ARGV[ARGV.index('--login')+1].capitalize
  9173. if ARGV.include?('--gemstone')
  9174. if ARGV.include?('--platinum')
  9175. data = entry_data.find { |d| (d[:char_name] == char_name) and (d[:game_code] == 'GSX') }
  9176. elsif ARGV.include?('--shattered')
  9177. data = entry_data.find { |d| (d[:char_name] == char_name) and (d[:game_code] == 'GSF') }
  9178. else
  9179. data = entry_data.find { |d| (d[:char_name] == char_name) and (d[:game_code] == 'GS3') }
  9180. end
  9181. elsif ARGV.include?('--shattered')
  9182. data = entry_data.find { |d| (d[:char_name] == char_name) and (d[:game_code] == 'GSF') }
  9183. else
  9184. data = entry_data.find { |d| (d[:char_name] == char_name) }
  9185. end
  9186. if data
  9187. $stderr.puts "info: using quick game entry settings for #{char_name}"
  9188. msgbox = proc { |msg|
  9189. if HAVE_GTK
  9190. done = false
  9191. Gtk.queue {
  9192. dialog = Gtk::MessageDialog.new(nil, Gtk::Dialog::DESTROY_WITH_PARENT, Gtk::MessageDialog::QUESTION, Gtk::MessageDialog::BUTTONS_CLOSE, msg)
  9193. dialog.run
  9194. dialog.destroy
  9195. done = true
  9196. }
  9197. sleep "0.1".to_f until done
  9198. else
  9199. $stdout.puts(msg)
  9200. $stderr.puts(msg)
  9201. end
  9202. }
  9203. login_server = nil
  9204. connect_thread = nil
  9205. timeout_thread = Thread.new {
  9206. sleep 30
  9207. $stdout.puts "error: timed out connecting to eaccess.play.net:7900"
  9208. $stderr.puts "error: timed out connecting to eaccess.play.net:7900"
  9209. connect_thread.kill rescue()
  9210. login_server = nil
  9211. }
  9212. connect_thread = Thread.new {
  9213. begin
  9214. login_server = TCPSocket.new('eaccess.play.net', 7900)
  9215. rescue
  9216. login_server = nil
  9217. $stdout.puts "error connecting to server: #{$!}"
  9218. $stderr.puts "error connecting to server: #{$!}"
  9219. end
  9220. }
  9221. connect_thread.join
  9222. timeout_thread.kill rescue()
  9223. if login_server
  9224. login_server.puts "K\n"
  9225. hashkey = login_server.gets
  9226. if 'test'[0].class == String
  9227. password = data[:password].split('').collect { |c| c.getbyte(0) }
  9228. hashkey = hashkey.split('').collect { |c| c.getbyte(0) }
  9229. else
  9230. password = data[:password].split('').collect { |c| c[0] }
  9231. hashkey = hashkey.split('').collect { |c| c[0] }
  9232. end
  9233. password.each_index { |i| password[i] = ((password[i]-32)^hashkey[i])+32 }
  9234. password = password.collect { |c| c.chr }.join
  9235. login_server.puts "A\t#{data[:user_id]}\t#{password}\n"
  9236. password = nil
  9237. response = login_server.gets
  9238. login_key = /KEY\t([^\t]+)\t/.match(response).captures.first
  9239. if login_key
  9240. login_server.puts "M\n"
  9241. response = login_server.gets
  9242. if response =~ /^M\t/
  9243. login_server.puts "F\t#{data[:game_code]}\n"
  9244. response = login_server.gets
  9245. if response =~ /NORMAL|PREMIUM|TRIAL|INTERNAL/
  9246. login_server.puts "G\t#{data[:game_code]}\n"
  9247. login_server.gets
  9248. login_server.puts "P\t#{data[:game_code]}\n"
  9249. login_server.gets
  9250. login_server.puts "C\n"
  9251. 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]
  9252. login_server.puts "L\t#{char_code}\tSTORM\n"
  9253. response = login_server.gets
  9254. if response =~ /^L\t/
  9255. login_server.close unless login_server.closed?
  9256. launch_data = response.sub(/^L\tOK\t/, '').split("\t")
  9257. if data[:frontend] == 'wizard'
  9258. launch_data.collect! { |line| line.sub(/GAMEFILE=.+/, 'GAMEFILE=WIZARD.EXE').sub(/GAME=.+/, 'GAME=WIZ').sub(/FULLGAMENAME=.+/, 'FULLGAMENAME=Wizard Front End') }
  9259. end
  9260. if data[:custom_launch]
  9261. launch_data.push "CUSTOMLAUNCH=#{data[:custom_launch]}"
  9262. if data[:custom_launch_dir]
  9263. launch_data.push "CUSTOMLAUNCHDIR=#{data[:custom_launch_dir]}"
  9264. end
  9265. end
  9266. else
  9267. login_server.close unless login_server.closed?
  9268. $stdout.puts "error: unrecognized response from server. (#{response})"
  9269. $stderr.puts "error: unrecognized response from server. (#{response})"
  9270. end
  9271. else
  9272. login_server.close unless login_server.closed?
  9273. $stdout.puts "error: unrecognized response from server. (#{response})"
  9274. $stderr.puts "error: unrecognized response from server. (#{response})"
  9275. end
  9276. else
  9277. login_server.close unless login_server.closed?
  9278. $stdout.puts "error: unrecognized response from server. (#{response})"
  9279. $stderr.puts "error: unrecognized response from server. (#{response})"
  9280. end
  9281. else
  9282. login_server.close unless login_server.closed?
  9283. $stdout.puts "Something went wrong... probably invalid user id and/or password.\nserver response: #{response}"
  9284. $stderr.puts "Something went wrong... probably invalid user id and/or password.\nserver response: #{response}"
  9285. reconnect_if_wanted.call
  9286. end
  9287. else
  9288. $stdout.puts "error: failed to connect to server"
  9289. $stderr.puts "error: failed to connect to server"
  9290. reconnect_if_wanted.call
  9291. $stderr.puts "info: exiting..."
  9292. Gtk.queue { Gtk.main_quit } if HAVE_GTK
  9293. exit
  9294. end
  9295. else
  9296. $stdout.puts "error: failed to find login data for #{char_name}"
  9297. $stderr.puts "error: failed to find login data for #{char_name}"
  9298. end
  9299. elsif HAVE_GTK and ARGV.empty?
  9300. if File.exists?("#{$data_dir}entry.dat")
  9301. entry_data = File.open("#{$data_dir}entry.dat", 'r') { |file|
  9302. begin
  9303. Marshal.load(file.read.unpack('m').first).sort { |a,b| a[:char_name] <=> b[:char_name] }
  9304. rescue
  9305. Array.new
  9306. end
  9307. }
  9308. else
  9309. entry_data = Array.new
  9310. end
  9311. save_entry_data = false
  9312. done = false
  9313. Gtk.queue {
  9314. login_server = nil
  9315. window = nil
  9316. install_tab_loaded = false
  9317. msgbox = proc { |msg|
  9318. dialog = Gtk::MessageDialog.new(window, Gtk::Dialog::DESTROY_WITH_PARENT, Gtk::MessageDialog::QUESTION, Gtk::MessageDialog::BUTTONS_CLOSE, msg)
  9319. dialog.run
  9320. dialog.destroy
  9321. }
  9322. #
  9323. # quick game entry tab
  9324. #
  9325. if entry_data.empty?
  9326. box = Gtk::HBox.new
  9327. box.pack_start(Gtk::Label.new('You have no saved login info.'), true, true, 0)
  9328. quick_game_entry_tab = Gtk::VBox.new
  9329. quick_game_entry_tab.border_width = 5
  9330. quick_game_entry_tab.pack_start(box, true, true, 0)
  9331. else
  9332. quick_box = Gtk::VBox.new
  9333. entry_data.each { |login_info|
  9334. label = Gtk::Label.new("#{login_info[:char_name]} (#{login_info[:game_name]})")
  9335. play_button = Gtk::Button.new('Play')
  9336. remove_button = Gtk::Button.new('X')
  9337. char_box = Gtk::HBox.new
  9338. char_box.pack_start(label, false, false, 6)
  9339. char_box.pack_end(remove_button, false, false, 0)
  9340. char_box.pack_end(play_button, false, false, 0)
  9341. quick_box.pack_start(char_box, false, false, 0)
  9342. play_button.signal_connect('clicked') {
  9343. play_button.sensitive = false
  9344. begin
  9345. login_server = nil
  9346. connect_thread = Thread.new {
  9347. login_server = TCPSocket.new('eaccess.play.net', 7900)
  9348. }
  9349. 300.times {
  9350. sleep 0.1
  9351. break unless connect_thread.status
  9352. }
  9353. if connect_thread.status
  9354. connect_thread.kill rescue()
  9355. msgbox.call "error: timed out connecting to eaccess.play.net:7900"
  9356. end
  9357. rescue
  9358. msgbox.call "error connecting to server: #{$!}"
  9359. play_button.sensitive = true
  9360. end
  9361. if login_server
  9362. login_server.puts "K\n"
  9363. hashkey = login_server.gets
  9364. if 'test'[0].class == String
  9365. password = login_info[:password].split('').collect { |c| c.getbyte(0) }
  9366. hashkey = hashkey.split('').collect { |c| c.getbyte(0) }
  9367. else
  9368. password = login_info[:password].split('').collect { |c| c[0] }
  9369. hashkey = hashkey.split('').collect { |c| c[0] }
  9370. end
  9371. password.each_index { |i| password[i] = ((password[i]-32)^hashkey[i])+32 }
  9372. password = password.collect { |c| c.chr }.join
  9373. login_server.puts "A\t#{login_info[:user_id]}\t#{password}\n"
  9374. password = nil
  9375. response = login_server.gets
  9376. login_key = /KEY\t([^\t]+)\t/.match(response).captures.first
  9377. if login_key
  9378. login_server.puts "M\n"
  9379. response = login_server.gets
  9380. if response =~ /^M\t/
  9381. login_server.puts "F\t#{login_info[:game_code]}\n"
  9382. response = login_server.gets
  9383. if response =~ /NORMAL|PREMIUM|TRIAL|INTERNAL/
  9384. login_server.puts "G\t#{login_info[:game_code]}\n"
  9385. login_server.gets
  9386. login_server.puts "P\t#{login_info[:game_code]}\n"
  9387. login_server.gets
  9388. login_server.puts "C\n"
  9389. 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]
  9390. login_server.puts "L\t#{char_code}\tSTORM\n"
  9391. response = login_server.gets
  9392. if response =~ /^L\t/
  9393. login_server.close unless login_server.closed?
  9394. launch_data = response.sub(/^L\tOK\t/, '').split("\t")
  9395. if login_info[:frontend] == 'wizard'
  9396. launch_data.collect! { |line| line.sub(/GAMEFILE=.+/, 'GAMEFILE=WIZARD.EXE').sub(/GAME=.+/, 'GAME=WIZ').sub(/FULLGAMENAME=.+/, 'FULLGAMENAME=Wizard Front End') }
  9397. end
  9398. if login_info[:custom_launch]
  9399. launch_data.push "CUSTOMLAUNCH=#{login_info[:custom_launch]}"
  9400. if login_info[:custom_launch_dir]
  9401. launch_data.push "CUSTOMLAUNCHDIR=#{login_info[:custom_launch_dir]}"
  9402. end
  9403. end
  9404. window.destroy
  9405. done = true
  9406. else
  9407. login_server.close unless login_server.closed?
  9408. msgbox.call("Unrecognized response from server. (#{response})")
  9409. play_button.sensitive = true
  9410. end
  9411. else
  9412. login_server.close unless login_server.closed?
  9413. msgbox.call("Unrecognized response from server. (#{response})")
  9414. play_button.sensitive = true
  9415. end
  9416. else
  9417. login_server.close unless login_server.closed?
  9418. msgbox.call("Unrecognized response from server. (#{response})")
  9419. play_button.sensitive = true
  9420. end
  9421. else
  9422. login_server.close unless login_server.closed?
  9423. msgbox.call "Something went wrong... probably invalid user id and/or password.\nserver response: #{response}"
  9424. play_button.sensitive = true
  9425. end
  9426. else
  9427. msgbox.call "error: failed to connect to server"
  9428. play_button.sensitive = true
  9429. end
  9430. }
  9431. remove_button.signal_connect('clicked') {
  9432. entry_data.delete(login_info)
  9433. save_entry_data = true
  9434. char_box.visible = false
  9435. }
  9436. }
  9437. adjustment = Gtk::Adjustment.new(0, 0, 1000, 5, 20, 500)
  9438. quick_vp = Gtk::Viewport.new(adjustment, adjustment)
  9439. quick_vp.add(quick_box)
  9440. quick_sw = Gtk::ScrolledWindow.new
  9441. quick_sw.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_ALWAYS)
  9442. quick_sw.add(quick_vp)
  9443. quick_game_entry_tab = Gtk::VBox.new
  9444. quick_game_entry_tab.border_width = 5
  9445. quick_game_entry_tab.pack_start(quick_sw, true, true, 5)
  9446. end
  9447. #
  9448. # game entry tab
  9449. #
  9450. user_id_entry = Gtk::Entry.new
  9451. pass_entry = Gtk::Entry.new
  9452. pass_entry.visibility = false
  9453. login_table = Gtk::Table.new(2, 2, false)
  9454. login_table.attach(Gtk::Label.new('User ID:'), 0, 1, 0, 1, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 5, 5)
  9455. login_table.attach(user_id_entry, 1, 2, 0, 1, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 5, 5)
  9456. login_table.attach(Gtk::Label.new('Password:'), 0, 1, 1, 2, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 5, 5)
  9457. login_table.attach(pass_entry, 1, 2, 1, 2, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 5, 5)
  9458. disconnect_button = Gtk::Button.new(' Disconnect ')
  9459. disconnect_button.sensitive = false
  9460. connect_button = Gtk::Button.new(' Connect ')
  9461. login_button_box = Gtk::HBox.new
  9462. login_button_box.pack_end(connect_button, false, false, 5)
  9463. login_button_box.pack_end(disconnect_button, false, false, 5)
  9464. liststore = Gtk::ListStore.new(String, String, String, String)
  9465. liststore.set_sort_column_id(1, Gtk::SORT_ASCENDING)
  9466. renderer = Gtk::CellRendererText.new
  9467. renderer.background = 'white'
  9468. treeview = Gtk::TreeView.new(liststore)
  9469. treeview.height_request = 160
  9470. col = Gtk::TreeViewColumn.new("Game", renderer, :text => 1, :background_set => 2)
  9471. col.resizable = true
  9472. treeview.append_column(col)
  9473. col = Gtk::TreeViewColumn.new("Character", renderer, :text => 3, :background_set => 2)
  9474. col.resizable = true
  9475. treeview.append_column(col)
  9476. sw = Gtk::ScrolledWindow.new
  9477. sw.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_ALWAYS)
  9478. sw.add(treeview)
  9479. wizard_option = Gtk::RadioButton.new('Wizard')
  9480. stormfront_option = Gtk::RadioButton.new(wizard_option, 'Stormfront')
  9481. suks_option = Gtk::RadioButton.new(wizard_option, 'suks')
  9482. frontend_box = Gtk::HBox.new(false, 10)
  9483. frontend_box.pack_start(wizard_option, false, false, 0)
  9484. frontend_box.pack_start(stormfront_option, false, false, 0)
  9485. #frontend_box.pack_start(suks_option, false, false, 0)
  9486. custom_launch_option = Gtk::CheckButton.new('Custom launch command')
  9487. custom_launch_entry = Gtk::ComboBoxEntry.new()
  9488. custom_launch_entry.child.text = "(enter custom launch command)"
  9489. custom_launch_entry.append_text("Wizard.Exe /GGS /H127.0.0.1 /P%port% /K%key%")
  9490. custom_launch_entry.append_text("Stormfront.exe /GGS /H127.0.0.1 /P%port% /K%key%")
  9491. custom_launch_dir = Gtk::ComboBoxEntry.new()
  9492. custom_launch_dir.child.text = "(enter working directory for command)"
  9493. custom_launch_dir.append_text("../wizard")
  9494. custom_launch_dir.append_text("../StormFront")
  9495. make_quick_option = Gtk::CheckButton.new('Save this info for quick game entry')
  9496. play_button = Gtk::Button.new(' Play ')
  9497. play_button.sensitive = false
  9498. play_button_box = Gtk::HBox.new
  9499. play_button_box.pack_end(play_button, false, false, 5)
  9500. game_entry_tab = Gtk::VBox.new
  9501. game_entry_tab.border_width = 5
  9502. game_entry_tab.pack_start(login_table, false, false, 0)
  9503. game_entry_tab.pack_start(login_button_box, false, false, 0)
  9504. game_entry_tab.pack_start(sw, true, true, 3)
  9505. game_entry_tab.pack_start(frontend_box, false, false, 3)
  9506. game_entry_tab.pack_start(custom_launch_option, false, false, 3)
  9507. game_entry_tab.pack_start(custom_launch_entry, false, false, 3)
  9508. game_entry_tab.pack_start(custom_launch_dir, false, false, 3)
  9509. game_entry_tab.pack_start(make_quick_option, false, false, 3)
  9510. game_entry_tab.pack_start(play_button_box, false, false, 3)
  9511. custom_launch_option.signal_connect('toggled') {
  9512. custom_launch_entry.visible = custom_launch_option.active?
  9513. custom_launch_dir.visible = custom_launch_option.active?
  9514. }
  9515. connect_button.signal_connect('clicked') {
  9516. connect_button.sensitive = false
  9517. user_id_entry.sensitive = false
  9518. pass_entry.sensitive = false
  9519. iter = liststore.append
  9520. iter[1] = 'working...'
  9521. Gtk.queue {
  9522. begin
  9523. login_server = nil
  9524. connect_thread = Thread.new {
  9525. login_server = TCPSocket.new('eaccess.play.net', 7900)
  9526. }
  9527. 300.times {
  9528. sleep 0.1
  9529. break unless connect_thread.status
  9530. }
  9531. if connect_thread.status
  9532. connect_thread.kill rescue()
  9533. msgbox.call "error: timed out connecting to eaccess.play.net:7900"
  9534. end
  9535. rescue
  9536. msgbox.call "error connecting to server: #{$!}"
  9537. connect_button.sensitive = true
  9538. user_id_entry.sensitive = true
  9539. pass_entry.sensitive = true
  9540. end
  9541. disconnect_button.sensitive = true
  9542. if login_server
  9543. login_server.puts "K\n"
  9544. hashkey = login_server.gets
  9545. if 'test'[0].class == String
  9546. password = pass_entry.text.split('').collect { |c| c.getbyte(0) }
  9547. hashkey = hashkey.split('').collect { |c| c.getbyte(0) }
  9548. else
  9549. password = pass_entry.text.split('').collect { |c| c[0] }
  9550. hashkey = hashkey.split('').collect { |c| c[0] }
  9551. end
  9552. # pass_entry.text = String.new
  9553. password.each_index { |i| password[i] = ((password[i]-32)^hashkey[i])+32 }
  9554. password = password.collect { |c| c.chr }.join
  9555. login_server.puts "A\t#{user_id_entry.text}\t#{password}\n"
  9556. password = nil
  9557. response = login_server.gets
  9558. login_key = /KEY\t([^\t]+)\t/.match(response).captures.first
  9559. if login_key
  9560. login_server.puts "M\n"
  9561. response = login_server.gets
  9562. if response =~ /^M\t/
  9563. liststore.clear
  9564. for game in response.sub(/^M\t/, '').scan(/[^\t]+\t[^\t^\n]+/)
  9565. game_code, game_name = game.split("\t")
  9566. login_server.puts "N\t#{game_code}\n"
  9567. if login_server.gets =~ /STORM/
  9568. login_server.puts "F\t#{game_code}\n"
  9569. if login_server.gets =~ /NORMAL|PREMIUM|TRIAL|INTERNAL/
  9570. login_server.puts "G\t#{game_code}\n"
  9571. login_server.gets
  9572. login_server.puts "P\t#{game_code}\n"
  9573. login_server.gets
  9574. login_server.puts "C\n"
  9575. 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]+/)
  9576. char_code, char_name = code_name.split("\t")
  9577. iter = liststore.append
  9578. iter[0] = game_code
  9579. iter[1] = game_name
  9580. iter[2] = char_code
  9581. iter[3] = char_name
  9582. end
  9583. end
  9584. end
  9585. end
  9586. disconnect_button.sensitive = true
  9587. else
  9588. login_server.close unless login_server.closed?
  9589. msgbox.call "Unrecognized response from server (#{response})"
  9590. end
  9591. else
  9592. login_server.close unless login_server.closed?
  9593. disconnect_button.sensitive = false
  9594. connect_button.sensitive = true
  9595. user_id_entry.sensitive = true
  9596. pass_entry.sensitive = true
  9597. msgbox.call "Something went wrong... probably invalid user id and/or password.\nserver response: #{response}"
  9598. end
  9599. end
  9600. }
  9601. }
  9602. treeview.signal_connect('cursor-changed') {
  9603. if login_server
  9604. play_button.sensitive = true
  9605. end
  9606. }
  9607. disconnect_button.signal_connect('clicked') {
  9608. disconnect_button.sensitive = false
  9609. play_button.sensitive = false
  9610. liststore.clear
  9611. login_server.close unless login_server.closed?
  9612. connect_button.sensitive = true
  9613. user_id_entry.sensitive = true
  9614. pass_entry.sensitive = true
  9615. }
  9616. play_button.signal_connect('clicked') {
  9617. play_button.sensitive = false
  9618. game_code = treeview.selection.selected[0]
  9619. char_code = treeview.selection.selected[2]
  9620. if login_server and not login_server.closed?
  9621. login_server.puts "F\t#{game_code}\n"
  9622. login_server.gets
  9623. login_server.puts "G\t#{game_code}\n"
  9624. login_server.gets
  9625. login_server.puts "P\t#{game_code}\n"
  9626. login_server.gets
  9627. login_server.puts "C\n"
  9628. login_server.gets
  9629. login_server.puts "L\t#{char_code}\tSTORM\n"
  9630. response = login_server.gets
  9631. if response =~ /^L\t/
  9632. login_server.close unless login_server.closed?
  9633. port = /GAMEPORT=([0-9]+)/.match(response).captures.first
  9634. host = /GAMEHOST=([^\t\n]+)/.match(response).captures.first
  9635. key = /KEY=([^\t\n]+)/.match(response).captures.first
  9636. launch_data = response.sub(/^L\tOK\t/, '').split("\t")
  9637. login_server.close unless login_server.closed?
  9638. if wizard_option.active?
  9639. launch_data.collect! { |line| line.sub(/GAMEFILE=.+/, "GAMEFILE=WIZARD.EXE").sub(/GAME=.+/, "GAME=WIZ") }
  9640. elsif suks_option.active?
  9641. launch_data.collect! { |line| line.sub(/GAMEFILE=.+/, "GAMEFILE=WIZARD.EXE").sub(/GAME=.+/, "GAME=SUKS") }
  9642. end
  9643. if custom_launch_option.active?
  9644. launch_data.push "CUSTOMLAUNCH=#{custom_launch_entry.child.text}"
  9645. unless custom_launch_dir.child.text.empty? or custom_launch_dir.child.text == "(enter working directory for command)"
  9646. launch_data.push "CUSTOMLAUNCHDIR=#{custom_launch_dir.child.text}"
  9647. end
  9648. end
  9649. if make_quick_option.active?
  9650. if wizard_option.active?
  9651. frontend = 'wizard'
  9652. else
  9653. frontend = 'stormfront'
  9654. end
  9655. if custom_launch_option.active?
  9656. custom_launch = custom_launch_entry.child.text
  9657. if custom_launch_dir.child.text.empty? or custom_launch_dir.child.text == "(enter working directory for command)"
  9658. custom_launch_dir = nil
  9659. else
  9660. custom_launch_dir = custom_launch_dir.child.text
  9661. end
  9662. else
  9663. custom_launch = nil
  9664. custom_launch_dir = nil
  9665. end
  9666. 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 }
  9667. save_entry_data = true
  9668. end
  9669. user_id_entry.text = String.new
  9670. pass_entry.text = String.new
  9671. window.destroy
  9672. done = true
  9673. else
  9674. login_server.close unless login_server.closed?
  9675. disconnect_button.sensitive = false
  9676. play_button.sensitive = false
  9677. connect_button.sensitive = true
  9678. user_id_entry.sensitive = true
  9679. pass_entry.sensitive = true
  9680. end
  9681. else
  9682. disconnect_button.sensitive = false
  9683. play_button.sensitive = false
  9684. connect_button.sensitive = true
  9685. user_id_entry.sensitive = true
  9686. pass_entry.sensitive = true
  9687. end
  9688. }
  9689. user_id_entry.signal_connect('activate') {
  9690. pass_entry.grab_focus
  9691. }
  9692. pass_entry.signal_connect('activate') {
  9693. connect_button.clicked
  9694. }
  9695. #
  9696. # install tab
  9697. #
  9698. if (RUBY_PLATFORM =~ /mingw|win/i) and (RUBY_PLATFORM !~ /darwin/i)
  9699. begin
  9700. kernel32 = DL.dlopen('kernel32.dll')
  9701. get_version_ex = kernel32['GetVersionEx', 'LP']
  9702. os_version_info = DL.malloc(DL.sizeof('LLLLLC128'))
  9703. os_version_info.struct!('LLLLLC128', :dwOSVersionInfoSize, :dwMajorVersion, :dwMinorVersion, :dwBuildNumber, :dwPlatformId, :szCSDVersion)
  9704. os_version_info[:dwOSVersionInfoSize] = DL.sizeof('LLLLLC128')
  9705. get_version_ex.call(os_version_info)
  9706. if os_version_info[:dwMajorVersion] >= 6 # >= Vista
  9707. get_current_process = kernel32['GetCurrentProcess', 'L']
  9708. close_handle = kernel32['CloseHandle', 'LL']
  9709. advapi32 = DL.dlopen('advapi32.dll')
  9710. open_process_token = advapi32['OpenProcessToken', 'LLLP']
  9711. get_token_information = advapi32['GetTokenInformation', 'LLLPLP']
  9712. token_handle = DL.malloc(DL.sizeof('L')); token_handle.struct!('L', :handle)
  9713. open_process_token.call(get_current_process.call[0], 8, token_handle) # 8 = TOKEN_QUERY
  9714. token_information = DL.malloc(DL.sizeof('L')); token_information.struct!('L', :info)
  9715. return_length = DL.malloc(DL.sizeof('L')); return_length.struct!('L', :length)
  9716. get_token_information.call(token_handle[:handle], 20, token_information, DL.sizeof('L'), return_length) # 20 = TokenElevation
  9717. close_handle.call(token_handle[:handle])
  9718. if token_information[:info] == 0
  9719. need_admin = true
  9720. else
  9721. need_admin = false
  9722. end
  9723. else
  9724. need_admin = false
  9725. end
  9726. rescue
  9727. need_admin = false
  9728. end
  9729. else
  9730. need_admin = false
  9731. end
  9732. if need_admin
  9733. refresh_button = nil
  9734. install_tab_loaded = true
  9735. restart_button = Gtk::Button.new('Restart Lich as Admin')
  9736. box = Gtk::HBox.new
  9737. box.pack_start(restart_button, true, false, 5)
  9738. install_tab = Gtk::VBox.new
  9739. install_tab.border_width = 5
  9740. install_tab.pack_start(box, true, false, 5)
  9741. restart_button.signal_connect('clicked') {
  9742. begin
  9743. shell32 = DL.dlopen('shell32.dll')
  9744. shell_execute_ex = shell32['ShellExecuteEx', 'LP']
  9745. shell_execute_info = DL.malloc(DL.sizeof('LLLSSSSLLLSLLLL'))
  9746. shell_execute_info.struct!('LLLSSSSLLLSLLLL', :cbSize, :fMask, :hwnd, :lpVerb, :lpFile, :lpParameters, :lpDirectory, :nShow, :hInstApp, :lpIDList, :lpClass, :hkeyClass, :dwHotKey, :hIcon, :hProcess)
  9747. shell_execute_info[:cbSize] = DL.sizeof('LLLSSSSLLLSLLLL')
  9748. shell_execute_info[:fMask] = 0
  9749. shell_execute_info[:hwnd] = 0
  9750. shell_execute_info[:lpVerb] = "runas"
  9751. if ENV['OCRA_EXECUTABLE']
  9752. shell_execute_info[:lpFile] = ENV['OCRA_EXECUTABLE'].split(/\/|\\/).last
  9753. shell_execute_info[:lpParameters] = ''
  9754. else
  9755. shell_execute_info[:lpFile] = 'rubyw.exe'
  9756. shell_execute_info[:lpParameters] = $PROGRAM_NAME.split(/\/|\\/).last
  9757. end
  9758. shell_execute_info[:lpDirectory] = $lich_dir.sub(/\/$/, '').tr("/", "\\")
  9759. shell_execute_info[:nShow] = 1
  9760. shell_execute_info[:hInstApp] = 0
  9761. shell_execute_info[:lpIDList] = 0
  9762. shell_execute_info[:lpClass] = ""
  9763. shell_execute_info[:hkeyClass] = 0
  9764. shell_execute_info[:dwHotKey] = 0
  9765. shell_execute_info[:hIcon] = 0
  9766. shell_execute_info[:hProcess] = 0
  9767. shell_execute_ex.call(shell_execute_info)
  9768. exit
  9769. rescue
  9770. msgbox.call($!)
  9771. end
  9772. }
  9773. else
  9774. website_order_entry_1 = Gtk::Entry.new
  9775. website_order_entry_1.editable = false
  9776. website_order_entry_2 = Gtk::Entry.new
  9777. website_order_entry_2.editable = false
  9778. website_order_entry_3 = Gtk::Entry.new
  9779. website_order_entry_3.editable = false
  9780. website_order_box = Gtk::VBox.new
  9781. website_order_box.pack_start(website_order_entry_1, true, true, 5)
  9782. website_order_box.pack_start(website_order_entry_2, true, true, 5)
  9783. website_order_box.pack_start(website_order_entry_3, true, true, 5)
  9784. website_order_frame = Gtk::Frame.new('Website Launch Order')
  9785. website_order_frame.add(website_order_box)
  9786. sge_order_entry_1 = Gtk::Entry.new
  9787. sge_order_entry_1.editable = false
  9788. sge_order_entry_2 = Gtk::Entry.new
  9789. sge_order_entry_2.editable = false
  9790. sge_order_entry_3 = Gtk::Entry.new
  9791. sge_order_entry_3.editable = false
  9792. sge_order_box = Gtk::VBox.new
  9793. sge_order_box.pack_start(sge_order_entry_1, true, true, 5)
  9794. sge_order_box.pack_start(sge_order_entry_2, true, true, 5)
  9795. sge_order_box.pack_start(sge_order_entry_3, true, true, 5)
  9796. sge_order_frame = Gtk::Frame.new('SGE Launch Order')
  9797. sge_order_frame.add(sge_order_box)
  9798. refresh_button = Gtk::Button.new(' Refresh ')
  9799. refresh_box = Gtk::HBox.new
  9800. refresh_box.pack_end(refresh_button, false, false, 5)
  9801. psinet_compatible_button = Gtk::CheckButton.new('Use PsiNet compatible install method')
  9802. install_button = Gtk::Button.new('Install')
  9803. uninstall_button = Gtk::Button.new('Uninstall')
  9804. install_table = Gtk::Table.new(1, 2, true)
  9805. install_table.attach(install_button, 0, 1, 0, 1, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 5, 5)
  9806. install_table.attach(uninstall_button, 1, 2, 0, 1, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 5, 5)
  9807. install_tab = Gtk::VBox.new
  9808. install_tab.border_width = 5
  9809. install_tab.pack_start(website_order_frame, false, false, 5)
  9810. install_tab.pack_start(sge_order_frame, false, false, 5)
  9811. install_tab.pack_start(refresh_box, false, false, 5)
  9812. install_tab.pack_start(psinet_compatible_button, false, false, 5)
  9813. install_tab.pack_start(install_table, false, false, 5)
  9814. psinet_installstate = nil
  9815. refresh_button.signal_connect('clicked') {
  9816. install_tab_loaded = true
  9817. launch_cmd = registry_get('HKEY_LOCAL_MACHINE\\Software\\Classes\\Simutronics.Autolaunch\\Shell\\Open\\command\\').to_s
  9818. website_order_entry_1.text = launch_cmd
  9819. if launch_cmd =~ /"(.*?)PsiNet2.exe"/i
  9820. website_order_entry_2.visible = true
  9821. psinet_dir = $1
  9822. if File.exists?(psinet_dir)
  9823. Dir.entries(psinet_dir).each { |f|
  9824. if f =~ /^SageInstaller.*\.InstallState$/i
  9825. psinet_installstate = read_psinet_installstate.call("#{psinet_dir}#{f}")
  9826. launch_cmd = psinet_installstate['UninstallSalCommand'].gsub(/&#([0-9]+);/) { $1.to_i.chr }
  9827. website_order_entry_2.text = launch_cmd
  9828. break
  9829. end
  9830. }
  9831. end
  9832. else
  9833. website_order_entry_2.visible = false
  9834. end
  9835. if launch_cmd =~ /lich/i
  9836. website_order_entry_3.visible = true
  9837. website_order_entry_3.text = registry_get('HKEY_LOCAL_MACHINE\\Software\\Classes\\Simutronics.Autolaunch\\Shell\\Open\\command\\RealCommand').to_s
  9838. else
  9839. website_order_entry_3.visible = false
  9840. end
  9841. launch_cmd = registry_get('HKEY_LOCAL_MACHINE\\Software\\Simutronics\\Launcher\\Directory').to_s
  9842. sge_order_entry_1.text = launch_cmd
  9843. sge_order_entry_1.text += '\Launcher.exe' unless sge_order_entry_1.text[-1].chr == ' '
  9844. if launch_cmd =~ /^"?(.*?)PsiNet2.exe/i
  9845. sge_order_entry_2.visible = true
  9846. psinet_dir = $1
  9847. if psinet_installstate.nil? or psinet_installstate.empty?
  9848. if File.exists?(psinet_dir)
  9849. Dir.entries(psinet_dir).each { |f|
  9850. if f =~ /^SageInstaller.*\.InstallState$/i
  9851. psinet_installstate = read_psinet_installstate.call("#{psinet_dir}#{f}")
  9852. launch_cmd = psinet_installstate['RollbackLauncherDirectory'].gsub(/&#([0-9]+);/) { $1.to_i.chr }
  9853. sge_order_entry_2.text = launch_cmd
  9854. sge_order_entry_2.text += '\Launcher.exe' unless sge_order_entry_2.text[-1].chr == ' '
  9855. break
  9856. end
  9857. }
  9858. end
  9859. else
  9860. launch_cmd = psinet_installstate['RollbackLauncherDirectory'].gsub(/&#([0-9]+);/) { $1.to_i.chr }
  9861. sge_order_entry_2.text = launch_cmd
  9862. end
  9863. else
  9864. sge_order_entry_2.visible = false
  9865. end
  9866. if ENV['OCRA_EXECUTABLE']
  9867. psinet_compatible_button.active = true
  9868. psinet_compatible_button.sensitive = false
  9869. end
  9870. if launch_cmd =~ /lich/i
  9871. if launch_cmd =~ /lich\.bat/i
  9872. psinet_compatible_button.active = true
  9873. end
  9874. sge_order_entry_3.visible = true
  9875. sge_order_entry_3.text = registry_get('HKEY_LOCAL_MACHINE\\Software\\Simutronics\\Launcher\\RealDirectory').to_s
  9876. sge_order_entry_3.text += '\Launcher.exe' unless sge_order_entry_3.text[-1].chr == ' '
  9877. else
  9878. sge_order_entry_3.visible = false
  9879. end
  9880. if (website_order_entry_1.text =~ /PsiNet/i) or (sge_order_entry_1.text =~ /PsiNet/i)
  9881. install_button.sensitive = false
  9882. uninstall_button.sensitive = false
  9883. psinet_compatible_button.sensitive = false
  9884. elsif (website_order_entry_1.text =~ /lich/i) or (sge_order_entry_1.text =~ /lich/i)
  9885. install_button.sensitive = false
  9886. uninstall_button.sensitive = true
  9887. psinet_compatible_button.sensitive = false
  9888. else
  9889. install_button.sensitive = true
  9890. uninstall_button.sensitive = false
  9891. psinet_compatible_button.sensitive = true unless ENV['OCRA_EXECUTABLE']
  9892. end
  9893. unless (RUBY_PLATFORM =~ /mingw|win/i) and (RUBY_PLATFORM !~ /darwin/i)
  9894. psinet_compatible_button.active = false
  9895. psinet_compatible_button.visible = false
  9896. end
  9897. }
  9898. install_button.signal_connect('clicked') {
  9899. install_to_registry(psinet_compatible_button.active?)
  9900. if (RUBY_PLATFORM =~ /mingw|win/i) and (RUBY_PLATFORM !~ /darwin/i)
  9901. refresh_button.clicked
  9902. else
  9903. msgbox.call('WINE will take 5-30 seconds (maybe more) to update the registry. Wait a while and click the refresh button.')
  9904. end
  9905. }
  9906. uninstall_button.signal_connect('clicked') {
  9907. uninstall_from_registry
  9908. if (RUBY_PLATFORM =~ /mingw|win/i) and (RUBY_PLATFORM !~ /darwin/i)
  9909. refresh_button.clicked
  9910. else
  9911. msgbox.call('WINE will take 5-30 seconds (maybe more) to update the registry. Wait a while and click the refresh button.')
  9912. end
  9913. }
  9914. end
  9915. #
  9916. # options tab
  9917. #
  9918. lich_char_label = Gtk::Label.new('Lich char:')
  9919. lich_char_label.xalign = 1
  9920. lich_char_entry = Gtk::Entry.new
  9921. lich_char_entry.text = LichSettings['lich_char'].to_s
  9922. lich_box = Gtk::HBox.new
  9923. lich_box.pack_end(lich_char_entry, true, true, 5)
  9924. lich_box.pack_end(lich_char_label, true, true, 5)
  9925. cache_serverbuffer_button = Gtk::CheckButton.new('Cache to disk')
  9926. cache_serverbuffer_button.active = LichSettings['cache_serverbuffer']
  9927. serverbuffer_max_label = Gtk::Label.new('Maximum lines in memory:')
  9928. serverbuffer_max_entry = Gtk::Entry.new
  9929. serverbuffer_max_entry.text = LichSettings['serverbuffer_max_size'].to_s
  9930. serverbuffer_min_label = Gtk::Label.new('Minumum lines in memory:')
  9931. serverbuffer_min_entry = Gtk::Entry.new
  9932. serverbuffer_min_entry.text = LichSettings['serverbuffer_min_size'].to_s
  9933. serverbuffer_min_entry.sensitive = cache_serverbuffer_button.active?
  9934. serverbuffer_table = Gtk::Table.new(2, 2, false)
  9935. serverbuffer_table.attach(serverbuffer_max_label, 0, 1, 0, 1, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 5, 5)
  9936. serverbuffer_table.attach(serverbuffer_max_entry, 1, 2, 0, 1, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 5, 5)
  9937. serverbuffer_table.attach(serverbuffer_min_label, 0, 1, 1, 2, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 5, 5)
  9938. serverbuffer_table.attach(serverbuffer_min_entry, 1, 2, 1, 2, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 5, 5)
  9939. serverbuffer_box = Gtk::VBox.new
  9940. serverbuffer_box.pack_start(cache_serverbuffer_button, false, false, 5)
  9941. serverbuffer_box.pack_start(serverbuffer_table, false, false, 5)
  9942. serverbuffer_frame = Gtk::Frame.new('Server Buffer')
  9943. serverbuffer_frame.add(serverbuffer_box)
  9944. cache_clientbuffer_button = Gtk::CheckButton.new('Cache to disk')
  9945. cache_clientbuffer_button.active = LichSettings['cache_clientbuffer']
  9946. clientbuffer_max_label = Gtk::Label.new('Maximum lines in memory:')
  9947. clientbuffer_max_entry = Gtk::Entry.new
  9948. clientbuffer_max_entry.text = LichSettings['clientbuffer_max_size'].to_s
  9949. clientbuffer_min_label = Gtk::Label.new('Minumum lines in memory:')
  9950. clientbuffer_min_entry = Gtk::Entry.new
  9951. clientbuffer_min_entry.text = LichSettings['clientbuffer_min_size'].to_s
  9952. clientbuffer_min_entry.sensitive = cache_clientbuffer_button.active?
  9953. clientbuffer_table = Gtk::Table.new(2, 2, false)
  9954. clientbuffer_table.attach(clientbuffer_max_label, 0, 1, 0, 1, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 5, 5)
  9955. clientbuffer_table.attach(clientbuffer_max_entry, 1, 2, 0, 1, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 5, 5)
  9956. clientbuffer_table.attach(clientbuffer_min_label, 0, 1, 1, 2, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 5, 5)
  9957. clientbuffer_table.attach(clientbuffer_min_entry, 1, 2, 1, 2, Gtk::EXPAND|Gtk::FILL, Gtk::EXPAND|Gtk::FILL, 5, 5)
  9958. clientbuffer_box = Gtk::VBox.new
  9959. clientbuffer_box.pack_start(cache_clientbuffer_button, false, false, 5)
  9960. clientbuffer_box.pack_start(clientbuffer_table, false, false, 5)
  9961. clientbuffer_frame = Gtk::Frame.new('Client Buffer')
  9962. clientbuffer_frame.add(clientbuffer_box)
  9963. save_button = Gtk::Button.new(' Save ')
  9964. save_button.sensitive = false
  9965. save_button_box = Gtk::HBox.new
  9966. save_button_box.pack_end(save_button, false, false, 5)
  9967. options_tab = Gtk::VBox.new
  9968. options_tab.border_width = 5
  9969. options_tab.pack_start(lich_box, false, false, 5)
  9970. options_tab.pack_start(serverbuffer_frame, false, false, 5)
  9971. options_tab.pack_start(clientbuffer_frame, false, false, 5)
  9972. options_tab.pack_start(save_button_box, false, false, 5)
  9973. check_changed = proc {
  9974. Gtk.queue {
  9975. 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)
  9976. save_button.sensitive = false
  9977. else
  9978. save_button.sensitive = true
  9979. end
  9980. }
  9981. }
  9982. lich_char_entry.signal_connect('key-press-event') {
  9983. check_changed.call
  9984. false
  9985. }
  9986. serverbuffer_max_entry.signal_connect('key-press-event') {
  9987. check_changed.call
  9988. false
  9989. }
  9990. serverbuffer_min_entry.signal_connect('key-press-event') {
  9991. check_changed.call
  9992. false
  9993. }
  9994. clientbuffer_max_entry.signal_connect('key-press-event') {
  9995. check_changed.call
  9996. false
  9997. }
  9998. clientbuffer_min_entry.signal_connect('key-press-event') {
  9999. check_changed.call
  10000. false
  10001. }
  10002. cache_serverbuffer_button.signal_connect('clicked') {
  10003. serverbuffer_min_entry.sensitive = cache_serverbuffer_button.active?
  10004. check_changed.call
  10005. }
  10006. cache_clientbuffer_button.signal_connect('clicked') {
  10007. clientbuffer_min_entry.sensitive = cache_clientbuffer_button.active?
  10008. check_changed.call
  10009. }
  10010. save_button.signal_connect('clicked') {
  10011. LichSettings['lich_char'] = lich_char_entry.text
  10012. LichSettings['cache_serverbuffer'] = cache_serverbuffer_button.active?
  10013. LichSettings['serverbuffer_max_size'] = serverbuffer_max_entry.text.to_i
  10014. LichSettings['serverbuffer_min_size'] = serverbuffer_min_entry.text.to_i
  10015. LichSettings['cache_clientbuffer'] = cache_clientbuffer_button.active?
  10016. LichSettings['clientbuffer_max_size'] = clientbuffer_max_entry.text.to_i
  10017. LichSettings['clientbuffer_min_size'] = clientbuffer_min_entry.text.to_i
  10018. LichSettings.save
  10019. save_button.sensitive = false
  10020. }
  10021. #
  10022. # put it together and show the window
  10023. #
  10024. notebook = Gtk::Notebook.new
  10025. notebook.append_page(quick_game_entry_tab, Gtk::Label.new('Quick Game Entry'))
  10026. notebook.append_page(game_entry_tab, Gtk::Label.new('Game Entry'))
  10027. notebook.append_page(install_tab, Gtk::Label.new('Install'))
  10028. notebook.append_page(options_tab, Gtk::Label.new('Options'))
  10029. notebook.signal_connect('switch-page') { |who,page,page_num|
  10030. refresh_button.clicked if (page_num == 2) and not install_tab_loaded
  10031. }
  10032. window = Gtk::Window.new
  10033. window.title = "Lich v#{$version}"
  10034. window.border_width = 5
  10035. window.add(notebook)
  10036. window.signal_connect('delete_event') { window.destroy; done = true }
  10037. window.show_all
  10038. custom_launch_entry.visible = false
  10039. custom_launch_dir.visible = false
  10040. notebook.set_page(1) if entry_data.empty?
  10041. }
  10042. wait_until { done }
  10043. if save_entry_data
  10044. File.open("#{$data_dir}entry.dat", 'w') { |file|
  10045. file.write([Marshal.dump(entry_data)].pack('m'))
  10046. }
  10047. end
  10048. entry_data = nil
  10049. unless launch_data
  10050. Gtk.queue { Gtk.main_quit }
  10051. Thread.kill
  10052. end
  10053. end
  10054. if LichSettings['cache_serverbuffer']
  10055. $_SERVERBUFFER_ = CachedArray.new
  10056. $_SERVERBUFFER_.max_size = LichSettings['serverbuffer_max_size']
  10057. $_SERVERBUFFER_.min_size = LichSettings['serverbuffer_min_size']
  10058. else
  10059. $_SERVERBUFFER_ = LimitedArray.new
  10060. $_SERVERBUFFER_.max_size = LichSettings['serverbuffer_max_size']
  10061. end
  10062. if LichSettings['cache_clientbuffer']
  10063. $_CLIENTBUFFER_ = CachedArray.new
  10064. $_CLIENTBUFFER_.max_size = LichSettings['clientbuffer_max_size']
  10065. $_CLIENTBUFFER_.min_size = LichSettings['clientbuffer_min_size']
  10066. else
  10067. $_CLIENTBUFFER_ = LimitedArray.new
  10068. $_CLIENTBUFFER_.max_size = LichSettings['clientbuffer_max_size']
  10069. end
  10070. trace_var(:$_CLIENT_, sock_keepalive_proc)
  10071. trace_var(:$_SERVER_, sock_keepalive_proc)
  10072. Socket.do_not_reverse_lookup = true
  10073. #
  10074. # open the client and have it connect to us
  10075. #
  10076. if launch_file
  10077. begin
  10078. launch_data = File.open(launch_file) { |file| file.readlines }.collect { |line| line.chomp }
  10079. rescue
  10080. $stdout.puts "error: failed to read launch_file: #{$!}"
  10081. $stderr.puts "info: launch_file: #{launch_file}"
  10082. $stderr.puts "error: failed to read launch_file: #{$!}"
  10083. $stderr.puts $!.backtrace
  10084. exit(1)
  10085. end
  10086. end
  10087. if launch_data
  10088. unless gamecode = launch_data.find { |line| line =~ /GAMECODE=/ }
  10089. $stdout.puts "error: launch_data contains no GAMECODE info"
  10090. $stderr.puts "error: launch_data contains no GAMECODE info"
  10091. exit(1)
  10092. end
  10093. unless gameport = launch_data.find { |line| line =~ /GAMEPORT=/ }
  10094. $stdout.puts "error: launch_data contains no GAMEPORT info"
  10095. $stderr.puts "error: launch_data contains no GAMEPORT info"
  10096. exit(1)
  10097. end
  10098. unless gamehost = launch_data.find { |opt| opt =~ /GAMEHOST=/ }
  10099. $stdout.puts "error: launch_data contains no GAMEHOST info"
  10100. $stderr.puts "error: launch_data contains no GAMEHOST info"
  10101. exit(1)
  10102. end
  10103. unless game = launch_data.find { |opt| opt =~ /GAME=/ }
  10104. $stdout.puts "error: launch_data contains no GAME info"
  10105. $stderr.puts "error: launch_data contains no GAME info"
  10106. exit(1)
  10107. end
  10108. if custom_launch = launch_data.find { |opt| opt =~ /CUSTOMLAUNCH=/ }
  10109. custom_launch.sub!(/^.*?\=/, '')
  10110. $stderr.puts "info: using custom launch command: #{custom_launch}"
  10111. end
  10112. if custom_launch_dir = launch_data.find { |opt| opt =~ /CUSTOMLAUNCHDIR=/ }
  10113. custom_launch_dir.sub!(/^.*?\=/, '')
  10114. $stderr.puts "info: using working directory for custom launch command: #{custom_launch_dir}"
  10115. end
  10116. if ARGV.include?('--without-frontend')
  10117. $frontend = 'unknown'
  10118. unless (game_key = launch_data.find { |opt| opt =~ /KEY=/ }) && (game_key = game_key.split('=').last.chomp)
  10119. $stdout.puts "error: launch_data contains no KEY info"
  10120. $stderr.puts "error: launch_data contains no KEY info"
  10121. exit(1)
  10122. end
  10123. elsif game =~ /SUKS/i
  10124. $frontend = 'suks'
  10125. unless (game_key = launch_data.find { |opt| opt =~ /KEY=/ }) && (game_key = game_key.split('=').last.chomp)
  10126. $stdout.puts "error: launch_data contains no KEY info"
  10127. $stderr.puts "error: launch_data contains no KEY info"
  10128. exit(1)
  10129. end
  10130. elsif custom_launch
  10131. unless (game_key = launch_data.find { |opt| opt =~ /KEY=/ }) && (game_key = game_key.split('=').last.chomp)
  10132. $stdout.puts "error: launch_data contains no KEY info"
  10133. $stderr.puts "error: launch_data contains no KEY info"
  10134. exit(1)
  10135. end
  10136. else
  10137. unless launcher_cmd = get_real_launcher_cmd.call
  10138. $stdout.puts 'error: failed to find the Simutronics launcher'
  10139. $stderr.puts 'error: failed to find the Simutronics launcher'
  10140. exit(1)
  10141. end
  10142. end
  10143. gamecode = gamecode.split('=').last
  10144. gameport = gameport.split('=').last
  10145. gamehost = gamehost.split('=').last
  10146. game = game.split('=').last
  10147. if (gamehost == '127.0.0.1') or (gamehost == 'localhost')
  10148. $psinet = true
  10149. if (game !~ /WIZ/i) and ( File.exists?('fakestormfront.txt') or ( registry_get('HKEY_LOCAL_MACHINE\\Software\\Simutronics\\WIZ32\\Directory') and not registry_get('HKEY_LOCAL_MACHINE\\Software\\Simutronics\\STORM32\\Directory') ) )
  10150. launch_data.collect! { |line| line.sub(/GAMEFILE=.+/, 'GAMEFILE=WIZARD.EXE').sub(/GAME=.+/, 'GAME=WIZ').sub(/FULLGAMENAME=.+/, 'FULLGAMENAME=Wizard Front End') }
  10151. game = 'WIZ'
  10152. end
  10153. else
  10154. $psinet = false
  10155. end
  10156. if (gameport == '10121') or (gameport == '10124')
  10157. $platinum = true
  10158. else
  10159. $platinum = false
  10160. end
  10161. $stderr.puts "info: gamehost: #{gamehost}"
  10162. $stderr.puts "info: gameport: #{gameport}"
  10163. $stderr.puts "info: game: #{game}"
  10164. if ARGV.include?('--without-frontend')
  10165. $_CLIENT_ = nil
  10166. elsif $frontend == 'suks'
  10167. nil
  10168. else
  10169. if game =~ /WIZ/i
  10170. $frontend = 'wizard'
  10171. elsif game =~ /STORM/i
  10172. $frontend = 'stormfront'
  10173. else
  10174. $frontend = 'unknown'
  10175. end
  10176. begin
  10177. listener = TCPServer.new('127.0.0.1', nil)
  10178. rescue
  10179. $stdout.puts "--- error: cannot bind listen socket to local port: #{$!}"
  10180. $stderr.puts "error: cannot bind listen socket to local port: #{$!}"
  10181. $stderr.puts $!.backtrace
  10182. exit(1)
  10183. end
  10184. begin
  10185. listener.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true)
  10186. rescue
  10187. $stderr.puts "Cannot set SO_REUSEADDR sockopt"
  10188. end
  10189. localport = listener.addr[1]
  10190. if custom_launch
  10191. sal_filename = nil
  10192. launcher_cmd = custom_launch.sub(/\%port\%/, localport.to_s).sub(/\%key\%/, game_key.to_s)
  10193. $stderr.puts "info: launcher_cmd: #{launcher_cmd}"
  10194. else
  10195. launch_data.collect! { |line| line.sub(/GAMEPORT=.+/, "GAMEPORT=#{localport}").sub(/GAMEHOST=.+/, "GAMEHOST=localhost") }
  10196. sal_filename = "#{$temp_dir}lich#{rand(10000)}.sal"
  10197. while File.exists?(sal_filename)
  10198. sal_filename = "#{$temp_dir}lich#{rand(10000)}.sal"
  10199. end
  10200. File.open(sal_filename, 'w') { |f| f.puts launch_data }
  10201. launcher_cmd = launcher_cmd.sub('%1', sal_filename)
  10202. launcher_cmd = launcher_cmd.tr('/', "\\") if (RUBY_PLATFORM =~ /mingw|win/i) and (RUBY_PLATFORM !~ /darwin/i)
  10203. launcher_cmd = "#{wine_bin} #{launcher_cmd}" if wine_bin
  10204. $stderr.puts "info: launcher_cmd: #{launcher_cmd}"
  10205. if get_process_list.call.any? { |line| line =~ /Launcher\.exe/i }
  10206. $stderr.puts "info: waiting for launcher to exit..."
  10207. 20.times {
  10208. sleep "0.5".to_f
  10209. break unless get_process_list.call.any? { |line| line =~ /Launcher\.exe/i }
  10210. }
  10211. end
  10212. end
  10213. Thread.new {
  10214. begin
  10215. if custom_launch_dir
  10216. Dir.chdir(custom_launch_dir)
  10217. end
  10218. system_win8fix.call(launcher_cmd)
  10219. rescue
  10220. $stderr.puts $!
  10221. end
  10222. }
  10223. timeout_thr = Thread.new {
  10224. sleep 30
  10225. $stdout.puts "error: timeout waiting for client to connect"
  10226. $stderr.puts "error: timeout waiting for client to connect"
  10227. Dir.chdir($lich_dir)
  10228. if sal_filename
  10229. File.delete(sal_filename) rescue()
  10230. end
  10231. listener.close rescue()
  10232. $_CLIENT_.close rescue()
  10233. reconnect_if_wanted.call
  10234. $stderr.puts "info: exiting..."
  10235. Gtk.queue { Gtk.main_quit } if HAVE_GTK
  10236. exit
  10237. }
  10238. $stderr.puts 'info: waiting for client to connect...'
  10239. $_CLIENT_ = listener.accept
  10240. $stderr.puts 'info: connected'
  10241. Dir.chdir($lich_dir)
  10242. if sal_filename
  10243. File.delete(sal_filename) rescue()
  10244. end
  10245. begin
  10246. timeout_thr.kill
  10247. listener.close
  10248. rescue
  10249. $stderr.puts "error: #{$!}"
  10250. end
  10251. end
  10252. gamehost, gameport = fix_game_host_port.call(gamehost, gameport)
  10253. $stderr.puts "info: connecting to game server (#{gamehost}:#{gameport})"
  10254. begin
  10255. connect_thread = Thread.new {
  10256. $_SERVER_ = TCPSocket.open(gamehost, gameport)
  10257. }
  10258. 300.times {
  10259. sleep 0.1
  10260. break unless connect_thread.status
  10261. }
  10262. if connect_thread.status
  10263. connect_thread.kill rescue()
  10264. raise "error: timed out connecting to #{gamehost}:#{gameport}"
  10265. end
  10266. rescue
  10267. $stderr.puts "error: #{$!}"
  10268. gamehost, gameport = break_game_host_port.call(gamehost, gameport)
  10269. $stderr.puts "info: connecting to game server (#{gamehost}:#{gameport})"
  10270. begin
  10271. connect_thread = Thread.new {
  10272. $_SERVER_ = TCPSocket.open(gamehost, gameport)
  10273. }
  10274. 300.times {
  10275. sleep 0.1
  10276. break unless connect_thread.status
  10277. }
  10278. if connect_thread.status
  10279. connect_thread.kill rescue()
  10280. raise "error: timed out connecting to #{gamehost}:#{gameport}"
  10281. end
  10282. rescue
  10283. $stderr.puts "error: #{$!}"
  10284. $_CLIENT_.close rescue()
  10285. reconnect_if_wanted.call
  10286. $stderr.puts "info: exiting..."
  10287. Gtk.queue { Gtk.main_quit } if HAVE_GTK
  10288. exit
  10289. end
  10290. end
  10291. $stderr.puts 'info: connected'
  10292. elsif game_host and game_port
  10293. hosts_dir ||= find_hosts_dir
  10294. unless hosts_dir and File.exists?(hosts_dir)
  10295. $stdout.puts "error: hosts_dir does not exist: #{hosts_dir}"
  10296. $stderr.puts "error: hosts_dir does not exist: #{hosts_dir}"
  10297. exit
  10298. end
  10299. game_quad_ip = IPSocket.getaddress(game_host)
  10300. error_count = 0
  10301. begin
  10302. listener = TCPServer.new('127.0.0.1', game_port)
  10303. begin
  10304. listener.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR,1)
  10305. rescue
  10306. $stderr.puts "warning: setsockopt with SO_REUSEADDR failed: #{$!}"
  10307. end
  10308. rescue
  10309. sleep 1
  10310. if (error_count += 1) >= 30
  10311. $stdout.puts 'error: failed to bind to the proper port'
  10312. $stderr.puts 'error: failed to bind to the proper port'
  10313. exit!
  10314. else
  10315. retry
  10316. end
  10317. end
  10318. hack_hosts(hosts_dir, game_host)
  10319. $stdout.puts "Pretending to be #{game_host}"
  10320. $stdout.puts "Listening on port #{game_port}"
  10321. $stdout.puts "Waiting for the client to connect..."
  10322. $stderr.puts "info: pretending to be #{game_host}"
  10323. $stderr.puts "info: listening on port #{game_port}"
  10324. $stderr.puts "info: waiting for the client to connect..."
  10325. timeout_thread = Thread.new {
  10326. sleep 120
  10327. $stdout.puts 'error: timed out waiting for client to connect'
  10328. $stderr.puts 'error: timed out waiting for client to connect'
  10329. heal_hosts(hosts_dir)
  10330. exit
  10331. }
  10332. $_CLIENT_ = listener.accept
  10333. timeout_thread.kill
  10334. $stdout.puts "Connection with the local game client is open."
  10335. $stderr.puts "info: connection with the game client is open"
  10336. heal_hosts(hosts_dir)
  10337. if test_mode
  10338. $_SERVER_ = $stdin
  10339. $_CLIENT_.puts "Running in test mode: host socket set to stdin."
  10340. else
  10341. $stderr.puts 'info: connecting to the real game host...'
  10342. game_host, game_port = fix_game_host_port.call(game_host, game_port)
  10343. begin
  10344. timeout_thread = Thread.new {
  10345. sleep 30
  10346. $stderr.puts "error: timed out connecting to #{game_host}:#{game_port}"
  10347. $stdout.puts "error: timed out connecting to #{game_host}:#{game_port}"
  10348. exit
  10349. }
  10350. begin
  10351. $_SERVER_ = TCPSocket.open(game_host, game_port)
  10352. rescue
  10353. $stderr.puts "error: #{$!}"
  10354. $stdout.puts "error: #{$!}"
  10355. exit
  10356. end
  10357. timeout_thread.kill rescue()
  10358. $stderr.puts 'info: connection with the game host is open'
  10359. end
  10360. end
  10361. else
  10362. #
  10363. # offline mode
  10364. #
  10365. $stderr.puts "info: offline mode"
  10366. $offline_mode = true
  10367. unless $frontend == 'suks'
  10368. begin
  10369. listener = TCPServer.new('127.0.0.1', nil)
  10370. rescue
  10371. $stdout.puts "Cannot bind listening socket to local port: #{$!}"
  10372. $stderr.puts "Cannot bind listening socket to local port: #{$!}"
  10373. exit(1)
  10374. end
  10375. begin
  10376. listener.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true)
  10377. rescue
  10378. $stderr.puts "Cannot set SO_REUSEADDR sockopt"
  10379. end
  10380. localport = listener.addr[1]
  10381. if ARGV.any? { |arg| (arg == '-s') or (arg == '--stormfront') }
  10382. $frontend = 'stormfront'
  10383. frontend_dir = registry_get('HKEY_LOCAL_MACHINE\\Software\\Simutronics\\STORM32\\Directory')
  10384. frontend_cmd = "\"#{frontend_dir}\\StormFront.exe\""
  10385. else
  10386. $frontend = 'wizard'
  10387. frontend_dir = registry_get('HKEY_LOCAL_MACHINE\\Software\\Simutronics\\WIZ32\\Directory')
  10388. frontend_cmd = "\"#{frontend_dir}\\wizard.exe\""
  10389. end
  10390. frontend_cmd += " /GGS /H127.0.0.1 /P#{localport} /Kfake_login_key"
  10391. frontend_cmd = "#{wine_bin} #{frontend_cmd}" if wine_bin
  10392. $stderr.puts "info: frontend_cmd: #{frontend_cmd}"
  10393. Thread.new {
  10394. Dir.chdir(frontend_dir) rescue()
  10395. system(frontend_cmd)
  10396. }
  10397. timeout_thr = Thread.new {
  10398. sleep 30
  10399. $stdout.puts "timeout waiting for connection"
  10400. $stderr.puts "error: timeout waiting for connection"
  10401. exit(1)
  10402. }
  10403. Dir.chdir($lich_dir)
  10404. $_CLIENT_ = listener.accept
  10405. begin
  10406. timeout_thr.kill
  10407. listener.close
  10408. rescue
  10409. $stderr.puts $!
  10410. end
  10411. $_SERVER_ = $stdin
  10412. if $frontend =~ /^(?:wizard|avalon)$/
  10413. $_CLIENT_.puts "\034GSB0000000000Lich\r\n\034GSA#{Time.now.to_i.to_s}GemStone IV\034GSD\r\n"
  10414. end
  10415. end
  10416. end
  10417. listener = timeout_thr = nil
  10418. undef :hack_hosts
  10419. #
  10420. # drop superuser privileges
  10421. #
  10422. unless (RUBY_PLATFORM =~ /mingw|win/i) and (RUBY_PLATFORM !~ /darwin/i)
  10423. $stderr.puts "info: dropping superuser privileges..."
  10424. begin
  10425. Process.uid = `id -ru`.strip.to_i
  10426. Process.gid = `id -rg`.strip.to_i
  10427. Process.egid = `id -rg`.strip.to_i
  10428. Process.euid = `id -ru`.strip.to_i
  10429. rescue SecurityError
  10430. $stderr.puts "error: failed to drop superuser privileges: #{$!}"
  10431. $stderr.puts $!.backtrace
  10432. rescue SystemCallError
  10433. $stderr.puts "error: failed to drop superuser privileges: #{$!}"
  10434. $stderr.puts $!.backtrace
  10435. rescue
  10436. $stderr.puts "error: failed to drop superuser privileges: #{$!}"
  10437. $stderr.puts $!.backtrace
  10438. end
  10439. end
  10440. # backward compatibility
  10441. if $frontend =~ /^(?:wizard|avalon)$/
  10442. $fake_stormfront = true
  10443. else
  10444. $fake_stormfront = false
  10445. end
  10446. undef :exit!
  10447. $_SERVER_.sync = true
  10448. if ARGV.include?('--without-frontend')
  10449. Thread.new {
  10450. client_thread = nil
  10451. #
  10452. # send the login key
  10453. #
  10454. $_SERVER_.write("#{game_key}\r\n")
  10455. game_key = nil
  10456. #
  10457. # send version string
  10458. #
  10459. client_string = "/FE:WIZARD /VERSION:1.0.1.22 /P:#{RUBY_PLATFORM} /XML\r\n"
  10460. $_CLIENTBUFFER_.push(client_string.dup)
  10461. $_SERVER_.write(client_string)
  10462. #
  10463. # tell the server we're ready
  10464. #
  10465. 2.times {
  10466. sleep "0.3".to_f
  10467. $_CLIENTBUFFER_.push("<c>\r\n")
  10468. $_SERVER_.write("<c>\r\n")
  10469. }
  10470. $login_time = Time.now
  10471. }
  10472. else
  10473. #
  10474. # shutdown listening socket
  10475. #
  10476. error_count = 0
  10477. begin
  10478. # Somehow... for some ridiculous reason... Windows doesn't let us close the socket if we shut it down first...
  10479. # listener.shutdown
  10480. listener.close unless listener.closed?
  10481. rescue
  10482. $stderr.puts "warning: failed to close listener socket: #{$!}"
  10483. if (error_count += 1) > 20
  10484. $stderr.puts 'warning: giving up...'
  10485. else
  10486. sleep "0.05".to_f
  10487. retry
  10488. end
  10489. end
  10490. $stdout = $_CLIENT_
  10491. $_CLIENT_.sync = true
  10492. client_thread = Thread.new {
  10493. $login_time = Time.now
  10494. if $offline_mode
  10495. nil
  10496. elsif $frontend =~ /^(?:wizard|avalon)$/
  10497. #
  10498. # send the login key
  10499. #
  10500. client_string = $_CLIENT_.gets
  10501. $_SERVER_.write(client_string)
  10502. #
  10503. # take the version string from the client, ignore it, and ask the server for xml
  10504. #
  10505. $_CLIENT_.gets
  10506. client_string = "/FE:WIZARD /VERSION:1.0.1.22 /P:#{RUBY_PLATFORM} /XML\r\n"
  10507. $_CLIENTBUFFER_.push(client_string.dup)
  10508. $_SERVER_.write(client_string)
  10509. #
  10510. # tell the server we're ready
  10511. #
  10512. 2.times {
  10513. sleep "0.3".to_f
  10514. $_CLIENTBUFFER_.push("#{$cmd_prefix}\r\n")
  10515. $_SERVER_.write("#{$cmd_prefix}\r\n")
  10516. }
  10517. #
  10518. # set up some stuff
  10519. #
  10520. for client_string in [ "#{$cmd_prefix}_injury 2\r\n", "#{$cmd_prefix}_flag Display Inventory Boxes 1\r\n", "#{$cmd_prefix}_flag Display Dialog Boxes 0\r\n" ]
  10521. $_CLIENTBUFFER_.push(client_string)
  10522. $_SERVER_.write(client_string)
  10523. end
  10524. #
  10525. # client wants to send "GOOD", xml server won't recognize it
  10526. #
  10527. $_CLIENT_.gets
  10528. else
  10529. inv_off_proc = proc { |server_string|
  10530. if server_string =~ /^<(?:container|clearContainer|exposeContainer)/
  10531. server_string.gsub!(/<(?:container|clearContainer|exposeContainer)[^>]*>/, '')
  10532. server_string.gsub!(/<inv.*\/inv>/, '')
  10533. if server_string.empty?
  10534. nil
  10535. else
  10536. server_string
  10537. end
  10538. elsif server_string =~ /^<flag id="Display Inventory Boxes" status='on' desc="Display all inventory and container windows."\/>/
  10539. server_string.sub("status='on'", "status='off'")
  10540. elsif server_string =~ /^\s*<d cmd="flag Inventory off">Inventory<\/d>\s+ON/
  10541. server_string.sub("flag Inventory off", "flag Inventory on").sub('ON', 'OFF')
  10542. else
  10543. server_string
  10544. end
  10545. }
  10546. DownstreamHook.add('inventory_boxes_off', inv_off_proc)
  10547. inv_toggle_proc = proc { |client_string|
  10548. if client_string =~ /^(?:<c>)?_flag Display Inventory Boxes ([01])/
  10549. if $1 == '1'
  10550. DownstreamHook.remove('inventory_boxes_off')
  10551. LichSettings[XMLData.player_id]['enable_inventory_boxes'] = true
  10552. LichSettings.save
  10553. else
  10554. DownstreamHook.add('inventory_boxes_off', inv_off_proc)
  10555. LichSettings[XMLData.player_id]['enable_inventory_boxes'] = false
  10556. LichSettings.save
  10557. end
  10558. nil
  10559. elsif client_string =~ /^(?:<c>)?\s*(?:set|flag)\s+inv(?:e|en|ent|ento|entor|entory)?\s+(on|off)/i
  10560. if $1.downcase == 'on'
  10561. DownstreamHook.remove('inventory_boxes_off')
  10562. respond 'You have enabled viewing of inventory and container windows.'
  10563. LichSettings[XMLData.player_id]['enable_inventory_boxes'] = true
  10564. LichSettings.save
  10565. else
  10566. DownstreamHook.add('inventory_boxes_off', inv_off_proc)
  10567. respond 'You have disabled viewing of inventory and container windows.'
  10568. LichSettings[XMLData.player_id]['enable_inventory_boxes'] = false
  10569. LichSettings.save
  10570. end
  10571. nil
  10572. else
  10573. client_string
  10574. end
  10575. }
  10576. UpstreamHook.add('inventory_boxes_toggle', inv_toggle_proc)
  10577. unless $offline_mode
  10578. client_string = $_CLIENT_.gets
  10579. $_SERVER_.write(client_string)
  10580. client_string = $_CLIENT_.gets
  10581. $_CLIENTBUFFER_.push(client_string.dup)
  10582. $_SERVER_.write(client_string)
  10583. end
  10584. end
  10585. begin
  10586. while client_string = $_CLIENT_.gets
  10587. client_string = "#{$cmd_prefix}#{client_string}" if $frontend =~ /^(?:wizard|avalon)$/
  10588. begin
  10589. $_IDLETIMESTAMP_ = Time.now
  10590. if Alias.find(client_string)
  10591. Alias.run(client_string)
  10592. else
  10593. do_client(client_string)
  10594. end
  10595. rescue
  10596. respond "--- error: client_thread: #{$!}"
  10597. respond $!.backtrace.first
  10598. $stderr.puts "error: client_thread: #{$!}"
  10599. $stderr.puts $!.backtrace
  10600. end
  10601. end
  10602. rescue
  10603. respond "--- error: client_thread: #{$!}"
  10604. respond $!.backtrace.first
  10605. $stderr.puts "error: client_thread: #{$!}"
  10606. $stderr.puts $!.backtrace
  10607. sleep "0.2".to_f
  10608. retry unless $_CLIENT_.closed? or $_SERVER_.closed? or !server_thread.alive?
  10609. end
  10610. server_thread.kill rescue()
  10611. }
  10612. end
  10613. # fixme: bare bones
  10614. wait_while { $offline_mode }
  10615. if $frontend == 'wizard'
  10616. $link_highlight_start = "\207"
  10617. $link_highlight_end = "\240"
  10618. $speech_highlight_start = "\212"
  10619. $speech_highlight_end = "\240"
  10620. end
  10621. last_server_thread_recv = Time.now
  10622. server_thread = nil
  10623. Thread.new {
  10624. loop {
  10625. if last_server_thread_recv + 300 < Time.now
  10626. $stderr.puts "#{Time.now}: error: nothing recieved from server in 5 minutes"
  10627. server_thread.kill rescue()
  10628. end
  10629. sleep (300 - (Time.now - last_server_thread_recv))
  10630. sleep 1
  10631. }
  10632. }
  10633. server_thread = Thread.new {
  10634. begin
  10635. while $_SERVERSTRING_ = $_SERVER_.gets
  10636. last_server_thread_recv = Time.now
  10637. begin
  10638. $cmd_prefix = String.new if $_SERVERSTRING_ =~ /^\034GSw/
  10639. # The Rift, Scatter is broken...
  10640. if $_SERVERSTRING_ =~ /<compDef id='room text'><\/compDef>/
  10641. $_SERVERSTRING_.sub!(/(.*)\s\s<compDef id='room text'><\/compDef>/) { "<compDef id='room desc'>#{$1}</compDef>" }
  10642. end
  10643. # Cry For Help spell is broken...
  10644. if $_SERVERSTRING_ =~ /<pushStream id="familiar" \/><prompt time="[0-9]+">&gt;<\/prompt>/
  10645. $_SERVERSTRING_.sub!('<pushStream id="familiar" />', '')
  10646. end
  10647. $_SERVERBUFFER_.push($_SERVERSTRING_)
  10648. if alt_string = DownstreamHook.run($_SERVERSTRING_)
  10649. if $frontend =~ /^(?:wizard|avalon)$/
  10650. alt_string = sf_to_wiz(alt_string)
  10651. end
  10652. $_CLIENT_.write(alt_string)
  10653. end
  10654. unless $_SERVERSTRING_ =~ /^<setting/
  10655. begin
  10656. REXML::Document.parse_stream($_SERVERSTRING_, XMLData)
  10657. # XMLData.parse($_SERVERSTRING_)
  10658. rescue
  10659. if $_SERVERSTRING_ =~ /<[^>]+='[^=>'\\]+'[^=>']+'[\s>]/
  10660. # Simu has a nasty habbit of bad quotes in XML. <tag attr='this's that'>
  10661. $_SERVERSTRING_.gsub!(/(<[^>]+=)'([^=>'\\]+'[^=>']+)'([\s>])/) { "#{$1}\"#{$2}\"#{$3}" }
  10662. retry
  10663. end
  10664. $stdout.puts "--- error: server_thread: #{$!}"
  10665. $stderr.puts "error: server_thread: #{$!}"
  10666. $stderr.puts $!.backtrace
  10667. XMLData.reset
  10668. end
  10669. Script.new_downstream_xml($_SERVERSTRING_)
  10670. stripped_server = strip_xml($_SERVERSTRING_)
  10671. stripped_server.split("\r\n").each { |line|
  10672. 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*$/
  10673. Script.new_downstream(line) unless line.empty?
  10674. end
  10675. }
  10676. end
  10677. rescue
  10678. $stdout.puts "--- error: server_thread: #{$!}"
  10679. $stderr.puts "error: server_thread: #{$!}"
  10680. $stderr.puts $!.backtrace
  10681. end
  10682. end
  10683. rescue Exception
  10684. $stdout.puts "--- error: server_thread: #{$!}"
  10685. $stderr.puts "error: server_thread: #{$!}"
  10686. $stderr.puts $!.backtrace
  10687. sleep "0.2".to_f
  10688. retry unless $_CLIENT_.closed? or $_SERVER_.closed? or ($!.to_s =~ /invalid argument/i)
  10689. rescue
  10690. $stdout.puts "--- error: server_thread: #{$!}"
  10691. $stderr.puts "error: server_thread: #{$!}"
  10692. $stderr.puts $!.backtrace
  10693. sleep "0.2".to_f
  10694. retry unless $_CLIENT_.closed? or $_SERVER_.closed?
  10695. end
  10696. }
  10697. server_thread.priority = 4
  10698. client_thread.priority = 3
  10699. $_CLIENT_.puts "\n--- Lich v#{$version} is active. Type #{$clean_lich_char}help for usage info.\n\n"
  10700. unless LichSettings['seen_notice']
  10701. output = "**\n"
  10702. output.concat "** NOTICE:\n"
  10703. output.concat "**\n"
  10704. output.concat "** Lich is not intended to facilitate AFK scripting.\n"
  10705. output.concat "** The authors do not condone violation of game policy,\n"
  10706. output.concat "** nor are they in any way attempting to encourage it.\n"
  10707. output.concat "**\n"
  10708. output.concat "** (this notice probably won't repeat) \n"
  10709. output.concat "**\n"
  10710. respond output
  10711. LichSettings['seen_notice'] = true
  10712. LichSettings.save
  10713. end
  10714. server_thread.join
  10715. client_thread.kill rescue()
  10716. $stderr.puts 'info: stopping scripts...'
  10717. Script.running.each { |script| script.kill }
  10718. Script.hidden.each { |script| script.kill }
  10719. 100.times { sleep "0.1".to_f; break if Script.running.empty? and Script.hidden.empty? }
  10720. $stderr.puts 'info: saving script data...'
  10721. UserVariables.save
  10722. Settings.save_all
  10723. GameSettings.save_all
  10724. CharSettings.save_all
  10725. # sending quit seems to cause Lich to hang. $_SERVER_.closed? is false even though it appears to be closed.
  10726. # $_SERVER_.puts('quit') unless $_SERVER_.closed?
  10727. $stderr.puts 'info: closing connections...'
  10728. $_SERVER_.close rescue()
  10729. $_CLIENT_.close rescue()
  10730. reconnect_if_wanted.call
  10731. $stderr.puts "info: exiting..."
  10732. Gtk.queue { Gtk.main_quit } if HAVE_GTK
  10733. exit
  10734. }
  10735. if HAVE_GTK
  10736. Thread.current.priority = -10
  10737. Gtk.main
  10738. else
  10739. main_thread.join
  10740. exit
  10741. end