(common.inc.plp)><:
use List::Util qw(max sum);
my %scvers = (
bw => {
name => 'Brood War',
title => 'starcraft',
game => 'StarCraft BW',
major => 1,
},
hots => {
name => 'Heart of the Swarm',
title => 'starcraft2 hots',
game => 'StarCraft II HotS',
major => 2,
},
lotv => {
name => 'Legacy of the Void',
title => 'starcraft2 lotv',
game => 'StarCraft II LotV',
major => 2,
},
index => 'bw',
1 => 'bw',
2 => 'lotv',
);
my $requestver = $scvers{$Request ||= 'index'}
or Html(), Abort("Requested version $Request
not available", '404 request not found');
if (ref $requestver ne 'HASH') {
$header{Location} = "/sc/$requestver";
Abort("Canonical URL for $Request is at $requestver", '302 subpage alias');
}
my %scver = %{$requestver};
my $datafile = "sc-units-$Request";
Html({
title => "$scver{title} unit cheat sheet",
version => '1.3',
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 patch attribute multiplayer
',
$scver{major} < 2 ? qw' bw broodwar brood war ' :
qw' starcraft2 lotv hots wol ',
],
stylesheet => [qw( light dark )],
raw => '',
data => ["$datafile.inc.pl"],
});
say "
Unit properties as seen or measured in $scver{name}\n$patch."; say "Also see the $_ tables." for join(' and ', (showlink('StarCraft 2: LotV', '/sc/lotv')) x ($Request ne 'lotv'), (showlink( 'HotS', '/sc/hots')) x ($Request ne 'hots'), (showlink('original SC: Brood War', '/sc/bw')) x ($Request ne 'bw'), ); say "
\n"; sub addupgrade { my ($ref, $increase, $org) = @_; if (ref $increase eq 'HASH') { addupgrade(\${$ref}->{$_}, $increase->{$_}, $org->{$_}) for keys %{$increase}; } elsif (ref $increase eq 'ARRAY') { addupgrade(\${$ref}->[$_], $increase->[$_], $org->[$_]) for 0 .. $#{$increase}; } ${$ref} //= $org; ${$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}, $increase, $unit->{$col}); } } for my $special (@{ $unit->{special} }) { for my $upgrade (@{ $special->{upgrade} }) { while (my ($col, $increase) = each %{$upgrade}) { defined $special->{$col} or next; addupgrade(\$special->{upgraded}->{$col}, $increase, $special->{$col}); } } } } sub coltoggle { my ($name, $id, $nolink) = @_; return "$name ▼" if defined $get{order} ? $get{order} eq $id : !$id; return $name if $nolink; return showlink($name, '?'.($id && "order=$id")); } :><:= coltoggle(exists $get{order} ? 'race' : 'source' => '') :> | <:= coltoggle(name => 'name') :> | <:= coltoggle(cost => 'cost') :> | gas | <:= coltoggle(build => 'build') :> | <:= coltoggle(qw'size size') :> | attr | <:= coltoggle(HP => 'hp') :> | shield | ⛨ | <:= coltoggle(attack => 'attack') :> | dps | range | sight | <:= coltoggle(speed => 'speed') :> | specials | '; my $upattack = $row->{upgraded}->{attack}->[$area]; my $damage = $attack->{damage}; my $maxdamage = $upattack->{damage} // $damage; $maxdamage += ($upattack->{upgrade} // $attack->{upgrade}) * 3; my $out = ' | '; $out .= sprintf '¤ ', showcost($attack, $row) if $attack->{cost}; $out .= sprintf('%s× ', showrangeint($attack->{count}, $upattack->{count}), ) if $attack->{count} > 1; $out .= '*' if $attack->{type} eq 'explosive'; $out .= '~' if $attack->{type} eq 'implosive'; if (my @bonus = sort grep { !/^-/ } keys %{ $attack->{bonus} }) { $out .= sprintf('≥', ( $_ eq 'light' ? 'unit-s' : $_ eq 'armored' ? 'unit-l' : $_ eq 'organic' ? 'unit-o' : $_ eq 'massive' ? 'unit-h' : $_ eq 'shields' ? 'unit-shield' : $_ eq 'structure' ? 'unit-x' : '', ), ( sprintf('+%s vs %s', showrangeint( $attack->{bonus}->{$_}, ($upattack->{bonus} // $attack->{bonus})->{$_} + ($upattack->{bonus} // $attack->{bonus})->{"-$_"} * 3, ), $_, ), ), ) for @bonus; } $out .= '•' if $attack->{type} eq 'projectile'; $out .= sprintf '', $attack->{name} if $attack->{name}; $out .= showrangeint($damage, $maxdamage); $out .= '' if $attack->{name}; $out .= sprintf('%s', $attack->{splash} eq 'line' ? ('linear', '+') : ('splash', '⁜') ) if $attack->{splash}; $out .= ' | '; if ($attack->{dps}) { # precalculated dps, do not touch $out .= showrangeint($attack->{dps}->[0], $upattack->{dps}->[-1] // $attack->{dps}->[-1] ); } elsif ($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 { $bonus->{$_} + $bonus->{"-$_"} * 3 } grep { !/^-/ } keys %{$bonus} ); } $maxdamage *= ($upattack->{count} // $attack->{count} // 1) / ($upattack->{cooldown} // $attack->{cooldown}); $out .= showrangeint($damage, $maxdamage); } $out .= ' | ' . '▽' x !!($attack->{anti} & 1); $out .= ' | ' . '△' x !!($attack->{anti} & 2); $out .= ' | ' . showrangeint($attack->{range}, $upattack->{range}); return $out; } sub showmagic { my ($row) = @_; my $specials = $row->{special} or return ''; return join ' ', map { sprintf '%s', $_->{duration} < 0 && ' class="magic-perma"', join('', $_->{name} // $_->{alt}, $_->{desc} ? ": $_->{desc}" : '', (map { $_ && " ($_)" } join ', ', #TODO: apply upgrades $_->{range} ? "range $_->{range}" : (), $_->{cost} ? showcost($_, $row) : $_->{cooldown} ? "cooldown $_->{cooldown}s" : (), ), ), sprintf($_->{build} ? '(%s)' : '%s', $_->{abbr}), } grep { defined $_->{abbr} } @{$specials}; } sub showunitcols { my ($row) = @_; local $_ = $row; $_->{hp} += $_->{shield} if $_->{shield}; return ( ' | ' . ($_->{min} // ''), ' | ' . ($_->{gas} || ''), defined $_->{transform} ? sprintf(' | %.0f', $_->{transform}, ) : !defined $_->{build} ? ' | ' : sprintf(' | %s%.0f', defined $_->{warp} && qq(title="$_->{build} without warpgate"), !!$_->{base} && sprintf( '+', 'from '.join('+', @{ $_->{base} }), ), $_->{warp} // $_->{build} || '0', ), sprintf(' | %s', $_-> {cargo} < 0 ? ('supply', T => 'transport') : $_->{upgraded}->{cargo} < 0 ? ('supply magic-opt', T => 'optional transport') : $_-> {attr}->{flying} ? ('air', F => 'flying') : $_->{upgraded}->{attr}->{flying} ? ('air magic-opt', F => 'potentially flying') : $_->{attr}->{structure} ? ('x', B => 'building') : ( [qw( x s m l l h h h h )]->[ $_->{cargo} ], $_->{cargo} || '-', $_->{cargo} ? 'transportable' : 'untransportable', ), defined $_->{size} && sprintf('⌀%.1f ', $_->{size}), ), sprintf(' | %s', defined $_->{pop} && $_->{pop} < 0 && ' unit-supply', defined $_->{pop} && $_->{pop} == .5 ? '½' : $_->{pop}, ), ' | ' . join('', grep { $_ } (defined $_->{organic} ? !$_->{organic} : $_->{attr}->{mech}) && 'm', ($_->{organic} || $_->{attr}->{organic}) && 'o', $_->{attr}->{psionic} && 'ψ', ), ' | ' . join('', grep { $_ } $_->{attr}->{armored} && 'A', $_->{attr}->{light} && 'L', $_->{suit} && sprintf( '%s', map { @{$_} } [ [qw( x ? unknown )], [qw( s S small )], [qw( m M medium )], [qw( l L large )], ]->[ $_->{suit} ], ), $_->{attr}->{massive} && '⚓', ), $_->{hp} < 0 ? ' | ∞' : ' | ' . showrangeint($_->{hp}, $_->{upgraded}->{hp}), $_->{shield} ? sprintf(' | %.0f%% | {shield} / $_->{hp} ) : ' | ' . showrangeint($_->{armor}, $_->{upgraded}->{armor}), showattack($_, 0), ' | ' . sprintf( $_->{detect} ? '%s' : '%s', showrangeint($_->{sight}, $_->{upgraded}->{sight}) ), sprintf(' | %s', showrange( map { $_ && sprintf '%.1f', $_ } $_->{speed}, $_->{upgraded}->{speed} ), defined $_->{creep} && sprintf(' title="%s on creep"', $_->{creep} == 1 ? 'same' : showrange( map { $_ && sprintf '%.1f', $_ } $_->{speed} * $_->{creep}, $_->{upgraded}->{speed} && $_->{upgraded}->{speed} * ($_->{upgraded}->{creep} // $_->{creep}), ), ), ), $_->{attr}->{jump} && qq'↕', ' | ' . showmagic($_), (map {( ' |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
', showattack($row, $_), ' | ', )} 1 .. $#{ $_->{attack} }), "\n" ); } my @rows = @{$units}; my $grouped = 1; # race headers if (exists $get{order}) { $grouped = 0; $get{order} ||= ''; if ($get{order} eq 'name') { @rows = sort {$a->{name} cmp $b->{name}} @rows; } elsif ($get{order} eq 'cost') { $_->{order} = ( $_->{gas}*1.5 + $_->{min} + $_->{pop}/8 + $_->{build}/256/8 ) for @rows; } elsif ($get{order} eq 'build') { my %unittime = map { ($_->{name} => $_->{warp} // $_->{build}) } @rows; $unittime{Templar} = $unittime{'High Templar'}; $_->{order} = ( ($_->{warp} // $_->{build}) + ($_->{gas}*1.5 + $_->{min} + $_->{pop}/8)/1024 + ($_->{base} ? ($unittime{$_->{base}->[0]} // 100) + 1 : 0) ) for @rows; } elsif ($get{order} eq 'size') { $_->{order} = ( $_->{pop}*16 + ($_->{size} // $_->{suit}) + $_->{cargo}/8 + $_->{hp}/512 + $_->{min}/8192 ) for @rows; } elsif ($get{order} eq 'hp') { $_->{order} = ( $_->{hp}*1.01 + $_->{armor} + $_->{shield} + $_->{size}/1024, ) for @rows; } elsif ($get{order} eq 'attack') { $_->{order} = $_->{hp} / 16384 + max( map { ($_->{dps} ? $_->{dps}->[-1] : ($_->{damage} + $_->{upgrade} * 3) * ($_->{count} // 1) / ($_->{cooldown} // 1) ) * ($_->{splash} ? 1.01 : 1) * ($_->{type} eq 'implosive' ? .96 : 1) * ($_->{type} eq 'explosive' ? .98 : 1) } @{ $_->{attack} } ) for @rows; } elsif ($get{order} eq 'speed') { $_->{order} = ( ($_->{upgraded}->{speed} // $_->{speed}*1.01) + $_->{sight}/1024 + $_->{detect}/2048 ) for @rows; } @rows = sort {$a->{order} <=> $b->{order}} @rows if exists $rows[0]->{order}; } my ($race, $cat) = ('', ''); for (@rows) { if ($grouped) { say sprintf ' | ||||||||||||||||||
%s', $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), the second value indicates the attribute after all possible upgrades.