(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 => '1.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, $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 sprintf( (defined $get{order} ? $get{order} eq $id : !$id) ? '%2$s ▼' : $nolink ? '%2$s' : '%s', $id && "order=$id", $name ); } :><:= coltoggle('name', '') :> | cost | gas | <:= coltoggle(qw'build cost') :> | <:= coltoggle(qw'size size') :> | attr | 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; $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('≥', (map { $_ eq 'light' ? 'unit-s' : $_ eq 'armored' ? 'unit-l' : $_ eq 'organic' ? 'unit-o' : $_ eq 'massive' ? 'unit-h' : $_ eq 'shields' ? 'unit-shield' : '', } join '_', @bonus), join(', ', map {( sprintf('+%s vs %s', showrangeint( $attack->{bonus}->{$_}, $attack->{bonus}->{$_} + $attack->{bonus}->{"-$_"} * 3, ), $_, ), )} @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}, $_->{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 $_->{build} ? ' | ' : sprintf(' | %s%.0f', !!$_->{base} && '+', $_->{build} || '0', ), sprintf(' | %s', $_-> {cargo} < 0 ? ('supply', T => 'transport') : $_->{upgraded}->{cargo} < 0 ? ('supply magic-opt', T => 'optional transport') : $_->{attr}->{flying} ? ('air', F => '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} // '', $_->{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($_), !$_->{attack}->[1] ? () : ( ' |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
', showattack($_, 1), ' | ' ), !$_->{attack}->[2] ? () : ( ' | ||||||||||||||||||
', showattack($_, 2), ' | ' ), "\n" ); } my $grouped = 1; # race headers if (exists $get{order}) { $grouped = 0; $get{order} ||= ''; if ($get{order} eq 'size') { $_->{order} = ( $_->{pop}*16 + ($_->{size} // $_->{suit}) + $_->{cargo}/8 + $_->{hp}/512 + $_->{min}/8192 ) for @$units; } elsif ($get{order} eq 'cost') { $_->{order} = ( $_->{gas}*1.5 + $_->{min} + $_->{pop}/8 + $_->{build}/256/8 ) for @$units; } elsif ($get{order} eq 'attack') { $_->{order} = $_->{hp} / 1024 + $_->{shield} / 1008 + max( map { ($_->{damage} + $_->{upgrade} * 3) * ($_->{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), the second value indicates the attribute after all possible upgrades.