1 |
#!/bin/perl -W |
2 |
|
3 |
use GD; |
4 |
|
5 |
$/ = "\r\n"; |
6 |
|
7 |
$near0 = 70; |
8 |
$near1 = 200; |
9 |
$thumbsize = 400; |
10 |
|
11 |
$black = 0x000000; |
12 |
$white = 0xffffff; |
13 |
$red = 0xff0000; |
14 |
$green = 0x00ff00; |
15 |
$blue = 0x0000ff; |
16 |
$magenta = 0xff00ff; |
17 |
$yellow = 0xffff00; |
18 |
$orange = 0xff8000; |
19 |
$grey = 0x404040; |
20 |
$teamcol[1] = $blue; |
21 |
$teamcol[2] = $magenta; |
22 |
|
23 |
# from damageTypes.cs |
24 |
$DamageTypeText[0] = 'default'; |
25 |
$DamageTypeText[1] = 'blaster'; |
26 |
$DamageTypeText[2] = 'plasma'; |
27 |
$DamageTypeText[3] = 'chaingun'; |
28 |
$DamageTypeText[4] = 'disc'; |
29 |
$DamageTypeText[5] = 'grenade'; |
30 |
$DamageTypeText[6] = 'laser'; |
31 |
$DamageTypeText[7] = 'ELF'; |
32 |
$DamageTypeText[8] = 'mortar'; |
33 |
$DamageTypeText[9] = 'missile'; |
34 |
$DamageTypeText[10] = 'shocklance'; |
35 |
$DamageTypeText[11] = 'mine'; |
36 |
$DamageTypeText[12] = 'explosion'; |
37 |
$DamageTypeText[13] = 'impact'; |
38 |
$DamageTypeText[14] = 'ground'; |
39 |
$DamageTypeText[15] = 'turret'; |
40 |
$DamageTypeText[16] = 'plasma turret'; |
41 |
$DamageTypeText[17] = 'AA turret'; |
42 |
$DamageTypeText[18] = 'ELF turret'; |
43 |
$DamageTypeText[19] = 'mortar turret'; |
44 |
$DamageTypeText[20] = 'missile turret'; |
45 |
$DamageTypeText[21] = 'clamp turret'; |
46 |
$DamageTypeText[22] = 'spike turret'; |
47 |
$DamageTypeText[23] = 'sentry turret'; |
48 |
$DamageTypeText[24] = 'out of bounds'; |
49 |
$DamageTypeText[25] = 'lava'; |
50 |
$DamageTypeText[26] = 'shrike blaster'; |
51 |
$DamageTypeText[27] = 'belly turret'; |
52 |
$DamageTypeText[28] = 'bomber bomb'; |
53 |
$DamageTypeText[29] = 'tank chaingun'; |
54 |
$DamageTypeText[30] = 'tank mortar'; |
55 |
$DamageTypeText[31] = 'satchel charge'; |
56 |
$DamageTypeText[32] = 'MPB missile'; |
57 |
$DamageTypeText[33] = 'lighting'; |
58 |
$DamageTypeText[35] = 'ForceField'; |
59 |
$DamageTypeText[36] = 'Crash'; |
60 |
$DamageTypeText[98] = 'nexus camping'; |
61 |
$DamageTypeText[99] = 'suicide'; |
62 |
|
63 |
$icon_player = load_image("icons/com_player_grey_24x.png"); |
64 |
$icon{'flag'} = load_image("icons/com_icon_flag_outside.png"); |
65 |
$icon{'gen'} = load_image("icons/com_icon_generator.png"); |
66 |
$icon{'turret'} = load_image("icons/com_icon_turretbase.png"); |
67 |
$icon{'inv'} = load_image("icons/com_icon_inventory.png"); |
68 |
$icon{'sensor'} = load_image("icons/com_icon_sensor.png"); |
69 |
$icon{'vpad'} = load_image("icons/com_icon_vehicle_inventory.png"); |
70 |
|
71 |
# read header |
72 |
$mission = <>; chomp $mission; |
73 |
$mission_type = <>; chomp $mission_type; |
74 |
$mission_name = <>; chomp $mission_name; |
75 |
$mission_type_name = <>; chomp $mission_type_name; |
76 |
$area = <>; chomp $area; ($x0, $y0, $width, $height) = split / /, $area; |
77 |
while (($_ = <>) ne $/) { |
78 |
chomp; |
79 |
($team, $type, $block, $name, $x, $y, $z, $desc) = split / /, $_, 8; |
80 |
$x -= $x0; $y = $height - $y + $y0; |
81 |
if ($type eq 'StaticShape' and $block eq 'GeneratorLarge') { |
82 |
push @landmarks, "$z gen $team $x $y"; |
83 |
} elsif ($type eq 'Turret' and $block eq 'TurretBaseLarge') { |
84 |
push @landmarks, "$z turret $team $x $y"; |
85 |
} elsif ($type eq 'StaticShape' and $block eq 'ExteriorFlagStand') { |
86 |
push @landmarks, "$z flag $team $x $y"; |
87 |
} elsif ($type eq 'StaticShape' and $block eq 'StationVehicle') { |
88 |
push @landmarks, "$z vpad $team $x $y"; |
89 |
} elsif ($type eq 'StaticShape' and $block eq 'StationInventory') { |
90 |
push @landmarks, "$z inv $team $x $y"; |
91 |
} elsif ($type eq 'StaticShape' and $block eq 'SensorLargePulse') { |
92 |
push @landmarks, "$z sensor $team $x $y"; |
93 |
} elsif ($type eq 'WayPoint' and $block eq 'WayPointMarker') { |
94 |
push @waypoints, "$team $x $y $z $desc"; |
95 |
} |
96 |
} |
97 |
@landmarks = sort { my ($az, $ra) = split / /, $a, 2; my ($bz, $rb) = split / /, $b, 2; |
98 |
return $az <=> $bz; } @landmarks; |
99 |
|
100 |
$html_index = create_html('Match Summary'); |
101 |
|
102 |
$time = 0; |
103 |
$nicetime = "0:00"; |
104 |
$imall = create_image(); |
105 |
%alive = (); |
106 |
@allevents = (); |
107 |
|
108 |
while (<>) { |
109 |
chomp; |
110 |
if ($_ eq '') { |
111 |
plot_frame(); |
112 |
$time++; |
113 |
$secs = $time % 60; |
114 |
$nicetime = int($time / 60) . ':' . (($secs < 10) ? "0$secs" : $secs); |
115 |
next; |
116 |
} |
117 |
|
118 |
($cmd, $params) = split / /, $_, 2; |
119 |
|
120 |
if ($cmd eq 'S' or $cmd eq 'V') { # player spawned / new vehicle |
121 |
($id, $team, $name) = split / /, $params, 3; |
122 |
if ($cmd eq 'V') { |
123 |
$vname{$id} = $name; |
124 |
$name = $id; |
125 |
} |
126 |
$name{$id} = $name; |
127 |
$life{$name}++; |
128 |
$type{$name} = $cmd eq 'S' ? 'player' : 'vehicle'; |
129 |
$team{$name} = $team; |
130 |
$im{$name} = create_image(); |
131 |
delete $last_pos{$name}; |
132 |
delete $pos{$name}; |
133 |
$alive{$name} = 1; |
134 |
$near{$name} = {}; |
135 |
$events{$name} = []; |
136 |
if (!exists $html{$name}) { |
137 |
$html{$name} = create_html($name); |
138 |
unless ($type{$name} eq 'vehicle') { |
139 |
$html{$name} .= <<END; |
140 |
<p>Total score FINALSCORE, deaths FINALDEATHS, kills FINALKILLS.</p> |
141 |
END |
142 |
} |
143 |
$score{$name} = 0; |
144 |
$deaths{$name} = 0; |
145 |
$kills{$name} = 0; |
146 |
} else { |
147 |
$html{$name} .= "</table></div>\n"; |
148 |
} |
149 |
$sname = safe_name($name); |
150 |
$html{$name} .= <<END; |
151 |
<div><h2 id="life$life{$name}">Life $life{$name} ($nicetime - ENDTIME)</h2> |
152 |
<div><a href="${sname}_$life{$name}.png"><img src="s${sname}_$life{$name}.png" |
153 |
alt="" title="Map of life $life{$name}"></a></div> |
154 |
<table> |
155 |
END |
156 |
event($name, "spawned on team $team", $yellow); |
157 |
|
158 |
} elsif ($cmd eq 'F') { # flag position |
159 |
|
160 |
} elsif ($cmd eq 'D') { # player damaged |
161 |
($victim, $attacker, $weapon) = split / /, $params; |
162 |
|
163 |
} elsif ($cmd eq 'K' or $cmd eq 'L' or $cmd eq 'W') { # player killed / left / vehicle destroyed |
164 |
if ($cmd eq 'K') { |
165 |
($victim, $killer, $weapon) = split / /, $params; |
166 |
$victim = $name{$victim}; |
167 |
$killer = $name{$killer}; |
168 |
event($victim, "killed by $DamageTypeText[$weapon]", $red, $killer); |
169 |
if (defined $killer) { |
170 |
event($killer, "killed ($DamageTypeText[$weapon])", $green, $victim); |
171 |
$kills{$killer}++; |
172 |
} |
173 |
$deaths{$victim}++; |
174 |
|
175 |
} elsif ($cmd eq 'L') { |
176 |
($victim, $score) = split / /, $params; |
177 |
$victim = $name{$victim}; |
178 |
undef $killer; |
179 |
event($victim, "left the game, score $score", $red); |
180 |
$score{$victim} += $score; |
181 |
|
182 |
} elsif ($cmd eq 'W') { |
183 |
$victim = $name{$params}; |
184 |
} |
185 |
|
186 |
foreach $name (keys %near) { |
187 |
delete ${$near{$name}}{$victim} if exists ${$near{$name}}{$victim}; |
188 |
} |
189 |
|
190 |
if ($im{$victim}) { |
191 |
plot_events($victim); |
192 |
$sname = safe_name($victim); |
193 |
save_image($im{$victim}, "${sname}_$life{$victim}.png"); |
194 |
} |
195 |
delete $alive{$victim}; |
196 |
$html{$victim} =~ s/ENDTIME/$nicetime/; |
197 |
|
198 |
} elsif ($cmd eq 'A') { # armour changed |
199 |
($id, $armour) = split / /, $params; |
200 |
$name = $name{$id}; |
201 |
$armour{$name} = $armour; |
202 |
event($name, "$armour armour", $orange); |
203 |
|
204 |
} elsif ($cmd eq 'Z') { # final score |
205 |
|
206 |
} else { # player / vehicle position |
207 |
$id = $cmd; |
208 |
$name = $name{$id}; |
209 |
next unless (exists $name{$id} and exists $im{$name}); |
210 |
($x, $y, $z, $vehicle, $seat) = split / /, $params; |
211 |
$x -= $x0; $y = $height - $y + $y0; |
212 |
$pos{$name} = "$x $y $z"; |
213 |
if (exists $pending{$name}) { |
214 |
push @{$events{$name}}, "$x $y $z $pending{$name}"; |
215 |
delete $pending{$name}; |
216 |
} |
217 |
|
218 |
} |
219 |
} |
220 |
|
221 |
foreach $victim (keys %alive) { |
222 |
if ($im{$victim}) { |
223 |
plot_events($victim); |
224 |
$sname = safe_name($victim); |
225 |
save_image($im{$victim}, "${sname}_$life{$victim}.png"); |
226 |
} |
227 |
} |
228 |
|
229 |
$html_index .= <<END; |
230 |
<div><a href="Overview.png"><img src="sOverview.png" |
231 |
alt="" title="Overview map"></a></div> |
232 |
<p>Playing time: $nicetime</p> |
233 |
<table> |
234 |
<tr><th>Player</th><th>Team</th><th>Score</th><th>Deaths</th><th>Kills</th></tr> |
235 |
END |
236 |
while (($name, $html) = each %html) { |
237 |
$html .= "</table></div>\n"; |
238 |
$html =~ s/FINALSCORE/$score{$name}/; |
239 |
$html =~ s/FINALDEATHS/$deaths{$name}/; |
240 |
$html =~ s/FINALKILLS/$kills{$name}/; |
241 |
$sname = safe_name($name); |
242 |
save_html("$sname.html", $html); |
243 |
if ($type{$name} eq 'player') { |
244 |
$html_index .= <<END; |
245 |
<tr><td><a href="$sname.html">$name</a></td><td>$team{$name}</td><td>$score{$name}</td><td>$deaths{$name}</td><td>$kills{$name}</td></tr> |
246 |
END |
247 |
} |
248 |
} |
249 |
|
250 |
$html_index .= "</table>\n"; |
251 |
|
252 |
|
253 |
#$html_index .= "<h2>Timeline<h2>\n<table class='timeline'>\n"; |
254 |
$header = "<tr><td></td>"; |
255 |
foreach $n (keys %type) { |
256 |
$header .= "<th>$n</th>"; |
257 |
} |
258 |
$header .= "</tr>\n"; |
259 |
#$html_index .= $header; |
260 |
$time = 0; |
261 |
%event = (); |
262 |
foreach $e (@allevents) { |
263 |
($t, $colour, $name) = split / /, $e, 3; |
264 |
while ($time < $t) { |
265 |
$secs = $time % 60; |
266 |
$nicetime = int($time / 60) . ':' . (($secs < 10) ? "0$secs" : $secs); |
267 |
# $html_index .= "<tr><td>$nicetime</td>"; |
268 |
foreach $n (keys %type) { |
269 |
if (exists $event{$n}) { |
270 |
$hexcol = sprintf "%.6lx", $event{$n}; |
271 |
# $html_index .= "<td style='background-color: #$hexcol'> </td>"; |
272 |
} else { |
273 |
# $html_index .= "<td> </td>"; |
274 |
} |
275 |
} |
276 |
%event = (); |
277 |
# $html_index .= "</tr>\n"; |
278 |
$time++; |
279 |
# $html_index .= $header if (($time % 30) == 0); |
280 |
} |
281 |
$event{$name} = $colour; |
282 |
} |
283 |
|
284 |
save_image($imall, "Overview.png"); |
285 |
#$html_index .= "</table>\n"; |
286 |
save_html("index.html", $html_index); |
287 |
|
288 |
|
289 |
sub plot_frame { |
290 |
$s = 1; |
291 |
$s = 2 if ($time % 10) == 0; |
292 |
foreach $name (keys %alive) { |
293 |
next unless exists $pos{$name}; |
294 |
($x, $y, $z) = split / /, $pos{$name}; |
295 |
%near0 = %{$near{$name}}; |
296 |
foreach $name1 (keys %alive) { |
297 |
next if $name1 eq $name; |
298 |
($x1, $y1, $z1) = split / /, $pos{$name1}; |
299 |
my $dist = distance($x, $y, $z, $x1, $y1, $z1); |
300 |
if (exists ${$near{$name}}{$name1}) { |
301 |
delete ${$near{$name}}{$name1} if $near1 < $dist; |
302 |
} elsif ($dist < $near0) { |
303 |
($x1, $y1, $z1) = split / /, $pos{$name1}; |
304 |
text($im{$name}, $x1 + 4, $y1, $name1); |
305 |
${$near{$name}}{$name1} = 1; |
306 |
} |
307 |
} |
308 |
foreach $name1 (keys %{$near{$name}}) { |
309 |
($x1, $y1, $z1) = split / /, $pos{$name1}; |
310 |
my $colour = $team{$name1} == $team{$name} ? $green : $red; |
311 |
if ($last_pos{$name1}) { |
312 |
($xp, $yp, $zp) = split / /, $last_pos{$name1}; |
313 |
line($im{$name}, $xp, $yp, $x1, $y1, $colour); |
314 |
} |
315 |
square($im{$name}, $x1, $y1, $s, $colour); |
316 |
line($im{$name}, $x, $y, $x1, $y1, $grey) if $s == 2; |
317 |
} |
318 |
text($im{$name}, $x, $y, $nicetime) if $s == 2; |
319 |
if (exists $last_pos{$name}) { |
320 |
($xp, $yp, $zp) = split / /, $last_pos{$name}; |
321 |
line($im{$name}, $xp, $yp, $x, $y, $white); |
322 |
line($imall, $xp, $yp, $x, $y, $teamcol[$team{$name}]); |
323 |
} |
324 |
square($im{$name}, $x, $y, $s, $white); |
325 |
|
326 |
} |
327 |
%last_pos = %pos; |
328 |
%pos = (); |
329 |
} |
330 |
|
331 |
sub create_image { |
332 |
my $image = GD::Image->newTrueColor($width, $height); |
333 |
my $lm; |
334 |
foreach $lm (@landmarks) { |
335 |
my ($z, $type, $team, $x, $y) = split / /, $lm; |
336 |
icon($image, $icon{$type}, $x, $y); |
337 |
} |
338 |
foreach $g (@waypoints) { |
339 |
my ($team, $x, $y, $z, $desc) = split / /, $g, 5; |
340 |
text($image, $x, $y, $desc); |
341 |
} |
342 |
return $image; |
343 |
} |
344 |
|
345 |
sub save_image { |
346 |
my $image = shift; |
347 |
my $file = shift; |
348 |
my $png = $image->png; |
349 |
open PNG, ">$file" or die "failed to open $file: $!"; |
350 |
binmode PNG; |
351 |
print PNG $png; |
352 |
close PNG; |
353 |
|
354 |
my $max = $width < $height ? $height : $width; |
355 |
my $thumb = GD::Image->newTrueColor($thumbsize * $width / $max, |
356 |
$thumbsize * $height / $max); |
357 |
$thumb->copyResampled($image, 0, 0, 0, 0, $thumbsize * $width / $max, |
358 |
$thumbsize * $height / $max, $width, $height); |
359 |
$png = $thumb->png; |
360 |
open PNG, ">s$file" or die "failed to open s$file: $!"; |
361 |
binmode PNG; |
362 |
print PNG $png; |
363 |
close PNG; |
364 |
} |
365 |
|
366 |
sub square { |
367 |
my $image = shift; |
368 |
return unless defined $image; |
369 |
my $x = shift; |
370 |
my $y = shift; |
371 |
my $s = shift; |
372 |
my $colour = shift; |
373 |
$image->filledRectangle($x - $s, $y - $s, $x + $s, $y + $s, $colour); |
374 |
} |
375 |
|
376 |
sub line { |
377 |
my $image = shift; |
378 |
return unless defined $image; |
379 |
my $x0 = shift; |
380 |
my $y0 = shift; |
381 |
my $x1 = shift; |
382 |
my $y1 = shift; |
383 |
my $colour = shift; |
384 |
$image->line($x0, $y0, $x1, $y1, $colour); |
385 |
} |
386 |
|
387 |
sub distance { |
388 |
return sqrt (($_[0] - $_[3]) ** 2 + ($_[1] - $_[4]) ** 2 + ($_[2] - $_[5]) ** 2); |
389 |
} |
390 |
|
391 |
sub load_image { |
392 |
my $file = shift; |
393 |
open PNG, $file or die "failed to open $file: $!"; |
394 |
my $im = GD::Image->newFromPng(\*PNG) or die "PNG load of $file failed"; |
395 |
close PNG; |
396 |
$im->transparent(0); |
397 |
return $im; |
398 |
} |
399 |
|
400 |
sub icon { |
401 |
my $image = shift; |
402 |
my $icon = shift; |
403 |
my $x = shift; |
404 |
my $y = shift; |
405 |
$image->setBrush($icon); |
406 |
$image->setPixel($x, $y, gdBrushed); |
407 |
} |
408 |
|
409 |
sub text { |
410 |
my $image = shift; |
411 |
my $x = shift; |
412 |
my $y = shift; |
413 |
my $text = shift; |
414 |
$image->string(gdSmallFont, $x, $y, $text, $white); |
415 |
} |
416 |
|
417 |
sub create_html { |
418 |
my $title = shift; |
419 |
my $html = <<END; |
420 |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" |
421 |
"http://www.w3.org/TR/html4/strict.dtd"> |
422 |
<html> |
423 |
<head> |
424 |
<link rel="stylesheet" type="text/css" href="t2matchlog.css"> |
425 |
<title>$title ($mission_name / $mission_type_name)</title> |
426 |
</head> |
427 |
|
428 |
<body> |
429 |
<h1>$title <em class="mission">($mission_name / $mission_type_name)</em></h1> |
430 |
|
431 |
END |
432 |
return $html; |
433 |
} |
434 |
|
435 |
sub save_html { |
436 |
my $file = shift; |
437 |
my $html = shift; |
438 |
$html .= "</body></html>\n"; |
439 |
open HTML, ">$file" or die "failed to open $file: $!"; |
440 |
print HTML $html; |
441 |
close HTML; |
442 |
} |
443 |
|
444 |
sub safe_name { |
445 |
my $s = shift; |
446 |
$s =~ tr/[a-zA-Z0-9]/_/cs; |
447 |
return $s; |
448 |
} |
449 |
|
450 |
sub event { |
451 |
my $name = shift; |
452 |
return unless exists $html{$name}; |
453 |
my $desc = shift; |
454 |
my $colour = shift; |
455 |
my $name2 = shift; |
456 |
print "$nicetime\t$name $desc"; |
457 |
my $hexcol = sprintf "%.6lx", $colour; |
458 |
my $line = "<tr id='t$time'><td class='time'>$nicetime</td><td style='color: #$hexcol'>$desc"; |
459 |
if (defined $name2 and exists $html{$name2}) { |
460 |
print " $name2"; |
461 |
$sname = safe_name($name2); |
462 |
$line .= " <a href='$sname.html#t$time'>$name2</a>"; |
463 |
} |
464 |
print "\n"; |
465 |
$line .= "</td></tr>\n"; |
466 |
$html{$name} .= $line; |
467 |
if (exists $last_pos{$name}) { |
468 |
push @{$events{$name}}, "$last_pos{$name} $colour $desc"; |
469 |
} else { |
470 |
$pending{$name} = "$colour $desc"; |
471 |
} |
472 |
push @allevents, "$time $colour $name"; |
473 |
} |
474 |
|
475 |
sub plot_events { |
476 |
my $name = shift; |
477 |
foreach $e (@{$events{$name}}) { |
478 |
my ($x, $y, $z, $colour, $desc) = split / /, $e, 5; |
479 |
square($im{$name}, $x, $y, 8, $colour); |
480 |
} |
481 |
} |
482 |
|