(common.inc.plp)><: use List::Util qw(max sum); my %scver = ( id => 'bw', name => 'Brood War', title => 'starcraft', game => 'StarCraft', major => 1, ); if ($ENV{PATH_INFO} and $ENV{PATH_INFO} eq '/2') { %scver = ( id => 'hots', name => 'Heart of the Swarm', title => 'starcraft2', game => 'StarCraft II', major => 2, ); } my $datafile = "sc-units-$scver{id}.inc.pl"; Html({ title => "$scver{title} unit cheat sheet", version => 'v1.1', description => [ "Reference of $scver{game} unit properties," . " comparing various statistics of all the units in $scver{name}" . ' including costs, damage, defense, speed, ranges, and abilities.', ], keywords => [ qw' starcraft game unit statistics stats comparison table sheet cheat reference software attributes properties ', $scver{major} < 2 ? qw' bw broodwar brood war ' : qw' starcraft2 hots ', ], stylesheet => [qw'light'], raw => '', data => [$datafile], }); print "
Unit properties as seen or measured in $scver{name}\n$patch.\n"; print "Also see the $_ table.\n" for join(', ', ('StarCraft 2: HotS') x ($scver{major} < 2), ('original SC: Brood War') x ($scver{major} > 1), ); print "
\n\n"; sub addupgrade { my ($ref, $increase) = @_; if (ref $increase eq 'HASH') { addupgrade(\${$ref}->{$_}, $increase->{$_}) for keys %{$increase}; } elsif (ref $increase eq 'ARRAY') { addupgrade(\${$ref}->[$_], $increase->[$_]) for 0 .. $#{$increase}; } ${$ref} += $increase if $increase =~ /^-?[0-9.]+/; } for my $unit (@{$units}) { for my $upgrade (@{ $unit->{upgrade} }) { while (my ($col, $increase) = each %{$upgrade}) { defined $unit->{$col} or next; addupgrade(\( $unit->{upgraded}->{$col} //= $unit->{$col} ), $increase); } } } sub coltoggle { my ($name, $id, $nolink) = @_; return sprintf( (defined $get{order} ? $get{order} eq $id : !$id) ? '%2$s ▼' : $nolink ? '%2$s' : '%s', $id && "order=$id", $name ); } :><:= coltoggle('name', '') :> | <:= coltoggle(qw'build cost') :> | <:= coltoggle(qw'size size') :> | HP | shield | ⛨ | attack | <:= coltoggle(qw'dps attack 1') :> | range | sight | speed | specials | '; my $upattack = $row->{upgraded}->{attack}->[$area]; my $damage = $attack->{damage}; my $maxdamage = $upattack->{damage} // $damage; $damage = $damage->[0] if ref $damage; $maxdamage = $maxdamage->[-1] if ref $maxdamage; my $out = ' | '; $out .= "$attack->{count}× " if $attack->{count} > 1; $out .= '*' if $attack->{type} eq 'explosive'; $out .= '~' if $attack->{type} eq 'implosive'; $out .= sprintf('≥', (map { $_ =~ /^light/ ? 'unit-s' : $_ eq 'armored' ? 'unit-l' : $_ eq 'organic' ? 'unit-o' : $_ =~ /^massive/ ? 'unit-h' : $_ eq 'shields' ? 'unit-shield' : '', } join '_', keys %{ $attack->{bonus} }), join(', ', map {( sprintf('+%s vs %s', (map { ref $_ ? showrange($_->[0], $_->[-1]) : $_ } $attack->{bonus}->{$_}), $_, ), )} keys %{ $attack->{bonus} }), ) if $attack->{bonus}; $out .= sprintf '', $attack->{name} if $attack->{name}; $out .= showrange($damage, $maxdamage); $out .= '' if $attack->{name}; $out .= sprintf('%s', $attack->{splash} eq 'line' ? ('linear', '×') : ('splash', '+') ) if $attack->{splash}; $out .= ' | '; if ($attack->{cooldown}) { if (my $type = $attack->{type}) { if ($type eq 'explosive') { $damage /= 2; } elsif ($type eq 'implosive') { $damage /= 4; } } $damage *= ($attack->{count} // 1) / $attack->{cooldown}; if (my $bonus = $upattack->{bonus} // $attack->{bonus}) { $maxdamage += $_ for max( map { ref $_ ? $_->[-1] : $_ } values %{$bonus} ); } $maxdamage *= ($upattack->{count} // $attack->{count} // 1) / ($upattack->{cooldown} // $attack->{cooldown}); $out .= showrange($damage, $maxdamage); } $out .= ' | ' . '▽' x !!($attack->{anti} & 1); $out .= ' | ' . '△' x !!($attack->{anti} & 2); return $out; } sub showmagic { my ($row) = @_; my $specials = $row->{special} or return ''; return join ' ', map { sprintf '%s', $_->{duration} < 0 && ' class="magic-perma"', join('', $_->{name}, $_->{desc} ? ": $_->{desc}" : '', (map { $_ && " ($_)" } join ', ', #TODO: apply upgrades $_->{range} ? "range $_->{range}" : (), $_->{cost} ? sprintf('cost %.0f%%%s', 100 * $_->{cost} / $row->{energy}, defined $_->{maint} && sprintf('+%.1f%%/s', 100 * $_->{maint} / $row->{energy}, ), ) : $_->{cooldown} ? "cooldown $_->{cooldown}s" : (), ), ), sprintf($_->{build} ? '(%s)' : '%s', $_->{abbr}), } grep { defined $_->{abbr} } @{$specials}; } sub showunitcols { my ($row) = @_; local $_ = $row; $_->{hp} += $_->{shield} if $_->{shield}; my $suitchar = ''; if ($_->{attr}->{structure}) { $suitchar = 'b'; } elsif ($_->{suit}) { $suitchar = [qw/? s m l/]->[$_->{suit}]; } elsif ($_->{cargo} > 0) { $suitchar = [qw/? s m l l h h h h/]->[abs $_->{cargo}]; } elsif ($_->{size}) { $suitchar = [qw/s m l h h h/]->[$_->{size}]; } elsif ($_->{attr} and $_->{attr}->{light}) { $suitchar = 's'; } elsif ($_->{attr} and $_->{attr}->{armored}) { $suitchar = 'l'; } return ( ' | ' . ($_->{min} // ''), ' | ' . ($_->{gas} || ''), !defined $_->{build} ? ' | ' : sprintf(' | %s%.0f', !!$_->{base} && '+', $_->{build} || '0', ), !$suitchar ? ' | ' : sprintf(' | %s%s', $suitchar, ucfirst $suitchar, $_->{attr}->{massive} && '⚓', ), ' | ' . ( defined $_->{unit} && $_->{unit} == .5 ? '½' : $_->{unit} ), ' | ' . join('', grep { $_ } (defined $_->{organic} ? !$_->{organic} : $_->{attr}->{mech}) && 'm', ($_->{organic} || $_->{attr}->{organic}) && 'o', $_->{attr}->{psionic} && 'ψ', ), ' | ' . join('', grep { $_ } $_->{attr}->{armored} && 'A', $_->{attr}->{light} && 'L', ), ' | ' . $_->{hp} // '', $_->{shield} ? sprintf(' | %.0f%% | {shield} / $_->{hp} ) : ' | ' . showrange($_->{armor}, $_->{upgraded}->{armor}), showattack($_, 0), ' | ' . showrange(map { $_->{attack}->[0]->{range} } $_, $_->{upgraded}), ' | ' . sprintf( $_->{detect} ? '%s' : '%s', showrange($_->{sight}, $_->{upgraded}->{sight}) ), ' | ' . showrange($_->{speed}, $_->{upgraded}->{speed}), $_->{attr}->{jump} && qq'↕', $_->{attr}->{flying} && qq'↑', ' | ' . showmagic($_), !$_->{attack}->[1] ? () : ( ' |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
', showattack($_, 1), ' | ' ), "\n" ); } my $grouped = 1; # race headers if (exists $get{order}) { $grouped = 0; $get{order} ||= ''; if ($get{order} eq 'size') { $_->{order} = ( $_->{unit}*16 + ($_->{size} // $_->{suit}) + $_->{cargo}/8 + $_->{hp}/512 + $_->{min}/8192 ) for @$units; } elsif ($get{order} eq 'cost') { $_->{order} = ( $_->{gas}*1.5 + $_->{min} + $_->{unit}/8 + $_->{build}/256/8 ) for @$units; } elsif ($get{order} eq 'attack') { $_->{order} = $_->{hp} / 1024 + $_->{shield} / 1008 + max( map { ((map { ref $_ ? $_->[-1] : $_ } $_->{damage})[0]) * ($_->{count} // 1) / ($_->{cooldown} // 1) * ($_->{splash} ? 1.01 : 1) * ($_->{type} eq 'implosive' ? .96 : 1) * ($_->{type} eq 'explosive' ? .98 : 1) } @{ $_->{attack} } ) for @$units; } else { $units->[$_]->{order} = $_ for 0 .. $#$units; } } my @rows = @{$units}; @rows = sort {$a->{order} <=> $b->{order}} @rows unless $grouped; my ($race, $cat) = ('', ''); for (@rows) { if ($grouped) { printf ' | ||||||||||||||||||
%s'."\n", $race = $_->{race}, ucfirst $race unless $race eq $_->{race}; } else { $_->{cat} = $_->{race}; } print( ' | |||||||||||||||||||
', $cat ne $_->{cat} && ($cat = $_->{cat}), ' | ', $_->{name}, showunitcols($_), ); for my $subrow (@{ $_->{special} }) { $subrow->{alt} or next; print( ' | ||||||||||||||||||
', $subrow->{alt}, showunitcols($subrow), ); } } :> |
When two values are given (1-2), second value indicates attribute after all possible upgrades.