Mercurial > hg > batmud > misc
view log2template.pl @ 274:7f4d966da5d9 misc
Cosmetics.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Sun, 08 Mar 2015 09:02:19 +0200 |
parents | b35c643a64d9 |
children | d5735e2cdb16 |
line wrap: on
line source
#!/usr/bin/perl -w # # log2template - Log parser for BatWiki NPC/Armour/Item/Weapon templates # Developed by Matti Hämäläinen (Ggr Pupunen) <ccr@tnsp.org> # (C) Copyright 2010-2012 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 log2template.pl < logfile.log > output.txt # # 7) command id say -ID-;blook at $*;say -%-;weigh $*;say -#-;look at $*;say -!-;cast identify at $* # # 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_getlocs = 0; my $opt_locpath; my $opt_identify = 1; my $opt_npc = 1; my $opt_dist = 1; # Define the regular expression to match any outputs of "say" # as this is somewhat race-dependant. my $sayregex = "(quack|say|howl)"; # 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", "mutters the holy words", "mutters the magic words", "makes shooing gestures with his hands and whispers", "frantically waves his hands and yells", "grabs a herb and chants", "consults her guide and chants", "utters the magic words", ); my %areaMappings = ( "Ocp west" => { "name" => "Old Central Park in Arelium", "continent" => "Laenor" }, ); my %genderMappings = ( "it" => "n", "she" => "f", "he" => "m", ); # 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/^agility in combat$/avoid/g }, sub { return $_[0] =~ s/^damage you do in combat$/dam/g }, sub { return $_[0] =~ s/^resistance you have against physical damage/physical resistance/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"); } elsif ($arg eq "-L") { $opt_locpath = shift or die("Locfile path option -L requires an argument.\n"); } elsif ($arg eq "-f") { $opt_dist = shift or die("Distance factor option -f requires an argument.\n"); if ($opt_dist < 1 || $opt_dist > 50) { die("-f value must be 1 .. 50.\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. -L <locfile_path> Path to <continent_name>.loc files. -f <value> Set spell/skill detection line distance factor. "); } } 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"); } if (defined($opt_locpath)) { $opt_getlocs = 1; print STDERR "Locfile path '".$opt_locpath."' set.\n"; } ### Translate stats sub trans_stat($) { my ($s) = @_; foreach my $pat (@statTransTable) { return $s if ($pat->($s)); } return $s; } my %locations = (); sub parse_locfile($) { my ($continent) = @_; return if defined($locations{$continent}{"OK"}); my $nline = 0; my $filename = $opt_locpath."/".$continent.".loc"; open(INFILE, '<', $filename) or print(STDERR "Could not open locfile '$filename'.\n"); while (defined(my $line = <INFILE>)) { $nline++; chomp($line); if ($line =~ /^(#|\s*$)/) { } elsif ($line =~ /^\s*(\d+)\s*;\s*(\d+)\s*;\s*(\d\S*)\s*;\s*([^;]+);(.*)/) { my ($xc, $yc, $flags, $namestr, $rest) = ($1,$2,$3,$4,$5); my @names = split(/\s*\|\s*/, $namestr); $locations{$continent}{$xc}{$yc} = \@names; } else { print STDERR "Error on LOCfile '$filename' #$nline.\n"; } } close(INFILE); $locations{$continent}{"OK"} = 1; } ### Globals my %monsters = (); my %aligns = (); my %spells = (); my %skills = (); my %races = (); 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 check_gender($$) { my ($mob, $s) = @_; if ($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) { my $gender = lc($1); if (defined($genderMappings{$gender})) { $$mob{"gender"} = $genderMappings{$gender}; } else { print STDERR "ERROR($line): Invalid gender '$gender'\n"; } return 1; } return 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(; Global: \d+x, \d+y)?\)$/o) { # Area, continent $$mob{"area"} = $1; $$mob{"continent"} = $2; $$mob{"xc"} = $3; $$mob{"yc"} = $4; } elsif ($s =~ /^You are in '.*?', which is on the continent of ([A-Z][a-z]+). \(Coordinates: (\d)+x, (\d)+y(; Global: \d+x, \d+y)?\)$/o) { # Area, continent $$mob{"area"} = $1; $$mob{"continent"} = $1; $$mob{"xc"} = $2; $$mob{"yc"} = $3; } elsif ($s =~ /^You are in '.*?' in (.+?)\.$/o) { my $area = $1; # Area, continent if (defined($areaMappings{$area})) { $$mob{"area"} = $areaMappings{$area}{"name"}; } $$mob{"continent"} = $areaMappings{$area}{"continent"}; $$mob{"xc"} = 0; $$mob{"yc"} = 0; } 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 (check_gender($mob, $s)) { 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 (check_gender($mob, $s)) { # 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); } } } ### Parse the big data blob sub parse_id_blob($) { my $info = $_[0]; # Weight & size if ($info =~ /It is ([A-Za-z -]+), ([A-Za-z -]+),/) { $$id{"weight2"} = $1; $$id{"size"} = $2; } # Quality if ($info =~ /of ([A-Za-z ]+) quality/) { $$id{"quality"} = $1; } # Emits darkness or light if ($info =~ /(emits darkness|emits light)/) { push(@{$$id{"stats"}}, $1); $$id{"cat"}{$1} = 1; } # Materials, figure out primary material 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 (defined($$id{"full"}) && $$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 (defined($$id{"full"})) { 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"} = ""; } } } } sub parse_identify($) { my ($s) = @_; if ($state == 10) { if ($s =~ /^You $sayregex '-%-\.'$/o) { set_state(11, $s); } elsif (!defined($$id{"full"})) { $$id{"full"} = $s; } else { set_state(11, $s); } } elsif ($state == 11) { if ($s =~ /^:\s*(\d+\.\d+)\s+(.+)$/) { # Weigh in kgs, and sdesc. Will have to match these later. $$id{"kg"}{$2} = $1; $$id{"full"} = $2; } elsif ($s =~ /^You $sayregex '-#-\.'$/o) { set_state(12, $s); } } elsif ($state == 12) { 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(13, $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); if (defined($$id{"len"}) && $len + $$id{"len"} < 79) { $$id{"ldesc"} .= "\n"; } else { $$id{"ldesc"} .= " "; } $$id{"ldesc"} .= $s; $$id{"len"} = $len; } } } elsif ($state == 13) { if ($s =~ /^The following messages seem to vibrate from (.+?):$/o) { $$id{"sdesc"} = $1; set_state(14, $s); } } elsif ($state == 14) { 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 blackness|evil wear only]]"); $$id{"cat"}{"aura of blackness"} = 1; $id_tale2 = 0; } elsif ($s =~ /^It will (.+?) (improve|reduce) (your|the) (.+?)\.$/) { my ($amt, $imp, $stat) = ($1, $2, $4); 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 (.+)$/) { parse_id_blob($2); if (defined($$id{"full"})) { $ids{$$id{"full"}} = $id; } set_state(0, $s); } elsif ($s =~ /^It has been in the game for (.+)$/) { parse_id_blob($1); $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 { $sdesc = $tmp . " (undead)"; 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); } elsif ($s =~ /^([A-Za-z][A-Za-z\'\ -]+?) starts grappling [A-Z][a-z]+\.$/) { $skills{$1}{$line} = "grapple"; } elsif ($s =~ /^([A-Za-z][A-Za-z\'\ -]+?) pushes [A-Z][a-z]+ extremely hard\.$/) { $skills{$1}{$line} = "push"; } elsif ($s =~ /^([A-Za-z][A-Za-z\'\ -]+?)\'s (powerful bash sends|tries to bash)/) { $skills{$1}{$line} = "bash"; } elsif ($s =~ /^([A-Za-z][A-Za-z\'\ -]+?) (aims a fast kick at|kicks [A-Z][a-z]+ in the groin very hard|swings a foot in a long arc|performs a fast kick)/) { $skills{$1}{$line} = "kick"; } } ### ### 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 $s = $$mob{"eqs"}{$slot}; $s =~ s/ \(glowing\)//; my $k = $s; $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 * $opt_dist * length($mname); my $mindist = length($mname) * -5 * $opt_dist * 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 = "; if ($opt_getlocs) { my $cont = lc($$mob{"continent"}); my $xc = $$mob{"xc"}; my $yc = $$mob{"yc"}; parse_locfile($cont); if (defined($locations{$cont}{$xc}{$yc})) { my $nm = $locations{$cont}{$xc}{$yc}[0]; $nm =~ s/^[\@]//; print $nm; } else { print $$mob{"area"}; } } else { print $$mob{"area"}; } print "\n"; # Skills my $hasskills = 0; if (defined($skills{$mname}) && length($mname) >= 4) { my $maxdist = length($mname) * 150 * $opt_dist * length($mname); my $mindist = length($mname) * -5 * $opt_dist * length($mname); my %mskills = (); foreach my $sline (sort { $a <=> $b } keys %{$skills{$mname}}) { my $offs = $sline - $mline; # print STDERR "TEST $mname @ $offs vs $mindist...$maxdist : '".$skills{$mname}{$sline}."'\n"; if ($offs < $maxdist && $offs > $mindist) { print STDERR "SPELL $mname \@ $offs / $mindist...$maxdist : '".$skills{$mname}{$sline}."'\n" if ($opt_debug); $mskills{$skills{$mname}{$sline}} = 1; $hasskills++; } } my $n = 1; foreach my $skill (sort { $a cmp $b } keys %mskills) { print "| skill".($n < 2 ? "" : $n)." = $skill\n"; $n++; } } unless ($hasskills > 0) { print "| skill = | skill# = "; } print " | 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 = | gender = "; if (defined($$mob{"gender"})) { print $$mob{"gender"}; } print " | other = }} --------------------- "; }