screp: parse player data
[perl/schtarr.git] / screp
diff --git a/screp b/screp
index 1922415d9b61c5f6f696054e634e4bde069d2612..910f3d2f5ad4bc465805883e2b9589d1f641aa65 100755 (executable)
--- a/screp
+++ b/screp
@@ -10,6 +10,8 @@ GetOptions(
        "verbose|v!" => \$SHOWWARN,
 );
 
+use constant { APM_FIRSTFRAME => 80 / .042 };
+
 {
 
 package Data::StarCraft::Replay;
@@ -310,6 +312,7 @@ my %cmdread = (
        0x18 => ["cancel"],
        0x19 => ["cancel hatch"],
        0x1A => ["stop", 1],
+#      0x1B => ["move-thing??"], # tim: after hotkey (unit, reaver??) select; soon after reselected and moved
        0x1E => ["return cargo", 1],
        0x1F => ["train", 2, \%unit],
        0x20 => ["cancel train", 2], # == 254
@@ -411,12 +414,79 @@ sub open {
 
 }
 
+my @race = (qw(Z T P), (undef) x 3, '-');
+
 sub showtime {
        my $time = shift() * .042;
        my $minutes = int($time / 60);
        return sprintf "%d:%04.1f", $minutes, $time - $minutes * 60;
 }
 
+sub unpackhash {
+       my ($template, $expr, @elements) = @_;
+       my @data = unpack $template, $expr;
+       my %map;
+       $map{$_} = shift @data for @elements;
+       return (\%map, @data);
+}
+
+local $_ = Data::StarCraft::Replay::_read(undef, \*STDIN, 633)
+       and my ($head, @headdata) = unpackhash("CVa3Va12Z28v2Z16Z24CZ26a38a*", $_, qw(
+               engine frames mag1 time mag2 name width height
+               unknown1 creator unknown2 map unknown3
+       ))
+       or die "Couldn't read replay header\n";
+
+$_ eq "\0\0\110" or warn sprintf(
+       "Mismatch in first header constant: %s\n",
+       join ",", map ord, split //, $_
+) for $head->{mag1};
+$_ eq "\10"x8 . "\0"x4 or warn sprintf(
+       "Mismatch in second header constant: %s\n",
+       join ",", map ord, split //, $_
+) for $head->{mag2};
+delete $head->{$_} for qw(mag1 mag2 unknown1 unknown2);
+
+my @playdata = unpack "Va32"x12 . "V8C8", $headdata[0]
+       or die "Couldn't parse player data in replay header\n";
+
+my (@player, @slot);
+for (0 .. 11) {
+       my $number = shift @playdata;
+       defined $player[$number] and warn "Player #$number redefined";
+       my ($data) = unpackhash("VcccZ25", shift @playdata, qw(
+               slot type race unknown name
+       ));
+       defined $race[$_] ? ($data->{race} = $race[$_]) :
+               warn "Unknown race #$_ for player $number"
+               for $data->{race};
+       $slot[$data->{slot}] = $number if $data->{slot} < 16;
+       $player[$number] = $data;
+}
+$player[$_]->{color} = shift @playdata for 0 .. 7;
+$player[$_]->{index} = shift @playdata for 0 .. 7;
+
+sub showplayer {
+       my $id = shift;
+       my $playdata = $player[$slot[$id]];
+       return defined $playdata ?
+               sprintf '%s (%s)', $playdata->{name}, $playdata->{race} : "#$id";
+}
+
+printf "%s: %s\n", $_, $head->{$_} for qw(name creator);
+use Date::Format;
+printf "created: %s\n", time2str('%Y-%m-%d %X', $_) for $head->{time};
+printf "map: %s (%dx%d)\n", map $head->{$_}, qw(map width height);
+printf "frames: %s (%s)\n", $_, showtime($_) for $head->{frames};
+print "\n";
+
+if ($SHOWWARN) {
+       print Dumper $head;
+       print Dumper \@player;
+       #printf ":%s\n", join ",", map sprintf('%X', ord $_), split // for @headdata;
+       print "\n";
+}
+
 my $map = Data::StarCraft::Replay->new->open(\*STDIN);
 
 if ($SHOWWARN) {
@@ -428,8 +498,6 @@ if ($SHOWWARN) {
        }
 }
 
-printf "duration: %s\n", showtime($map->[-1][0]);
-
 my %cmdmacro = map {$_ => 1} (
        (map {$_, "cancel $_"}
                qw/train build hatch research upgrade arm/,
@@ -441,7 +509,7 @@ my %cmdmacro = map {$_ => 1} (
 my %stats; # player => count
 for (@$map) {
        $stats{$_->[1]}{actions}++;
-       $stats{$_->[1]}{gameactions}++ if $_->[0] > 80 / .042;
+       $stats{$_->[1]}{gameactions}++ if $_->[0] >= APM_FIRSTFRAME;
        $stats{$_->[1]}{last} = $_->[0] if $_->[2] eq "part";
        $stats{$_->[1]}{$cmdmacro{$_->[2]} ? "macro" : "micro"}++;
        $stats{$_->[1]}{count}{$_->[2]}++;
@@ -450,14 +518,14 @@ for (@$map) {
 for my $player (sort keys %stats) {
        my $row = $stats{$player};
        $row->{last} ||= $map->[-1][0];
-#      printf("%d:%6d actions (%3d micro,%4d macro);%4d APM\n",
-       printf("%d:%6d actions;%4d APM\n",
-               $player,
-               $row->{actions},
+#      printf("%-16s%6d actions (%3d micro,%4d macro);%4d APM\n",
+       my $name = showplayer($player);
+       printf("%-16s%6d actions in%7d frames (%s) = %d APM\n",
+               $name, $row->{actions}, $row->{last},
+               showtime($row->{last}),
 #              $row->{micro} / $row->{last} * 60 / .042 * 1.05,
 #              $row->{macro} / $row->{last} * 60 / .042 * 1.05,
-               $row->{gameactions} / $row->{last} * 60 / .042 * 1.042,
-       #       $row->{gameactions} / $map->[-1][0] * 60 / .042,
+               $row->{gameactions} / ($row->{last} - APM_FIRSTFRAME) * 60 / .042,
        );
 
        if (0) {