1 |
james |
36 |
#!/bin/perl -W |
2 |
|
|
|
3 |
james |
37 |
use strict; |
4 |
james |
36 |
use GD; |
5 |
|
|
|
6 |
james |
38 |
die "Usage: perl $0 logfile outputdir" unless @ARGV == 2; |
7 |
james |
37 |
open LOG, $ARGV[0] or die "Failed to open $ARGV[0]: $!"; |
8 |
|
|
my $outdir = $ARGV[1]; |
9 |
|
|
mkdir $outdir or die "Failed to create output directory $outdir: $!"; |
10 |
james |
38 |
my $dir = $0; |
11 |
james |
46 |
$dir =~ s#(^|/)[^/]+$#$1#; |
12 |
james |
38 |
(system "cp ${dir}t2matchlog.css $outdir/") == 0 or die "Failed to copy stylesheet: $!"; |
13 |
james |
37 |
|
14 |
james |
36 |
$/ = "\r\n"; |
15 |
james |
37 |
$| = 1; |
16 |
james |
36 |
|
17 |
james |
37 |
my $near0 = 70; |
18 |
|
|
my $near1 = 200; |
19 |
|
|
my $thumbsize = 400; |
20 |
james |
36 |
|
21 |
|
|
# from damageTypes.cs |
22 |
james |
37 |
my @DamageTypeText; |
23 |
james |
36 |
$DamageTypeText[0] = 'default'; |
24 |
|
|
$DamageTypeText[1] = 'blaster'; |
25 |
|
|
$DamageTypeText[2] = 'plasma'; |
26 |
|
|
$DamageTypeText[3] = 'chaingun'; |
27 |
|
|
$DamageTypeText[4] = 'disc'; |
28 |
|
|
$DamageTypeText[5] = 'grenade'; |
29 |
|
|
$DamageTypeText[6] = 'laser'; |
30 |
|
|
$DamageTypeText[7] = 'ELF'; |
31 |
|
|
$DamageTypeText[8] = 'mortar'; |
32 |
|
|
$DamageTypeText[9] = 'missile'; |
33 |
|
|
$DamageTypeText[10] = 'shocklance'; |
34 |
|
|
$DamageTypeText[11] = 'mine'; |
35 |
|
|
$DamageTypeText[12] = 'explosion'; |
36 |
|
|
$DamageTypeText[13] = 'impact'; |
37 |
|
|
$DamageTypeText[14] = 'ground'; |
38 |
|
|
$DamageTypeText[15] = 'turret'; |
39 |
|
|
$DamageTypeText[16] = 'plasma turret'; |
40 |
|
|
$DamageTypeText[17] = 'AA turret'; |
41 |
|
|
$DamageTypeText[18] = 'ELF turret'; |
42 |
|
|
$DamageTypeText[19] = 'mortar turret'; |
43 |
|
|
$DamageTypeText[20] = 'missile turret'; |
44 |
|
|
$DamageTypeText[21] = 'clamp turret'; |
45 |
|
|
$DamageTypeText[22] = 'spike turret'; |
46 |
|
|
$DamageTypeText[23] = 'sentry turret'; |
47 |
|
|
$DamageTypeText[24] = 'out of bounds'; |
48 |
|
|
$DamageTypeText[25] = 'lava'; |
49 |
|
|
$DamageTypeText[26] = 'shrike blaster'; |
50 |
|
|
$DamageTypeText[27] = 'belly turret'; |
51 |
|
|
$DamageTypeText[28] = 'bomber bomb'; |
52 |
|
|
$DamageTypeText[29] = 'tank chaingun'; |
53 |
|
|
$DamageTypeText[30] = 'tank mortar'; |
54 |
|
|
$DamageTypeText[31] = 'satchel charge'; |
55 |
|
|
$DamageTypeText[32] = 'MPB missile'; |
56 |
|
|
$DamageTypeText[33] = 'lighting'; |
57 |
|
|
$DamageTypeText[35] = 'ForceField'; |
58 |
|
|
$DamageTypeText[36] = 'Crash'; |
59 |
|
|
$DamageTypeText[98] = 'nexus camping'; |
60 |
|
|
$DamageTypeText[99] = 'suicide'; |
61 |
|
|
|
62 |
james |
38 |
my $icon_player = load_image("${dir}icons/com_player_grey_24x.png"); |
63 |
james |
37 |
my %icon; |
64 |
james |
38 |
$icon{'flag'} = load_image("${dir}icons/com_icon_flag_outside.png"); |
65 |
|
|
$icon{'gen'} = load_image("${dir}icons/com_icon_generator.png"); |
66 |
|
|
$icon{'turret'} = load_image("${dir}icons/com_icon_turretbase.png"); |
67 |
|
|
$icon{'inv'} = load_image("${dir}icons/com_icon_inventory.png"); |
68 |
|
|
$icon{'sensor'} = load_image("${dir}icons/com_icon_sensor.png"); |
69 |
|
|
$icon{'vpad'} = load_image("${dir}icons/com_icon_vehicle_inventory.png"); |
70 |
|
|
$icon{'solar'} = load_image("${dir}icons/com_icon_solar_gen.png"); |
71 |
james |
36 |
|
72 |
james |
37 |
my %colour = ('S' => 0xffff00, 'A' => 0xff8000, 'KK' => 0x00ff00, 'K' => 0xff0000, |
73 |
james |
46 |
'KS' => 0xff0000, |
74 |
james |
37 |
'L' => 0xff8000, 'F+' => 0x00ffff, 'F-' => 0xff00ff, 'P' => 0xff8000, |
75 |
|
|
'V' => 0xffff00, 'W' => 0xff0000, 'G' => 0x0080ff, 'J' => 0x00ff80, |
76 |
james |
46 |
'GG' => 0x0080ff, 'JJ' => 0x00ff80, |
77 |
|
|
'C' => 0x00ff00, 'R' => 0x00ff00); |
78 |
james |
37 |
my $colour_me = 0xffffff; |
79 |
|
|
my %desc = ('S' => 'spawned on team $team_name[$p1]', |
80 |
|
|
'A' => '$p1 armour', |
81 |
|
|
'KK' => 'killed $enemy using $DamageTypeText[$p2]', |
82 |
|
|
'K' => 'killed by $DamageTypeText[$p2] $enemy', |
83 |
james |
46 |
'KS' => 'killed himself using $DamageTypeText[$p2]', |
84 |
james |
37 |
'L' => 'left the game', |
85 |
james |
46 |
'F+' => '${melink}took the $team_name[$p1] flag', |
86 |
|
|
'F-' => '${melink}dropped the $team_name[$p1] flag', |
87 |
james |
37 |
'P' => '$p1', |
88 |
|
|
'V' => 'vehicle created', |
89 |
|
|
'W' => 'vehicle destroyed', |
90 |
|
|
'G' => 'entered " . vehicle_link($p1) . " as ${seat{$p2}}', |
91 |
|
|
'J' => 'ejected from vehicle', |
92 |
|
|
'GG' => '<a href=\'" . safe_name($p1) . ".html#t$time\'>$p1</a> entered as ${seat{$p2}}', |
93 |
james |
46 |
'JJ' => '<a href=\'" . safe_name($p1) . ".html#t$time\'>$p1</a> ejected', |
94 |
|
|
'C' => '<strong>${melink}captured the $team_name[$p1] flag!</strong>', |
95 |
|
|
'R' => '${melink}returned the $team_name[$p1] flag',); |
96 |
james |
37 |
my @teamcol = (0xffffff, 0x0000ff, 0xff00ff); |
97 |
|
|
my %seat = ('' => 'a passenger', 'p' => 'pilot', 'w' => 'gunner'); |
98 |
|
|
my %veh_name = ('BomberFlyer' => 'Bomber', |
99 |
|
|
'HAPCFlyer' => 'Havoc', |
100 |
|
|
'MobileBaseVehicle' => 'MPB', |
101 |
|
|
'ScoutFlyer' => 'Shrike', |
102 |
|
|
'AssaultVehicle' => 'Tank', |
103 |
|
|
'ScoutVehicle' => 'Wildcat'); |
104 |
|
|
|
105 |
|
|
my ($mission, $mission_type, $mission_name, $mission_type_name, $x0, $y0, $width, $height); |
106 |
|
|
my (@landmarks, @waypoints); |
107 |
|
|
|
108 |
|
|
print "reading data"; |
109 |
|
|
read_header(); |
110 |
|
|
|
111 |
|
|
my @events = (); |
112 |
|
|
my @coords = (); |
113 |
|
|
my %score = (); |
114 |
|
|
my %kills = (); |
115 |
|
|
my %deaths = (); |
116 |
james |
46 |
my %grabs = (); |
117 |
|
|
my %caps = (); |
118 |
|
|
my %returns = (); |
119 |
james |
37 |
my %player = (); |
120 |
|
|
my %vehicle = (); |
121 |
|
|
my %team = (); |
122 |
|
|
my $end_time; |
123 |
|
|
my (@final_score, @team_name); |
124 |
james |
46 |
my %flag = (); |
125 |
|
|
my @flag_coords = (); |
126 |
james |
37 |
|
127 |
|
|
read_data(); |
128 |
|
|
print ".\n"; |
129 |
|
|
|
130 |
|
|
my $imall = create_image(); |
131 |
|
|
|
132 |
|
|
my $player; |
133 |
|
|
foreach $player (keys %player) { |
134 |
|
|
print "$player"; |
135 |
|
|
write_player_report($player); |
136 |
|
|
print ".\n"; |
137 |
james |
36 |
} |
138 |
james |
37 |
my $vehicle; |
139 |
|
|
foreach $vehicle (keys %vehicle) { |
140 |
|
|
my ($team, $name) = split / /, $vehicle, 2; |
141 |
|
|
print "$team_name[$team] $veh_name{$name}s"; |
142 |
|
|
write_vehicle_report($name, $team); |
143 |
|
|
print ".\n"; |
144 |
|
|
} |
145 |
james |
46 |
my $team; |
146 |
|
|
foreach $team (keys %flag) { |
147 |
|
|
print "$team_name[$team] flag"; |
148 |
|
|
write_flag_report($team); |
149 |
|
|
print ".\n"; |
150 |
|
|
} |
151 |
james |
37 |
write_summary_report(); |
152 |
james |
36 |
|
153 |
james |
37 |
exit; |
154 |
james |
36 |
|
155 |
james |
37 |
#################################################################################################### |
156 |
james |
36 |
|
157 |
james |
37 |
sub read_header { |
158 |
|
|
$mission = <LOG>; chomp $mission; |
159 |
|
|
$mission_type = <LOG>; chomp $mission_type; |
160 |
|
|
$mission_name = <LOG>; chomp $mission_name; |
161 |
|
|
$mission_type_name = <LOG>; chomp $mission_type_name; |
162 |
|
|
my $area = <LOG>; chomp $area; ($x0, $y0, $width, $height) = split / /, $area; |
163 |
|
|
|
164 |
|
|
while (($_ = <LOG>) ne $/) { |
165 |
|
|
chomp; |
166 |
|
|
my ($team, $type, $block, $name, $x, $y, $z, $desc) = split / /, $_, 8; |
167 |
|
|
$x -= $x0; $y = $height - $y + $y0; |
168 |
|
|
if ($type eq 'StaticShape' and $block eq 'GeneratorLarge') { |
169 |
|
|
push @landmarks, "$z gen $team $x $y"; |
170 |
|
|
} elsif ($type eq 'Turret' and $block eq 'TurretBaseLarge') { |
171 |
|
|
push @landmarks, "$z turret $team $x $y"; |
172 |
|
|
} elsif ($type eq 'StaticShape' and $block eq 'ExteriorFlagStand') { |
173 |
|
|
push @landmarks, "$z flag $team $x $y"; |
174 |
|
|
} elsif ($type eq 'StaticShape' and $block eq 'StationVehicle') { |
175 |
|
|
push @landmarks, "$z vpad $team $x $y"; |
176 |
|
|
} elsif ($type eq 'StaticShape' and $block eq 'StationInventory') { |
177 |
|
|
push @landmarks, "$z inv $team $x $y"; |
178 |
|
|
} elsif ($type eq 'StaticShape' and $block eq 'SensorLargePulse') { |
179 |
|
|
push @landmarks, "$z sensor $team $x $y"; |
180 |
|
|
} elsif ($type eq 'StaticShape' and $block eq 'SolarPanel') { |
181 |
|
|
push @landmarks, "$z solar $team $x $y"; |
182 |
|
|
} elsif ($type eq 'WayPoint' and $block eq 'WayPointMarker') { |
183 |
|
|
push @waypoints, "$team $x $y $z $desc"; |
184 |
|
|
} |
185 |
james |
36 |
} |
186 |
|
|
|
187 |
james |
37 |
# sort by height for nicer plotting |
188 |
|
|
@landmarks = sort { my ($az, $ra) = split / /, $a, 2; my ($bz, $rb) = split / /, $b, 2; |
189 |
|
|
return $az <=> $bz; } @landmarks; |
190 |
|
|
} |
191 |
james |
36 |
|
192 |
james |
37 |
#################################################################################################### |
193 |
|
|
|
194 |
|
|
sub read_data { |
195 |
|
|
my $time = 0; |
196 |
|
|
my %name = (); |
197 |
|
|
my @flag_carrier; |
198 |
|
|
my %player_veh; |
199 |
james |
46 |
my %player_flag; |
200 |
james |
37 |
|
201 |
|
|
while (<LOG>) { |
202 |
|
|
chomp; |
203 |
|
|
if ($_ eq '') { |
204 |
|
|
$time++; |
205 |
|
|
next; |
206 |
james |
36 |
} |
207 |
james |
37 |
|
208 |
|
|
my ($cmd, $params) = split / /, $_, 2; |
209 |
|
|
|
210 |
|
|
if ($cmd eq 'S') { # player spawned |
211 |
|
|
my ($id, $team, $name) = split / /, $params, 3; |
212 |
|
|
$name{$id} = $name; |
213 |
|
|
push @events, [$time, 'S', $name, $team]; |
214 |
|
|
$player{$name} = 1; |
215 |
|
|
$team{$name} = $team; |
216 |
|
|
$player_veh{$name} = 0; |
217 |
|
|
|
218 |
|
|
} elsif ($cmd eq 'K') { # player killed |
219 |
|
|
my ($victim, $killer, $weapon) = split / /, $params; |
220 |
james |
46 |
next unless exists $name{$victim}; |
221 |
james |
37 |
$deaths{$name{$victim}}++; |
222 |
|
|
$kills{$name{$killer}}++ if exists $name{$killer}; |
223 |
|
|
push @events, [$time, 'K', $name{$victim}, $name{$killer}, $weapon]; |
224 |
|
|
push @events, [$time, 'KK', $name{$killer}, $name{$victim}, $weapon] |
225 |
james |
46 |
if exists $name{$killer} and $killer != $victim; |
226 |
james |
37 |
if (exists $name{$killer} and $player_veh{$name{$killer}}) { |
227 |
|
|
push @events, [$time, 'KK', $name{$player_veh{$name{$killer}}}, |
228 |
|
|
$name{$victim}, $weapon]; |
229 |
james |
36 |
} |
230 |
james |
37 |
# if (defined $player_veh{$name}) TODO |
231 |
james |
46 |
# delete $name{$victim}; |
232 |
james |
37 |
|
233 |
|
|
} elsif ($cmd eq 'L') { # player left |
234 |
|
|
my ($id, $score) = split / /, $params; |
235 |
james |
46 |
next unless exists $name{$id}; |
236 |
james |
37 |
push @events, [$time, 'L', $name{$id}]; |
237 |
|
|
$score{$name{$id}} += $score; |
238 |
|
|
delete $name{$id}; |
239 |
|
|
|
240 |
|
|
} elsif ($cmd eq 'A') { # armour changed |
241 |
|
|
my ($id, $armour) = split / /, $params; |
242 |
|
|
push @events, [$time, 'A', $name{$id}, $armour]; |
243 |
|
|
|
244 |
|
|
} elsif ($cmd eq 'I') { # inventory changed |
245 |
|
|
my ($id, $item, $count) = split / /, $params; |
246 |
|
|
if ($item =~ m/(Pack|SatchelCharge)$/ and $count == 1) { |
247 |
|
|
$item =~ s/Pack$/ pack/; |
248 |
|
|
push @events, [$time, 'P', $name{$id}, $item]; |
249 |
|
|
} |
250 |
|
|
|
251 |
|
|
} elsif ($cmd eq 'D') { # player damaged |
252 |
|
|
my ($victim, $attacker, $weapon) = split / /, $params; |
253 |
|
|
|
254 |
|
|
} elsif ($cmd eq 'V') { # new vehicle |
255 |
|
|
my ($id, $team, $name) = split / /, $params, 3; |
256 |
|
|
$vehicle{"$team $name"}++; |
257 |
|
|
$name{$id} = "$team $name " . $vehicle{"$team $name"}; |
258 |
|
|
$team{$name{$id}} = $team; |
259 |
|
|
push @events, [$time, 'V', $name{$id}]; |
260 |
|
|
|
261 |
|
|
} elsif ($cmd eq 'W') { # vehicle destroyed |
262 |
|
|
my $id = $params; |
263 |
|
|
push @events, [$time, 'W', $name{$id}]; |
264 |
|
|
#delete $name{$id}; |
265 |
|
|
|
266 |
|
|
} elsif ($cmd eq 'Z') { # final score |
267 |
|
|
my ($team, $score, $team_name) = split / /, $params, 3; |
268 |
|
|
$final_score[$team] = $score; |
269 |
|
|
$team_name[$team] = $team_name; |
270 |
|
|
|
271 |
|
|
} elsif ($cmd eq 'F') { # flag position |
272 |
|
|
my ($team, @data) = split / /, $params; |
273 |
|
|
my $carrier; |
274 |
james |
46 |
$flag{$team} = 1; |
275 |
|
|
$team{"$team flag"} = $team; |
276 |
james |
37 |
$flag_carrier[$team] = 0 unless defined $flag_carrier[$team]; |
277 |
|
|
if (@data == 3) { |
278 |
|
|
my ($x, $y, $z) = @data; |
279 |
|
|
$x -= $x0; $y = $height - $y + $y0; |
280 |
james |
46 |
${$coords[$time]}{"$team flag"} = "$x $y $z"; |
281 |
james |
37 |
$carrier = 0; |
282 |
|
|
} else { |
283 |
|
|
$carrier = $name{$data[0]}; |
284 |
|
|
} |
285 |
|
|
if ($carrier ne $flag_carrier[$team]) { |
286 |
|
|
if ($flag_carrier[$team]) { |
287 |
|
|
push @events, [$time, 'F-', $flag_carrier[$team], $team]; |
288 |
james |
46 |
delete $player_flag{$flag_carrier[$team]}; |
289 |
james |
37 |
} |
290 |
|
|
if ($carrier) { |
291 |
|
|
push @events, [$time, 'F+', $carrier, $team]; |
292 |
james |
46 |
$grabs{$carrier}++; |
293 |
|
|
$player_flag{$carrier} = $team; |
294 |
james |
37 |
} |
295 |
|
|
$flag_carrier[$team] = $carrier; |
296 |
|
|
} |
297 |
|
|
|
298 |
james |
46 |
} elsif ($cmd eq 'C') { # flag captured (CTF) |
299 |
|
|
my ($team, $id) = split / /, $params; |
300 |
|
|
push @events, [$time, 'C', $name{$id}, $team]; |
301 |
|
|
$flag_carrier[$team] = 0; |
302 |
|
|
$caps{$name{$id}}++; |
303 |
|
|
|
304 |
|
|
} elsif ($cmd eq 'R') { # flag returned (CTF) |
305 |
|
|
my ($team, $id) = split / /, $params; |
306 |
|
|
push @events, [$time, 'R', exists $name{$id} ? $name{$id} : '', $team]; |
307 |
|
|
$returns{$name{$id}}++; |
308 |
|
|
|
309 |
james |
37 |
} else { # player / vehicle position |
310 |
|
|
my $id = $cmd; |
311 |
|
|
next unless exists $name{$id}; |
312 |
|
|
my $name = $name{$id}; |
313 |
|
|
my ($x, $y, $z, $vehicle, $seat) = split / /, $params; |
314 |
|
|
$x -= $x0; $y = $height - $y + $y0; |
315 |
|
|
${$coords[$time]}{$name} = "$x $y $z"; |
316 |
|
|
next unless defined $player{$name}; |
317 |
|
|
if ($player_veh{$name} != $vehicle) { |
318 |
|
|
if ($player_veh{$name}) { |
319 |
|
|
push @events, [$time, 'J', $name]; |
320 |
|
|
push @events, [$time, 'JJ', |
321 |
|
|
$name{$player_veh{$name}}, $name]; |
322 |
|
|
} |
323 |
|
|
if ($vehicle != 0) { |
324 |
|
|
push @events, [$time, 'G', $name, |
325 |
|
|
$name{$vehicle}, $seat]; |
326 |
|
|
push @events, [$time, 'GG', $name{$vehicle}, |
327 |
|
|
$name, $seat]; |
328 |
|
|
} |
329 |
|
|
$player_veh{$name} = $vehicle; |
330 |
|
|
} |
331 |
james |
46 |
if (exists $player_flag{$name}) { |
332 |
|
|
${$coords[$time]}{$player_flag{$name} . " flag"} = "$x $y $z"; |
333 |
|
|
} |
334 |
james |
36 |
} |
335 |
james |
37 |
} |
336 |
|
|
$end_time = $time + 1; |
337 |
|
|
} |
338 |
|
|
|
339 |
|
|
#################################################################################################### |
340 |
|
|
|
341 |
|
|
sub write_player_report { |
342 |
|
|
my $player = shift; |
343 |
|
|
|
344 |
|
|
my $score = exists $score{$player} ? $score{$player} : 0; |
345 |
|
|
my $deaths = exists $deaths{$player} ? $deaths{$player} : 0; |
346 |
|
|
my $kills = exists $kills{$player} ? $kills{$player} : 0; |
347 |
|
|
my $sname = safe_name($player); |
348 |
|
|
my $html = create_html($player); |
349 |
|
|
|
350 |
|
|
$html .= <<END; |
351 |
|
|
<p>Total score $score, deaths $deaths, kills $kills. <a href="./">Match summary.</a></p> |
352 |
james |
36 |
END |
353 |
|
|
|
354 |
james |
37 |
# find spawn / death times |
355 |
|
|
my @spawn = map $$_[0], (grep {$$_[2] eq $player and $$_[1] eq 'S'} @events); |
356 |
|
|
my @death = map $$_[0], (grep {$$_[2] eq $player and |
357 |
|
|
($$_[1] eq 'K' or $$_[1] eq 'L')} @events); |
358 |
|
|
push @spawn, $end_time; |
359 |
james |
36 |
|
360 |
james |
37 |
for (my $life = 0; $life != @spawn - 1; $life++) { |
361 |
|
|
my $nicetime = nice_time($spawn[$life]); |
362 |
|
|
my $nicedtime = nice_time($death[$life]); |
363 |
|
|
my $life1 = $life + 1; |
364 |
|
|
$html .= <<END; |
365 |
|
|
<div><h2 id="life$life1">Life $life1 ($nicetime - $nicedtime)</h2> |
366 |
|
|
<div><a href="${sname}_$life1.png"><img src="s${sname}_$life1.png" |
367 |
|
|
alt="" title="Map of life $life1"></a></div> |
368 |
|
|
END |
369 |
|
|
$html .= player_life($player, "${sname}_$life1.png", |
370 |
|
|
$spawn[$life], $spawn[$life1] - 1); |
371 |
|
|
$html .= '</div>'; |
372 |
|
|
} |
373 |
james |
36 |
|
374 |
james |
37 |
save_html(safe_name($player) . '.html', $html); |
375 |
|
|
} |
376 |
james |
36 |
|
377 |
james |
37 |
#################################################################################################### |
378 |
james |
36 |
|
379 |
james |
37 |
sub player_life { |
380 |
|
|
my $player = shift; |
381 |
|
|
my $imname = shift; |
382 |
|
|
my $time0 = shift; |
383 |
|
|
my $time1 = shift; |
384 |
|
|
|
385 |
|
|
my $im = create_image(); |
386 |
|
|
plot_route($im, $player, $time0, $time1); |
387 |
|
|
|
388 |
|
|
my @evs = grep {$$_[2] eq $player and $time0 <= $$_[0] and $$_[0] <= $time1} @events; |
389 |
james |
46 |
my $html = write_events($player, \@evs, $im); |
390 |
james |
37 |
|
391 |
james |
46 |
save_image($im, $imname); |
392 |
|
|
|
393 |
|
|
return $html; |
394 |
|
|
} |
395 |
|
|
|
396 |
|
|
#################################################################################################### |
397 |
|
|
|
398 |
|
|
sub write_events { |
399 |
|
|
my $player = shift; |
400 |
|
|
my $evs = shift; |
401 |
|
|
my $im = shift; |
402 |
|
|
|
403 |
|
|
my $prev_time = -1; |
404 |
|
|
my $html = "<table>\n"; |
405 |
|
|
|
406 |
james |
37 |
my $event; |
407 |
james |
46 |
foreach $event (@$evs) { |
408 |
james |
37 |
my ($time, $ev, $me, $p1, $p2) = @$event; |
409 |
|
|
|
410 |
|
|
my $nicetime = nice_time($time); |
411 |
|
|
if ($time == $prev_time) { |
412 |
|
|
$html .= '<tr><td></td>'; |
413 |
|
|
} else { |
414 |
|
|
$html .= "<tr id='t$time'><td class='time'>$nicetime</td>"; |
415 |
james |
36 |
} |
416 |
|
|
|
417 |
james |
46 |
my $melink = ''; |
418 |
james |
37 |
my $enemy = ''; |
419 |
james |
46 |
$ev = 'KS' if ($ev eq 'K' and defined $p1 and $p1 eq $player); |
420 |
|
|
if (defined $me and $me ne $player) { |
421 |
|
|
my $sname_me = safe_name($me); |
422 |
|
|
$melink = "<a href='$sname_me.html#t$time'>$me</a> "; |
423 |
|
|
} |
424 |
|
|
if (defined $p1) { |
425 |
james |
37 |
my $sname_en = safe_name($p1); |
426 |
|
|
$enemy = "<a href='$sname_en.html#t$time'>$p1</a>"; |
427 |
james |
36 |
} |
428 |
james |
37 |
my $desc = eval '"' . $desc{$ev} . '"'; |
429 |
|
|
my $hexcol = sprintf "%.6lx", $colour{$ev}; |
430 |
|
|
$html .= "<td style='color: #$hexcol'>$desc</td></tr>\n"; |
431 |
james |
36 |
|
432 |
james |
37 |
if (exists ${$coords[$time]}{$player}) { |
433 |
|
|
my ($x, $y, $z) = split / /, ${$coords[$time]}{$player}; |
434 |
|
|
square($im, $x, $y, 8, $colour{$ev}); |
435 |
|
|
} elsif (exists ${$coords[$time - 1]}{$player}) { |
436 |
|
|
my ($x, $y, $z) = split / /, ${$coords[$time - 1]}{$player}; |
437 |
|
|
square($im, $x, $y, 8, $colour{$ev}); |
438 |
james |
36 |
} |
439 |
james |
37 |
$prev_time = $time; |
440 |
|
|
} |
441 |
james |
36 |
|
442 |
james |
37 |
$html .= "</table>\n"; |
443 |
james |
36 |
|
444 |
james |
37 |
return $html; |
445 |
|
|
} |
446 |
james |
36 |
|
447 |
james |
37 |
#################################################################################################### |
448 |
|
|
|
449 |
|
|
sub plot_route { |
450 |
|
|
my $image = shift; |
451 |
|
|
my $name = shift; |
452 |
|
|
my $t0 = shift; |
453 |
|
|
my $t1 = shift; |
454 |
|
|
my %near = (); |
455 |
|
|
for (my $t = $t0; $t != $t1; $t++) { |
456 |
|
|
my $s = ($t % 10) == 0 ? 2 : 1; |
457 |
|
|
if (exists ${$coords[$t]}{$name}) { |
458 |
|
|
my ($x0, $y0, $z0) = split / /, ${$coords[$t]}{$name}; |
459 |
|
|
my $near; |
460 |
|
|
foreach $near (keys %near) { |
461 |
|
|
if (exists ${$coords[$t]}{$near}) { |
462 |
|
|
my ($xn, $yn, $zn) = split / /, ${$coords[$t]}{$near}; |
463 |
|
|
delete $near{$near} if $near1 < distance($x0, $y0, $z0, |
464 |
|
|
$xn, $yn, $zn); |
465 |
|
|
} else { |
466 |
|
|
delete $near{$near}; |
467 |
|
|
} |
468 |
|
|
} |
469 |
|
|
foreach $near (keys %player) { |
470 |
|
|
next if $near eq $name; |
471 |
|
|
next if exists $near{$near}; |
472 |
|
|
if (exists ${$coords[$t]}{$near}) { |
473 |
|
|
my ($xn, $yn, $zn) = split / /, ${$coords[$t]}{$near}; |
474 |
|
|
if (distance($x0, $y0, $z0, $xn, $yn, $zn) < $near0) { |
475 |
|
|
$near{$near} = 1; |
476 |
|
|
line($image, $x0, $y0, $xn, $yn, 0x444444); |
477 |
|
|
text($image, $xn, $yn, $near); |
478 |
|
|
} |
479 |
|
|
} |
480 |
|
|
} |
481 |
|
|
foreach $near (keys %near) { |
482 |
|
|
my ($xn0, $yn0, $zn0) = split / /, ${$coords[$t]}{$near}; |
483 |
|
|
my $colour = $team{$name} == $team{$near} ? 0x00ff00 : 0xff0000; |
484 |
|
|
line($image, $x0, $y0, $xn0, $yn0, 0x444444) if $s == 2; |
485 |
|
|
if (exists ${$coords[$t + 1]}{$near}) { |
486 |
|
|
my ($xn1, $yn1, $zn1) = split / /, |
487 |
|
|
${$coords[$t + 1]}{$near}; |
488 |
|
|
line($image, $xn0, $yn0, $xn1, $yn1, $colour); |
489 |
|
|
} |
490 |
|
|
square($image, $xn0, $yn0, $s, $colour); |
491 |
|
|
} |
492 |
|
|
if (exists ${$coords[$t + 1]}{$name}) { |
493 |
|
|
my ($x1, $y1, $z1) = split / /, ${$coords[$t + 1]}{$name}; |
494 |
|
|
line($image, $x0, $y0, $x1, $y1, $colour_me); |
495 |
|
|
line($imall, $x0, $y0, $x1, $y1, $teamcol[$team{$name}]); |
496 |
|
|
} |
497 |
|
|
square($image, $x0, $y0, $s, $colour_me); |
498 |
|
|
text($image, $x0, $y0, nice_time($t)) if $s == 2; |
499 |
james |
36 |
} |
500 |
|
|
} |
501 |
|
|
} |
502 |
|
|
|
503 |
james |
37 |
#################################################################################################### |
504 |
|
|
|
505 |
|
|
sub write_vehicle_report { |
506 |
|
|
my $name = shift; |
507 |
|
|
my $team = shift; |
508 |
|
|
|
509 |
|
|
my $sname = safe_name($name); |
510 |
|
|
my $html = create_html($team_name[$team] . ' ' . $veh_name{$name} . 's'); |
511 |
|
|
$html .= '<p><a href="./">Match summary.</a></p>'; |
512 |
|
|
|
513 |
|
|
my $run; |
514 |
|
|
for ($run = 1; $run != $vehicle{"$team $name"} + 1; $run++) { |
515 |
|
|
my @ev = grep {$$_[2] eq "$team $name $run"} @events; |
516 |
|
|
my $time0 = ${$ev[0]}[0]; |
517 |
|
|
my $time1 = ${$ev[-1]}[1] eq 'W' ? ${$ev[-1]}[0] : $end_time; |
518 |
|
|
my $nicetime = nice_time($time0); |
519 |
|
|
my $nicedtime = nice_time($time1); |
520 |
|
|
|
521 |
|
|
$html .= <<END; |
522 |
|
|
<div><h2 id="life$run">$veh_name{$name} $run ($nicetime - $nicedtime)</h2> |
523 |
|
|
<div><a href="${team}_${sname}_$run.png"><img src="s${team}_${sname}_$run.png" |
524 |
|
|
alt="" title="Map of $veh_name{$name} $run"></a></div> |
525 |
|
|
END |
526 |
|
|
$html .= player_life("$team $name $run", "${team}_${sname}_$run.png", |
527 |
|
|
$time0, $time1); |
528 |
|
|
$html .= '</div>'; |
529 |
james |
36 |
} |
530 |
james |
37 |
|
531 |
|
|
save_html("${team}_$sname.html", $html); |
532 |
james |
36 |
} |
533 |
|
|
|
534 |
james |
37 |
#################################################################################################### |
535 |
|
|
|
536 |
|
|
sub vehicle_link { |
537 |
|
|
my $s = shift; |
538 |
|
|
my ($team, $name, $run) = split / /, $s; |
539 |
|
|
return "<a href='${team}_$name.html#life$run'>$team_name[$team] $veh_name{$name} $run</a>"; |
540 |
|
|
} |
541 |
|
|
|
542 |
|
|
#################################################################################################### |
543 |
|
|
|
544 |
james |
46 |
sub write_flag_report { |
545 |
|
|
my $team = shift; |
546 |
|
|
|
547 |
|
|
my $html = create_html($team_name[$team] . ' flag'); |
548 |
|
|
|
549 |
|
|
my @grab; |
550 |
|
|
my @return; |
551 |
|
|
|
552 |
|
|
my @fevs = grep {($$_[1] eq 'F+' or $$_[1] eq 'F-' or $$_[1] eq 'C' or $$_[1] eq 'R') and |
553 |
|
|
$$_[3] == $team} @events; |
554 |
|
|
my $event; |
555 |
|
|
my $at_stand = 1; |
556 |
|
|
foreach $event (@fevs) { |
557 |
|
|
if ($at_stand and $$event[1] eq 'F+') { |
558 |
|
|
push @grab, $$event[0]; |
559 |
|
|
$at_stand = 0; |
560 |
|
|
} elsif (!$at_stand and ($$event[1] eq 'C' or $$event[1] eq 'R')) { |
561 |
|
|
push @return, $$event[0]; |
562 |
|
|
$at_stand = 1; |
563 |
|
|
} |
564 |
|
|
} |
565 |
|
|
|
566 |
|
|
for (my $grab = 0; $grab != @grab; $grab++) { |
567 |
|
|
my $nicetime = nice_time($grab[$grab]); |
568 |
|
|
my $nicedtime = nice_time($return[$grab]); |
569 |
|
|
my $grab1 = $grab + 1; |
570 |
|
|
$html .= <<END; |
571 |
|
|
<div><h2 id="grab$grab1">Grab $grab1 ($nicetime - $nicedtime)</h2> |
572 |
|
|
<div><a href="${team}_flag_$grab1.png"><img src="s${team}_flag_$grab1.png" |
573 |
|
|
alt="" title="Map of ${team_name[$team]} flag grab $grab1"></a></div> |
574 |
|
|
END |
575 |
|
|
|
576 |
|
|
my $im = create_image(); |
577 |
|
|
plot_route($im, "$team flag", $grab[$grab], $return[$grab] - 1); |
578 |
|
|
|
579 |
|
|
my @fevs; |
580 |
|
|
my $carrier = ''; |
581 |
|
|
foreach $event (grep {$grab[$grab] <= $$_[0] and $$_[0] <= $return[$grab]} @events) { |
582 |
|
|
if ($$event[1] eq 'F+' and $$event[3] == $team) { |
583 |
|
|
$carrier = $$event[2]; |
584 |
|
|
} elsif (($$event[1] eq 'F-' or $$event[1] eq 'C' or $$event[1] eq 'R') |
585 |
|
|
and $$event[3] == $team) { |
586 |
|
|
$carrier = ''; |
587 |
|
|
} elsif ($$event[1] eq 'K' and $$event[2] eq $carrier) { |
588 |
|
|
; |
589 |
|
|
} else { |
590 |
|
|
next; |
591 |
|
|
} |
592 |
|
|
push @fevs, $event; |
593 |
|
|
} |
594 |
|
|
$html .= write_events("$team flag", \@fevs, $im); |
595 |
|
|
$html .= '</div>'; |
596 |
|
|
|
597 |
|
|
save_image($im, "${team}_flag_$grab1.png"); |
598 |
|
|
} |
599 |
|
|
|
600 |
|
|
save_html("${team}_flag.html", $html); |
601 |
|
|
} |
602 |
|
|
|
603 |
|
|
#################################################################################################### |
604 |
|
|
|
605 |
james |
37 |
sub write_summary_report { |
606 |
|
|
my $html = create_html("Match Summary"); |
607 |
|
|
my $name; |
608 |
|
|
my $nicetime = nice_time($end_time); |
609 |
|
|
my $i; |
610 |
|
|
|
611 |
|
|
$html .= <<END; |
612 |
james |
36 |
<div><a href="Overview.png"><img src="sOverview.png" |
613 |
|
|
alt="" title="Overview map"></a></div> |
614 |
james |
37 |
<p>Playing time $nicetime</p> |
615 |
|
|
<table><tr><th>Final scores</th></tr> |
616 |
|
|
END |
617 |
|
|
for ($i = 1; $i != @team_name; $i++) { |
618 |
|
|
$html .= "<tr><td>${team_name[$i]}</td><td>${final_score[$i]}</td></tr>\n"; |
619 |
|
|
} |
620 |
james |
46 |
$html .= "</table>\n"; |
621 |
james |
37 |
|
622 |
james |
46 |
my $team; |
623 |
|
|
foreach $team (keys %flag) { |
624 |
|
|
$html .= "<p><a href=\"${team}_flag.html\">${team_name[$team]} flag</a></p>\n"; |
625 |
|
|
} |
626 |
|
|
|
627 |
james |
37 |
$html .= <<END; |
628 |
james |
46 |
<h2>Players and Vehicles</h2> |
629 |
james |
36 |
<table> |
630 |
james |
46 |
<tr><th>Player</th><th>Team</th><th>Score</th><th>Deaths</th><th>Kills</th><th>Grabs</th><th>Caps</th><th>Returns</th></tr> |
631 |
james |
36 |
END |
632 |
james |
37 |
|
633 |
james |
46 |
sub lookup { |
634 |
|
|
my $hash = shift; |
635 |
|
|
my $name = shift; |
636 |
|
|
my $max = 0; grep { $max = $_ if $max < $_ } values %$hash; |
637 |
|
|
my $n = exists $$hash{$name} ? $$hash{$name} : 0; |
638 |
|
|
return $n == $max ? "<td><strong class=\"max\">$n</strong></td>" : "<td>$n</td>"; |
639 |
|
|
} |
640 |
|
|
|
641 |
james |
37 |
foreach $name (sort keys %player) { |
642 |
|
|
my $sname = safe_name($name); |
643 |
james |
46 |
$html .= "<tr><td><a href=\"$sname.html\">$name</a></td>" . |
644 |
|
|
"<td>$team_name[$team{$name}]</td>" . |
645 |
|
|
lookup(\%score, $name) . |
646 |
|
|
lookup(\%deaths, $name) . |
647 |
|
|
lookup(\%kills, $name) . |
648 |
|
|
lookup(\%grabs, $name) . |
649 |
|
|
lookup(\%caps, $name) . |
650 |
|
|
lookup(\%returns, $name) . |
651 |
|
|
"</tr>\n"; |
652 |
james |
36 |
} |
653 |
|
|
|
654 |
james |
37 |
$html .= "</table>\n"; |
655 |
james |
36 |
|
656 |
james |
46 |
if (keys %vehicle) { |
657 |
|
|
$html .= "<table><tr><th>Vehicles</th><th>Total used</th></tr>\n"; |
658 |
|
|
foreach $vehicle (sort keys %vehicle) { |
659 |
|
|
my ($team, $name) = split / /, $vehicle, 2; |
660 |
|
|
my $sname = safe_name($name); |
661 |
|
|
$html .= <<END; |
662 |
james |
37 |
<tr><td><a href="${team}_$sname.html">$team_name[$team] $veh_name{$name}s</a></td><td>${vehicle{"$team $name"}}</td></tr> |
663 |
|
|
END |
664 |
james |
46 |
} |
665 |
|
|
$html .= "</table>\n"; |
666 |
james |
37 |
} |
667 |
james |
36 |
|
668 |
james |
37 |
save_html('index.html', $html); |
669 |
|
|
save_image($imall, 'Overview.png'); |
670 |
james |
36 |
} |
671 |
|
|
|
672 |
james |
37 |
#################################################################################################### |
673 |
james |
36 |
|
674 |
|
|
sub create_image { |
675 |
|
|
my $image = GD::Image->newTrueColor($width, $height); |
676 |
james |
37 |
my ($lm, $g); |
677 |
james |
36 |
foreach $lm (@landmarks) { |
678 |
|
|
my ($z, $type, $team, $x, $y) = split / /, $lm; |
679 |
|
|
icon($image, $icon{$type}, $x, $y); |
680 |
|
|
} |
681 |
|
|
foreach $g (@waypoints) { |
682 |
|
|
my ($team, $x, $y, $z, $desc) = split / /, $g, 5; |
683 |
|
|
text($image, $x, $y, $desc); |
684 |
|
|
} |
685 |
|
|
return $image; |
686 |
|
|
} |
687 |
|
|
|
688 |
|
|
sub save_image { |
689 |
|
|
my $image = shift; |
690 |
|
|
my $file = shift; |
691 |
|
|
my $png = $image->png; |
692 |
james |
37 |
open PNG, ">$outdir/$file" or die "failed to open $outdir/$file: $!"; |
693 |
james |
36 |
binmode PNG; |
694 |
|
|
print PNG $png; |
695 |
|
|
close PNG; |
696 |
|
|
|
697 |
|
|
my $max = $width < $height ? $height : $width; |
698 |
|
|
my $thumb = GD::Image->newTrueColor($thumbsize * $width / $max, |
699 |
|
|
$thumbsize * $height / $max); |
700 |
|
|
$thumb->copyResampled($image, 0, 0, 0, 0, $thumbsize * $width / $max, |
701 |
|
|
$thumbsize * $height / $max, $width, $height); |
702 |
|
|
$png = $thumb->png; |
703 |
james |
37 |
open PNG, ">$outdir/s$file" or die "failed to open $outdir/s$file: $!"; |
704 |
james |
36 |
binmode PNG; |
705 |
|
|
print PNG $png; |
706 |
|
|
close PNG; |
707 |
|
|
} |
708 |
|
|
|
709 |
|
|
sub square { |
710 |
|
|
my $image = shift; |
711 |
|
|
return unless defined $image; |
712 |
|
|
my $x = shift; |
713 |
|
|
my $y = shift; |
714 |
|
|
my $s = shift; |
715 |
|
|
my $colour = shift; |
716 |
|
|
$image->filledRectangle($x - $s, $y - $s, $x + $s, $y + $s, $colour); |
717 |
|
|
} |
718 |
|
|
|
719 |
|
|
sub line { |
720 |
|
|
my $image = shift; |
721 |
|
|
return unless defined $image; |
722 |
|
|
my $x0 = shift; |
723 |
|
|
my $y0 = shift; |
724 |
|
|
my $x1 = shift; |
725 |
|
|
my $y1 = shift; |
726 |
|
|
my $colour = shift; |
727 |
|
|
$image->line($x0, $y0, $x1, $y1, $colour); |
728 |
|
|
} |
729 |
|
|
|
730 |
|
|
sub load_image { |
731 |
|
|
my $file = shift; |
732 |
|
|
open PNG, $file or die "failed to open $file: $!"; |
733 |
|
|
my $im = GD::Image->newFromPng(\*PNG) or die "PNG load of $file failed"; |
734 |
|
|
close PNG; |
735 |
|
|
$im->transparent(0); |
736 |
|
|
return $im; |
737 |
|
|
} |
738 |
|
|
|
739 |
|
|
sub icon { |
740 |
|
|
my $image = shift; |
741 |
|
|
my $icon = shift; |
742 |
|
|
my $x = shift; |
743 |
|
|
my $y = shift; |
744 |
|
|
$image->setBrush($icon); |
745 |
|
|
$image->setPixel($x, $y, gdBrushed); |
746 |
|
|
} |
747 |
|
|
|
748 |
|
|
sub text { |
749 |
|
|
my $image = shift; |
750 |
|
|
my $x = shift; |
751 |
|
|
my $y = shift; |
752 |
|
|
my $text = shift; |
753 |
james |
37 |
$image->string(gdSmallFont, $x, $y, $text, 0xffffff); |
754 |
james |
36 |
} |
755 |
|
|
|
756 |
james |
37 |
#################################################################################################### |
757 |
|
|
|
758 |
james |
36 |
sub create_html { |
759 |
|
|
my $title = shift; |
760 |
|
|
my $html = <<END; |
761 |
|
|
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" |
762 |
|
|
"http://www.w3.org/TR/html4/strict.dtd"> |
763 |
|
|
<html> |
764 |
|
|
<head> |
765 |
|
|
<link rel="stylesheet" type="text/css" href="t2matchlog.css"> |
766 |
|
|
<title>$title ($mission_name / $mission_type_name)</title> |
767 |
|
|
</head> |
768 |
|
|
|
769 |
|
|
<body> |
770 |
|
|
<h1>$title <em class="mission">($mission_name / $mission_type_name)</em></h1> |
771 |
|
|
|
772 |
|
|
END |
773 |
|
|
return $html; |
774 |
|
|
} |
775 |
|
|
|
776 |
|
|
sub save_html { |
777 |
|
|
my $file = shift; |
778 |
|
|
my $html = shift; |
779 |
|
|
$html .= "</body></html>\n"; |
780 |
james |
37 |
open HTML, ">$outdir/$file" or die "failed to open $outdir/$file: $!"; |
781 |
james |
36 |
print HTML $html; |
782 |
|
|
close HTML; |
783 |
|
|
} |
784 |
|
|
|
785 |
james |
37 |
#################################################################################################### |
786 |
|
|
|
787 |
james |
36 |
sub safe_name { |
788 |
|
|
my $s = shift; |
789 |
|
|
$s =~ tr/[a-zA-Z0-9]/_/cs; |
790 |
|
|
return $s; |
791 |
|
|
} |
792 |
|
|
|
793 |
james |
37 |
sub nice_time { |
794 |
|
|
my $time = shift; |
795 |
|
|
my $secs = $time % 60; |
796 |
|
|
return int($time / 60) . ':' . (($secs < 10) ? "0$secs" : $secs); |
797 |
james |
36 |
} |
798 |
|
|
|
799 |
james |
37 |
sub distance { |
800 |
|
|
return sqrt (($_[0] - $_[3]) ** 2 + ($_[1] - $_[4]) ** 2 + ($_[2] - $_[5]) ** 2); |
801 |
james |
36 |
} |
802 |
|
|
|
803 |
james |
37 |
#################################################################################################### |
804 |
|
|
|