view magestats.pl @ 73:54e33015ba7c misc

Lots of fixes, and minor improvements.
author Matti Hamalainen <ccr@tnsp.org>
date Fri, 09 Apr 2010 16:16:01 +0000
parents bc05f9d391bb
children 6483aa47f297
line wrap: on
line source

#!/usr/bin/perl -w
# Magestats - BatMUD Mage guild statistics generator
# Developed by Matti Hämäläinen (Ggr), <ccr@tnsp.org>
# (C) Copyright 2010 Tecnic Software productions (TNSP)
#
# Requires Perl 5.8 and following modules:
# In Debian / Ubuntu, apt-get install libgd-graph-perl

use strict;
use Data::Dumper;
use GD::Graph;
use GD::Graph::linespoints;

# List of spells per damage type. Only major blasts/areas.
my %spell_data = (
  "acid" => [ "acid blast",          "acid storm",      "acid ray",              "acid rain",       "acid arrow",      "acid wind", "disruption" ],
  "fire" => [ "lava blast",          "lava storm",      "meteor blast",          "meteor swarm",    "fire blast",      "fire bolt", "flame arrow" ],
  "elec" => [ "electrocution",       "lightning storm", "forked lightning",      "chain lightning", "blast lightning", "lightning bolt", "shocking grasp" ],
  "pois" => [ "summon carnal spores","killing cloud",   "power blast",           "venom strike",    "poison blast",    "poison spray", "thorn spray" ],
  "cold" => [ "cold ray",            "hailstorm",       "ice bolt",              "cone of cold",    "darkfire",        "flaming ice", "chill touch" ],
  "mana" => [ "golden arrow",        "magic eruption",  "summon greater spores", "magic wave",      "levin bolt",      "summon lesser spores", "magic missile" ],
  "asph" => [ "blast vacuum",        "vacuum globe",    "strangulation",         "chaos bolt",      "vacuum ball",     "suffocation", "vacuumbolt" ],
);

# Settings and defaults
my $progName = "magestats.pl";
my $opt_cachefile;
my $opt_verbosity = 1;
my $opt_prefix = "magestats";
my $opt_imgfmt = "png";
my $opt_noinput = 0;
my $opt_width = 500;
my $opt_height = 250;

# Log level support
sub mlog($$)
{
  my $level = shift;
  my $msg = shift;
  print STDERR "* $msg\n" if ($opt_verbosity >= $level);
}

