PageRenderTime 68ms CodeModel.GetById 21ms RepoModel.GetById 1ms 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

Large files files are truncated, but you can click here to view the full 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[

Large files files are truncated, but you can click here to view the full file