root/veekun/trunk/lib/Vee/Bot.pm

Revision 406, 7.7 KB (checked in by eevee, 22 months ago)

Database refactoring. Renamed columns and tables to be more consistent and more readable. (#58)

Line 
1package Vee::Bot;
2
3use strict;
4use warnings;
5
6use YAML;
7use Vee::Schema;
8use Vee::Dex;
9
10# initialize database stuff, being careful not to get caught by reload
11our $schema;
12our $conf;
13unless (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
22our %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
33sub 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
56sub 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
86sub 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
188sub 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
236sub do_eval {
237    my ($code) = @_;
238
239    return "Lol no.";
240}
241
242# ------------------------------------------------------------------------------
243# Reloader
244
245sub 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
272sub REPLACEME {
273    warn shift;
274}
275
276
2771;
Note: See TracBrowser for help on using the browser.