#!/bin/perl -W use GD; $/ = "\r\n"; $near0 = 70; $near1 = 200; $thumbsize = 400; $black = 0x000000; $white = 0xffffff; $red = 0xff0000; $green = 0x00ff00; $blue = 0x0000ff; $magenta = 0xff00ff; $yellow = 0xffff00; $orange = 0xff8000; $grey = 0x404040; $teamcol[1] = $blue; $teamcol[2] = $magenta; # from damageTypes.cs $DamageTypeText[0] = 'default'; $DamageTypeText[1] = 'blaster'; $DamageTypeText[2] = 'plasma'; $DamageTypeText[3] = 'chaingun'; $DamageTypeText[4] = 'disc'; $DamageTypeText[5] = 'grenade'; $DamageTypeText[6] = 'laser'; $DamageTypeText[7] = 'ELF'; $DamageTypeText[8] = 'mortar'; $DamageTypeText[9] = 'missile'; $DamageTypeText[10] = 'shocklance'; $DamageTypeText[11] = 'mine'; $DamageTypeText[12] = 'explosion'; $DamageTypeText[13] = 'impact'; $DamageTypeText[14] = 'ground'; $DamageTypeText[15] = 'turret'; $DamageTypeText[16] = 'plasma turret'; $DamageTypeText[17] = 'AA turret'; $DamageTypeText[18] = 'ELF turret'; $DamageTypeText[19] = 'mortar turret'; $DamageTypeText[20] = 'missile turret'; $DamageTypeText[21] = 'clamp turret'; $DamageTypeText[22] = 'spike turret'; $DamageTypeText[23] = 'sentry turret'; $DamageTypeText[24] = 'out of bounds'; $DamageTypeText[25] = 'lava'; $DamageTypeText[26] = 'shrike blaster'; $DamageTypeText[27] = 'belly turret'; $DamageTypeText[28] = 'bomber bomb'; $DamageTypeText[29] = 'tank chaingun'; $DamageTypeText[30] = 'tank mortar'; $DamageTypeText[31] = 'satchel charge'; $DamageTypeText[32] = 'MPB missile'; $DamageTypeText[33] = 'lighting'; $DamageTypeText[35] = 'ForceField'; $DamageTypeText[36] = 'Crash'; $DamageTypeText[98] = 'nexus camping'; $DamageTypeText[99] = 'suicide'; $icon_player = load_image("icons/com_player_grey_24x.png"); $icon{'flag'} = load_image("icons/com_icon_flag_outside.png"); $icon{'gen'} = load_image("icons/com_icon_generator.png"); $icon{'turret'} = load_image("icons/com_icon_turretbase.png"); $icon{'inv'} = load_image("icons/com_icon_inventory.png"); $icon{'sensor'} = load_image("icons/com_icon_sensor.png"); $icon{'vpad'} = load_image("icons/com_icon_vehicle_inventory.png"); # read header $mission = <>; chomp $mission; $mission_type = <>; chomp $mission_type; $mission_name = <>; chomp $mission_name; $mission_type_name = <>; chomp $mission_type_name; $area = <>; chomp $area; ($x0, $y0, $width, $height) = split / /, $area; while (($_ = <>) ne $/) { chomp; ($team, $type, $block, $name, $x, $y, $z, $desc) = split / /, $_, 8; $x -= $x0; $y = $height - $y + $y0; if ($type eq 'StaticShape' and $block eq 'GeneratorLarge') { push @landmarks, "$z gen $team $x $y"; } elsif ($type eq 'Turret' and $block eq 'TurretBaseLarge') { push @landmarks, "$z turret $team $x $y"; } elsif ($type eq 'StaticShape' and $block eq 'ExteriorFlagStand') { push @landmarks, "$z flag $team $x $y"; } elsif ($type eq 'StaticShape' and $block eq 'StationVehicle') { push @landmarks, "$z vpad $team $x $y"; } elsif ($type eq 'StaticShape' and $block eq 'StationInventory') { push @landmarks, "$z inv $team $x $y"; } elsif ($type eq 'StaticShape' and $block eq 'SensorLargePulse') { push @landmarks, "$z sensor $team $x $y"; } elsif ($type eq 'WayPoint' and $block eq 'WayPointMarker') { push @waypoints, "$team $x $y $z $desc"; } } @landmarks = sort { my ($az, $ra) = split / /, $a, 2; my ($bz, $rb) = split / /, $b, 2; return $az <=> $bz; } @landmarks; $html_index = create_html('Match Summary'); $time = 0; $nicetime = "0:00"; $imall = create_image(); %alive = (); @allevents = (); while (<>) { chomp; if ($_ eq '') { plot_frame(); $time++; $secs = $time % 60; $nicetime = int($time / 60) . ':' . (($secs < 10) ? "0$secs" : $secs); next; } ($cmd, $params) = split / /, $_, 2; if ($cmd eq 'S' or $cmd eq 'V') { # player spawned / new vehicle ($id, $team, $name) = split / /, $params, 3; if ($cmd eq 'V') { $vname{$id} = $name; $name = $id; } $name{$id} = $name; $life{$name}++; $type{$name} = $cmd eq 'S' ? 'player' : 'vehicle'; $team{$name} = $team; $im{$name} = create_image(); delete $last_pos{$name}; delete $pos{$name}; $alive{$name} = 1; $near{$name} = {}; $events{$name} = []; if (!exists $html{$name}) { $html{$name} = create_html($name); unless ($type{$name} eq 'vehicle') { $html{$name} .= <Total score FINALSCORE, deaths FINALDEATHS, kills FINALKILLS.