###
### Print out help if no arguments given
###
my $opt_mode = shift or die(
"Magestats v0.4 - BatMUD Mage guild statistics generator
Developed by Matti Hamalainen (Ggr \@ bat), <ccr\@tnsp.org>
(C) Copyright 2010 Tecnic Software productions (TNSP)

Usage: $progName dump [options] < logfile
       $progName stats [options] < logfile

 -v                Verbose mode
 -c <cachefile>    Specify a cache file to restore from
 -p <prefix>       Output filename prefix ('$opt_prefix')
 -t <png|gif>      Image format to be used ('$opt_imgfmt')
 -s <WxH>          Graph dimensions in pixels ($opt_width x $opt_height)
 -n                No input (only handle cache, if specified)
");

die("Invalid operation mode '$opt_mode'!\n") unless ($opt_mode =~ /^(dump|stats)$/);

while (my $opt = shift) {
  if ($opt eq "-c") {
    # Restore cache from file
    $opt_cachefile = shift or die("-c option requires an argument.\n");
  } elsif ($opt eq "-v") {
    $opt_verbosity++;
  } elsif ($opt eq "-t") {
    $opt_imgfmt = shift or die("-t option requires an argument.\n");
    die("Invalid format '$opt_imgfmt' specified!\n") unless ($opt_imgfmt =~ /^(png|gif)$/);
  } elsif ($opt eq "-p") {
    $opt_prefix = shift or die("-p option requires an argument.\n");
  } elsif ($opt eq "-n") {
    $opt_noinput = 1;
  }
}


###
### Construct full data structure
###
mlog(1, "Initializing structures.");
my $spells = {};
foreach my $type (keys %spell_data) {
  my $src = $spell_data{$type};

  foreach my $spell (@{$src}) {
    $spells->{$spell}{"type"} = $type;
    $spells->{$spell}{"blasts"} = 0;
  }

  $spells->{$type}{"essence"}{"increase"} = 0;
  $spells->{$type}{"essence"}{"blasts"}{"single"} = [];
  $spells->{$type}{"essence"}{"blasts"}{"area"} = [];

  $spells->{$type}{"reagents"} = 0;

  $spells->{$type}{"single"} = $src->[0];
  $spells->{$type}{"area"} = $src->[1];
  
  push(@{$spells->{"single"}}, $src->[0]);
  push(@{$spells->{"area"}}, $src->[1]);
}
$spells->{"total"}{"blasts"} = 0;
$spells->{"total"}{"essence"} = 0;
$spells->{"total"}{"reagents"} = 0;

sub get_spell_type($)
{
  my $spell = $_[0];
  die("get_spell_type($spell): No such spell.\n") unless exists($spells->{$spell}{"type"});
  return $spells->{$spell}{"type"};
}


###
### Read cache
###
if (defined($opt_cachefile)) {
  mlog(1, "Restoring cache from '$opt_cachefile'.");
  open(CACHE, "<", $opt_cachefile) or die("Could not open cache file '$opt_cachefile'!\n");
  my $s = <CACHE>;
  close(CACHE);
  eval $s;
}


###
### Scan input for blasts etc.
###
my @all_spells = ();
foreach my $type (keys %spell_data) {
  push(@all_spells, @{$spell_data{$type}});
}
my $match = join("|", @all_spells);
my $essence_flag = 0;
my $last_spell = "";

#print STDERR "$match\n";

if ($opt_noinput) {
  mlog(1, "Skipping input parsing.");
} else {
  mlog(1, "Parsing log from stdin...");
  while (defined(my $s = <STDIN>)) {
    if ($s =~ /^You watch with selfpride as your ($match) hits / ||
      $s =~ /^You hit .+ with your ($match)\.$/ ||
      $s =~ /^Your ($match) hits /) {
      $last_spell = $1;

      $spells->{$last_spell}{"blasts"}++;
      $spells->{"total"}{"blasts"}++;

      # If essence was gained, get the type etc ..
      if ($essence_flag) {
        my $type = get_spell_type($last_spell);
        $spells->{$type}{"essence"}{"increase"}++;

        # Save amount of major blasts for each "essence gain" step
        foreach my $class ("single", "area") {
          my $name = $spells->{$type}{$class};
          push(@{$spells->{$type}{"essence"}{"blasts"}{$class}}, $spells->{$name}{"blasts"});
        }

        $essence_flag = 0;
      }
    } elsif ($s =~ /^Your knowledge in elemental powers helps you to save the reagent for further use\./) {
      $spells->{get_spell_type($last_spell)}{"reagents"}++;
      $spells->{"total"}{"reagents"}++;
    } elsif ($s =~ /^You feel your skills in handling elemental forces improve\./) {
      $essence_flag = 1;
      $spells->{"total"}{"essence"}++;
    }
  }
}


###
### Dump cache to stdout
###
if ($opt_mode eq "dump") {
  $Data::Dumper::Indent = 0;
  $Data::Dumper::Useqq  = 1;
  $Data::Dumper::Purity = 1;
  
  my $dumper = Data::Dumper->new([$spells], ["spells"]);
  print $dumper->Dump();
  exit;
}


###
### Output statistics
###
if ($opt_mode eq "stats") {
my $filename = $opt_prefix.".html";
open(OUT, ">", $filename) or die("Could not create output file '$filename'.\n");

mlog(1, "Outputting stats HTML to '$filename'");

print OUT qq|
<html>
<head>
 <title>Mage statistics</title>
</head>
<body>

<h1>Mage statistics</h1>

<table border=\"1\">
<tr>
 <th>Type</th>
 <th>Blasts</th>
 <th>Reagents saved</th>
 <th>Essence gained</th>
 <th>Blasts per essence gain</th>
</tr>
|;

my $major_blasts = 0;
foreach my $type (sort { $a cmp $b } keys %spell_data) {
  my $s_s = $spells->{$type}{"single"};
  my $s_a = $spells->{$type}{"area"};

  my $b_s = $spells->{$s_s}{"blasts"};
  my $b_a = $spells->{$s_a}{"blasts"};
  
  $major_blasts += $b_s + $b_a;
  
  print OUT "<tr><td>$type</td><td>
  <table border=\"1\"><th></th><th>Single</th><th>Area</th></tr>
  <tr><th>Name</th><td>$s_s</td><td>$s_a</td></tr>
  <tr><th>Blasts</th><td>".$b_s."</td><td>".$b_a."</td></tr>
  </table>";

  my $total_blasts = $b_s + $b_a;
  
  printf OUT "</td><td><b>%d</b> (%1.2f %%)</td><td>%d</td><td>",
  $spells->{$type}{"reagents"},
  ($total_blasts > 0) ? ($spells->{$type}{"reagents"} * 100.0) / $total_blasts : 0.0,
  $spells->{$type}{"essence"}{"increase"};
  
  if (exists($spells->{$type}{"essence"}{"blasts"})) {
    print OUT "<img src=\"".$opt_prefix."_".$type.".".$opt_imgfmt."\" alt=\"?\" />";
  }
  
  print OUT "
  </td>
  </tr>\n";
}

print OUT "</table>

<p>
Total major blasts: <b>".$major_blasts."</b><br />
Total blasts: <b>".$spells->{"total"}{"blasts"}."</b> (including minor)<br />
Total reagents saved: <b>".$spells->{"total"}{"reagents"}."</b><br />
Total essence gained: <b>".$spells->{"total"}{"essence"}."</b><br />
</p>

</body>
</html>
";

mlog(1, "Outputting graphs '".$opt_prefix."_*.".$opt_imgfmt."'...");

foreach my $type (sort { $a cmp $b } keys %spell_data) {
  my $s = $spells->{$type}{"single"};
  my $a = $spells->{$type}{"area"};
  
  my $graph = GD::Graph::linespoints->new($opt_width, $opt_height);
  $graph->set(
    y_label => 'Blasts',
    x_label => 'Essence gained', 
  );

  my @titles = ();
  my @total = ();
  for (my $i = 1; $i <= $spells->{$type}{"essence"}{"increase"}; $i++) {
    push(@titles, "+".$i);
  }

  for (my $i = 0; $i < $spells->{$type}{"essence"}{"increase"}; $i++) {
    push(@total, $spells->{$type}{"essence"}{"blasts"}{"single"}[$i] + $spells->{$type}{"essence"}{"blasts"}{"area"}[$i]);
  }
  
  my @data = (
    \@titles,
    $spells->{$type}{"essence"}{"blasts"}{"single"},
    $spells->{$type}{"essence"}{"blasts"}{"area"},
    \@total,
  );
  
  my $gd = $graph->plot(\@data) or mlog(0, "Error creating graph ($type): ".$graph->error);

  if ($gd) {
    open(IMG, ">", $opt_prefix."_".$type.".".$opt_imgfmt) or die("Error openiong file ".$!."\n");
    binmode IMG;
    print IMG $gd->gif if ($opt_imgfmt eq "gif");
    print IMG $gd->png if ($opt_imgfmt eq "png");
    close IMG;
  }
}

}