Mercurial > hg > batmud > misc
changeset 188:31ac75a88dee misc
Renamed the utility more appropriately.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Mon, 24 Jan 2011 18:45:43 +0200 |
parents | 18b142c83761 |
children | 0342f0bd6101 |
files | log2npctemplate.pl log2template.pl |
diffstat | 2 files changed, 679 insertions(+), 679 deletions(-) [+] |
line wrap: on
line diff
--- a/log2npctemplate.pl Mon Jan 24 18:45:09 2011 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,679 +0,0 @@ -#!/usr/bin/perl -w -# -# log2npctemplate - Log parser for BatWiki NPC templates -# Developed by Matti Hämäläinen (Ggr Pupunen) <ccr@tnsp.org> -# (C) Copyright 2010-2011 Tecnic Software productions (TNSP) -# -# Requires Perl 5.8. -# -# Usage -# ===== -# 1) command qla say -- $* --;prod $*;whereami;say -??-;blook at $*;say -==-;look at $*;say ==== -# 2) rip_action set party kills (or spam party kills every once in a while to collect expvalue data) -# 3) qla monster -# 4) Cast detect race, detect alignment, whatnot at monster (if you wish/can/have those). -# 5) Fight and kill monster. -# 6) run log2npctemplate.pl < logfile.log > output.txt -# -# 7) command id -# -# Notes -# ===== -# - You must have spellname translator, preferably one with format: -# Xxxx claps its hands and says (cure critical wounds) -# -# - There is some flexibility in the parser, it can filter out ANSI from the -# input log, etc. but you may have to customize it. And that requires Perl -# knowledge. -# -use strict; - -# Options -my $opt_ansi = 1; # 1 = strip ANSI, 0 = don't (is faster) -my $opt_debug = 0; # 1 = enable debug output -my $opt_pkills_crop = 60; -my $opt_outfile; -my $opt_identify = 1; -my $opt_npc = 1; - - -# Define the regular expression to match any outputs of "say" -# as this is somewhat race-dependant. -my $sayregex = "(quack|say)"; - - -# Define the various spell chant prefixes here, we need them -# for matching the spell cast lines themselves. -my @spellchants = ( -"claps its hands and says", -"claps his hands and whispers", -"fills up his cheeks with air and exhales", -"booms in sinister voice", -"throws a pinch of magic dust in air and chants", -"rolls her eyes wildly and exclaims", -"rolls its eyes frantically and states", -"utters the magic words", -"spreads her fingers and whispers", -"waves its index finger while uttering", -"points an accusing finger and cries", -"gets an evil gleam in its eyes and chants", -"flaps arms and utters the magic words", -"flaps his arms and utters the magic words", -"taps its foot three times and utters the words", -"is surrounded by blue waves as you hear the words:", -"closes his eyes and with a dry, dark voice entones", -"shakes with firey rage and yells", -"traces fiery demonic night runes in the air", -"weaves a mystic matrix with glowing red lines in arcane ways and chants", -#"", -); - - -# Stat name translations -my @statTransTable = ( - sub { return $_[0] =~ s/^ability at the (skill|spell) (.+)$/$2/ }, - sub { return $_[0] =~ s/^mental regeneration$/spr/g }, - sub { return $_[0] =~ s/^physical regeneration$/hpr/g }, -# sub { return $_[0] =~ s///g }, -); - - -### Options -while (defined(my $arg = shift)) { - if ($arg eq "-d") { - $opt_debug++; - } - elsif ($arg eq "-i") { - $opt_identify = 0; - } - elsif ($arg eq "-n") { - $opt_npc = 0; - } - elsif ($arg eq "-o") { - $opt_outfile = shift or die("Output file option -o requires an argument.\n"); - } - else { - die("Invalid option '$arg'. -Usage: $0 [options] < input.log > output.txt - - -o <filename> Output filename. - -d Enable debug mode. - -i Disable identify parser. - -n Disable NPC parser. - -"); - } -} - -print STDERR "Debug mode enabled, level=".$opt_debug.".\n" if ($opt_debug); - -if (defined($opt_outfile)) { - open(STDOUT, '>', $opt_outfile) or die("Could not open output file '$opt_outfile'.\n"); -} - - -### Translate stats -sub trans_stat($) -{ - my ($s) = @_; - foreach my $pat (@statTransTable) { - return $s if ($pat->($s)); - } - return $s; -} - - -### Globals -my %monsters = (); -my %aligns = (); -my %spells = (); -my %races = (); -my %areas = (); -my %ids = (); - -my $line = 0; - -my $mob_sdesc; -my $mob = { }; -my $state = 0; - -my $id_tale; -my $id_tale2; -my $id = {}; - - -### Convert spellchants array to regexp -my $spellregexps = join("|", map { s/ (his|its|her) / ... /g; $_ } @spellchants); - - -### Change parsing state -sub set_state($$) -{ - return unless ($_[0] != $state); - print STDERR "STATE = $_[0], old = $state\n" if ($opt_debug > 1); - $state = $_[0]; - if ($state < 0) { - print STDERR "ERROR($line): $_[1]\n"; - $state = 0; - } -} - - -sub parse_npc($) -{ - my ($s) = @_; - - if ($state == 1) { - if ($s =~ /^You prod (.+?) like a stray cow\.$/o) { - # Get 'short name' - $$mob{"sname"} = $1; - } elsif ($s =~ /^You are in '.*?' in (.+?) on the continent of ([A-Z][a-z]+). \(Coordinates: (\d+)x, (\d+)y\)$/o) { - # Area, continent - $$mob{"area"} = $1; - $$mob{"cont"} = $2; - - $areas{$1}{"cont"} = $2; - $areas{$1}{"x"} = $3; - $areas{$1}{"y"} = $4; - } elsif ($s =~ /^You are in '.*?', which is on the continent of ([A-Z][a-z]+). \(Coordinates: (\d)+x, (\d)+y\)$/o) { - # Area, continent - $$mob{"area"} = $1; - $$mob{"cont"} = $1; - - $areas{$1}{"cont"} = $1; - $areas{$1}{"x"} = $2; - $areas{$1}{"y"} = $3; - } elsif ($s =~ /^You $sayregex '(-\?\?-|--\?\?--\?\?--\?\?--\?\?--)\.'$/o) { - set_state(2, $s); - } else { - set_state(-1, $s); - } - } - elsif ($state == 2) { - # Mostly just grab shortdesc here - if ($s =~ /^You $sayregex '(-==-|-=-=-=-=-=-=-=-=-)\.'$/o) { - set_state(3, $s); - } elsif ($s =~ /^(He|She|It) +\[[a-z]+\] \([0-9-]+\%\)/o) { - set_state(-1, $s); - } elsif ($s =~ /^You $sayregex /o) { - set_state(-1, $s); - } else { - $mob_sdesc = $s; - } - } - elsif ($state == 3) { - if ($s =~ /^ +([a-zA-Z ,-]+?)'s equipment:$/o) { - # End of long desc - set_state(4, $s); - } elsif ($s =~ /^(He|She|It) +\[[a-z]+\] \([0-9-]+\%\)$/o || - $s =~ /^(He|She|It) is (in (a )?)?(excellent shape|good shape|slightly hurt|noticeably hurt|not in a good shape|bad shape|very bad shape|near death)/o) { - # End of long desc - set_state(4, $s); - } elsif ($s =~ /^You $sayregex /o) { - set_state(-1, $s); - } else { - # Collect long desc - $$mob{"ldesc"} .= $s." "; - } - } - elsif ($state == 4) { - if ($s =~ /^You $sayregex '====*'?\.'$/o) { - # Finished, submit information to hash - $monsters{$mob_sdesc} = $mob; - set_state(0, $s); - } elsif ($s =~ /^([A-Z][a-z, ]+): (.*)$/o) { - # Collect EQ and slot information - $$mob{"eqs"}{$1} = $2; - } elsif ($s =~ /^([A-Z][a-z, ]+)\s+\(partially hidden\)\s*: (.*)$/o) { - # Collect EQ and slot information - $$mob{"eqs"}{$1} = $2; - } elsif ($s =~ /^ +([a-zA-Z -]+?)'s equipment:$/o) { - # Ignore this line - } elsif ($s =~ /^((It|She|He) has a blueish black kiss on ... cheek\.|(It|She|He) suffers from scourge\.|Some glittering red mist is surrounding this creature.|Nothing\.|(It|He|She) has a faint ring of .+ magic mist around ... neck\.)$/o) { - # Ignore various useless lines such as holz markings - } else { - # Error. - set_state(-1, $s); - } - } -} - - -sub parse_identify($) -{ - my ($s) = @_; - - if ($state == 10) { - if ($s =~ /^You $sayregex '-#-\.'$/o) { - set_state(11, $s); - } - elsif ($s =~ /^:\s*(\d+\.\d+)\s+(.+)$/) { - # Weigh in kgs, and sdesc. Will have to match these later. - $$id{"kg"}{$2} = $1; - $$id{"full"} = $2; - } - } - elsif ($state == 11) { - if ($s =~ /^It is surrounded by .+? glow\.$|^It is labeled as|^This item is a present from santa to /) { - # Just skip these - } - elsif ($s =~ /You $sayregex '-!-\.'$/o) { - set_state(12, $s); - } - elsif ($s =~ /^It contains a tale;$/) { - $id_tale = 1; - } - elsif ($s =~ /^This (.+?) is in .+? condition\.$/) { - $$id{"type"} = $1; - } - elsif ($s =~ /^These (.+?) are in .+? condition\.$/) { - $$id{"type"} = $1; - } - elsif ($s =~ /^It looks (light weight|a bit heavy|heavy|very very heavy|very light weight|very heavy|ridiculously heavy)\.$/) { - $$id{"weight"} = $1; - } - else { - if ($id_tale) { - $$id{"tale"} .= $s." "; - } else { - my $len = length($s); - $$id{"ldesc"} .= $s; - if (defined($$id{"len"}) && $len + $$id{"len"} < 79) { - $$id{"ldesc"} .= "\n"; - } else { - $$id{"ldesc"} .= " "; - } - $$id{"len"} = $len; - } - } - } - elsif ($state == 12) { - if ($s =~ /^The following messages seem to vibrate from (.+?):$/o) { - $$id{"sdesc"} = $1; - set_state(13, $s); - } - } - elsif ($state == 13) { - if ($s =~ /^It is called (.+)\.$/) { - $$id{"handles"} = $1; - $id_tale2 = 0; - } - elsif ($s =~ /^It takes the following slots: (.+)\.$/) { - $$id{"slots"} = $1; - $id_tale2 = 0; - } - elsif ($s =~ /^A halo of purity surrounds it\.$/) { - push(@{$$id{"stats"}}, "[[:category:halo of purity|good wear only]]"); - $$id{"cat"}{"halo of purity"} = 1; - $id_tale2 = 0; - } - elsif ($s =~ /^An aura of blackness surrounds it\.$/) { - push(@{$$id{"stats"}}, "[[:category:aura of darkness|evil wear only]]"); - $$id{"cat"}{"aura of darkness"} = 1; - $id_tale2 = 0; - } - elsif ($s =~ /^It will (.+?) (improve|reduce) your (.+?)\.$/) { - my ($amt, $imp, $stat) = ($1, $2, $3); - push(@{$$id{"stats"}}, ($imp eq "improve" ? "+" : "-").$amt." ".trans_stat($stat)); - $$id{"cat"}{trans_stat($stat)} = 1 if ($imp eq "improve"); - $id_tale2 = 0; - } - elsif ($s =~ /^It will (raise|lower) your (.+?)\.$/) { - my $imp = $1; - my $stat = substr($2, 0, 3); - push(@{$$id{"stats"}}, ($imp eq "raise" ? "+" : "-").$stat); - $$id{"cat"}{$stat} = 1 if ($imp eq "raise"); - $id_tale2 = 0; - } - elsif ($s =~ /^It contains a tale;$/) { - $id_tale2 = 1; - } - elsif ($s =~ /^(.+?) did the heroic deed to bring this piece of equipment before you (.+)$/) { - my $info = $2; - $$id{""} = ""; - - # Parse the big data blob - if ($info =~ /It is ([A-Za-z -]+), ([A-Za-z -]+),/) { - $$id{"weight2"} = $1; - $$id{"size"} = $2; - } - - if ($info =~ /of ([A-Za-z ]+) quality/) { - $$id{"quality"} = $1; - } - - if ($info =~ /(emits darkness|emits light)/) { - push(@{$$id{"stats"}}, $1); - $$id{"cat"}{$1} = 1; - } - - if ($info =~ /made of,? ?(.+?), (feather|worth)/) { - @{$$id{"materials"}} = split(/\s*,\s*/, $1); - my $qmat = $$id{"materials"}[0]; - my $qmax = -1; - foreach my $mat (@{$$id{"materials"}}) { - if ($mat =~ /^(\d+)% (.+)$/) { - if ($1 >= $qmax) { - $qmax = $1; - $qmat = $2; - } - } - } - $$id{"material"} = $qmat; - $$id{"cat"}{$qmat} = 1; - } - - # Check for nun relics - if ($$id{"full"} =~ / \(holy\)/) { - $$id{"cat"}{"relic"} = 1; - } - - # Set item class - $$id{"class"} = "Item"; - if ($info =~ /The weapon skill to best use this in combat/) { - $$id{"class"} = "Weapon"; - } elsif (defined($$id{"type"}) && $$id{"type"} ne "") { - $$id{"class"} = "Armour"; - } - - # Check if the weight data is for FW'd item - if ($$id{"full"} =~ / <(yellow|green|white) glow>$/) { - $$id{"kg"} = ""; - $$id{"weight"} = ""; - } else { - if (defined($$id{"kg"}{$$id{"full"}})) { - $$id{"kg"} = $$id{"kg"}{$$id{"full"}}; - } else { - $$id{"kg"} = ""; - } - } - - # Submit data - $ids{$$id{"full"}} = $id; - set_state(0, $s); - } - else { - if ($id_tale2) { - $$id{"tale"} .= $s." "; - } else { - push(@{$$id{"other"}}, $s); - } - } - } -} - - -### -### Main parsing loop -### -while (defined(my $s = <STDIN>)) { - $line++; - chomp($s); - - # Strip ANSI colours and escapes - $s =~ s/\e\[?.*?[\@-~]//g if ($opt_ansi); - - if - ($s =~ /^([A-Za-z][A-Za-z\ -]+?)\ ($spellregexps)\ \(([A-Za-z][A-Za-z\ ]+)\)/o) - { - # Grab cast spells - $spells{$1}{$line} = lc($3); - } - elsif ($s =~ /\| \d\d:\d\d +(\d+): (.+?) +\|$/) { - # Grab exp worth - my $exp = $1; - my $tmp = $2; - my $sdesc = substr($tmp, 0, $opt_pkills_crop); - if (defined($monsters{$sdesc}) && $line > $monsters{$sdesc}{"line"}) { - $monsters{$sdesc}{"exp"} = $exp; - } else { - $tmp .= " (undead)"; - $sdesc = substr($tmp, 0, $opt_pkills_crop); - if (defined($monsters{$sdesc}) && $line > $monsters{$sdesc}{"line"}) { - $monsters{$sdesc}{"exp"} = $exp; - } - } - } - elsif ($s =~ /^\[\d\d:\d\d:\d\d\] hp|^You laugh out loud\.$|^Dunk dunk$/) { - # Ignore these lines - } - elsif ($s =~ /^You see nothing special\.$/o) { - # Reset parsing state if the target obviously didn't exist - set_state(0, $s); - } - elsif ($state == 0 && $s =~ /^You $sayregex '-+ .+? -+\.'$/o) { - next unless ($opt_npc); - # Beginning of NPC info block - set_state(1, $s); - undef($mob); - $mob_sdesc = ""; - $$mob{"line"} = $line; - } - elsif ($state == 0 && $s =~ /^You $sayregex '-ID-\.'$/o) { - next unless ($opt_identify); - # Beginning of identify info block - set_state(10, $s); - $id = {}; - undef($id); - $id_tale = 0; - $id_tale2 = 0; - $$id{"line"} = $line; - } - elsif ($state > 0 && $state < 10) { - # States for NPC parsing - parse_npc($s); - } - elsif ($state >= 10) { - parse_identify($s); - } - elsif ($s =~ /^(.+?) is (a bit good|a bit evil|evil|good|neutral)\.$/o) { - # Grab detect alignment - $aligns{$1}{$line} = $2; - } - elsif ($s =~ /^([A-Za-z][A-Za-z\ .-]+?)\ is an? ([a-z-]+)\.$/o) { - # Grab detect race - $races{$1}{$line} = $2; - } - elsif ($s =~ /^You $sayregex /o) { - # Error, reset parsing state - set_state(0, $s); - } -} - - -### -### Print out item/identify information -### -foreach my $sdesc (sort { $a cmp $b } keys %ids) { - my $item = $ids{$sdesc}; - my $name = $$item{"sdesc"}; - $name =~ s/ labeled as .*$//; - my $qdesc = $name; - if ($$item{"full"} =~ / <(red|purple|white) glow>$/) { - $name .= " <red glow>"; - } - - print "---------------------------\n". - $qdesc."\n\n". - "{{ Infobox ".$$item{"class"}."\n". - "| name = ".$name."\n". - "| description = ".$$item{"ldesc"}."\n"; - - if (defined($$item{"tale"}) && $$item{"tale"} ne "") { - print "| tale = ".$$item{"tale"}."\n"; - } - - if (defined($$item{"type"})) { - print "| type = ".$$item{"type"}."\n"; - } - - if (defined($$item{"stats"})) { - print "| stats = ".join(", ", @{$$item{"stats"}})."\n"; - } else { - print "| stats = \n"; - } - - print - "| weight = ".$$item{"weight"}."\n". - "| kg = ".$$item{"kg"}."\n". - "| sacvalue = \n". - "| handles = ".$$item{"handles"}."\n"; - - if (defined($$item{"materials"})) { - print "| material = ".join(", ", @{$$item{"materials"}})."\n"; - } - - print - "| size = ".$$item{"size"}."\n". - "| quality = ".$$item{"quality"}."\n". - "| from = \n"; - - if ($$item{"class"} eq "Weapon") { - print - "| wep1 = \n". - "| wep2 = \n"; - } - - if (defined($$item{"other"})) { - print "| other = ".join("\n", @{$$item{"other"}})."\n"; - } - - print - "}}\n"; - - foreach my $cat (keys %{$$item{"cat"}}) { - print "[[Category:$cat]]\n"; - } - - print "---------------------\n"; -} - - -### -### Print out the NPC information -### -foreach my $sdesc (sort { $a cmp $b } keys %monsters) { - my $mob = $monsters{$sdesc}; - - print "--------------------------- -$sdesc - -{{ NPC -| name = ".$$mob{"sname"}." -| description = ".$$mob{"ldesc"}." -| eqs = "; - - foreach my $slot (sort { $a cmp $b } keys %{$$mob{"eqs"}}) { - my ($k, $s); - $k = $s = $$mob{"eqs"}{$slot}; - $k =~ s/ <[a-z]+ glow>$//; - if ($k eq $s) { - print "$slot: [[$s]]\n\n"; - } else { - print "$slot: [[$k|$s]]\n\n"; - } - } - - print "none" unless (scalar keys %{$$mob{"eqs"}} > 0); - print "\n"; - - if ($sdesc =~ / \(undead\)$/) { - print "| undead = x\n"; - } - - # Check if we find a matching alignment: find a cast of 'detect alignment' - # with matching short name and withing appropriate distance from the mob look. - my $mname = $$mob{"sname"}; - my $mline = $$mob{"line"}; - - print "| alignment = "; - if (defined($aligns{$mname})) { - my $qoffs = 1000; - my $qline = -1; - foreach my $aline (sort { $a <=> $b } keys %{$aligns{$mname}}) { - my $offs = ($aline - $mline); - if ($offs < $qoffs) { - $qoffs = $offs; - $qline = $aline; - } - } - if ($qline >= 0) { - print STDERR "ALIGN: $mname ($sdesc) \@ #$qline ($qoffs)\n" if ($opt_debug); - print $aligns{$mname}{$qline}; - } - } - print "\n". - "| race = "; - - # Check for race - if (defined($races{$mname})) { - my $qoffs = 1000; - my $qline = -1; - foreach my $aline (sort { $a <=> $b } keys %{$races{$mname}}) { - my $offs = ($aline - $mline); - if ($offs < $qoffs) { - $qoffs = $offs; - $qline = $aline; - } - } - if ($qline >= 0) { - print STDERR "RACE: $mname ($sdesc) \@ #$qline ($qoffs)\n" if ($opt_debug); - print $races{$mname}{$qline}; - } - } - print "\n"; - - - # Spells - my $hasspells = 0; - if (defined($spells{$mname}) && length($mname) >= 4) { - my $maxdist = length($mname) * 150 * length($mname); - my $mindist = length($mname) * -5 * length($mname); - my %mspells = (); - foreach my $sline (sort { $a <=> $b } keys %{$spells{$mname}}) { - my $offs = $sline - $mline; -# print STDERR "TEST $mname @ $offs vs $mindist...$maxdist : '".$spells{$mname}{$sline}."'\n"; - if ($offs < $maxdist && $offs > $mindist) { - print STDERR "SPELL $mname \@ $offs / $mindist...$maxdist : '".$spells{$mname}{$sline}."'\n" if ($opt_debug); - $mspells{$spells{$mname}{$sline}} = 1; - $hasspells++; - } - } - my $n = 1; - foreach my $spell (sort { $a cmp $b } keys %mspells) { - print "| spell".($n < 2 ? "" : $n)." = $spell\n"; - $n++; - } - } - -unless ($hasspells > 0) { -print "| spell = -| spell# = -"; -} - - print " -| area = ".$$mob{"area"}." -| skill = -| skill# = -| exp = "; - - if (defined($$mob{"exp"})) { - if ($$mob{"exp"} > 1000000) { - printf "%1.2fM", $$mob{"exp"} / 1000000.0; - } elsif ($$mob{"exp"} > 1000) { - printf "%dk", $$mob{"exp"} / 1000; - } else { - printf "%d", $$mob{"exp"}; - } - } - -print " -| GAP = -| PAP = -| kerbholz = -| other = -}} ---------------------- -"; -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/log2template.pl Mon Jan 24 18:45:43 2011 +0200 @@ -0,0 +1,679 @@ +#!/usr/bin/perl -w +# +# log2npctemplate - Log parser for BatWiki NPC templates +# Developed by Matti Hämäläinen (Ggr Pupunen) <ccr@tnsp.org> +# (C) Copyright 2010-2011 Tecnic Software productions (TNSP) +# +# Requires Perl 5.8. +# +# Usage +# ===== +# 1) command qla say -- $* --;prod $*;whereami;say -??-;blook at $*;say -==-;look at $*;say ==== +# 2) rip_action set party kills (or spam party kills every once in a while to collect expvalue data) +# 3) qla monster +# 4) Cast detect race, detect alignment, whatnot at monster (if you wish/can/have those). +# 5) Fight and kill monster. +# 6) run log2npctemplate.pl < logfile.log > output.txt +# +# 7) command id +# +# Notes +# ===== +# - You must have spellname translator, preferably one with format: +# Xxxx claps its hands and says (cure critical wounds) +# +# - There is some flexibility in the parser, it can filter out ANSI from the +# input log, etc. but you may have to customize it. And that requires Perl +# knowledge. +# +use strict; + +# Options +my $opt_ansi = 1; # 1 = strip ANSI, 0 = don't (is faster) +my $opt_debug = 0; # 1 = enable debug output +my $opt_pkills_crop = 60; +my $opt_outfile; +my $opt_identify = 1; +my $opt_npc = 1; + + +# Define the regular expression to match any outputs of "say" +# as this is somewhat race-dependant. +my $sayregex = "(quack|say)"; + + +# Define the various spell chant prefixes here, we need them +# for matching the spell cast lines themselves. +my @spellchants = ( +"claps its hands and says", +"claps his hands and whispers", +"fills up his cheeks with air and exhales", +"booms in sinister voice", +"throws a pinch of magic dust in air and chants", +"rolls her eyes wildly and exclaims", +"rolls its eyes frantically and states", +"utters the magic words", +"spreads her fingers and whispers", +"waves its index finger while uttering", +"points an accusing finger and cries", +"gets an evil gleam in its eyes and chants", +"flaps arms and utters the magic words", +"flaps his arms and utters the magic words", +"taps its foot three times and utters the words", +"is surrounded by blue waves as you hear the words:", +"closes his eyes and with a dry, dark voice entones", +"shakes with firey rage and yells", +"traces fiery demonic night runes in the air", +"weaves a mystic matrix with glowing red lines in arcane ways and chants", +#"", +); + + +# Stat name translations +my @statTransTable = ( + sub { return $_[0] =~ s/^ability at the (skill|spell) (.+)$/$2/ }, + sub { return $_[0] =~ s/^mental regeneration$/spr/g }, + sub { return $_[0] =~ s/^physical regeneration$/hpr/g }, +# sub { return $_[0] =~ s///g }, +); + + +### Options +while (defined(my $arg = shift)) { + if ($arg eq "-d") { + $opt_debug++; + } + elsif ($arg eq "-i") { + $opt_identify = 0; + } + elsif ($arg eq "-n") { + $opt_npc = 0; + } + elsif ($arg eq "-o") { + $opt_outfile = shift or die("Output file option -o requires an argument.\n"); + } + else { + die("Invalid option '$arg'. +Usage: $0 [options] < input.log > output.txt + + -o <filename> Output filename. + -d Enable debug mode. + -i Disable identify parser. + -n Disable NPC parser. + +"); + } +} + +print STDERR "Debug mode enabled, level=".$opt_debug.".\n" if ($opt_debug); + +if (defined($opt_outfile)) { + open(STDOUT, '>', $opt_outfile) or die("Could not open output file '$opt_outfile'.\n"); +} + + +### Translate stats +sub trans_stat($) +{ + my ($s) = @_; + foreach my $pat (@statTransTable) { + return $s if ($pat->($s)); + } + return $s; +} + + +### Globals +my %monsters = (); +my %aligns = (); +my %spells = (); +my %races = (); +my %areas = (); +my %ids = (); + +my $line = 0; + +my $mob_sdesc; +my $mob = { }; +my $state = 0; + +my $id_tale; +my $id_tale2; +my $id = {}; + + +### Convert spellchants array to regexp +my $spellregexps = join("|", map { s/ (his|its|her) / ... /g; $_ } @spellchants); + + +### Change parsing state +sub set_state($$) +{ + return unless ($_[0] != $state); + print STDERR "STATE = $_[0], old = $state\n" if ($opt_debug > 1); + $state = $_[0]; + if ($state < 0) { + print STDERR "ERROR($line): $_[1]\n"; + $state = 0; + } +} + + +sub parse_npc($) +{ + my ($s) = @_; + + if ($state == 1) { + if ($s =~ /^You prod (.+?) like a stray cow\.$/o) { + # Get 'short name' + $$mob{"sname"} = $1; + } elsif ($s =~ /^You are in '.*?' in (.+?) on the continent of ([A-Z][a-z]+). \(Coordinates: (\d+)x, (\d+)y\)$/o) { + # Area, continent + $$mob{"area"} = $1; + $$mob{"cont"} = $2; + + $areas{$1}{"cont"} = $2; + $areas{$1}{"x"} = $3; + $areas{$1}{"y"} = $4; + } elsif ($s =~ /^You are in '.*?', which is on the continent of ([A-Z][a-z]+). \(Coordinates: (\d)+x, (\d)+y\)$/o) { + # Area, continent + $$mob{"area"} = $1; + $$mob{"cont"} = $1; + + $areas{$1}{"cont"} = $1; + $areas{$1}{"x"} = $2; + $areas{$1}{"y"} = $3; + } elsif ($s =~ /^You $sayregex '(-\?\?-|--\?\?--\?\?--\?\?--\?\?--)\.'$/o) { + set_state(2, $s); + } else { + set_state(-1, $s); + } + } + elsif ($state == 2) { + # Mostly just grab shortdesc here + if ($s =~ /^You $sayregex '(-==-|-=-=-=-=-=-=-=-=-)\.'$/o) { + set_state(3, $s); + } elsif ($s =~ /^(He|She|It) +\[[a-z]+\] \([0-9-]+\%\)/o) { + set_state(-1, $s); + } elsif ($s =~ /^You $sayregex /o) { + set_state(-1, $s); + } else { + $mob_sdesc = $s; + } + } + elsif ($state == 3) { + if ($s =~ /^ +([a-zA-Z ,-]+?)'s equipment:$/o) { + # End of long desc + set_state(4, $s); + } elsif ($s =~ /^(He|She|It) +\[[a-z]+\] \([0-9-]+\%\)$/o || + $s =~ /^(He|She|It) is (in (a )?)?(excellent shape|good shape|slightly hurt|noticeably hurt|not in a good shape|bad shape|very bad shape|near death)/o) { + # End of long desc + set_state(4, $s); + } elsif ($s =~ /^You $sayregex /o) { + set_state(-1, $s); + } else { + # Collect long desc + $$mob{"ldesc"} .= $s." "; + } + } + elsif ($state == 4) { + if ($s =~ /^You $sayregex '====*'?\.'$/o) { + # Finished, submit information to hash + $monsters{$mob_sdesc} = $mob; + set_state(0, $s); + } elsif ($s =~ /^([A-Z][a-z, ]+): (.*)$/o) { + # Collect EQ and slot information + $$mob{"eqs"}{$1} = $2; + } elsif ($s =~ /^([A-Z][a-z, ]+)\s+\(partially hidden\)\s*: (.*)$/o) { + # Collect EQ and slot information + $$mob{"eqs"}{$1} = $2; + } elsif ($s =~ /^ +([a-zA-Z -]+?)'s equipment:$/o) { + # Ignore this line + } elsif ($s =~ /^((It|She|He) has a blueish black kiss on ... cheek\.|(It|She|He) suffers from scourge\.|Some glittering red mist is surrounding this creature.|Nothing\.|(It|He|She) has a faint ring of .+ magic mist around ... neck\.)$/o) { + # Ignore various useless lines such as holz markings + } else { + # Error. + set_state(-1, $s); + } + } +} + + +sub parse_identify($) +{ + my ($s) = @_; + + if ($state == 10) { + if ($s =~ /^You $sayregex '-#-\.'$/o) { + set_state(11, $s); + } + elsif ($s =~ /^:\s*(\d+\.\d+)\s+(.+)$/) { + # Weigh in kgs, and sdesc. Will have to match these later. + $$id{"kg"}{$2} = $1; + $$id{"full"} = $2; + } + } + elsif ($state == 11) { + if ($s =~ /^It is surrounded by .+? glow\.$|^It is labeled as|^This item is a present from santa to /) { + # Just skip these + } + elsif ($s =~ /You $sayregex '-!-\.'$/o) { + set_state(12, $s); + } + elsif ($s =~ /^It contains a tale;$/) { + $id_tale = 1; + } + elsif ($s =~ /^This (.+?) is in .+? condition\.$/) { + $$id{"type"} = $1; + } + elsif ($s =~ /^These (.+?) are in .+? condition\.$/) { + $$id{"type"} = $1; + } + elsif ($s =~ /^It looks (light weight|a bit heavy|heavy|very very heavy|very light weight|very heavy|ridiculously heavy)\.$/) { + $$id{"weight"} = $1; + } + else { + if ($id_tale) { + $$id{"tale"} .= $s." "; + } else { + my $len = length($s); + $$id{"ldesc"} .= $s; + if (defined($$id{"len"}) && $len + $$id{"len"} < 79) { + $$id{"ldesc"} .= "\n"; + } else { + $$id{"ldesc"} .= " "; + } + $$id{"len"} = $len; + } + } + } + elsif ($state == 12) { + if ($s =~ /^The following messages seem to vibrate from (.+?):$/o) { + $$id{"sdesc"} = $1; + set_state(13, $s); + } + } + elsif ($state == 13) { + if ($s =~ /^It is called (.+)\.$/) { + $$id{"handles"} = $1; + $id_tale2 = 0; + } + elsif ($s =~ /^It takes the following slots: (.+)\.$/) { + $$id{"slots"} = $1; + $id_tale2 = 0; + } + elsif ($s =~ /^A halo of purity surrounds it\.$/) { + push(@{$$id{"stats"}}, "[[:category:halo of purity|good wear only]]"); + $$id{"cat"}{"halo of purity"} = 1; + $id_tale2 = 0; + } + elsif ($s =~ /^An aura of blackness surrounds it\.$/) { + push(@{$$id{"stats"}}, "[[:category:aura of darkness|evil wear only]]"); + $$id{"cat"}{"aura of darkness"} = 1; + $id_tale2 = 0; + } + elsif ($s =~ /^It will (.+?) (improve|reduce) your (.+?)\.$/) { + my ($amt, $imp, $stat) = ($1, $2, $3); + push(@{$$id{"stats"}}, ($imp eq "improve" ? "+" : "-").$amt." ".trans_stat($stat)); + $$id{"cat"}{trans_stat($stat)} = 1 if ($imp eq "improve"); + $id_tale2 = 0; + } + elsif ($s =~ /^It will (raise|lower) your (.+?)\.$/) { + my $imp = $1; + my $stat = substr($2, 0, 3); + push(@{$$id{"stats"}}, ($imp eq "raise" ? "+" : "-").$stat); + $$id{"cat"}{$stat} = 1 if ($imp eq "raise"); + $id_tale2 = 0; + } + elsif ($s =~ /^It contains a tale;$/) { + $id_tale2 = 1; + } + elsif ($s =~ /^(.+?) did the heroic deed to bring this piece of equipment before you (.+)$/) { + my $info = $2; + $$id{""} = ""; + + # Parse the big data blob + if ($info =~ /It is ([A-Za-z -]+), ([A-Za-z -]+),/) { + $$id{"weight2"} = $1; + $$id{"size"} = $2; + } + + if ($info =~ /of ([A-Za-z ]+) quality/) { + $$id{"quality"} = $1; + } + + if ($info =~ /(emits darkness|emits light)/) { + push(@{$$id{"stats"}}, $1); + $$id{"cat"}{$1} = 1; + } + + if ($info =~ /made of,? ?(.+?), (feather|worth)/) { + @{$$id{"materials"}} = split(/\s*,\s*/, $1); + my $qmat = $$id{"materials"}[0]; + my $qmax = -1; + foreach my $mat (@{$$id{"materials"}}) { + if ($mat =~ /^(\d+)% (.+)$/) { + if ($1 >= $qmax) { + $qmax = $1; + $qmat = $2; + } + } + } + $$id{"material"} = $qmat; + $$id{"cat"}{$qmat} = 1; + } + + # Check for nun relics + if ($$id{"full"} =~ / \(holy\)/) { + $$id{"cat"}{"relic"} = 1; + } + + # Set item class + $$id{"class"} = "Item"; + if ($info =~ /The weapon skill to best use this in combat/) { + $$id{"class"} = "Weapon"; + } elsif (defined($$id{"type"}) && $$id{"type"} ne "") { + $$id{"class"} = "Armour"; + } + + # Check if the weight data is for FW'd item + if ($$id{"full"} =~ / <(yellow|green|white) glow>$/) { + $$id{"kg"} = ""; + $$id{"weight"} = ""; + } else { + if (defined($$id{"kg"}{$$id{"full"}})) { + $$id{"kg"} = $$id{"kg"}{$$id{"full"}}; + } else { + $$id{"kg"} = ""; + } + } + + # Submit data + $ids{$$id{"full"}} = $id; + set_state(0, $s); + } + else { + if ($id_tale2) { + $$id{"tale"} .= $s." "; + } else { + push(@{$$id{"other"}}, $s); + } + } + } +} + + +### +### Main parsing loop +### +while (defined(my $s = <STDIN>)) { + $line++; + chomp($s); + + # Strip ANSI colours and escapes + $s =~ s/\e\[?.*?[\@-~]//g if ($opt_ansi); + + if + ($s =~ /^([A-Za-z][A-Za-z\ -]+?)\ ($spellregexps)\ \(([A-Za-z][A-Za-z\ ]+)\)/o) + { + # Grab cast spells + $spells{$1}{$line} = lc($3); + } + elsif ($s =~ /\| \d\d:\d\d +(\d+): (.+?) +\|$/) { + # Grab exp worth + my $exp = $1; + my $tmp = $2; + my $sdesc = substr($tmp, 0, $opt_pkills_crop); + if (defined($monsters{$sdesc}) && $line > $monsters{$sdesc}{"line"}) { + $monsters{$sdesc}{"exp"} = $exp; + } else { + $tmp .= " (undead)"; + $sdesc = substr($tmp, 0, $opt_pkills_crop); + if (defined($monsters{$sdesc}) && $line > $monsters{$sdesc}{"line"}) { + $monsters{$sdesc}{"exp"} = $exp; + } + } + } + elsif ($s =~ /^\[\d\d:\d\d:\d\d\] hp|^You laugh out loud\.$|^Dunk dunk$/) { + # Ignore these lines + } + elsif ($s =~ /^You see nothing special\.$/o) { + # Reset parsing state if the target obviously didn't exist + set_state(0, $s); + } + elsif ($state == 0 && $s =~ /^You $sayregex '-+ .+? -+\.'$/o) { + next unless ($opt_npc); + # Beginning of NPC info block + set_state(1, $s); + undef($mob); + $mob_sdesc = ""; + $$mob{"line"} = $line; + } + elsif ($state == 0 && $s =~ /^You $sayregex '-ID-\.'$/o) { + next unless ($opt_identify); + # Beginning of identify info block + set_state(10, $s); + $id = {}; + undef($id); + $id_tale = 0; + $id_tale2 = 0; + $$id{"line"} = $line; + } + elsif ($state > 0 && $state < 10) { + # States for NPC parsing + parse_npc($s); + } + elsif ($state >= 10) { + parse_identify($s); + } + elsif ($s =~ /^(.+?) is (a bit good|a bit evil|evil|good|neutral)\.$/o) { + # Grab detect alignment + $aligns{$1}{$line} = $2; + } + elsif ($s =~ /^([A-Za-z][A-Za-z\ .-]+?)\ is an? ([a-z-]+)\.$/o) { + # Grab detect race + $races{$1}{$line} = $2; + } + elsif ($s =~ /^You $sayregex /o) { + # Error, reset parsing state + set_state(0, $s); + } +} + + +### +### Print out item/identify information +### +foreach my $sdesc (sort { $a cmp $b } keys %ids) { + my $item = $ids{$sdesc}; + my $name = $$item{"sdesc"}; + $name =~ s/ labeled as .*$//; + my $qdesc = $name; + if ($$item{"full"} =~ / <(red|purple|white) glow>$/) { + $name .= " <red glow>"; + } + + print "---------------------------\n". + $qdesc."\n\n". + "{{ Infobox ".$$item{"class"}."\n". + "| name = ".$name."\n". + "| description = ".$$item{"ldesc"}."\n"; + + if (defined($$item{"tale"}) && $$item{"tale"} ne "") { + print "| tale = ".$$item{"tale"}."\n"; + } + + if (defined($$item{"type"})) { + print "| type = ".$$item{"type"}."\n"; + } + + if (defined($$item{"stats"})) { + print "| stats = ".join(", ", @{$$item{"stats"}})."\n"; + } else { + print "| stats = \n"; + } + + print + "| weight = ".$$item{"weight"}."\n". + "| kg = ".$$item{"kg"}."\n". + "| sacvalue = \n". + "| handles = ".$$item{"handles"}."\n"; + + if (defined($$item{"materials"})) { + print "| material = ".join(", ", @{$$item{"materials"}})."\n"; + } + + print + "| size = ".$$item{"size"}."\n". + "| quality = ".$$item{"quality"}."\n". + "| from = \n"; + + if ($$item{"class"} eq "Weapon") { + print + "| wep1 = \n". + "| wep2 = \n"; + } + + if (defined($$item{"other"})) { + print "| other = ".join("\n", @{$$item{"other"}})."\n"; + } + + print + "}}\n"; + + foreach my $cat (keys %{$$item{"cat"}}) { + print "[[Category:$cat]]\n"; + } + + print "---------------------\n"; +} + + +### +### Print out the NPC information +### +foreach my $sdesc (sort { $a cmp $b } keys %monsters) { + my $mob = $monsters{$sdesc}; + + print "--------------------------- +$sdesc + +{{ NPC +| name = ".$$mob{"sname"}." +| description = ".$$mob{"ldesc"}." +| eqs = "; + + foreach my $slot (sort { $a cmp $b } keys %{$$mob{"eqs"}}) { + my ($k, $s); + $k = $s = $$mob{"eqs"}{$slot}; + $k =~ s/ <[a-z]+ glow>$//; + if ($k eq $s) { + print "$slot: [[$s]]\n\n"; + } else { + print "$slot: [[$k|$s]]\n\n"; + } + } + + print "none" unless (scalar keys %{$$mob{"eqs"}} > 0); + print "\n"; + + if ($sdesc =~ / \(undead\)$/) { + print "| undead = x\n"; + } + + # Check if we find a matching alignment: find a cast of 'detect alignment' + # with matching short name and withing appropriate distance from the mob look. + my $mname = $$mob{"sname"}; + my $mline = $$mob{"line"}; + + print "| alignment = "; + if (defined($aligns{$mname})) { + my $qoffs = 1000; + my $qline = -1; + foreach my $aline (sort { $a <=> $b } keys %{$aligns{$mname}}) { + my $offs = ($aline - $mline); + if ($offs < $qoffs) { + $qoffs = $offs; + $qline = $aline; + } + } + if ($qline >= 0) { + print STDERR "ALIGN: $mname ($sdesc) \@ #$qline ($qoffs)\n" if ($opt_debug); + print $aligns{$mname}{$qline}; + } + } + print "\n". + "| race = "; + + # Check for race + if (defined($races{$mname})) { + my $qoffs = 1000; + my $qline = -1; + foreach my $aline (sort { $a <=> $b } keys %{$races{$mname}}) { + my $offs = ($aline - $mline); + if ($offs < $qoffs) { + $qoffs = $offs; + $qline = $aline; + } + } + if ($qline >= 0) { + print STDERR "RACE: $mname ($sdesc) \@ #$qline ($qoffs)\n" if ($opt_debug); + print $races{$mname}{$qline}; + } + } + print "\n"; + + + # Spells + my $hasspells = 0; + if (defined($spells{$mname}) && length($mname) >= 4) { + my $maxdist = length($mname) * 150 * length($mname); + my $mindist = length($mname) * -5 * length($mname); + my %mspells = (); + foreach my $sline (sort { $a <=> $b } keys %{$spells{$mname}}) { + my $offs = $sline - $mline; +# print STDERR "TEST $mname @ $offs vs $mindist...$maxdist : '".$spells{$mname}{$sline}."'\n"; + if ($offs < $maxdist && $offs > $mindist) { + print STDERR "SPELL $mname \@ $offs / $mindist...$maxdist : '".$spells{$mname}{$sline}."'\n" if ($opt_debug); + $mspells{$spells{$mname}{$sline}} = 1; + $hasspells++; + } + } + my $n = 1; + foreach my $spell (sort { $a cmp $b } keys %mspells) { + print "| spell".($n < 2 ? "" : $n)." = $spell\n"; + $n++; + } + } + +unless ($hasspells > 0) { +print "| spell = +| spell# = +"; +} + + print " +| area = ".$$mob{"area"}." +| skill = +| skill# = +| exp = "; + + if (defined($$mob{"exp"})) { + if ($$mob{"exp"} > 1000000) { + printf "%1.2fM", $$mob{"exp"} / 1000000.0; + } elsif ($$mob{"exp"} > 1000) { + printf "%dk", $$mob{"exp"} / 1000; + } else { + printf "%d", $$mob{"exp"}; + } + } + +print " +| GAP = +| PAP = +| kerbholz = +| other = +}} +--------------------- +"; +}