END } $score{$name} = 0; $deaths{$name} = 0; $kills{$name} = 0; } else { $html{$name} .= "\n"; } $sname = safe_name($name); $html{$name} .= <

Life $life{$name} ($nicetime - ENDTIME)

END event($name, "spawned on team $team", $yellow); } elsif ($cmd eq 'F') { # flag position } elsif ($cmd eq 'D') { # player damaged ($victim, $attacker, $weapon) = split / /, $params; } elsif ($cmd eq 'K' or $cmd eq 'L' or $cmd eq 'W') { # player killed / left / vehicle destroyed if ($cmd eq 'K') { ($victim, $killer, $weapon) = split / /, $params; $victim = $name{$victim}; $killer = $name{$killer}; event($victim, "killed by $DamageTypeText[$weapon]", $red, $killer); if (defined $killer) { event($killer, "killed ($DamageTypeText[$weapon])", $green, $victim); $kills{$killer}++; } $deaths{$victim}++; } elsif ($cmd eq 'L') { ($victim, $score) = split / /, $params; $victim = $name{$victim}; undef $killer; event($victim, "left the game, score $score", $red); $score{$victim} += $score; } elsif ($cmd eq 'W') { $victim = $name{$params}; } foreach $name (keys %near) { delete ${$near{$name}}{$victim} if exists ${$near{$name}}{$victim}; } if ($im{$victim}) { plot_events($victim); $sname = safe_name($victim); save_image($im{$victim}, "${sname}_$life{$victim}.png"); } delete $alive{$victim}; $html{$victim} =~ s/ENDTIME/$nicetime/; } elsif ($cmd eq 'A') { # armour changed ($id, $armour) = split / /, $params; $name = $name{$id}; $armour{$name} = $armour; event($name, "$armour armour", $orange); } elsif ($cmd eq 'Z') { # final score } else { # player / vehicle position $id = $cmd; $name = $name{$id}; next unless (exists $name{$id} and exists $im{$name}); ($x, $y, $z, $vehicle, $seat) = split / /, $params; $x -= $x0; $y = $height - $y + $y0; $pos{$name} = "$x $y $z"; if (exists $pending{$name}) { push @{$events{$name}}, "$x $y $z $pending{$name}"; delete $pending{$name}; } } } foreach $victim (keys %alive) { if ($im{$victim}) { plot_events($victim); $sname = safe_name($victim); save_image($im{$victim}, "${sname}_$life{$victim}.png"); } } $html_index .= <

Playing time: $nicetime

END while (($name, $html) = each %html) { $html .= "
PlayerTeamScoreDeathsKills
\n"; $html =~ s/FINALSCORE/$score{$name}/; $html =~ s/FINALDEATHS/$deaths{$name}/; $html =~ s/FINALKILLS/$kills{$name}/; $sname = safe_name($name); save_html("$sname.html", $html); if ($type{$name} eq 'player') { $html_index .= <$name$team{$name}$score{$name}$deaths{$name}$kills{$name} END } } $html_index .= "\n"; #$html_index .= "

Timeline

\n\n"; $header = ""; foreach $n (keys %type) { $header .= ""; } $header .= "\n"; #$html_index .= $header; $time = 0; %event = (); foreach $e (@allevents) { ($t, $colour, $name) = split / /, $e, 3; while ($time < $t) { $secs = $time % 60; $nicetime = int($time / 60) . ':' . (($secs < 10) ? "0$secs" : $secs); # $html_index .= ""; foreach $n (keys %type) { if (exists $event{$n}) { $hexcol = sprintf "%.6lx", $event{$n}; # $html_index .= ""; } else { # $html_index .= ""; } } %event = (); # $html_index .= "\n"; $time++; # $html_index .= $header if (($time % 30) == 0); } $event{$name} = $colour; } save_image($imall, "Overview.png"); #$html_index .= "
$n
$nicetime  
\n"; save_html("index.html", $html_index); sub plot_frame { $s = 1; $s = 2 if ($time % 10) == 0; foreach $name (keys %alive) { next unless exists $pos{$name}; ($x, $y, $z) = split / /, $pos{$name}; %near0 = %{$near{$name}}; foreach $name1 (keys %alive) { next if $name1 eq $name; ($x1, $y1, $z1) = split / /, $pos{$name1}; my $dist = distance($x, $y, $z, $x1, $y1, $z1); if (exists ${$near{$name}}{$name1}) { delete ${$near{$name}}{$name1} if $near1 < $dist; } elsif ($dist < $near0) { ($x1, $y1, $z1) = split / /, $pos{$name1}; text($im{$name}, $x1 + 4, $y1, $name1); ${$near{$name}}{$name1} = 1; } } foreach $name1 (keys %{$near{$name}}) { ($x1, $y1, $z1) = split / /, $pos{$name1}; my $colour = $team{$name1} == $team{$name} ? $green : $red; if ($last_pos{$name1}) { ($xp, $yp, $zp) = split / /, $last_pos{$name1}; line($im{$name}, $xp, $yp, $x1, $y1, $colour); } square($im{$name}, $x1, $y1, $s, $colour); line($im{$name}, $x, $y, $x1, $y1, $grey) if $s == 2; } text($im{$name}, $x, $y, $nicetime) if $s == 2; if (exists $last_pos{$name}) { ($xp, $yp, $zp) = split / /, $last_pos{$name}; line($im{$name}, $xp, $yp, $x, $y, $white); line($imall, $xp, $yp, $x, $y, $teamcol[$team{$name}]); } square($im{$name}, $x, $y, $s, $white); } %last_pos = %pos; %pos = (); } sub create_image { my $image = GD::Image->newTrueColor($width, $height); my $lm; foreach $lm (@landmarks) { my ($z, $type, $team, $x, $y) = split / /, $lm; icon($image, $icon{$type}, $x, $y); } foreach $g (@waypoints) { my ($team, $x, $y, $z, $desc) = split / /, $g, 5; text($image, $x, $y, $desc); } return $image; } sub save_image { my $image = shift; my $file = shift; my $png = $image->png; open PNG, ">$file" or die "failed to open $file: $!"; binmode PNG; print PNG $png; close PNG; my $max = $width < $height ? $height : $width; my $thumb = GD::Image->newTrueColor($thumbsize * $width / $max, $thumbsize * $height / $max); $thumb->copyResampled($image, 0, 0, 0, 0, $thumbsize * $width / $max, $thumbsize * $height / $max, $width, $height); $png = $thumb->png; open PNG, ">s$file" or die "failed to open s$file: $!"; binmode PNG; print PNG $png; close PNG; } sub square { my $image = shift; return unless defined $image; my $x = shift; my $y = shift; my $s = shift; my $colour = shift; $image->filledRectangle($x - $s, $y - $s, $x + $s, $y + $s, $colour); } sub line { my $image = shift; return unless defined $image; my $x0 = shift; my $y0 = shift; my $x1 = shift; my $y1 = shift; my $colour = shift; $image->line($x0, $y0, $x1, $y1, $colour); } sub distance { return sqrt (($_[0] - $_[3]) ** 2 + ($_[1] - $_[4]) ** 2 + ($_[2] - $_[5]) ** 2); } sub load_image { my $file = shift; open PNG, $file or die "failed to open $file: $!"; my $im = GD::Image->newFromPng(\*PNG) or die "PNG load of $file failed"; close PNG; $im->transparent(0); return $im; } sub icon { my $image = shift; my $icon = shift; my $x = shift; my $y = shift; $image->setBrush($icon); $image->setPixel($x, $y, gdBrushed); } sub text { my $image = shift; my $x = shift; my $y = shift; my $text = shift; $image->string(gdSmallFont, $x, $y, $text, $white); } sub create_html { my $title = shift; my $html = < $title ($mission_name / $mission_type_name)

$title ($mission_name / $mission_type_name)

END return $html; } sub save_html { my $file = shift; my $html = shift; $html .= "\n"; open HTML, ">$file" or die "failed to open $file: $!"; print HTML $html; close HTML; } sub safe_name { my $s = shift; $s =~ tr/[a-zA-Z0-9]/_/cs; return $s; } sub event { my $name = shift; return unless exists $html{$name}; my $desc = shift; my $colour = shift; my $name2 = shift; print "$nicetime\t$name $desc"; my $hexcol = sprintf "%.6lx", $colour; my $line = "$nicetime$desc"; if (defined $name2 and exists $html{$name2}) { print " $name2"; $sname = safe_name($name2); $line .= " $name2"; } print "\n"; $line .= "\n"; $html{$name} .= $line; if (exists $last_pos{$name}) { push @{$events{$name}}, "$last_pos{$name} $colour $desc"; } else { $pending{$name} = "$colour $desc"; } push @allevents, "$time $colour $name"; } sub plot_events { my $name = shift; foreach $e (@{$events{$name}}) { my ($x, $y, $z, $colour, $desc) = split / /, $e, 5; square($im{$name}, $x, $y, 8, $colour); } }