Pokedex: Update for the new Pokémon form schema.
[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 form matches
134 if isinstance(obj, tables.PokemonForm):
135 obj = obj.unique_pokemon or obj.form_base_pokemon
136
137 if isinstance(obj, tables.Pokemon):
138 reply_template = \
139 u"""#{id} {name}, {type}-type Pokémon. Has {abilities}. """ \
140 """{stats}. """ \
141 """http://veekun.com/dex/pokemon/{link_name}"""
142
143 if not obj.is_base_form:
144 # Can't use urllib.quote() on the whole thing or it'll
145 # catch "?" and "=" where it shouldn't.
146 # XXX Also we need to pass urllib.quote() things explicitly
147 # encoded as utf8 or else we get a UnicodeEncodeError.
148 link_name = '{name}?form={form}'.format(
149 name=urllib.quote(obj.name.lower().encode('utf8')),
150 form=urllib.quote(obj.form_name.lower().encode('utf8')),
151 )
152 else:
153 link_name = urllib.quote(obj.name.lower().encode('utf8'))
154
155 # a/b/c/d/e/f sucks. Put stats in a more readable format.
156 # Also, color-code them by good-osity.
157 colored_stats = []
158 stat_total = 0
159 for pokemon_stat in obj.stats:
160 base_stat = pokemon_stat.base_stat
161 stat_total += base_stat
162 color = get_stat_color(base_stat)
163
164 colored_stats.append(
165 "\x03{0:02d}{1}\x0f".format(color, base_stat)
166 )
167
168 colored_stat_total = "\x03{0:02d}{1}\x0f".format(
169 get_stat_color(stat_total / 6),
170 stat_total,
171 )
172 stats = """{0} HP, {1}/{2} phys, {3}/{4} spec, {5} speed; {total} total""" \
173 .format(*colored_stats, total=colored_stat_total)
174 self._reply(irc, reply_template.format(
175 id=obj.normal_form.id,
176 name=obj.full_name or obj.name,
177 type='/'.join(_.name for _ in obj.types),
178 abilities=' or '.join(_.name for _ in obj.abilities),
179 stats=stats,
180 link_name=link_name,
181 )
182 )
183
184 elif isinstance(obj, tables.Move):
185 reply_template = \
186 u"""{name}, {type}-type {damage_class} move. """ \
187 """{power} power; {accuracy}% accuracy; {pp} PP. """ \
188 """{effect} """ \
189 """http://veekun.com/dex/moves/{link_name}"""
190 self._reply(irc, reply_template.format(
191 name=obj.name,
192 type=obj.type.name,
193 damage_class=obj.damage_class.name,
194 power=obj.power,
195 accuracy=obj.accuracy,
196 pp=obj.pp,
197 effect=unicode(obj.short_effect.as_text),
198 link_name=urllib.quote(obj.name.lower().encode('utf8')),
199 )
200 )
201
202 elif isinstance(obj, tables.Type):
203 reply_template = u"""{name}, a type. """
204
205 offensive_reply_factors = {
206 200: u'\x03092×\x0f',
207 50: u'\x0304½×\x0f',
208 0: u'\x03140×\x0f',
209 }
210
211 offensive_modifiers = {}
212 for matchup in obj.damage_efficacies:
213 if matchup.damage_factor != 100:
214 offensive_modifiers.setdefault(matchup.damage_factor, []) \
215 .append(matchup.target_type.name)
216 if offensive_modifiers:
217 reply_template += u"""{offensive_modifiers}. """
218 for factor in offensive_modifiers:
219 offensive_modifiers[factor] = u'{factor} against {types}'.format(
220 factor=offensive_reply_factors[factor],
221 types=', '.join(sorted(offensive_modifiers[factor]))
222 )
223
224 defensive_reply_factors = {
225 200: u'\x03042×\x0f',
226 50: u'\x0309½×\x0f',
227 0: u'\x03110×\x0f',
228 }
229
230 defensive_modifiers = {}
231 for matchup in obj.target_efficacies:
232 if matchup.damage_factor != 100:
233 defensive_modifiers.setdefault(matchup.damage_factor, []) \
234 .append(matchup.damage_type.name)
235 if defensive_modifiers:
236 reply_template += u"""{defensive_modifiers}. """
237 for factor in defensive_modifiers:
238 defensive_modifiers[factor] = u'{factor} from {types}'.format(
239 factor=defensive_reply_factors[factor],
240 types=', '.join(sorted(defensive_modifiers[factor]))
241 )
242
243 reply_template += u"""http://veekun.com/dex/types/{link_name}"""
244
245 self._reply(irc, reply_template.format(
246 name=obj.name.capitalize(),
247 offensive_modifiers='; '.join(offensive_modifiers[_]
248 for _ in sorted(offensive_modifiers)),
249 defensive_modifiers='; '.join(defensive_modifiers[_]
250 for _ in sorted(defensive_modifiers)),
251 link_name=urllib.quote(obj.name.lower().encode('utf8')),
252 )
253 )
254
255 elif isinstance(obj, tables.Item):
256 reply_template = \
257 u"""{name}, an item. """ \
258 """http://veekun.com/dex/items/{link_pocket}/{link_name}"""
259 self._reply(irc, reply_template.format(
260 name=obj.name,
261 link_pocket=urllib.quote(obj.pocket.name.lower().encode('utf8')),
262 link_name=urllib.quote(obj.name.lower().encode('utf8')),
263 )
264 )
265
266 elif isinstance(obj, tables.Ability):
267 reply_template = \
268 u"""{name}, an ability. {effect} """ \
269 """http://veekun.com/dex/abilities/{link_name}"""
270 self._reply(irc, reply_template.format(
271 name=obj.name,
272 effect=obj.short_effect.as_text,
273 link_name=urllib.quote(obj.name.lower().encode('utf8')),
274 )
275 )
276
277 elif isinstance(obj, tables.Nature):
278 reply_template = \
279 u"""{name}, a nature. """ \
280 u"""Raises \x0303{up}\x0f, lowers \x0304{down}\x0f. """ \
281 u"""http://veekun.com/dex/natures/{link_name}"""
282 self._reply(irc, reply_template.format(
283 name=obj.name,
284 up=obj.increased_stat.name,
285 down=obj.decreased_stat.name,
286 link_name=urllib.quote(obj.name.lower().encode('utf8')),
287 )
288 )
289
290 else:
291 # This can only happen if lookup.py is upgraded and we are not
292 self._reply(irc, "Uhh.. I found that, but I don't know what it is. :(")
293
294 pokedex = wrap(pokedex, [rest('something')])
295
296 def _reply(self, irc, response):
297 """Wraps irc.reply() to do some Unicode decoding."""
298 if isinstance(response, str):
299 irc.reply(response)
300 else:
301 irc.reply(response.encode('utf8'))
302
303
304 Class = Pokedex
305
306
307 # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: