| 1 | package Vee::Bot; |
|---|
| 2 | |
|---|
| 3 | use strict; |
|---|
| 4 | use warnings; |
|---|
| 5 | |
|---|
| 6 | use YAML; |
|---|
| 7 | use Vee::Schema; |
|---|
| 8 | use Vee::Dex; |
|---|
| 9 | |
|---|
| 10 | # initialize database stuff, being careful not to get caught by reload |
|---|
| 11 | our $schema; |
|---|
| 12 | our $conf; |
|---|
| 13 | unless (defined $schema) { |
|---|
| 14 | $conf = YAML::LoadFile('vee.yml'); |
|---|
| 15 | $schema = Vee::Schema->connect( @{ $conf->{'Model::DBIC'}{connect_info} }[0 .. 2] ); |
|---|
| 16 | REPLACEME("Connected to database"); |
|---|
| 17 | Vee::Dex::initialize($schema); |
|---|
| 18 | REPLACEME("Loaded Pokedex variables"); |
|---|
| 19 | } |
|---|
| 20 | |
|---|
| 21 | # TODO: regexes if needed |
|---|
| 22 | our %command_map = ( |
|---|
| 23 | reload => \&do_reload, |
|---|
| 24 | dex => \&do_pokedex, |
|---|
| 25 | ivs => \&do_ivcalc, |
|---|
| 26 | eval => \&do_eval, |
|---|
| 27 | can => \&do_can, |
|---|
| 28 | ); |
|---|
| 29 | |
|---|
| 30 | # ------------------------------------------------------------------------------ |
|---|
| 31 | |
|---|
| 32 | # Dispatch to various other subs, depending on what input we just got |
|---|
| 33 | sub dispatcher { |
|---|
| 34 | my ($in) = @_; |
|---|
| 35 | |
|---|
| 36 | my ($command, $rest) = split /\s+/, $in, 2; |
|---|
| 37 | my $reply; |
|---|
| 38 | |
|---|
| 39 | return unless $command_map{$command}; |
|---|
| 40 | |
|---|
| 41 | my $ret = eval { |
|---|
| 42 | return $command_map{$command}->($rest); |
|---|
| 43 | }; |
|---|
| 44 | |
|---|
| 45 | if ($@) { |
|---|
| 46 | warn $@; |
|---|
| 47 | return "Flagrant error! Sorry."; |
|---|
| 48 | } |
|---|
| 49 | |
|---|
| 50 | return $ret; |
|---|
| 51 | } |
|---|
| 52 | |
|---|
| 53 | # ------------------------------------------------------------------------------ |
|---|
| 54 | # Pokedex lookup |
|---|
| 55 | |
|---|
| 56 | sub do_pokedex { |
|---|
| 57 | my ($entry) = @_; |
|---|
| 58 | |
|---|
| 59 | my $hashref = $FuzzyMatches{ lc $entry }; |
|---|
| 60 | |
|---|
| 61 | if (not $hashref) { |
|---|
| 62 | return "No Pokedex entry for $entry!"; |
|---|
| 63 | } |
|---|
| 64 | |
|---|
| 65 | if ($hashref->{type} eq 'pokemon') { |
|---|
| 66 | my $pokemon = $schema->resultset('Pokemon')->find($hashref->{id}); |
|---|
| 67 | return |
|---|
| 68 | sprintf "Pokémon number %d, %s. Type: %s. Abilities: %s.", |
|---|
| 69 | $pokemon->id, $pokemon->name, $pokemon->type1 . ($pokemon->type2 && '/'.$pokemon->type2), |
|---|
| 70 | join(' and ', map { $_->name } $pokemon->abilities); |
|---|
| 71 | |
|---|
| 72 | } elsif ($hashref->{type} eq 'ability') { |
|---|
| 73 | my $ability = $schema->resultset('Abilities')->find($hashref->{id}); |
|---|
| 74 | return sprintf "%s: %s", |
|---|
| 75 | $ability->name, $ability->description; |
|---|
| 76 | |
|---|
| 77 | } else { |
|---|
| 78 | return "$hashref->{name} is $hashref->{type} number $hashref->{id}."; |
|---|
| 79 | } |
|---|
| 80 | } |
|---|
| 81 | |
|---|
| 82 | # ------------------------------------------------------------------------------ |
|---|
| 83 | # Little IV calculator |
|---|
| 84 | # Code stolen and simplified from V::C::Dex::Utils |
|---|
| 85 | |
|---|
| 86 | sub do_ivcalc { |
|---|
| 87 | my ($entry) = @_; |
|---|
| 88 | |
|---|
| 89 | my ($nature, $pokemon_name, $level, $stats) = ($entry =~ /^ |
|---|
| 90 | for \s+ |
|---|
| 91 | (\w+) \s+ |
|---|
| 92 | (.+?) \s+ |
|---|
| 93 | at \s+ |
|---|
| 94 | l(?: evel \s+ )? |
|---|
| 95 | (\d+) \s+ |
|---|
| 96 | with |
|---|
| 97 | ( (?: \s+ \d+ (?: \+ \d+ )? ){6} ) |
|---|
| 98 | $/ix); |
|---|
| 99 | |
|---|
| 100 | if (not defined $nature) { |
|---|
| 101 | # didn't match |
|---|
| 102 | return "Syntax for that command is 'ivs for {nature} {pokemon} " . |
|---|
| 103 | "at level {level} with {hp} {atk} {def} {spatk} {spdef} {spd}'. " . |
|---|
| 104 | "You may specify EVs as +{effort} immediately after a stat."; |
|---|
| 105 | } |
|---|
| 106 | |
|---|
| 107 | # I cheat here and use the special ' ' delimeter to avoid the leading empty |
|---|
| 108 | # string I'd get otherwise. It IS documented, trust me. |
|---|
| 109 | my (@stats, @evs); |
|---|
| 110 | for my $stat_string (split ' ', $stats) { |
|---|
| 111 | my ($iv, $ev) = split /\+/, $stat_string; |
|---|
| 112 | push @stats, $iv; |
|---|
| 113 | push @evs, $ev || 0; |
|---|
| 114 | } |
|---|
| 115 | |
|---|
| 116 | my $pokemon_id = get_pokemon($pokemon_name) |
|---|
| 117 | or return "No such Pokemon."; |
|---|
| 118 | my $pokemon = $schema->resultset('Pokemon')->find($pokemon_id); |
|---|
| 119 | |
|---|
| 120 | if ($nature ne 'neutral' and not exists $Natures{$nature}) { |
|---|
| 121 | return "No such nature."; |
|---|
| 122 | } |
|---|
| 123 | if ($level < 1 or $level > 100 or $level ne int $level) { |
|---|
| 124 | return "Invalid level."; |
|---|
| 125 | } |
|---|
| 126 | |
|---|
| 127 | # Actual calculation past here... |
|---|
| 128 | |
|---|
| 129 | my @nature_changes = (1) x @StatColumns; |
|---|
| 130 | if (defined $Natures{$nature}) { |
|---|
| 131 | $nature_changes[ $Natures{$nature}{up} ] = 1.1; |
|---|
| 132 | $nature_changes[ $Natures{$nature}{down} ] = 0.9; |
|---|
| 133 | } |
|---|
| 134 | |
|---|
| 135 | my @ivs; |
|---|
| 136 | for my $s (0 .. $#StatColumns) { |
|---|
| 137 | my $stat = $StatColumns[$s]; |
|---|
| 138 | my $base = $pokemon->$stat; |
|---|
| 139 | my $func = ($stat =~ /hp/) ? \&ingame_hp : \&ingame_stats; |
|---|
| 140 | |
|---|
| 141 | # This is longer but faster; grab just the endpoints |
|---|
| 142 | my ($min, $max); |
|---|
| 143 | for my $iv (0 .. 31) { |
|---|
| 144 | my $calculated = $func->($base, $level, $iv, $evs[$s]); |
|---|
| 145 | $calculated = int( $calculated * $nature_changes[$s] ); |
|---|
| 146 | if ($calculated == $stats[$s]) { |
|---|
| 147 | $min = $iv; |
|---|
| 148 | last; |
|---|
| 149 | } |
|---|
| 150 | } |
|---|
| 151 | if (not defined $min) { |
|---|
| 152 | $ivs[$s] = 'impossible'; |
|---|
| 153 | next; |
|---|
| 154 | } |
|---|
| 155 | |
|---|
| 156 | for my $iv (reverse $min .. 31) { |
|---|
| 157 | my $calculated = $func->($base, $level, $iv, $evs[$s]); |
|---|
| 158 | $calculated = int( $calculated * $nature_changes[$s] ); |
|---|
| 159 | if ($calculated == $stats[$s]) { |
|---|
| 160 | $max = $iv; |
|---|
| 161 | last; |
|---|
| 162 | } |
|---|
| 163 | } |
|---|
| 164 | if (not defined $max) { |
|---|
| 165 | $ivs[$s] = 'impossible'; |
|---|
| 166 | next; |
|---|
| 167 | } |
|---|
| 168 | |
|---|
| 169 | if ($min == $max) { |
|---|
| 170 | $ivs[$s] = $min; |
|---|
| 171 | } else { |
|---|
| 172 | $ivs[$s] = [ $min, $max ]; |
|---|
| 173 | } |
|---|
| 174 | } |
|---|
| 175 | |
|---|
| 176 | for my $s (0 .. $#StatColumns) { |
|---|
| 177 | my $display = ref $ivs[$s] ? "$ivs[$s][0] - $ivs[$s][1]" : $ivs[$s]; |
|---|
| 178 | $ivs[$s] = "$StatShortNames[$s]: $display"; |
|---|
| 179 | } |
|---|
| 180 | |
|---|
| 181 | return join ' | ', @ivs; |
|---|
| 182 | } |
|---|
| 183 | |
|---|
| 184 | # ------------------------------------------------------------------------------ |
|---|
| 185 | # Test if a Pokemon can get a move |
|---|
| 186 | # can {pokemon} [learn] {move} |
|---|
| 187 | |
|---|
| 188 | sub do_can { |
|---|
| 189 | my ($entry) = @_; |
|---|
| 190 | |
|---|
| 191 | my ($pokemon_in, $move_in, $ver) = ($entry =~ /^(.+) learn (.+?)(?: in (\w+))?$/); |
|---|
| 192 | my $pokemon_id = get_pokemon($pokemon_in); |
|---|
| 193 | return "'$pokemon_in' is not a Pokemon." if not defined $pokemon_id; |
|---|
| 194 | return $$pokemon_id if ref $pokemon_id; |
|---|
| 195 | my $move_id = get_move($move_in); |
|---|
| 196 | return "'$move_in' is not a move." if not defined $move_id; |
|---|
| 197 | return $$move_id if ref $move_id; |
|---|
| 198 | |
|---|
| 199 | # lol validation? |
|---|
| 200 | if ($ver) { |
|---|
| 201 | $ver =~ tr/a-z//cd; |
|---|
| 202 | } else { |
|---|
| 203 | $ver = 'dp'; |
|---|
| 204 | } |
|---|
| 205 | |
|---|
| 206 | my @pm = $schema->resultset('PokemonMoves')->search({ |
|---|
| 207 | pokemon_id => $pokemon_id, |
|---|
| 208 | move_id => $move_id, |
|---|
| 209 | -nest => \ "FIND_IN_SET('$ver', versions)", |
|---|
| 210 | }); |
|---|
| 211 | |
|---|
| 212 | if (@pm) { |
|---|
| 213 | my %pm_map = map { $_->method => $_ } @pm; |
|---|
| 214 | my @methods; |
|---|
| 215 | if ($pm_map{level}) { |
|---|
| 216 | push @methods, 'at level ' . $pm_map{level}->level; |
|---|
| 217 | } |
|---|
| 218 | if ($pm_map{egg}) { |
|---|
| 219 | push @methods, 'via breeding'; |
|---|
| 220 | } |
|---|
| 221 | if ($pm_map{machine}) { |
|---|
| 222 | push @methods, 'from ' . tm_name( $MoveTMs{$move_id}[3] ); |
|---|
| 223 | } |
|---|
| 224 | if ($pm_map{tutor}) { |
|---|
| 225 | push @methods, 'from a move tutor'; |
|---|
| 226 | } |
|---|
| 227 | return 'Yes: ' . join(', or ', @methods) . '.'; |
|---|
| 228 | } |
|---|
| 229 | |
|---|
| 230 | return "No."; |
|---|
| 231 | } |
|---|
| 232 | |
|---|
| 233 | # ------------------------------------------------------------------------------ |
|---|
| 234 | # Arbitrary code evaluation -- ENABLE WITH CAUTION |
|---|
| 235 | |
|---|
| 236 | sub do_eval { |
|---|
| 237 | my ($code) = @_; |
|---|
| 238 | |
|---|
| 239 | return "Lol no."; |
|---|
| 240 | } |
|---|
| 241 | |
|---|
| 242 | # ------------------------------------------------------------------------------ |
|---|
| 243 | # Reloader |
|---|
| 244 | |
|---|
| 245 | sub do_reload { |
|---|
| 246 | # muffle redef warnings |
|---|
| 247 | local $SIG{__WARN__} = sub { |
|---|
| 248 | warn "$_[0]" unless $_[0] =~ /^Subroutine .+ redefined/; |
|---|
| 249 | }; |
|---|
| 250 | |
|---|
| 251 | # pretend we haven't seen ourselves and require ourselves |
|---|
| 252 | (my $filename = __PACKAGE__) =~ s|::|/|g; |
|---|
| 253 | $filename .= '.pm'; |
|---|
| 254 | delete $INC{$filename}; |
|---|
| 255 | require $filename; |
|---|
| 256 | |
|---|
| 257 | # count the number of reloaded subroutines... here be dragons |
|---|
| 258 | my $pkg_ref; |
|---|
| 259 | { |
|---|
| 260 | no strict 'refs'; |
|---|
| 261 | $pkg_ref = \%{__PACKAGE__ . '::'}; |
|---|
| 262 | } |
|---|
| 263 | my $sub_ct = grep { |
|---|
| 264 | /^do_/ and defined *{ $pkg_ref->{$_} }{CODE} |
|---|
| 265 | } keys %$pkg_ref; |
|---|
| 266 | # end dragons |
|---|
| 267 | |
|---|
| 268 | return "Reloaded $sub_ct command@{[ $sub_ct > 1 && 's' ]}."; |
|---|
| 269 | } |
|---|
| 270 | |
|---|
| 271 | # this needs to be a real logger or something |
|---|
| 272 | sub REPLACEME { |
|---|
| 273 | warn shift; |
|---|
| 274 | } |
|---|
| 275 | |
|---|
| 276 | |
|---|
| 277 | 1; |
|---|