Update for current pokedex as of 2012-06-12
[zzz-dywypi.git] / plugins / Pokedex / plugin.py
1 # encoding: utf8
2 ###
3 # Copyright (c) 2010, Alex Munroe
4 # All rights reserved.
5 #
6 # Redistribution and use in source and binary forms, with or without
7 # modification, are permitted provided that the following conditions are met:
8 #
9 # * Redistributions of source code must retain the above copyright notice,
10 # this list of conditions, and the following disclaimer.
11 # * Redistributions in binary form must reproduce the above copyright notice,
12 # this list of conditions, and the following disclaimer in the
13 # documentation and/or other materials provided with the distribution.
14 # * Neither the name of the author of this software nor the name of
15 # contributors to this software may be used to endorse or promote products
16 # derived from this software without specific prior written consent.
17 #
18 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
22 # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23 # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24 # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25 # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26 # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27 # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28 # POSSIBILITY OF SUCH DAMAGE.
29
30 ###
31
32 import supybot.conf as conf
33 import supybot.utils as utils
34 from supybot.commands import *
35 import supybot.plugins as plugins
36 import supybot.ircutils as ircutils
37 import supybot.callbacks as callbacks
38
39 import pokedex.db
40 import pokedex.db.tables as tables
41 import pokedex.lookup
42
43 import urllib
44
45
46 def get_stat_color(stat):
47 if stat < 41:
48 return 4 # red
49 elif stat < 52:
50 return 7 # orange
51 elif stat < 61:
52 return 8 # yellow
53 elif stat < 71:
54 return 9 # green
55 elif stat < 85:
56 return 11 # cyan
57 elif stat < 100:
58 return 12 # blue
59 else:
60 return 13 # purple
61
62
63 class Pokedex(callbacks.Plugin):
64 """Add the help for "@plugin help Pokedex" here
65 This should describe *how* to use this plugin."""
66 def __init__(self, irc):
67 self.__parent = super(Pokedex, self)
68 self.__parent.__init__(irc)
69 self.db = pokedex.db.connect(self.registryValue('databaseURL'))
70 self.lookup = pokedex.lookup.PokedexLookup(
71 directory=conf.supybot.directories.data.dirize('pokedex-index'),
72 session=self.db,
73 )
74
75 def pokedex(self, irc, msg, args, thing):
76 """<thing...>
77
78 Looks up <thing> in the veekun Pokédex."""
79
80 # Fix encoding. Sigh.
81 if not isinstance(thing, unicode):
82 ascii_thing = thing
83 try:
84 thing = ascii_thing.decode('utf8')
85 except UnicodeDecodeError:
86 thing = ascii_thing.decode('latin1')
87
88 # Similar logic to the site, here.
89 results = self.lookup.lookup(thing)
90
91 # Nothing found
92 if len(results) == 0:
93 self._reply(irc, "I don't know what that is.")
94 return
95
96 # Multiple matches; propose them all
97 if len(results) > 1:
98 if results[0].exact:
99 reply = "Are you looking for"
100 else:
101 reply = "Did you mean"
102
103 # For exact name matches with multiple results, use type prefixes
104 # (item:Metronome). For anything else, omit them
105 use_prefixes = (results[0].exact
106 and '*' not in thing
107 and '?' not in thing)
108
109 result_strings = []
110 for result in results:
111 result_string = result.object.name
112
113 # Prepend, e.g., pokemon: if necessary
114 if use_prefixes:
115 # Table classes know their singular names
116 prefix = result.object.__singlename__
117 result_string = prefix + ':' + result_string
118
119 # Identify foreign language names
120 if result.language:
121 result_string += u""" ({0}: {1})""".format(
122 result.iso3166, result.name)
123
124 result_strings.append(result_string)
125
126 self._reply(irc, u"{0}: {1}?".format(reply, '; '.join(result_strings)))
127 return
128
129 # If we got here, there's an exact match; hurrah!
130 result = results[0]
131 obj = result.object
132
133 # Deal with Pokémon shenanigans
134 if isinstance(obj, tables.PokemonForm):
135 obj = obj.pokemon
136 elif isinstance(obj, tables.PokemonSpecies):
137 obj = obj.default_pokemon
138
139 if isinstance(obj, tables.Pokemon):
140 reply_template = \
141 u"""#{id} {name}, {type}-type Pokémon. Has {abilities}. """ \
142 """{stats}. """ \
143 """http://veekun.com/dex/pokemon/{link_name}"""
144
145 if not obj.is_default:
146 # Can't use urllib.quote() on the whole thing or it'll
147 # catch "?" and "=" where it shouldn't.
148 # XXX Also we need to pass urllib.quote() things explicitly
149 # encoded as utf8 or else we get a UnicodeEncodeError.
150 link_name = '{name}?form={form}'.format(
151 name=urllib.quote(obj.species.name.lower().encode('utf8')),
152 form=urllib.quote(obj.default_form.form_identifier.lower().encode('utf8')),
153 )
154 else:
155 link_name = urllib.quote(obj.species.name.lower().encode('utf8'))
156
157 # a/b/c/d/e/f sucks. Put stats in a more readable format.
158 # Also, color-code them by good-osity.
159 colored_stats = []
160 stat_total = 0
161 for pokemon_stat in obj.stats:
162 base_stat = pokemon_stat.base_stat
163 stat_total += base_stat
164 color = get_stat_color(base_stat)
165
166 colored_stats.append(
167 "\x03{0:02d}{1}\x0f".format(color, base_stat)
168 )
169
170 colored_stat_total = "\x03{0:02d}{1}\x0f".format(
171 get_stat_color(stat_total / 6),
172 stat_total,
173 )
174 stats = """{0} HP, {1}/{2} phys, {3}/{4} spec, {5} speed; {total} total""" \
175 .format(*colored_stats, total=colored_stat_total)
176 self._reply(irc, reply_template.format(
177 id=obj.species.id,
178 name=obj.name,
179 type='/'.join(_.name for _ in obj.types),
180 abilities=' or '.join(_.name for _ in obj.abilities),
181 stats=stats,
182 link_name=link_name,
183 )
184 )
185
186 elif isinstance(obj, tables.Move):
187 reply_template = \
188 u"""{name}, {type}-type {damage_class} move. """ \
189 """{power} power; {accuracy}% accuracy; {pp} PP. """ \
190 """{effect} """ \
191 """http://veekun.com/dex/moves/{link_name}"""
192 self._reply(irc, reply_template.format(
193 name=obj.name,
194 type=obj.type.name,
195 damage_class=obj.damage_class.name,
196 power=obj.power,
197 accuracy=obj.accuracy,
198 pp=obj.pp,
199 effect=unicode(obj.short_effect.as_text()),
200 link_name=urllib.quote(obj.name.lower().encode('utf8')),
201 )
202 )
203
204 elif isinstance(obj, tables.Type):
205 reply_template = u"""{name}, a type. """
206
207 offensive_reply_factors = {
208 200: u'\x03092×\x0f',
209 50: u'\x0304½×\x0f',
210 0: u'\x03140×\x0f',
211 }
212
213 offensive_modifiers = {}
214 for matchup in obj.damage_efficacies:
215 if matchup.damage_factor != 100:
216 offensive_modifiers.setdefault(matchup.damage_factor, []) \
217 .append(matchup.target_type.name)
218 if offensive_modifiers:
219 reply_template += u"""{offensive_modifiers}. """
220 for factor in offensive_modifiers:
221 offensive_modifiers[factor] = u'{factor} against {types}'.format(
222 factor=offensive_reply_factors[factor],
223 types=', '.join(sorted(offensive_modifiers[factor]))
224 )
225
226 defensive_reply_factors = {
227 200: u'\x03042×\x0f',
228 50: u'\x0309½×\x0f',
229 0: u'\x03110×\x0f',
230 }
231
232 defensive_modifiers = {}
233 for matchup in obj.target_efficacies:
234 if matchup.damage_factor != 100:
235 defensive_modifiers.setdefault(matchup.damage_factor, []) \
236 .append(matchup.damage_type.name)
237 if defensive_modifiers:
238 reply_template += u"""{defensive_modifiers}. """
239 for factor in defensive_modifiers:
240 defensive_modifiers[factor] = u'{factor} from {types}'.format(
241 factor=defensive_reply_factors[factor],
242 types=', '.join(sorted(defensive_modifiers[factor]))
243 )
244
245 reply_template += u"""http://veekun.com/dex/types/{link_name}"""
246
247 self._reply(irc, reply_template.format(
248 name=obj.name.capitalize(),
249 offensive_modifiers='; '.join(offensive_modifiers[_]
250 for _ in sorted(offensive_modifiers)),
251 defensive_modifiers='; '.join(defensive_modifiers[_]
252 for _ in sorted(defensive_modifiers)),
253 link_name=urllib.quote(obj.name.lower().encode('utf8')),
254 )
255 )
256
257 elif isinstance(obj, tables.Item):
258 reply_template = \
259 u"""{name}, an item. {effect} """ \
260 """http://veekun.com/dex/items/{link_pocket}/{link_name}"""
261
262 effect = obj.short_effect
263 if effect is None:
264 effect = 'No short effect, sorry. :('
265 else:
266 effect = unicode(effect.as_text())
267
268 self._reply(irc, reply_template.format(
269 name=obj.name,
270 effect=effect,
271 link_pocket=urllib.quote(obj.pocket.name.lower().encode('utf8')),
272 link_name=urllib.quote(obj.name.lower().encode('utf8')),
273 )
274 )
275
276 elif isinstance(obj, tables.Ability):
277 reply_template = \
278 u"""{name}, an ability. {effect} """ \
279 """http://veekun.com/dex/abilities/{link_name}"""
280 self._reply(irc, reply_template.format(
281 name=obj.name,
282 effect=obj.short_effect.as_text(),
283 link_name=urllib.quote(obj.name.lower().encode('utf8')),
284 )
285 )
286
287 elif isinstance(obj, tables.Nature):
288 reply_template = \
289 u"""{name}, a nature. """ \
290 u"""Raises \x0303{up}\x0f, lowers \x0304{down}\x0f. """ \
291 u"""http://veekun.com/dex/natures/{link_name}"""
292 self._reply(irc, reply_template.format(
293 name=obj.name,
294 up=obj.increased_stat.name,
295 down=obj.decreased_stat.name,
296 link_name=urllib.quote(obj.name.lower().encode('utf8')),
297 )
298 )
299
300 else:
301 # This can only happen if lookup.py is upgraded and we are not
302 self._reply(irc, "Uhh.. I found that, but I don't know what it is. :(")
303
304 pokedex = wrap(pokedex, [rest('something')])
305
306 def _reply(self, irc, response):
307 """Wraps irc.reply() to do some Unicode decoding."""
308 if isinstance(response, str):
309 irc.reply(response)
310 else:
311 irc.reply(response.encode('utf8'))
312
313
314 Class = Pokedex
315
316
317 # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: