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