index: link writing page
[sheet.git] / sc.plp
1 <(common.inc.plp)><:
2
3 Html({
4         title => 'starcraft unit cheat sheets',
5         version => 'v1.0',
6         description => [
7                 'Reference of StarCraft unit properties,'
8                 . ' comparing various statistics of all the units in Brood War'
9                 . ' including costs, damage, defense, speed, ranges, and abilities.',
10         ],
11         keywords => [qw'
12                 starcraft game unit statistics stats comparison table sheet cheat
13                 reference software attributes properties
14         '],
15         stylesheet => [qw'light'],
16 });
17
18 :>
19 <h1>StarCraft unit cheat sheet</h1>
20
21 <p>
22 Unit properties as seen or measured in Brood War
23 <span title="no known changes as of v1.16.1">version≥1.08</span>.
24 </p>
25
26 <style type="text/css">
27         .units {
28                 border-spacing: 0;
29                 width: auto;
30         }
31         .units th, .units td {
32                 border-top: 1px solid #040;
33                 padding: 0 0.2em;
34         }
35         .units {
36                 border-bottom: 1px solid #040;
37         }
38         tr.alt td {
39                 font-size: 70%;
40                 border-top-style: dashed;
41         }
42         .units tr th:first-child {
43                 padding-left: 0;
44         }
45         .units tr:hover:not(.race) {
46                 background: #030;
47         }
48
49         table h3 {
50                 padding: 1ex 0;
51                 margin: 0;
52         }
53         .units th.cat {
54                 font-size: 70%;
55                 text-transform: uppercase;
56         }
57         .units td.cat {
58                 border: 0;
59         }
60         .units thead th, .units tfoot th {
61                 border: 0;
62                 font-size: 70%;
63                 font-weight: normal;
64         }
65         thead + * + tbody tr:first-child th {
66                 border: 0; /* border below thead */
67         }
68
69         .unit-o {color: #FF8080} /* organic */
70         .unit-u {color: #8080FF} /* mechanic */
71         img.unit-o, img.unit-u {
72                 margin-left: 0.2em;
73                 vertical-align: middle;
74         }
75         .unit {
76                 text-align: center;
77                 white-space: nowrap;
78         }
79         .unit.unit-s {color: #CC7}
80         .unit.unit-m {color: #D86}
81         .unit.unit-l {color: #D77}
82         .hurt.unit-s::before {
83                 content: '~';
84                 color: #CC9;
85         }
86         .hurt.unit-l::before {
87                 content: '*';
88                 color: #D88;
89         }
90         .hurt .unit-splash {
91                 position: absolute;
92         }
93         .hurtrel, .units .hurtrel {
94                 padding-left: 1em;
95                 font-size: 70%;
96                 color: #AAA;
97         }
98         .unit-splash {
99                 color: #8C8;
100         }
101         .unit-detect::before {
102                 content: '!';
103                 color: #4A4;
104                 font-size: 70%;
105                 vertical-align: super;
106         }
107         .unit-magic {
108                 padding-left: 0.5em;
109         }
110
111         .val {
112                 text-align: right;
113         }
114 </style>
115
116 <:
117 sub coltoggle {
118         my ($name, $id) = @_;
119         return sprintf(
120                 (defined $get{order} ? $get{order} eq $id : !$id) ? '%2$s ▼'
121                         : '<a href="?%s">%s</a>',
122                 $id && "order=$id", $name
123         );
124 }
125 :><table class="units">
126 <thead><tr>
127         <th></th>
128         <th><:= coltoggle('name', '') :></th>
129         <th class="val min"><img src="minerals.png" alt="min"></th>
130         <th class="val gas"><img src="gas.png" alt="gas"></th>
131         <th class="val time"><:= coltoggle(qw'build cost') :></th>
132         <th class="unit" colspan="2"><:= coltoggle(qw'size size') :></th>
133         <th class="val unit-hp">HP</th>
134         <th class="val unit-shield">shield</th>
135         <th class="val unit-armor">armor</th>
136         <th class="val hurt" colspan="2">ground</th>
137         <th class="val hurt" colspan="2">air</th>
138         <th class="val unit-range">range</th>
139         <th class="val unit-sight">sight</th>
140         <th class="val unit-speed">speed</th>
141         <th class="unit-magic">specials</th>
142 </tr></thead>
143 <tbody>
144 <:
145 sub showrange {
146         my ($row, @elements) = @_;
147         my ($min, $max);
148
149         my $value = $row;
150         $value = ref $value eq 'HASH' && $value->{$_} or last for @elements;
151         if (ref $value eq 'ARRAY') {
152                 $min = $value->[0];
153                 $max = $value->[-1];
154         }
155         else {
156                 $min = $max = $value;
157         }
158         defined $min or return '';
159
160         if ($row->{upgrade}) {
161                 for (@{ $row->{upgrade} }) {
162                         my $increase = $_ or next;
163                         $increase = ref $increase eq 'HASH' && $increase->{$_} or last for @elements;
164                         $increase = $increase->[-1] if ref $increase eq 'ARRAY';
165                         $max += $increase if $increase;
166                 }
167         }
168
169         if ($elements[0] eq 'attack' and $elements[1] ne 'range' and $elements[2] eq 'cmp') {{
170                 my $type = $row->{$elements[0]}->{$elements[1]}->{type} or next;
171                 if ($type eq 'explosive') {
172                         $min /= 2;
173                 }
174                 elsif ($type eq 'implosive') {
175                         $min /= 4;
176                 }
177                 $min = int($min + .5);  # round halves up
178         }}
179
180         return $min == $max ? $min : "$min-$max";
181 }
182
183         sub showattack {
184                 my ($row, $area) = @_;
185                 local $_ = $row->{attack}->{$area};
186
187                 return '<td colspan="2" class="hurt">' unless $_;
188
189                 my $tagbase = '<td class="val hurt';
190                 if (ref $_ and $_->{type}) {
191                         if ($_->{type} eq 'explosive') {
192                                 $tagbase .= ' unit-l';
193                         }
194                         elsif ($_->{type} eq 'implosive') {
195                                 $tagbase .= ' unit-s';
196                         }
197                 }
198                 $tagbase .= '">';
199
200                 my $out = showrange($row, 'attack', $area, 'damage');
201                 $out .= '<span class="unit-splash">+</span>' if $_->{splash};
202                 $out .= '<td class="val hurt hurtrel">' . showrange($row, 'attack', $area, 'cmp');
203                 return $tagbase . $out;
204         }
205
206         sub showmagic {
207                 my ($row) = @_;
208                 my $specials = $row->{special} or return '';
209                 return join ' ', map {
210                         sprintf '<span title="%s">%s</span>',
211                                 join('',
212                                         $_->{name},
213                                         $_->{desc} ? ": $_->{desc}" : '',
214                                         $_->{range} ? sprintf(' (%s)', join ', ',
215                                                 "range $_->{range}",
216 #                                               "cost $_->{cost}",
217                                         ) : '',
218                                 ),
219                                 $_->{abbr},
220                 } @$specials;
221         }
222
223         my $units = do 'sc-units.inc.pl';
224         die "Cannot open unit data: $_\n" for $! || $@ || ();
225         my $grouped = !exists $get{order};
226         if (exists $get{order}) {
227                 $get{order} ||= '';
228                 if ($get{order} eq 'size') {
229                         $_->{order} = $_->{unit}*8 + $_->{size} + $_->{hp}/512 + $_->{min}/8192 for @$units;
230                 }
231                 elsif ($get{order} eq 'cost') {
232                         $_->{order} = $_->{gas}*1.5 + $_->{min} + $_->{unit}/8 + $_->{build}/256/8 for @$units;
233                 }
234                 else {
235                         $units->[$_]->{order} = $_ for 0 .. $#$units;
236                 }
237         }
238         my @rows = $grouped ? @$units : sort {$a->{order} <=> $b->{order}} @$units;
239
240         my ($race, $cat) = ('', '');
241         for (@rows) {
242                 $race = $_->{race},
243                 printf '</tbody><tbody id="%s"><tr class="race"><th colspan="18"><h2>%1$s</h2>'."\n", $race
244                         if $grouped and $race ne $_->{race};
245                 $_->{cat} = $_->{race} if not $grouped;
246                 my $sizechar = [qw/? s m l/]->[$_->{size}];
247                 print(
248                         '<tr>',
249                         sprintf('<t%s class="cat">%s', $cat ne $_->{cat} ? ('h', $cat = $_->{cat}) : ('d', '&nbsp;')),
250                         '<td>' . $_->{name},
251                         '<td class="val min">' . ($_->{min} || '0'),
252                         '<td class="val gas">' . ($_->{gas} || ''),
253                         '<td class="val time">' . sprintf('%.0f', $_->{build} || '0'),
254                         sprintf('<td class="unit unit-%s">%s', $sizechar, ucfirst $sizechar),
255                         '<td class="val unit">' . join('',
256                                 $_->{unit} ? $_->{unit} == .5 ? '½' : $_->{unit} : '&nbsp;',
257                                 defined $_->{organic} && sprintf(
258                                         '<img class="unit-%s" src="s%s.png" alt="%s">',
259                                         $_->{organic} ? 'o' : 'u',
260                                         $_->{race} . ($_->{organic} ? 'o' : ''),
261                                         $_->{organic} ? 'o' : 'm'
262                                 ),
263                         ),
264                         '<td class="val unit-hp">' . $_->{hp},
265                         '<td class="val unit-shield">' . ($_->{shield} ? $_->{shield}.'%' : '&nbsp;'),
266                         '<td class="val unit-armor">' . showrange($_, 'armor'),
267                         showattack($_, 'ground'),
268                         showattack($_, 'air'),
269                         '<td class="val unit-range">' . showrange($_, 'attack', 'range'),
270                         '<td class="val unit-sight">' . sprintf(
271                                 $_->{detect} ? '<strong class="unit-detect">%s</strong>' : '%s',
272                                 showrange($_, 'sight')
273                         ),
274                         '<td class="val unit-speed">' . showrange($_, 'speed'),
275                         '<td class="unit-magic">' . showmagic($_),
276                         "\n"
277                 );
278
279                 for my $alt (grep { $_->{alt} } @{ $_->{special} }) {
280                         print(
281                                 '<tr class="alt"><td class="cat"><td colspan="9">' . $alt->{alt},
282                                 showattack($alt, 'ground'),
283                                 showattack($alt, 'air'),
284                                 '<td class="val unit-range">' . showrange($alt, 'attack', 'range'),
285                                 '<td class="val unit-sight">' . sprintf(
286                                         $alt->{detect} ? '<strong class="unit-detect">%s</strong>' : '%s',
287                                         showrange($alt, 'sight')
288                                 ),
289                                 '<td class="val unit-speed">' . showrange($alt, 'speed'),
290                                 '<td>',
291                                 "\n",
292                         );
293                 }
294         }
295 :>
296 </tbody>
297 </table>
298
299 <h2>Legend</h2>
300
301 <dl class="compact">
302 <dt>cost
303         <dd>minerals+gas required to create one unit
304 <dt>build
305         <dd>relative time needed to create at least one unit
306 <dt>size
307         <dd><span class="unit unit-s">S</span>mall,
308                 <span class="unit unit-m">M</span>edium,
309                 or <span class="unit unit-l">L</span>arge unit damage
310         <dd>number of command points taken per unit
311         <dd><span class="unit unit-o">organic</span>/<span class="unit unit-u">mechanic</span> unit
312 <dt>HP<dd>
313         total number of hitpoints (including shields)
314 <dt>shield
315         <dd>percentage of HP in shields
316         <dd>shields always take full damage, irrelevant of unit size
317         <dd>does not take armor bonuses, but upgrades can decrease damage to any shield hit by upto 3
318 <dt>armor
319         <dd>base unit armor
320         <dd>can be increased by upto 3 at various facilities
321         <dd>each point decreases damage per hit by one, upto a minimum of ½
322         <dd>reduction applies to initial damage, before size penalties <small>(so a large plasma hit of 12 to 4 armor deals 2 damage, not ½)</small>
323 <dt>ground/air
324         <dd>damage done per single attack against ground/air units
325         <dd>2nd column indicates relative amount of damage done in
326                 <span title="the time in which a dragoon fires a shot">a certain
327                 amount</span> of time
328         <dd>splash damage<span class="unit-splash">+</span> hits nearby objects as well
329         <dd><span class="hurt unit-l">explosive</span> damage does only
330                 50% damage to small units, 75% to medium, 100% to large
331         <dd><span class="hurt unit-s">concussive/plasma</span> damage does
332                 25% to large, 50% medium, 100% to small units
333 <dt>sight
334         <dd>range in which the unit detects other units
335         <dd><strong class="unit-detect">emphasis</strong> indicates ability to detect cloaked units
336 <dt>range
337         <dd>maximum range of weapon (note siege tank also has a minimum range)
338 <dt>speed
339         <dd>relative speed of movement (when in full motion, startup speed ignored)
340 <dt>specials
341         <dd>special abilities
342         <dd>parentheses () indicated that it needs to be researched first
343         <dd>hover for description
344         <dd>range is maximum range required to activate
345         <dd>cost is percentage of total energy lost
346 </dl>
347
348 <p>
349 When two values are given (1-2), second value indicates attribute after all
350 possible upgrades.
351 </p>
352
353 <: exit :>
354 <h2>Magic</h2>
355
356 <ul class="maps"><:
357 for (@$units) {
358         print '<li>', $_->{name};
359         print '<br>';
360 #       my $units = {qw/protoss W  zerg B  terran R/}->{$_->{race}} x int($_->{unit} + .5);
361 #       my $cost = int(($_->{min} + $_->{gas}) / 50 - $_->{unit}) || '';
362         my $units = {qw/protoss W  zerg B  terran R/}->{$_->{race}} x int($_->{gas} / 50) || '';
363         my $cost = int($_->{min} / 50) || 0;
364         my @desc;
365         push @desc, "Flying" if $_->{flying};
366         push @desc, "Cloaking" if $_->{cloak};
367         push @desc, "First Strike" if $_->{range} and $_->{range} >= 4;
368         my $att = $_->{attack}->{ground};
369         push @desc, "Trample" if $att and $att->{splash};
370         $att = $att->{damage} if $att;
371         $att = $att->[0] if ref $att eq "ARRAY";
372         $att = int($att / 5);
373         my $def = int($_->{hp} / 45);
374         printf "%s<br>%s<br>%s/%s", $cost . $units, join(",", @desc), $att, $def;
375 }
376 :></ul>
377