Module: Lich::Gemstone::Infomon::XMLParser

Defined in:
documented/gemstone/infomon/xmlparser.rb

Overview

this module handles all of the logic for parsing game lines that infomon depends on

Defined Under Namespace

Modules: Pattern

Class Method Summary collapse

Class Method Details

.parse(line) ⇒ Symbol

Note:

Errors are logged to Lich.log and also printed as server responses.

Parses a single line or multi-line block of game server XML output and dispatches to appropriate handlers.

Uses an optimized two-tier strategy: for single-line input or buffered blocks with no embedded newlines, attempts anchored patterns (line-start ^) only at \A and scans just mid-line patterns via AllMidline. For multi-line strings (e.g. buffered combat blocks), falls back to the full union All so inner-line anchors (e.g. NPC death messages) still match. Recognized patterns trigger side effects (NPC status updates, group notifications, container/item roster updates, parser state transitions) and return a status symbol.

Matches trigger:

Parameters:

  • line (String) —

    a line or block of XML text from the game server

Returns:

  • (Symbol) —

    :ok if a pattern matched and was processed, :noop if no pattern matched, or on error during processing

Raises:

  • none; all errors are logged and return :ok to prevent stream disruption

See Also:

  • NpcDeathMessage, Group::Observer, Overwatch::Observer, StowList, ReadyList


582
583
584
585
586
587
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
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
# File 'documented/gemstone/infomon/xmlparser.rb', line 582

def self.parse(line)
  # Fast path: attempt the anchored union only at line start and scan just
  # the mid-line patterns. A non-terminal newline means a combined
  # multi-line string (e.g. buffered combat); fall back to the full union
  # so inner-line ^ anchors (e.g. death messages) still match.
  nl = line.index("\n")
  matched = if nl && nl < line.length - 1
              Pattern::All.match?(line)
            else
              Pattern::AllStart.match?(line) || Pattern::AllMidline.match?(line)
            end
  return :noop unless matched

  begin
    case line
    # this detects for death messages in XML that are not matched with appropriate combat attributes above
    when Pattern::NpcDeathMessage
      match = Regexp.last_match
      if (npc = GameObj.npcs.find { |obj| obj.id == match[:npc_id] && obj.status !~ /\b(?:dead|gone)\b/ })
        npc.status = 'dead'
      end
      :ok
    when Pattern::Group_Short
      return :noop unless (match_data = Group::Observer.wants?(line))
      Group::Observer.consume(line.strip, match_data)
      :ok
    when Pattern::Overwatch_Short
      return :noop unless (match_data = Overwatch::Observer.wants?(line))
      Overwatch::Observer.consume(line, match_data)
      :ok
    when Pattern::Also_Here_Arrival
      return :noop unless Lich::Claim::Lock.locked?
      line.scan(%r{<a exist=(?:'|")(?<id>.*?)(?:'|") noun=(?:'|")(?<noun>.*?)(?:'|")>(?<name>.*?)</a>}).each { |player_found|
        next unless player_found[0].to_s.start_with?('-')
        next if XMLData.arrival_pcs.include?(player_found[1])

        XMLData.arrival_pcs.push(player_found[1])
      }
      :ok
    when Pattern::StowListOutputStart
      StowList.reset
      :ok
    when Pattern::StowListContainer, Pattern::StowSetContainer1, Pattern::StowSetContainer2
      match = Regexp.last_match
      StowList.__send__("#{match[:type].downcase}=", GameObj.index_or_create(match[:id], match[:noun], match[:name], (match[:before].nil? ? nil : match[:before].strip), (match[:after].nil? ? nil : match[:after].strip)))
      StowList.checked = true if line =~ Pattern::StowListContainer
      :ok
    when Pattern::ReadyListOutputStart
      ReadyList.reset
      :ok
    when Pattern::ReadyListNormal, Pattern::ReadyListAmmo2, Pattern::ReadyListSheathsSet, Pattern::ReadyItemSet
      match = Regexp.last_match
      unless match[:id].nil?
        ReadyList.__send__("#{Lich::Util.normalize_name(match[:type].downcase)}=", GameObj.index_or_create(match[:id], match[:noun], match[:name], (match[:before].nil? ? nil : match[:before].strip), (match[:after].nil? ? nil : match[:after].strip)))
      end
      if match.named_captures.include?("store")
        ReadyList.__send__("store_#{Lich::Util.normalize_name(match[:type].downcase)}=", match[:store])
      end
      :ok
    when Pattern::ReadyListFinished
      ReadyList.checked = true
      :ok
    when Pattern::ReadyItemClear
      match = Regexp.last_match
      ReadyList.__send__("#{Lich::Util.normalize_name(match[:type].downcase)}=", nil)
      :ok
    when Pattern::ReadyStoreSet
      match = Regexp.last_match
      ReadyList.__send__("store_#{Lich::Util.normalize_name(match[:type].downcase)}=", match[:store])
      :ok
    when Pattern::StatusPrompt
      Infomon::Parser::State.set(Infomon::Parser::State::Ready) unless Infomon::Parser::State.get.eql?(Infomon::Parser::State::Ready)
      :ok
    else
      :noop
    end
  rescue StandardError
    respond "--- Lich: error: Infomon::XMLParser.parse: #{$!}"
    respond "--- Lich: error: line: #{line}"
    Lich.log "error: Infomon::XMLParser.parse: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
    Lich.log "error: line: #{line}\n\t"
  end
end