| 1 | package Vee::Controller::Dex::Utils; |
|---|
| 2 | |
|---|
| 3 | use strict; |
|---|
| 4 | use warnings; |
|---|
| 5 | use base 'Catalyst::Controller'; |
|---|
| 6 | |
|---|
| 7 | use List::MoreUtils qw/first_index last_index/; |
|---|
| 8 | use Vee::Dex; |
|---|
| 9 | use Vee::Form; |
|---|
| 10 | |
|---|
| 11 | __PACKAGE__->config->{namespace} = 'dex'; |
|---|
| 12 | |
|---|
| 13 | =head1 NAME |
|---|
| 14 | |
|---|
| 15 | Vee::Controller::Dex::Utils - Pokedex utility Controller |
|---|
| 16 | |
|---|
| 17 | =head1 SYNOPSIS |
|---|
| 18 | |
|---|
| 19 | See L<Vee> |
|---|
| 20 | |
|---|
| 21 | =head1 DESCRIPTION |
|---|
| 22 | |
|---|
| 23 | Catalyst Controller for Pokedex-related utilities, calculators, etc. |
|---|
| 24 | |
|---|
| 25 | =head1 METHODS |
|---|
| 26 | |
|---|
| 27 | =cut |
|---|
| 28 | |
|---|
| 29 | =head2 compare |
|---|
| 30 | |
|---|
| 31 | =cut |
|---|
| 32 | |
|---|
| 33 | my $MAX_POKEMON = 8; |
|---|
| 34 | my $FONT_MAX = 24; |
|---|
| 35 | my $FONT_MIN = 8; |
|---|
| 36 | my @versions = qw/rb y gs c rusa frlg dp/; |
|---|
| 37 | |
|---|
| 38 | sub compare : Path('pokemon/compare') : Args(0) { |
|---|
| 39 | my ($self, $c) = @_; |
|---|
| 40 | my $s = $c->stash; |
|---|
| 41 | |
|---|
| 42 | $s->{page_title} = 'Pokémon Comparifier'; |
|---|
| 43 | $s->{template} = 'dex/utils/compare.tt'; |
|---|
| 44 | |
|---|
| 45 | $s->{versions} = \@versions; |
|---|
| 46 | $s->{MAX_POKEMON} = $MAX_POKEMON; |
|---|
| 47 | $s->{FONT_MAX} = $FONT_MAX; |
|---|
| 48 | $s->{FONT_MIN} = $FONT_MIN; |
|---|
| 49 | |
|---|
| 50 | my $params = $c->req->params; |
|---|
| 51 | my $version = $params->{version}; |
|---|
| 52 | $version = $versions[-1] unless $version and Vee::Utils::in($version, @versions); |
|---|
| 53 | $s->{version} = $version; |
|---|
| 54 | |
|---|
| 55 | return unless %{$c->req->params}; |
|---|
| 56 | |
|---|
| 57 | # get pokemons |
|---|
| 58 | my @pokemon_raw = Vee::Utils::array($params->{poke}); |
|---|
| 59 | @pokemon_raw = @pokemon_raw[0 .. $MAX_POKEMON - 1] if $#pokemon_raw >= $MAX_POKEMON; |
|---|
| 60 | |
|---|
| 61 | my %randoms; # used to make sure no Pokemon is randomly picked twice |
|---|
| 62 | my (@pokemon_ids, @bad_pokemon, @pokemon_notes); |
|---|
| 63 | for my $p (0 .. $#pokemon_raw) { |
|---|
| 64 | my $poke = $pokemon_raw[$p]; |
|---|
| 65 | next unless $poke; |
|---|
| 66 | |
|---|
| 67 | if ($poke eq 'random') { # special case: random |
|---|
| 68 | $pokemon_ids[$p] = 1 + int rand $#PokemonNames; |
|---|
| 69 | while ($randoms{ $pokemon_ids[$p] }) { |
|---|
| 70 | $pokemon_ids[$p] = 1 + int rand $#PokemonNames; |
|---|
| 71 | } |
|---|
| 72 | $randoms{ $pokemon_ids[$p] } = 1; |
|---|
| 73 | |
|---|
| 74 | } elsif (my $result = get_pokemon($poke)) { |
|---|
| 75 | if (ref $result eq 'SCALAR') { |
|---|
| 76 | $pokemon_notes[$p] = $$result |
|---|
| 77 | } else { |
|---|
| 78 | $pokemon_ids[$p] = $result |
|---|
| 79 | } |
|---|
| 80 | |
|---|
| 81 | } elsif (4 > length $poke) { |
|---|
| 82 | $pokemon_notes[$p] = 'Too short for fuzzy search.'; |
|---|
| 83 | } else { |
|---|
| 84 | my @fuzzies = get_fuzzy($poke, 'pokemon'); |
|---|
| 85 | if (@fuzzies) { |
|---|
| 86 | $pokemon_ids[$p] = ( shift @fuzzies )->{id}; |
|---|
| 87 | @fuzzies = @fuzzies[0 .. 3] if @fuzzies > 4; |
|---|
| 88 | $pokemon_notes[$p] = \@fuzzies; |
|---|
| 89 | } else { |
|---|
| 90 | $pokemon_notes[$p] = "No match found."; |
|---|
| 91 | } |
|---|
| 92 | } |
|---|
| 93 | } |
|---|
| 94 | $s->{pokemon_notes} = \@pokemon_notes; |
|---|
| 95 | |
|---|
| 96 | # grab the Pokemon's rows and stick them into an array in the correct order |
|---|
| 97 | my %pokemon_order; |
|---|
| 98 | for my $i (0 .. $#pokemon_ids) { |
|---|
| 99 | next unless $pokemon_ids[$i]; |
|---|
| 100 | push @{ $pokemon_order{ $pokemon_ids[$i] } }, $i; |
|---|
| 101 | } |
|---|
| 102 | my @pokemon; |
|---|
| 103 | |
|---|
| 104 | my (@rows, @pokemon_moves); |
|---|
| 105 | # only do this if there are actually correct Pokemon to look up! |
|---|
| 106 | if (@pokemon_ids) { |
|---|
| 107 | my $pokemon_rs = $c->model('DBIC::Pokemon')->search({ |
|---|
| 108 | 'me.id' => \@pokemon_ids |
|---|
| 109 | }, { |
|---|
| 110 | # TODO: this sucks; patch dbic |
|---|
| 111 | prefetch => { pokemon_abilities => 'ability' }, |
|---|
| 112 | }); |
|---|
| 113 | while (my $poke = $pokemon_rs->next) { |
|---|
| 114 | $pokemon[$_] = $poke for @{ $pokemon_order{$poke->id} }; |
|---|
| 115 | } |
|---|
| 116 | |
|---|
| 117 | # get moves |
|---|
| 118 | my $move_rs = $c->model('DBIC::PokemonMoves')->search({ |
|---|
| 119 | pokemon_id => [ keys %pokemon_order ], |
|---|
| 120 | method => [qw/level machine egg tutor/], |
|---|
| 121 | -nest => \ "FIND_IN_SET('$version', versions)", |
|---|
| 122 | }); |
|---|
| 123 | while (my $row = $move_rs->next) { |
|---|
| 124 | push @{ $pokemon_moves[ $_ ]{ $row->method } }, $row for @{ $pokemon_order{$row->pokemon_id} } |
|---|
| 125 | } |
|---|
| 126 | } |
|---|
| 127 | |
|---|
| 128 | $s->{pokemon} = \@pokemon; |
|---|
| 129 | $s->{pokemon_raw} = \@pokemon_raw; |
|---|
| 130 | $s->{pokemon_moves} = \@pokemon_moves; |
|---|
| 131 | |
|---|
| 132 | $s->{template} = 'dex/utils/compare-results.tt'; |
|---|
| 133 | } |
|---|
| 134 | |
|---|
| 135 | =head2 backtrace |
|---|
| 136 | |
|---|
| 137 | Calculates parents for an egg move. |
|---|
| 138 | |
|---|
| 139 | =cut |
|---|
| 140 | |
|---|
| 141 | sub backtrace : Chained('pokemon_chain') : Args(1) { |
|---|
| 142 | my ($self, $c) = @_; |
|---|
| 143 | my $s = $c->stash; |
|---|
| 144 | |
|---|
| 145 | my $gen = 'dp'; # TODO: :( |
|---|
| 146 | |
|---|
| 147 | my $pokemon_name = $c->req->captures->[0]; |
|---|
| 148 | my $move_name = $c->req->args ->[0]; |
|---|
| 149 | |
|---|
| 150 | # For some reason, Catalyst::DispatchType::Chained doesn't url-decode either |
|---|
| 151 | # args or captures, so do it here; this is safe to do since valid move and |
|---|
| 152 | # Pokemon names will never ever contain percent signs.. I hope. |
|---|
| 153 | $pokemon_name =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg; |
|---|
| 154 | $move_name =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg; |
|---|
| 155 | |
|---|
| 156 | my $pokemon = $c->model('DBIC::Pokemon')->single({ name => $pokemon_name }, { prefetch => 'breeds' }); |
|---|
| 157 | my $move = $c->model('DBIC::Moves') ->single({ name => $move_name }); |
|---|
| 158 | |
|---|
| 159 | $c->vee_stop('No such Pokémon ', $pokemon_name, '.') |
|---|
| 160 | if not $pokemon; |
|---|
| 161 | $c->vee_stop('No such move ', $move_name, '.') |
|---|
| 162 | if not $move; |
|---|
| 163 | |
|---|
| 164 | $s->{page_title} = $pokemon->name . ' - ' . $move->name . ' parents'; |
|---|
| 165 | $s->{crumbs} = [ |
|---|
| 166 | '<a href="' . $c->uri('Dex') . '">Pokédex</a>', |
|---|
| 167 | '<a href="' . $c->uri('Dex', 'pokemon_list') . '">Pokémon</a>', |
|---|
| 168 | '<a href="' . $c->uri('Dex', 'pokemon', lc $pokemon->name) . '">' . $pokemon->name . '</a>', |
|---|
| 169 | 'Breeding chains', |
|---|
| 170 | '<a href="' . $c->uri('Dex', 'moves', lc $move->name) . '">' . $move->name . '</a>', |
|---|
| 171 | ]; |
|---|
| 172 | |
|---|
| 173 | # ensure this move is actually inheritable |
|---|
| 174 | # TODO: should I error if the move is learnable normally? |
|---|
| 175 | my $inheritable_ct = $c->model('DBIC::PokemonMoves')->count({ |
|---|
| 176 | pokemon_id => $pokemon->id, |
|---|
| 177 | move_id => $move->id, |
|---|
| 178 | method => [qw/ egg machine /], |
|---|
| 179 | -nest => \ "FIND_IN_SET('$gen', versions)", |
|---|
| 180 | }); |
|---|
| 181 | $c->vee_stop('', $pokemon->name, " can't inherit ", $move->name, '.') unless $inheritable_ct; |
|---|
| 182 | |
|---|
| 183 | my $gender_restriction; |
|---|
| 184 | if ($pokemon->gender_rate == 255) { |
|---|
| 185 | # must also be genderless, i.e. bred with Ditto |
|---|
| 186 | $gender_restriction = 255; |
|---|
| 187 | } else { |
|---|
| 188 | # cannot be single-gender or genderless |
|---|
| 189 | $gender_restriction = { -not_in => [ 0, 254, 255 ] }; |
|---|
| 190 | } |
|---|
| 191 | |
|---|
| 192 | ### grab the methods by which any Pokemon learn the move |
|---|
| 193 | |
|---|
| 194 | my $methods_rs = $c->model('DBIC::PokemonMoves')->search({ |
|---|
| 195 | move_id => $move->id, |
|---|
| 196 | method => [qw[ level egg machine ]], |
|---|
| 197 | -nest => \ "FIND_IN_SET('$gen', versions)", |
|---|
| 198 | 'pokemon.gender_rate' => $gender_restriction, |
|---|
| 199 | }, { |
|---|
| 200 | join => 'pokemon', |
|---|
| 201 | }); |
|---|
| 202 | my %learn_methods; |
|---|
| 203 | while (my $row = $methods_rs->next) { |
|---|
| 204 | my $method = ucfirst $row->method; |
|---|
| 205 | if ($row->method eq 'level') { |
|---|
| 206 | $method .= ' ' . $row->level; |
|---|
| 207 | } elsif ($row->method eq 'machine') { |
|---|
| 208 | $method = 'TM/HM'; |
|---|
| 209 | } |
|---|
| 210 | push @{ $learn_methods{ $row->pokemon_id } }, $method; |
|---|
| 211 | } |
|---|
| 212 | |
|---|
| 213 | ### do a SEPARATE query to grab all the possible Pokemon at once |
|---|
| 214 | # (DBIC seems to get confused if I prefetch { pokemon => breeds } above) |
|---|
| 215 | # XXX: is this my fault or theirs? |
|---|
| 216 | |
|---|
| 217 | my $learners_rs = $c->model('DBIC::Pokemon')->search({ |
|---|
| 218 | id => [ keys %learn_methods ], |
|---|
| 219 | }, { |
|---|
| 220 | prefetch => 'breeds', |
|---|
| 221 | order_by => 'me.id ASC', |
|---|
| 222 | }); |
|---|
| 223 | |
|---|
| 224 | my (%pokemon); |
|---|
| 225 | while (my $row = $learners_rs->next) { |
|---|
| 226 | $pokemon{ $row->id } = $row; |
|---|
| 227 | } |
|---|
| 228 | |
|---|
| 229 | my %breeding_tree = ( $pokemon->id => {} ); |
|---|
| 230 | |
|---|
| 231 | my %seen = ( $pokemon->id => 0 ); # hash of ( poke_id => 1, ... ) |
|---|
| 232 | my $cur_level = 0; # current tree level |
|---|
| 233 | my @this_level = %breeding_tree; # array of ( poke_id, hashref, ... ) on this level |
|---|
| 234 | |
|---|
| 235 | ### build the breeding-chain tree |
|---|
| 236 | # What we need is a tree of ancestors that can learn the move and pass it |
|---|
| 237 | # down; it should look something like this: |
|---|
| 238 | # pokemon => { |
|---|
| 239 | # a => { |
|---|
| 240 | # b => {}, |
|---|
| 241 | # c => {}, |
|---|
| 242 | # d => { |
|---|
| 243 | # e => {}, |
|---|
| 244 | # }, |
|---|
| 245 | # }, |
|---|
| 246 | # f => { |
|---|
| 247 | # g => {}, |
|---|
| 248 | # d => [ref to former d], |
|---|
| 249 | # }, |
|---|
| 250 | # } |
|---|
| 251 | # The rules are basically that: |
|---|
| 252 | # 1. Every Pokemon is a key in a hash |
|---|
| 253 | # 2. Each corresponding value is a similar hash, containing a list of |
|---|
| 254 | # Pokemon that are compatible |
|---|
| 255 | # 3. No infinite looping! |
|---|
| 256 | |
|---|
| 257 | do { |
|---|
| 258 | $cur_level++; |
|---|
| 259 | my %branches; |
|---|
| 260 | |
|---|
| 261 | while (my $pokemon_id = shift @this_level) { |
|---|
| 262 | my $this_poke = $pokemon{$pokemon_id}; |
|---|
| 263 | my $hashref = shift @this_level; |
|---|
| 264 | |
|---|
| 265 | for my $pokemon_id (keys %pokemon) { |
|---|
| 266 | next if not $this_poke->can_breed_with( $pokemon{$pokemon_id} ); |
|---|
| 267 | |
|---|
| 268 | if (exists $seen{ $pokemon_id }) { |
|---|
| 269 | # don't put anything if this Pokemon has been seen higher |
|---|
| 270 | # in the chain; needless descending is bad |
|---|
| 271 | $hashref->{ $pokemon_id } = undef |
|---|
| 272 | unless $seen{ $pokemon_id } < $cur_level; |
|---|
| 273 | } else { |
|---|
| 274 | $hashref->{$pokemon_id} = ( $branches{$pokemon_id} ||= {} ); |
|---|
| 275 | $seen{$pokemon_id} = $cur_level; |
|---|
| 276 | } |
|---|
| 277 | } |
|---|
| 278 | } |
|---|
| 279 | |
|---|
| 280 | @this_level = %branches; |
|---|
| 281 | } while ($cur_level < 10 and @this_level); |
|---|
| 282 | |
|---|
| 283 | ### find the optimal path |
|---|
| 284 | |
|---|
| 285 | # since there should only be a small finite number of Pokemon, we can just |
|---|
| 286 | # iterate over every possibility; flatten into arrays, and take whichever |
|---|
| 287 | # is shortest |
|---|
| 288 | |
|---|
| 289 | # TODO: merge this with the actual building of the tree, and toss useless |
|---|
| 290 | # branches as we go; right now this sub MODIFIES THE TREE, pruning any |
|---|
| 291 | # branches it finds that don't end with a level; this makes the dupe |
|---|
| 292 | # removal up top a little silly! |
|---|
| 293 | my $flattener; $flattener = sub { |
|---|
| 294 | my ($branch) = @_; |
|---|
| 295 | my @subchains; |
|---|
| 296 | |
|---|
| 297 | for my $key (keys %$branch) { |
|---|
| 298 | if (grep /level/i, @{ $learn_methods{$key} }) { |
|---|
| 299 | # consider this Pokemon an endpoint if it learns the move |
|---|
| 300 | # naturally; TMs don't count since they are of limited quantity |
|---|
| 301 | push @subchains, [ $key ]; |
|---|
| 302 | $branch->{$key} &&= {}; |
|---|
| 303 | next; |
|---|
| 304 | } |
|---|
| 305 | |
|---|
| 306 | # ...otherwise, only add more chains if this branch has any valid |
|---|
| 307 | # subchains of its own |
|---|
| 308 | my @child_chains = $flattener->( $branch->{$key} ); |
|---|
| 309 | if (@child_chains) { |
|---|
| 310 | push @subchains, map [ $key, @$_ ], @child_chains; |
|---|
| 311 | } else { |
|---|
| 312 | delete $branch->{$key}; |
|---|
| 313 | } |
|---|
| 314 | } |
|---|
| 315 | |
|---|
| 316 | return @subchains; |
|---|
| 317 | }; |
|---|
| 318 | |
|---|
| 319 | # only take the shortest chains; it's possible there could be several! |
|---|
| 320 | my @flat_chains = sort { $#$a <=> $#$b } $flattener->( \%breeding_tree ); |
|---|
| 321 | my @optimal_path = grep { $#$_ == $#{ $flat_chains[0] } } @flat_chains; |
|---|
| 322 | @$_ = reverse @$_ for @optimal_path; |
|---|
| 323 | |
|---|
| 324 | ### cram everything important into the stash |
|---|
| 325 | $s->{breeding_tree} = \%breeding_tree; |
|---|
| 326 | $s->{pokemon} = \%pokemon; |
|---|
| 327 | $s->{learn_methods} = \%learn_methods; |
|---|
| 328 | $s->{optimal} = \@optimal_path; |
|---|
| 329 | |
|---|
| 330 | $s->{template} = 'dex/utils/backtrace.tt'; |
|---|
| 331 | |
|---|
| 332 | # $c->res->body(join "<br/>", map { qq'<a href="/dex/pokemon/$PokemonNames[$_]">$PokemonNames[$_]</a>' } @other_learners); |
|---|
| 333 | |
|---|
| 334 | # TODO: |
|---|
| 335 | # ! CHECK to make sure this is a move that the Pokemon can learn via egg *or* TM |
|---|
| 336 | # - female is obvious and is the species of the target child |
|---|
| 337 | # - for previous generations, just look for a Pokemon that only learns via breeding |
|---|
| 338 | # - I suggest removing the breed requirement and doing that with perl code so you can reuse the data |
|---|
| 339 | # - do not bother with anything elses! |
|---|
| 340 | # - also: later look for entire families that match and combine them somehow? ugh that will be a pain in the ass |
|---|
| 341 | # - GENDERLESS! they must be dealt with entirely separately |
|---|
| 342 | # - use gender distro, steps, and other stats to figure out optimal chain? |
|---|
| 343 | # |
|---|
| 344 | # - db: merge the egg moves that need it, not sure why that hasn't happened |
|---|
| 345 | # MYSTERY SOLVED: d/p data used babies as bases, I use the first evo as the base. have to go through babies and fix this :( |
|---|
| 346 | # - start on my own visual refresh? use some stock background to try for now I dunno |
|---|
| 347 | # ! don't slurp @other_learners |
|---|
| 348 | # - make a /dex/pokemon/eevee/backtrace that lists all traceable moves? |
|---|
| 349 | |
|---|
| 350 | } |
|---|
| 351 | |
|---|
| 352 | =head2 damage |
|---|
| 353 | |
|---|
| 354 | Calculates damage. |
|---|
| 355 | |
|---|
| 356 | =cut |
|---|
| 357 | |
|---|
| 358 | sub damage : Path('calculators/damage') : Form('dex/damage') : Args(0) { |
|---|
| 359 | my ($self, $c) = @_; |
|---|
| 360 | my $s = $c->stash; |
|---|
| 361 | |
|---|
| 362 | if (%{$c->req->params} and $c->form->validate) { |
|---|
| 363 | my $attack = $c->req->params->{attack}; |
|---|
| 364 | my $defense = $c->req->params->{defense}; |
|---|
| 365 | my $power = $c->req->params->{power}; |
|---|
| 366 | my $level = $c->req->params->{level}; |
|---|
| 367 | |
|---|
| 368 | my $damage = int(int( (2 + int($level * 0.4)) * $attack * $power / $defense ) / 50) + 2; |
|---|
| 369 | $damage = 999 if $damage > 999; |
|---|
| 370 | |
|---|
| 371 | my $random = int rand 39; |
|---|
| 372 | |
|---|
| 373 | $s->{damage} = $damage; |
|---|
| 374 | $s->{min_damage} = int($damage * (217 + $random) / 255); |
|---|
| 375 | } |
|---|
| 376 | |
|---|
| 377 | $s->{page_title} = 'Damage Calculator'; |
|---|
| 378 | $s->{extra_css} = 'dex'; |
|---|
| 379 | $s->{template} = 'dex/utils/damage.tt'; |
|---|
| 380 | |
|---|
| 381 | # $c->res->body("Max damage: $damage <br/> Min damage: " . int($damage * ($random + 217) / 255)); |
|---|
| 382 | } |
|---|
| 383 | |
|---|
| 384 | =head2 iv |
|---|
| 385 | |
|---|
| 386 | Calculates IVs. |
|---|
| 387 | |
|---|
| 388 | =cut |
|---|
| 389 | |
|---|
| 390 | my $iv_fields = { |
|---|
| 391 | pokemon => { type => 'text', class => 'js-dexsuggest js-dexsuggest-pokemon' }, |
|---|
| 392 | level => { type => 'text' }, |
|---|
| 393 | nature => { type => 'select', options => [ ['ignore' => 'Ignore'], map [ $_ => ucfirst $_ ], sort keys %Natures ], }, |
|---|
| 394 | }; |
|---|
| 395 | |
|---|
| 396 | for my $s (0 .. $#StatColumns) { |
|---|
| 397 | my $stat = $StatColumns[$s]; |
|---|
| 398 | $iv_fields->{ $stat } = { type => 'text', maxlength => 3, tabindex => $s + 1, title => "Enter your Pokemon's calculated stat.", }; |
|---|
| 399 | $iv_fields->{ $stat . '_ev' } = { type => 'text', maxlength => 3, tabindex => $s + @StatColumns + 1, title => "Enter your Pokemon's effort value; this is 0 if it was just caught.", }; |
|---|
| 400 | } |
|---|
| 401 | |
|---|
| 402 | |
|---|
| 403 | sub iv : Path('calculators/iv') : Args(0) { |
|---|
| 404 | my ($self, $c) = @_; |
|---|
| 405 | my $s = $c->stash; |
|---|
| 406 | my $p = $c->req->params; |
|---|
| 407 | |
|---|
| 408 | my $form = $s->{form} = Vee::Form->new( |
|---|
| 409 | id => 'stat_calculator', |
|---|
| 410 | fields => $iv_fields, |
|---|
| 411 | params => $c->req->params, |
|---|
| 412 | ); |
|---|
| 413 | |
|---|
| 414 | $s->{page_title} = 'IV/Stat Calculator'; |
|---|
| 415 | $s->{extra_css} = 'dex'; |
|---|
| 416 | $s->{template} = 'dex/utils/iv.tt'; |
|---|
| 417 | |
|---|
| 418 | if ($form->submitted) { |
|---|
| 419 | # TODO: hide this nonsense in Vee::Form? |
|---|
| 420 | my @errors; |
|---|
| 421 | my $pokemon = $c->model('DBIC::Pokemon')->find( get_pokemon($p->{pokemon}) ); |
|---|
| 422 | |
|---|
| 423 | push @errors, "Invalid Pokémon entered." if not $pokemon; |
|---|
| 424 | push @errors, "Invalid level entered." if $p->{level} !~ /^(?: [1-9] | \d\d | 100 )$/x; |
|---|
| 425 | if (@errors) { |
|---|
| 426 | $s->{error_msg} = \@errors; |
|---|
| 427 | return; |
|---|
| 428 | } |
|---|
| 429 | |
|---|
| 430 | # ok done with that validation nonsense |
|---|
| 431 | |
|---|
| 432 | my (@ivs, @iv_ranges, @possible_stats); |
|---|
| 433 | my @nature_change = (1) x 6; |
|---|
| 434 | my $nature_updown = $Natures{ $p->{nature} }; |
|---|
| 435 | if (defined $nature_updown) { |
|---|
| 436 | $nature_change[ $nature_updown->{up} ] = 1.1; |
|---|
| 437 | $nature_change[ $nature_updown->{down} ] = 0.9; |
|---|
| 438 | } |
|---|
| 439 | |
|---|
| 440 | my $all_exact = 1; |
|---|
| 441 | for my $s (0 .. $#StatColumns) { |
|---|
| 442 | my $stat = $StatColumns[$s]; |
|---|
| 443 | my $effort = $p->{ $stat . '_ev' } || 0; |
|---|
| 444 | my $base = $pokemon->$stat; |
|---|
| 445 | my $func = ($stat =~ /hp/) ? \&ingame_hp : \&ingame_stats; |
|---|
| 446 | |
|---|
| 447 | # possible IVs; doing this inversely is very difficult due to rounding |
|---|
| 448 | for my $iv (0 .. 31) { |
|---|
| 449 | my $calculated = $func->($base, $p->{level}, $iv, $effort); |
|---|
| 450 | $calculated = int( $calculated * $nature_change[$s] ); |
|---|
| 451 | $ivs[$s][$iv] = ($calculated == $p->{$stat}); |
|---|
| 452 | } |
|---|
| 453 | |
|---|
| 454 | # IV range in more readable text |
|---|
| 455 | $iv_ranges[$s]{min} = first_index { $_ } @{ $ivs[$s] }; |
|---|
| 456 | $iv_ranges[$s]{max} = last_index { $_ } @{ $ivs[$s] }; |
|---|
| 457 | $all_exact = 0 |
|---|
| 458 | if $iv_ranges[$s]{min} != $iv_ranges[$s]{max} |
|---|
| 459 | or $iv_ranges[$s]{min} == -1; |
|---|
| 460 | |
|---|
| 461 | # entire range of possible stats for this Pokemon |
|---|
| 462 | $possible_stats[$s] = |
|---|
| 463 | int( $nature_change[$s] * $func->($base, $p->{level}, 0, $effort) ) . ' - ' . |
|---|
| 464 | int( $nature_change[$s] * $func->($base, $p->{level}, 31, $effort) ); |
|---|
| 465 | } |
|---|
| 466 | $s->{ivs} = \@ivs; |
|---|
| 467 | $s->{iv_ranges} = \@iv_ranges; |
|---|
| 468 | $s->{possible_stats} = \@possible_stats; |
|---|
| 469 | |
|---|
| 470 | # TODO: improve this a bit since even at L100 you rarely get perfect results |
|---|
| 471 | if ($all_exact) { |
|---|
| 472 | $s->{hp_power} = ingame_hp_power( map { $_->{min} } @iv_ranges ); |
|---|
| 473 | $s->{hp_type} = ingame_hp_type ( map { $_->{min} } @iv_ranges ); |
|---|
| 474 | } |
|---|
| 475 | |
|---|
| 476 | $s->{nature} = $p->{nature}; |
|---|
| 477 | $s->{pokemon} = $pokemon; |
|---|
| 478 | } |
|---|
| 479 | } |
|---|
| 480 | |
|---|
| 481 | =head1 AUTHOR |
|---|
| 482 | |
|---|
| 483 | Maintainer: Alex "Eevee" Munroe (C<veekun@veekun.com>) |
|---|
| 484 | |
|---|
| 485 | See the included F<AUTHORS> file for a full list of contributers. |
|---|
| 486 | |
|---|
| 487 | =head1 LICENSE |
|---|
| 488 | |
|---|
| 489 | See the included F<LICENSE> file. |
|---|
| 490 | |
|---|
| 491 | =cut |
|---|
| 492 | |
|---|
| 493 | 1; |
|---|