| 1 | package Vee::Controller::Dex::Search; |
|---|
| 2 | |
|---|
| 3 | use strict; |
|---|
| 4 | use warnings; |
|---|
| 5 | use base 'Catalyst::Controller'; |
|---|
| 6 | |
|---|
| 7 | use Vee::Form; |
|---|
| 8 | use Vee::Dex; |
|---|
| 9 | |
|---|
| 10 | use List::MoreUtils qw/uniq/; |
|---|
| 11 | |
|---|
| 12 | __PACKAGE__->config->{namespace} = 'dex'; |
|---|
| 13 | |
|---|
| 14 | =head1 NAME |
|---|
| 15 | |
|---|
| 16 | Vee::Controller::Dex::Search - Pokedex search Controller |
|---|
| 17 | |
|---|
| 18 | =head1 SYNOPSIS |
|---|
| 19 | |
|---|
| 20 | See L<Vee> |
|---|
| 21 | |
|---|
| 22 | =head1 DESCRIPTION |
|---|
| 23 | |
|---|
| 24 | Catalyst Controller for searching for thingers in the Pokedex. |
|---|
| 25 | |
|---|
| 26 | =cut |
|---|
| 27 | |
|---|
| 28 | =head1 METHODS |
|---|
| 29 | |
|---|
| 30 | =cut |
|---|
| 31 | |
|---|
| 32 | =head2 pokemon_search |
|---|
| 33 | |
|---|
| 34 | Search for pokemachus. |
|---|
| 35 | |
|---|
| 36 | =cut |
|---|
| 37 | |
|---|
| 38 | my %endpoints = ( |
|---|
| 39 | lb => { english => 'Minimum', operator => '>=', polarity => -1 }, |
|---|
| 40 | ub => { english => 'Maximum', operator => '<=', polarity => 1 }, |
|---|
| 41 | ); |
|---|
| 42 | |
|---|
| 43 | # Pokemon search form |
|---|
| 44 | our $pokemon_search_fields = { |
|---|
| 45 | name => { type => 'text', size => 15, title => 'Enter a name or part of a name' }, |
|---|
| 46 | habitat => { type => 'select', options => ['any', 'cave', 'forest', 'grassland', 'mountain', 'rare', 'rough terrain', 'sea', 'urban', 'water\'s edge'] }, |
|---|
| 47 | generation => { type => 'checkbox', options => [ map { [ $_ => $Generations[$_]{games} ] } 0 .. $#Generations ] }, |
|---|
| 48 | basedex => { type => 'checkbox', options => [ map { [ $_ => $Generations[$_]{games} ] } 1 .. $#Generations ] }, |
|---|
| 49 | breed => { type => 'select', options => [ [ 0 => 'n/a' ], map { [ $_ => ($_ ? "$_: " : '') . $BreedingGroups[$_] ] } 1 .. $#BreedingGroups ], count => 2 }, |
|---|
| 50 | breed_mode => { type => 'select', options => [ [ and => 'exactly' ], [ or => 'either of' ] ], default => 'or' }, |
|---|
| 51 | gender_rate => { type => 'select', options => [ [ any => 'anything' ], [ 255 => 'no gender' ], [ not255 => 'any gender' ], map { [ $_ => lc gender_text($_) ] } qw/0 31 63 127 191 254/ ] }, |
|---|
| 52 | ability => { type => 'text', size => 20, title => 'Enter the name or number of an ability' }, |
|---|
| 53 | color => { type => 'select', options => [qw/any black blue brown gray green pink purple red white yellow/], title => 'I have no explanation for why this is here' }, |
|---|
| 54 | type_mode => { type => 'radio', options => [[ ignore => 'Ignore types' ], [ any => 'Find Pokémon with any type that matches one selected' ], [ all => 'Find Pokémon with exactly the selected type combination' ]], default => 'ignore', title => 'Select how types must be matched' }, |
|---|
| 55 | type => { type => 'checkbox', options => [ @TypeNames ], title => 'Select types for the Pokemon to match' }, |
|---|
| 56 | move => { type => 'text', count => 4, class => 'js-dexsuggest js-dexsuggest-move', title => 'Enter moves the Pokemon must have' }, |
|---|
| 57 | move_method => { type => 'checkbox', options => { level => 'Level up', machine => 'TM or HM', egg => 'Egg', tutor => 'Move Tutor' }, title => 'Select ways the Pokemon may learn the move' }, |
|---|
| 58 | move_version=> { type => 'checkbox', options => [ map { [ $_ => $Icons{$_} ] } qw/rb y gs c rusa frlg e dp/ ], title => 'Select ways the Pokemon may learn the move' }, |
|---|
| 59 | evo_stage => { type => 'checkbox', options => ['base', 'final'] }, |
|---|
| 60 | |
|---|
| 61 | view => { type => 'select', options => [[ list => 'boring list' ], [ icons => "small compact icons" ], [ sprites => "sprites" ], [ table => "detailed table" ]], default => 'table', title => 'Select how you want the results displayed' }, |
|---|
| 62 | sort => { type => 'select', options => [[ id => 'National ID' ], [ name => 'Name' ], [ height => 'Height' ], [ weight => 'Weight' ], ( map { [ $StatColumns[$_] => $StatNames[$_] ] } 0 .. $#StatColumns ), [ stat_avg => 'Average stats' ] ], default => 'name', title => 'Select how you want the results ordered' }, |
|---|
| 63 | sort_desc => { type => 'checkbox', title => 'Check this for descending order' }, |
|---|
| 64 | }; |
|---|
| 65 | |
|---|
| 66 | for my $stat (0 .. $#StatColumns) { |
|---|
| 67 | for my $endpoint (keys %endpoints) { |
|---|
| 68 | $pokemon_search_fields->{ $StatColumns[$stat] . '_' . $endpoint } = { type => 'text', size => 4, maxlength => 3, title => "\u$endpoints{$endpoint}{english} $StatNames[$stat]" }; |
|---|
| 69 | } |
|---|
| 70 | } |
|---|
| 71 | for my $stat (0 .. $#StatColumns) { |
|---|
| 72 | for my $endpoint (keys %endpoints) { |
|---|
| 73 | $pokemon_search_fields->{ $StatColumns[$stat] . '_effort_' . $endpoint } = { type => 'text', size => 4, maxlength => 3, title => "\u$endpoints{$endpoint}{english} $StatNames[$stat] effort" }; |
|---|
| 74 | } |
|---|
| 75 | } |
|---|
| 76 | for my $nonstat (qw/height weight/) { |
|---|
| 77 | for my $endpoint (keys %endpoints) { |
|---|
| 78 | $pokemon_search_fields->{ $nonstat . '_' . $endpoint } = { type => 'text', size => 4, maxlength => 8, title => "\u$endpoints{$endpoint}{english} $nonstat" }; |
|---|
| 79 | } |
|---|
| 80 | } |
|---|
| 81 | |
|---|
| 82 | my %view_sizes = ( |
|---|
| 83 | icons => 250, |
|---|
| 84 | sprites => 100, |
|---|
| 85 | default => 50, |
|---|
| 86 | ); |
|---|
| 87 | |
|---|
| 88 | sub pokemon_search : Path('pokemon/search') : Args(0) { |
|---|
| 89 | my ($self, $c) = @_; |
|---|
| 90 | my $s = $c->stash; |
|---|
| 91 | my $p = $c->req->params; |
|---|
| 92 | my $skip = $p->{skip} || 0; |
|---|
| 93 | $s->{before} = {%{ $c->req->params }}; |
|---|
| 94 | |
|---|
| 95 | my $form = $s->{form} = Vee::Form->new( |
|---|
| 96 | id => 'pokemon_search', |
|---|
| 97 | fields => $pokemon_search_fields, |
|---|
| 98 | params => $c->req->params, |
|---|
| 99 | ); |
|---|
| 100 | |
|---|
| 101 | my $PAGESIZE = $s->{PAGESIZE} = $view_sizes{ $p->{view} } || $view_sizes{default} || 10; |
|---|
| 102 | |
|---|
| 103 | if ($form->submitted) { |
|---|
| 104 | my %criteria; |
|---|
| 105 | $criteria{'-and'} = [ { 'me.id' => { '<=', $Generations[-1]{maxid} } } ]; |
|---|
| 106 | my (%joins, %clauses); |
|---|
| 107 | $clauses{columns} = ['me.id'] unless $p->{view} eq 'table'; |
|---|
| 108 | |
|---|
| 109 | # BASIC |
|---|
| 110 | if ($p->{name}) { |
|---|
| 111 | my $name = $p->{name}; |
|---|
| 112 | if ($name =~ /[*?]/) { |
|---|
| 113 | $name =~ tr/*?/%_/; |
|---|
| 114 | $criteria{'me.name'} = { like => $name }; |
|---|
| 115 | } else { |
|---|
| 116 | $criteria{'me.name'} = { like => "%$name%" }; |
|---|
| 117 | } |
|---|
| 118 | } |
|---|
| 119 | if ($p->{habitat} ne 'any') { $criteria{'me.habitat'} = $p->{habitat} } |
|---|
| 120 | if ($p->{color} and $p->{color} ne 'any') { $criteria{'me.color'} = $p->{color} }; |
|---|
| 121 | if ($p->{ability}) { |
|---|
| 122 | my $ability = $c->model('DBIC::Abilities')->single( [ id => $p->{ability}, name => $p->{ability} ], { columns => ['id'] } ); |
|---|
| 123 | if ($ability) { |
|---|
| 124 | $joins{pokemon_abilities} = 1; |
|---|
| 125 | $criteria{'pokemon_abilities.ability_id'} = $ability->id; |
|---|
| 126 | } |
|---|
| 127 | } |
|---|
| 128 | |
|---|
| 129 | # GENERATIONS |
|---|
| 130 | if (defined $p->{generation}) { |
|---|
| 131 | my $gen_ref = []; |
|---|
| 132 | for my $gen (Vee::Utils::array($p->{generation})) { |
|---|
| 133 | push @$gen_ref, { '-between' => [ ($gen > 0 ? $Generations[$gen - 1]{maxid} : 0) + 1, $Generations[$gen]{maxid} ] }; |
|---|
| 134 | } |
|---|
| 135 | push @{$criteria{'-and'}}, { 'me.id' => $gen_ref }; |
|---|
| 136 | } |
|---|
| 137 | if (defined $p->{basedex}) { |
|---|
| 138 | for my $gen (Vee::Utils::array($p->{basedex})) { |
|---|
| 139 | $criteria{ 'me.id_' . lc $Generations[$gen]{region} } = { '!=' => 0 }; |
|---|
| 140 | } |
|---|
| 141 | } |
|---|
| 142 | |
|---|
| 143 | # BREEDING AND STUFF |
|---|
| 144 | if (defined $p->{gender_rate} and $p->{gender_rate} ne 'any') { |
|---|
| 145 | if ($p->{gender_rate} eq 'not255') { |
|---|
| 146 | $criteria{'me.gender_rate'} = { '!=' => 255 }; |
|---|
| 147 | } else { |
|---|
| 148 | $criteria{'me.gender_rate'} = $p->{gender_rate}; |
|---|
| 149 | } |
|---|
| 150 | } |
|---|
| 151 | my @breeds = uniq grep { $_ } Vee::Utils::array($p->{breed}); |
|---|
| 152 | if (@breeds) { |
|---|
| 153 | $joins{breeds} = 1; |
|---|
| 154 | $criteria{'breeds.breed'} = \@breeds; |
|---|
| 155 | if ($p->{breed_mode} eq 'and') { |
|---|
| 156 | $clauses{having}{'COUNT(DISTINCT breeds.pokemon_id, breeds.breed)'} = scalar @breeds; |
|---|
| 157 | |
|---|
| 158 | if (@breeds == 1) { |
|---|
| 159 | # special hackery here to say one and ONLY one. |
|---|
| 160 | # I have to use SUM() because only grouped columns are recognized in HAVING. |
|---|
| 161 | delete $criteria{'breeds.breed'}; |
|---|
| 162 | $clauses{having}{'SUM(breeds.breed)'} = $breeds[0]; |
|---|
| 163 | } |
|---|
| 164 | } |
|---|
| 165 | |
|---|
| 166 | # TODO: do.. something.. if someone sticks in 3+? |
|---|
| 167 | } |
|---|
| 168 | |
|---|
| 169 | # TYPES |
|---|
| 170 | if ($p->{type_mode} eq 'any') { |
|---|
| 171 | push @{$criteria{'-and'}}, [ |
|---|
| 172 | '-or', |
|---|
| 173 | { 'me.type1' => $p->{type}, 'me.type2' => $p->{type} }, |
|---|
| 174 | ]; |
|---|
| 175 | } elsif ($p->{type_mode} eq 'all') { |
|---|
| 176 | if (not ref $p->{type}) { |
|---|
| 177 | push @{$criteria{'-and'}}, { 'me.type1' => $p->{type}, 'me.type2' => undef }; |
|---|
| 178 | } elsif (scalar @{ $p->{type} } == 2) { |
|---|
| 179 | push @{$criteria{'-and'}}, { -or => [ |
|---|
| 180 | { 'me.type1' => $p->{type}[0], 'me.type2' => $p->{type}[1] }, |
|---|
| 181 | { 'me.type1' => $p->{type}[1], 'me.type2' => $p->{type}[0] }, |
|---|
| 182 | ] }; |
|---|
| 183 | } else { |
|---|
| 184 | # TODO: this should probably be better and throw an error of some sort |
|---|
| 185 | push @{$criteria{'-and'}}, \ 0; |
|---|
| 186 | } |
|---|
| 187 | } |
|---|
| 188 | |
|---|
| 189 | # MOVES |
|---|
| 190 | if (ref $p->{move} eq 'ARRAY') { |
|---|
| 191 | my @moveids; |
|---|
| 192 | for my $move (@{ $p->{move} }) { |
|---|
| 193 | # XXX: show some message if a move is invalid? |
|---|
| 194 | my $move_id = get_move($move); |
|---|
| 195 | push @moveids, $move_id if defined $move_id; |
|---|
| 196 | } |
|---|
| 197 | if (@moveids) { |
|---|
| 198 | $joins{pokemon_moves} = 1; |
|---|
| 199 | $criteria{'pokemon_moves.move_id'} = \@moveids; |
|---|
| 200 | $clauses{having}{'COUNT(DISTINCT pokemon_moves.pokemon_id, pokemon_moves.move_id)'} = scalar @moveids; |
|---|
| 201 | |
|---|
| 202 | if ($p->{move_method}) { |
|---|
| 203 | $criteria{'pokemon_moves.method'} = $p->{move_method}; |
|---|
| 204 | } |
|---|
| 205 | if ($p->{move_version}) { |
|---|
| 206 | push @{$criteria{'-and'}}, { '-or', [ map { \ "FIND_IN_SET('$_', pokemon_moves.versions)" } Vee::Utils::array($p->{move_version}) ] }; |
|---|
| 207 | } |
|---|
| 208 | } |
|---|
| 209 | } |
|---|
| 210 | |
|---|
| 211 | # NUMBERS |
|---|
| 212 | for my $numbar (@StatColumns, qw/height weight/) { |
|---|
| 213 | for my $endpoint (keys %endpoints) { |
|---|
| 214 | my $value = $p->{$numbar . '_' . $endpoint}; |
|---|
| 215 | if ($numbar eq 'height') { |
|---|
| 216 | $value = parse_height($value, $endpoints{$endpoint}{polarity}); |
|---|
| 217 | } elsif ($numbar eq 'weight') { |
|---|
| 218 | $value = parse_weight($value, $endpoints{$endpoint}{polarity}); |
|---|
| 219 | } |
|---|
| 220 | # TODO: umm, make this throw an error for invalid height/weight? |
|---|
| 221 | next unless Vee::Utils::isnum($value) and $value > 0; |
|---|
| 222 | $criteria{"me.$numbar"}{ $endpoints{$endpoint}{operator} } = $value; |
|---|
| 223 | } |
|---|
| 224 | } |
|---|
| 225 | for my $stat (0 .. $#StatColumns) { |
|---|
| 226 | for my $endpoint (keys %endpoints) { |
|---|
| 227 | my $value = $p->{ $StatColumns[$stat] . '_effort_' . $endpoint }; |
|---|
| 228 | next unless Vee::Utils::isnum($value) and $value > 0; |
|---|
| 229 | my $op = $endpoints{$endpoint}{operator}; |
|---|
| 230 | push @{$criteria{'-and'}}, \ "SUBSTR(effort, $stat + 1, 1) $op $value"; |
|---|
| 231 | } |
|---|
| 232 | } |
|---|
| 233 | |
|---|
| 234 | # EVOLUTION STAGE |
|---|
| 235 | if ($p->{evo_stage}) { |
|---|
| 236 | my %evo_stages = map { $_ => 1 } Vee::Utils::array($p->{evo_stage}); |
|---|
| 237 | |
|---|
| 238 | if ($evo_stages{base}) { |
|---|
| 239 | $criteria{'me.evo_parent_id'} = 0; |
|---|
| 240 | } |
|---|
| 241 | |
|---|
| 242 | if ($evo_stages{final}) { |
|---|
| 243 | $joins{descendants} = 1; |
|---|
| 244 | $criteria{'descendants.id'} = undef; |
|---|
| 245 | } |
|---|
| 246 | } |
|---|
| 247 | |
|---|
| 248 | # SORTING |
|---|
| 249 | if ($p->{sort} eq 'stat_avg') { |
|---|
| 250 | $clauses{order_by} = '(' . join(' + ', map { "me.$_" } @StatColumns) . ')'; |
|---|
| 251 | } else { |
|---|
| 252 | $clauses{order_by} = 'me.' . $p->{sort}; |
|---|
| 253 | } |
|---|
| 254 | $clauses{order_by} .= $p->{sort_desc} ? ' DESC' : ' ASC'; |
|---|
| 255 | |
|---|
| 256 | $s->{sql} = SQL::Abstract->new->where(\%criteria) if $c->debug; |
|---|
| 257 | $s->{criteria} = \%criteria; |
|---|
| 258 | |
|---|
| 259 | my $rs = $c->model('DBIC::Pokemon')->search( |
|---|
| 260 | \%criteria, |
|---|
| 261 | { group_by => 'me.id', join => [ keys %joins ], %clauses } |
|---|
| 262 | ); |
|---|
| 263 | |
|---|
| 264 | my @results; |
|---|
| 265 | # presence of a HAVING clause will break COUNT(*) so we have to do this nonsense instead |
|---|
| 266 | if ($clauses{having}) { |
|---|
| 267 | @results = $rs->all; |
|---|
| 268 | $s->{total} = scalar @results; |
|---|
| 269 | @results = @results[$skip .. $#results]; |
|---|
| 270 | @results = @results[0 .. $PAGESIZE - 1] if @results > $PAGESIZE; |
|---|
| 271 | } else { |
|---|
| 272 | @results = $rs->search(undef, { rows => $PAGESIZE, offset => $skip }); |
|---|
| 273 | $s->{total} = $rs->count; |
|---|
| 274 | } |
|---|
| 275 | $s->{results} = \@results; |
|---|
| 276 | } |
|---|
| 277 | |
|---|
| 278 | $s->{page_title } = "Pokémon Search"; |
|---|
| 279 | $s->{link_name } = 'dex/search'; |
|---|
| 280 | $s->{crumbs } = [ |
|---|
| 281 | '<a href="' . $c->uri('Dex') . '">Pokédex</a>', |
|---|
| 282 | '<a href="' . $c->uri('Dex', 'pokemon_list') . '">Pokémon</a>', |
|---|
| 283 | 'Search', |
|---|
| 284 | ]; |
|---|
| 285 | |
|---|
| 286 | $s->{template} = 'dex/search/pokemon.tt'; |
|---|
| 287 | } |
|---|
| 288 | |
|---|
| 289 | =head2 move_search |
|---|
| 290 | |
|---|
| 291 | Search for moves. |
|---|
| 292 | |
|---|
| 293 | =cut |
|---|
| 294 | |
|---|
| 295 | # move search form |
|---|
| 296 | our $move_search_fields = { |
|---|
| 297 | name => { type => 'text', size => 15, title => 'Enter a name or part of a name' }, |
|---|
| 298 | class => { type => 'select', options => [qw/any physical special/], default => 'any' }, |
|---|
| 299 | generation => { type => 'checkbox', options => [ map { [ $_ => $Generations[$_]{games} ] } 0 .. $#Generations ] }, |
|---|
| 300 | type => { type => 'select', options => ['any', @TypeNames], default => 'any' }, |
|---|
| 301 | pokemon => { type => 'text', count => 6, class => 'js-dexsuggest js-dexsuggest-pokemon', title => 'Enter Pokemon that must be able to learn the move' }, |
|---|
| 302 | move_method => { type => 'checkbox', options => { level => 'Level up', machine => 'TM or HM', egg => 'Egg', tutor => 'Move Tutor' }, title => 'Select ways the Pokemon may learn the move' }, |
|---|
| 303 | move_version=> { type => 'checkbox', options => [ map { [ $_ => $Icons{$_} ] } qw/rb y gs c rusa frlg e dp/ ], title => 'Select ways the Pokemon may learn the move' }, |
|---|
| 304 | |
|---|
| 305 | view => { type => 'select', options => [[ list => 'boring list' ], [ table => 'detailed table' ], [ contest => 'contest table' ]], title => 'Select how you want the results displayed' }, |
|---|
| 306 | sort => { type => 'select', options => [qw/name id/], default => 'name', title => 'Select how you want the results ordered' }, |
|---|
| 307 | sort_desc => { type => 'checkbox', title => 'Check this for descending order' }, |
|---|
| 308 | }; |
|---|
| 309 | |
|---|
| 310 | for my $col (qw/power accuracy pp effect_chance priority/) { |
|---|
| 311 | for my $endpoint (keys %endpoints) { |
|---|
| 312 | $move_search_fields->{ $col . '_' . $endpoint } = { type => 'text', size => 4, maxlength => 3, title => "\u$endpoints{$endpoint}{english} $col" }; |
|---|
| 313 | } |
|---|
| 314 | } |
|---|
| 315 | |
|---|
| 316 | sub move_search : Path('moves/search') : Args(0) { |
|---|
| 317 | my ($self, $c) = @_; |
|---|
| 318 | my $s = $c->stash; |
|---|
| 319 | my $p = $c->req->params; |
|---|
| 320 | my $skip = $p->{skip} || 0; |
|---|
| 321 | $s->{before} = {%{ $c->req->params }}; |
|---|
| 322 | |
|---|
| 323 | my $form = $s->{form} = Vee::Form->new( |
|---|
| 324 | id => 'move_search', |
|---|
| 325 | fields => $move_search_fields, |
|---|
| 326 | params => $c->req->params, |
|---|
| 327 | ); |
|---|
| 328 | |
|---|
| 329 | if ($form->submitted) { |
|---|
| 330 | my %criteria; |
|---|
| 331 | $criteria{'-and'} = [ { 'me.id' => { '<=', $Generations[-1]{maxmoveid} } } ]; |
|---|
| 332 | my (%joins, %clauses); |
|---|
| 333 | |
|---|
| 334 | # BASIC |
|---|
| 335 | if ($p->{name}) { |
|---|
| 336 | my $name = $p->{name}; |
|---|
| 337 | if ($name =~ /[*?]/) { |
|---|
| 338 | $name =~ tr/*?/%_/; |
|---|
| 339 | $criteria{'me.name'} = { like => $name }; |
|---|
| 340 | } else { |
|---|
| 341 | $criteria{'me.name'} = { like => "%$name%" }; |
|---|
| 342 | } |
|---|
| 343 | } |
|---|
| 344 | if ($p->{class} ne 'any') { $criteria{class} = $p->{class} } |
|---|
| 345 | |
|---|
| 346 | # TYPE |
|---|
| 347 | if ($p->{type} ne 'any') { $criteria{type} = $p->{type} } |
|---|
| 348 | |
|---|
| 349 | |
|---|
| 350 | # GENERATIONS |
|---|
| 351 | if (defined $p->{generation}) { |
|---|
| 352 | my $gen_ref = []; |
|---|
| 353 | for my $gen (Vee::Utils::array($p->{generation})) { |
|---|
| 354 | push @$gen_ref, { '-between' => [ ($gen > 0 ? $Generations[$gen - 1]{maxmoveid} : 0) + 1, $Generations[$gen]{maxmoveid} ] }; |
|---|
| 355 | } |
|---|
| 356 | push @{$criteria{'-and'}}, { 'me.id' => $gen_ref }; |
|---|
| 357 | } |
|---|
| 358 | |
|---|
| 359 | # POKEMON |
|---|
| 360 | if (ref $p->{pokemon} eq 'ARRAY') { |
|---|
| 361 | my @pokemonids; |
|---|
| 362 | for my $pokemon (@{ $p->{pokemon} }) { |
|---|
| 363 | # XXX: show some message if a pokemon is invalid? |
|---|
| 364 | my $pokemonid = get_pokemon($pokemon); |
|---|
| 365 | push @pokemonids, $pokemonid if defined $pokemonid; |
|---|
| 366 | } |
|---|
| 367 | if (@pokemonids) { |
|---|
| 368 | $joins{pokemon_moves} = 1; |
|---|
| 369 | $criteria{'pokemon_moves.pokemon_id'} = \@pokemonids; |
|---|
| 370 | $clauses{having}{'COUNT(DISTINCT pokemon_moves.pokemon_id, pokemon_moves.move_id)'} = scalar @pokemonids; |
|---|
| 371 | |
|---|
| 372 | if ($p->{move_method}) { |
|---|
| 373 | $criteria{'pokemon_moves.method'} = $p->{move_method}; |
|---|
| 374 | } |
|---|
| 375 | if ($p->{move_version}) { |
|---|
| 376 | push @{$criteria{'-and'}}, { '-or', [ map { \ "FIND_IN_SET('$_', pokemon_moves.versions)" } Vee::Utils::array($p->{move_version}) ] }; |
|---|
| 377 | } |
|---|
| 378 | } |
|---|
| 379 | } |
|---|
| 380 | |
|---|
| 381 | # NUMBERS |
|---|
| 382 | for my $numbar (qw/power accuracy pp effect_chance priority/) { |
|---|
| 383 | for my $endpoint (keys %endpoints) { |
|---|
| 384 | my $value = $p->{$numbar . '_' . $endpoint}; |
|---|
| 385 | next unless Vee::Utils::isnum($value) and ($numbar eq 'priority' or $value > 0); |
|---|
| 386 | |
|---|
| 387 | my $column = $numbar; |
|---|
| 388 | if ($numbar eq 'priority') { |
|---|
| 389 | $joins{effect} = 1; |
|---|
| 390 | $column = 'effect.' . $numbar; |
|---|
| 391 | } |
|---|
| 392 | $criteria{$column}{ $endpoints{$endpoint}{operator} } = $value; |
|---|
| 393 | } |
|---|
| 394 | } |
|---|
| 395 | |
|---|
| 396 | # SORTING |
|---|
| 397 | $clauses{order_by} = 'me.' . $p->{sort} . ' '; |
|---|
| 398 | $clauses{order_by} .= $criteria{sort_desc} ? 'DESC' : 'ASC'; |
|---|
| 399 | |
|---|
| 400 | if ($p->{view} eq 'contest') { |
|---|
| 401 | push @{ $clauses{prefetch} }, 'contest'; |
|---|
| 402 | } else { |
|---|
| 403 | $clauses{columns} = ['me.id']; |
|---|
| 404 | } |
|---|
| 405 | |
|---|
| 406 | $s->{sql} = SQL::Abstract->new->where(\%criteria) if $c->debug; |
|---|
| 407 | $s->{criteria} = \%criteria; |
|---|
| 408 | |
|---|
| 409 | my $rs = $c->model('DBIC::Moves')->search( |
|---|
| 410 | \%criteria, |
|---|
| 411 | { order_by => 'me.name ASC', group_by => 'me.id', join => [ keys %joins ], %clauses } |
|---|
| 412 | ); |
|---|
| 413 | |
|---|
| 414 | my @results; |
|---|
| 415 | # presence of a HAVING clause will break COUNT(*) so we have to do this nonsense instead |
|---|
| 416 | if ($clauses{having}) { |
|---|
| 417 | @results = $rs->all; |
|---|
| 418 | $s->{total} = scalar @results; |
|---|
| 419 | @results = @results[$skip .. $#results]; |
|---|
| 420 | @results = @results[0 .. 49] if @results > 50; |
|---|
| 421 | } else { |
|---|
| 422 | @results = $rs->search(undef, { rows => 50, offset => $skip }); |
|---|
| 423 | $s->{total} = $rs->count; |
|---|
| 424 | } |
|---|
| 425 | $s->{results} = \@results; |
|---|
| 426 | } |
|---|
| 427 | |
|---|
| 428 | $s->{page_title } = "Move Search"; |
|---|
| 429 | $s->{link_name } = 'dex/movesearch'; |
|---|
| 430 | $s->{crumbs } = [ |
|---|
| 431 | '<a href="' . $c->uri('Dex') . '">Pokédex</a>', |
|---|
| 432 | '<a href="' . $c->uri('Dex', 'move_list') . '">Moves</a>', |
|---|
| 433 | 'Search', |
|---|
| 434 | ]; |
|---|
| 435 | |
|---|
| 436 | $s->{template} = 'dex/search/moves.tt'; |
|---|
| 437 | } |
|---|
| 438 | |
|---|
| 439 | =head2 parse_height($height, $polarity) |
|---|
| 440 | |
|---|
| 441 | Figure out what a height is supposed to be and convert it to decimeters. |
|---|
| 442 | |
|---|
| 443 | C<$polarity> is either -1 (if this is a lower bound) or 1 (if this is an upper |
|---|
| 444 | bound), and is used to add an error margin to Imperial heights to compensate for |
|---|
| 445 | the database's not storing exact numbers of inches. |
|---|
| 446 | |
|---|
| 447 | =cut |
|---|
| 448 | |
|---|
| 449 | sub parse_height { |
|---|
| 450 | my ($input, $polarity) = @_; |
|---|
| 451 | $input =~ s/^\s+|\s+$//g; |
|---|
| 452 | return if not $input; |
|---|
| 453 | |
|---|
| 454 | my $number = qr/ (?: \d* \.)? \d+ /x; |
|---|
| 455 | if ($input =~ /^ ($number) \s* m $/x) { |
|---|
| 456 | return $1 * 10; |
|---|
| 457 | } elsif ($input =~ /^ ($number) \s* dm $/x) { |
|---|
| 458 | return $1; |
|---|
| 459 | } elsif ($input =~ /^ ($number) \s* cm $/x) { |
|---|
| 460 | return $1 / 10; |
|---|
| 461 | } elsif ($input =~ /^ (?: ($number) \s* (?:ft|'))? \s* (?: ($number) \s* (?:in|")?)? $/x) { |
|---|
| 462 | # NOTE: with the above regex, just a number will give inches |
|---|
| 463 | return +(($1 || 0) * 12 + ($2 || 0) + $polarity * 0.5) * 0.254; |
|---|
| 464 | } else { |
|---|
| 465 | # no idea, sorry |
|---|
| 466 | return; |
|---|
| 467 | } |
|---|
| 468 | } |
|---|
| 469 | |
|---|
| 470 | =head2 parse_weight($height, $polarity) |
|---|
| 471 | |
|---|
| 472 | Figure out what a weight is supposed to be and convert it to kilograms. |
|---|
| 473 | |
|---|
| 474 | C<$polarity> is either -1 (if this is a lower bound) or 1 (if this is an upper |
|---|
| 475 | bound), and is used to add an error margin to Imperial weights to compensate for |
|---|
| 476 | the database's not storing exact numbers of ounces. |
|---|
| 477 | |
|---|
| 478 | =cut |
|---|
| 479 | |
|---|
| 480 | sub parse_weight { |
|---|
| 481 | my ($input, $polarity) = @_; |
|---|
| 482 | $input =~ s/^\s+|\s+$//g; |
|---|
| 483 | return if not $input; |
|---|
| 484 | |
|---|
| 485 | my $number = qr/ (?: \d* \.)? \d+ /x; |
|---|
| 486 | if ($input =~ /^ ($number) \s* kg $/x) { |
|---|
| 487 | return $1; |
|---|
| 488 | } elsif ($input =~ /^ ($number) \s* gm? $/x) { |
|---|
| 489 | return $1 / 1000; |
|---|
| 490 | } elsif ($input =~ /^ (?: ($number) \s* (?:lbs?)?)? \s* (?: ($number) \s* oz)? $/x) { |
|---|
| 491 | # NOTE: with the above regex, just a number will give pounds |
|---|
| 492 | return +(($1 || 0) * 16 + ($2 || 0) + $polarity * 0.5) * 0.02835; |
|---|
| 493 | } else { |
|---|
| 494 | # no idea, sorry |
|---|
| 495 | return; |
|---|
| 496 | } |
|---|
| 497 | } |
|---|
| 498 | |
|---|
| 499 | =head1 AUTHOR |
|---|
| 500 | |
|---|
| 501 | Maintainer: Alex "Eevee" Munroe (C<veekun@veekun.com>) |
|---|
| 502 | |
|---|
| 503 | See the included F<AUTHORS> file for a full list of contributers. |
|---|
| 504 | |
|---|
| 505 | =head1 LICENSE |
|---|
| 506 | |
|---|
| 507 | See the included F<LICENSE> file. |
|---|
| 508 | |
|---|
| 509 | =cut |
|---|
| 510 | |
|---|
| 511 | 1; |
|---|