Class: Lich::GameBase::Game
- Inherits:
-
Object
- Object
- Lich::GameBase::Game
- Defined in:
- documented/games.rb
Overview
Base Game class with common functionality Base Game class with common functionality Provides methods and properties for game management
Direct Known Subclasses
Class Attribute Summary collapse
-
._buffer ⇒ Object
readonly
Returns the value of attribute _buffer.
-
.buffer ⇒ Object
readonly
Returns the value of attribute buffer.
-
.game_instance ⇒ Object
readonly
Returns the value of attribute game_instance.
-
.thread ⇒ Object
readonly
Returns the value of attribute thread.
Class Method Summary collapse
-
._gets ⇒ String
Retrieves a string from the internal buffer.
-
._puts(str) ⇒ void
Sends a string to the game server.
-
.close ⇒ void
Closes the game connection.
-
.closed? ⇒ Boolean
Checks if the game connection is closed.
-
.display_ruby_warning ⇒ void
Displays a warning if the Ruby version is outdated.
-
.gets ⇒ String
Retrieves a string from the game server.
-
.handle_autostart ⇒ void
Handles the autostart process for the game.
-
.handle_thread_error(error) ⇒ Boolean
Handles errors that occur in the server thread.
-
.handle_xml_error(server_string, error) ⇒ void
Handles XML errors during processing.
-
.initialize_buffers ⇒ void
Initializes the buffers for the game.
- .log_error(message, error) ⇒ Object protected
-
.open(host, port) ⇒ TCPSocket
Opens a connection to the game server.
-
.process_downstream_hooks(server_string) ⇒ void
Processes downstream hooks for the server string.
-
.process_room_information(alt_string) ⇒ String
Processes room information from the server string.
-
.process_server_string(server_string) ⇒ void
Processes the server string received from the game.
-
.process_xml_data(server_string) ⇒ void
Processes XML data from the server string.
-
.puts(str) ⇒ void
Sends a formatted string to the game server.
-
.send_to_client(alt_string) ⇒ void
Sends a string to the client.
-
.set_game_instance(game_type) ⇒ void
Sets the game instance based on the game type.
-
.start_cli_scripts ⇒ void
Starts CLI scripts if specified.
-
.start_main_thread ⇒ void
Starts the main thread for processing server messages.
-
.start_wrap_thread ⇒ void
Starts a thread to wrap the game connection.
Class Attribute Details
._buffer ⇒ Object (readonly)
Returns the value of attribute _buffer.
235 236 237 |
# File 'documented/games.rb', line 235 def _buffer @_buffer end |
.buffer ⇒ Object (readonly)
Returns the value of attribute buffer.
235 236 237 |
# File 'documented/games.rb', line 235 def buffer @buffer end |
.game_instance ⇒ Object (readonly)
Returns the value of attribute game_instance.
235 236 237 |
# File 'documented/games.rb', line 235 def game_instance @game_instance end |
.thread ⇒ Object (readonly)
Returns the value of attribute thread.
235 236 237 |
# File 'documented/games.rb', line 235 def thread @thread end |
Class Method Details
._gets ⇒ String
Retrieves a string from the internal buffer
368 369 370 |
# File 'documented/games.rb', line 368 def _gets @_buffer.gets end |
._puts(str) ⇒ void
This method returns an undefined value.
Sends a string to the game server
328 329 330 331 332 |
# File 'documented/games.rb', line 328 def _puts(str) @mutex.synchronize do @socket.puts(str) end end |
.close ⇒ void
This method returns an undefined value.
Closes the game connection
316 317 318 319 320 321 |
# File 'documented/games.rb', line 316 def close if @socket @socket.close rescue nil @thread.kill rescue nil end end |
.closed? ⇒ Boolean
Checks if the game connection is closed
308 309 310 |
# File 'documented/games.rb', line 308 def closed? @socket.nil? || @socket.closed? end |
.display_ruby_warning ⇒ void
This method returns an undefined value.
Displays a warning if the Ruby version is outdated
472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 |
# File 'documented/games.rb', line 472 def display_ruby_warning ruby_warning = Terminal::Table.new ruby_warning.title = "Ruby Recommended Version Warning" ruby_warning.add_row(["Please update your Ruby installation."]) ruby_warning.add_row(["You're currently running Ruby v#{Gem::Version.new(RUBY_VERSION)}!"]) ruby_warning.add_row(["It's recommended to run Ruby v#{Gem::Version.new(RECOMMENDED_RUBY)} or higher!"]) ruby_warning.add_row(["Future Lich5 releases will soon require this newer version."]) ruby_warning.add_row([" "]) ruby_warning.add_row(["Visit the following link for info on updating:"]) # Use instance to get the appropriate documentation URL if @game_instance ruby_warning.add_row([@game_instance.get_documentation_url]) else ruby_warning.add_row(["Unknown game type detected."]) ruby_warning.add_row(["Unsure of proper documentation, please seek assistance via discord!"]) end ruby_warning.to_s.split("\n").each do |row| Lich::Messaging.mono(Lich::Messaging.monsterbold(row)) end end |
.gets ⇒ String
Retrieves a string from the game server
360 361 362 |
# File 'documented/games.rb', line 360 def gets @buffer.gets end |
.handle_autostart ⇒ void
This method returns an undefined value.
Handles the autostart process for the game
453 454 455 456 457 458 459 460 461 462 463 464 465 466 |
# File 'documented/games.rb', line 453 def handle_autostart if defined?(LICH_VERSION) && defined?(Lich.core_updated_with_lich_version) && Gem::Version.new(LICH_VERSION) > Gem::Version.new(Lich.core_updated_with_lich_version) Lich::Messaging.mono(Lich::Messaging.monsterbold("New installation or updated version of Lich5 detected!")) Lich::Messaging.mono(Lich::Messaging.monsterbold("Installing newest core scripts available to ensure you're up-to-date!")) Lich::Messaging.mono("") Lich::Util::Update.update_core_data_and_scripts end Script.start('autostart') if defined?(Script) && Script.respond_to?(:exists?) && Script.exists?('autostart') @autostarted = true display_ruby_warning if defined?(RECOMMENDED_RUBY) && Gem::Version.new(RUBY_VERSION) < Gem::Version.new(RECOMMENDED_RUBY) end |
.handle_thread_error(error) ⇒ Boolean
Handles errors that occur in the server thread
658 659 660 661 662 663 664 665 |
# File 'documented/games.rb', line 658 def handle_thread_error(error) Lich.log "error: server_thread: #{error}\n\t#{error.backtrace.join("\n\t")}" $stdout.puts "error: server_thread: #{error}\n\t#{error.backtrace.slice(0..10).join("\n\t")}" sleep 0.2 # Cannot use retry here as it's not in a rescue block # Instead, we'll return a boolean indicating whether to retry return !($_CLIENT_.closed? || @socket.closed? || (error.to_s =~ /invalid argument|A connection attempt failed|An existing connection was forcibly closed|An established connection was aborted by the software in your host machine./i)) end |
.handle_xml_error(server_string, error) ⇒ void
This method returns an undefined value.
Handles XML errors during processing
566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 |
# File 'documented/games.rb', line 566 def handle_xml_error(server_string, error) # Ignoring certain XML errors unless error.to_s =~ /invalid byte sequence/ # Handle specific XML errors if server_string =~ /<settingsInfo .*?space not found / Lich.log "Invalid settingsInfo XML tags detected: #{server_string.inspect}" server_string.sub!('space not found', '') Lich.log "Invalid settingsInfo XML tags fixed to: #{server_string.inspect}" return process_xml_data(server_string) # Return to retry with fixed string end $stdout.puts "error: server_thread: #{error}\n\t#{error.backtrace.join("\n\t")}" Lich.log "Invalid XML detected - please report this: #{server_string.inspect}" Lich.log "error: server_thread: #{error}\n\t#{error.backtrace.join("\n\t")}" end end |
.initialize_buffers ⇒ void
This method returns an undefined value.
Initializes the buffers for the game
241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 |
# File 'documented/games.rb', line 241 def initialize_buffers @socket = nil @mutex = Mutex.new @last_recv = nil @thread = nil @buffer = Lich::Common::SharedBuffer.new @_buffer = Lich::Common::SharedBuffer.new @_buffer.max_size = 1000 @autostarted = false @cli_scripts = false @infomon_loaded = false @room_number_after_ready = false @last_id_shown_room_window = 0 @game_instance = nil end |
.log_error(message, error) ⇒ Object (protected)
669 670 671 |
# File 'documented/games.rb', line 669 def log_error(, error) Lich.log "#{}: #{error}\n\t#{error.backtrace.join("\n\t")}" end |
.open(host, port) ⇒ TCPSocket
Opens a connection to the game server
273 274 275 276 277 278 279 280 281 282 283 284 285 286 |
# File 'documented/games.rb', line 273 def open(host, port) @socket = TCPSocket.open(host, port) begin @socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true) rescue StandardError => e log_error("Socket option error", e) end @socket.sync = true start_wrap_thread start_main_thread @socket end |
.process_downstream_hooks(server_string) ⇒ void
This method returns an undefined value.
Processes downstream hooks for the server string
588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 |
# File 'documented/games.rb', line 588 def process_downstream_hooks(server_string) if (alt_string = DownstreamHook.run(server_string)) process_room_information(alt_string) # Handle frontend-specific modifications if $frontend =~ /genie/i && alt_string =~ /^<streamWindow id='room' title='Room' subtitle=" - \[.*\] \((?:\d+|\*\*)\)"/ alt_string.sub!(/] \((?:\d+|\*\*)\)/) { "]" } end if $frontend =~ /frostbite/i && alt_string =~ /^<streamWindow id='main' title='Story' subtitle=" - \[.*\] \((?:\d+|\*\*)\)"/ alt_string.sub!(/] \((?:\d+|\*\*)\)/) { "]" } end # Handle room number display if @room_number_after_ready && alt_string =~ /<prompt / alt_string = @game_instance ? @game_instance.process_room_display(alt_string) : alt_string @room_number_after_ready = false end # Handle frontend-specific conversions if $frontend =~ /^(?:wizard|avalon)$/ alt_string = sf_to_wiz(alt_string) end # Send to client send_to_client(alt_string) end end |
.process_room_information(alt_string) ⇒ String
Processes room information from the server string
622 623 624 625 626 627 628 629 630 |
# File 'documented/games.rb', line 622 def process_room_information(alt_string) if alt_string =~ /^(<pushStream id="familiar" ifClosedStyle="watching"\/>)?(?:<resource picture="\d+"\/>|<popBold\/>)?<style id="roomName"\s+\/>/ if (Lich.display_lichid == true || Lich.display_uid == true || Lich.hide_uid_flag == true) @game_instance ? @game_instance.modify_room_display(alt_string) : alt_string end @room_number_after_ready = true alt_string end end |
.process_server_string(server_string) ⇒ void
This method returns an undefined value.
Processes the server string received from the game
401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 |
# File 'documented/games.rb', line 401 def process_server_string(server_string) $cmd_prefix = String.new if server_string =~ /^\034GSw/ # Load game-specific modules if needed unless (XMLData.game.nil? || XMLData.game.empty?) unless Module.const_defined?(:GameLoader) require_relative 'common/game-loader' GameLoader.load! end end # Set instance if not already set if @game_instance.nil? && !XMLData.game.nil? && !XMLData.game.empty? set_game_instance(XMLData.game) end # Clean server string based on game type if @game_instance server_string = @game_instance.clean_serverstring(server_string) end # Debug output if needed pp server_string if defined?($deep_debug) && $deep_debug # Push to server buffer $_SERVERBUFFER_.push(server_string) # Handle autostart handle_autostart if !@autostarted && server_string =~ /<app char/ # Handle infomon loading if !@infomon_loaded && (defined?(Infomon) || !$DRINFOMON_VERSION.nil?) && !XMLData.name.nil? && !XMLData.name.empty? && !XMLData.dialogs.empty? ExecScript.start("Infomon.redo!", { quiet: true, name: "infomon_reset" }) if XMLData.game !~ /^DR/ && Infomon.db_refresh_needed? @infomon_loaded = true end # Handle CLI scripts if !@cli_scripts && @autostarted && !XMLData.name.nil? && !XMLData.name.empty? start_cli_scripts end # Process XML data process_xml_data(server_string) unless server_string =~ /^<settings / # Run downstream hooks process_downstream_hooks(server_string) end |
.process_xml_data(server_string) ⇒ void
This method returns an undefined value.
Processes XML data from the server string
514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 |
# File 'documented/games.rb', line 514 def process_xml_data(server_string) begin # Check for valid XML REXML::Document.parse_stream("<root>#{server_string}</root>", XMLData) rescue => e case e.to_s # Missing attribute equal: <s> - in dynamic dialogs with a single apostrophe for possessive 'Tsetem's Items' when /nested single quotes|nested double quotes|Missing attribute equal: <[^>]+>|Invalid attribute name: <[^>]+>/ original_server_string = server_string.dup server_string = XMLCleaner.clean_nested_quotes(server_string) if original_server_string != server_string retry else handle_xml_error(server_string, e) XMLData.reset return end when /invalid characters/ server_string = XMLCleaner.fix_invalid_characters(server_string) retry when /Missing end tag for 'd'/ server_string = XMLCleaner.(server_string) retry else handle_xml_error(server_string, e) XMLData.reset return end end # Process game-specific data using instance if @game_instance && Module.const_defined?(:GameLoader) @game_instance.process_game_specific_data(server_string) end # Process downstream XML Script.new_downstream_xml(server_string) if defined?(Script) # Process stripped server string stripped_server = strip_xml(server_string, type: 'main') stripped_server.split("\r\n").each do |line| @buffer.update(line) if defined?(TESTING) && TESTING Script.new_downstream(line) if defined?(Script) && !line.empty? end end |
.puts(str) ⇒ void
This method returns an undefined value.
Sends a formatted string to the game server
339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 |
# File 'documented/games.rb', line 339 def puts(str) if Script.current&.file_name script_name = "#{Script.current.custom? ? 'custom/' : ''}#{Script.current&.name}" else script_name = Script.current&.name || '(unknown script)' end $_CLIENTBUFFER_.push "[#{script_name}]#{$SEND_CHARACTER}#{$cmd_prefix}#{str}\r\n" unless Script.current&.silent respond "[#{script_name}]#{$SEND_CHARACTER}#{str}\r\n" end _puts "#{$cmd_prefix}#{str}" $_LASTUPSTREAM_ = "[#{script_name}]#{$SEND_CHARACTER}#{str}" end |
.send_to_client(alt_string) ⇒ void
This method returns an undefined value.
Sends a string to the client
637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 |
# File 'documented/games.rb', line 637 def send_to_client(alt_string) if $_DETACHABLE_CLIENT_ begin $_DETACHABLE_CLIENT_.write(alt_string) rescue $_DETACHABLE_CLIENT_.close rescue nil $_DETACHABLE_CLIENT_ = nil respond "--- Lich: error: client_thread: #{$!}" respond $!.backtrace.first Lich.log "error: client_thread: #{$!}\n\t#{$!.backtrace.join("\n\t")}" end else $_CLIENT_.write(alt_string) end end |
.set_game_instance(game_type) ⇒ void
This method returns an undefined value.
Sets the game instance based on the game type
262 263 264 |
# File 'documented/games.rb', line 262 def set_game_instance(game_type) @game_instance = GameInstanceFactory.create(game_type) end |
.start_cli_scripts ⇒ void
This method returns an undefined value.
Starts CLI scripts if specified
499 500 501 502 503 504 505 506 507 |
# File 'documented/games.rb', line 499 def start_cli_scripts if (arg = ARGV.find { |a| a =~ /^\-\-start\-scripts=/ }) arg.sub('--start-scripts=', '').split(',').each do |script_name| Script.start(script_name) end end @cli_scripts = true Lich.log("info: logged in as #{XMLData.game}:#{XMLData.name}") end |
.start_main_thread ⇒ void
This method returns an undefined value.
Starts the main thread for processing server messages
376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 |
# File 'documented/games.rb', line 376 def start_main_thread @thread = Thread.new do begin while (server_string = @socket.gets) @last_recv = Time.now @_buffer.update(server_string) if defined?(TESTING) && TESTING begin process_server_string(server_string) rescue StandardError => e log_error("Error processing server string", e) end end rescue StandardError => e handle_thread_error(e) end end @thread.priority = 4 end |
.start_wrap_thread ⇒ void
This method returns an undefined value.
Starts a thread to wrap the game connection
292 293 294 295 296 297 298 299 300 301 302 |
# File 'documented/games.rb', line 292 def start_wrap_thread @wrap_thread = Thread.new do @last_recv = Time.now until @autostarted || (Time.now - @last_recv >= 6) break if @autostarted sleep 0.2 end puts 'look' unless @autostarted end end |