Pokedex: Make stat display colorful and readable.
[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.indices = pokedex.lookup.open_index(
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 = pokedex.lookup.lookup(thing, session=self.db,
90 indices=self.indices)
91
92 # Nothing found
93 if len(results) == 0:
94 self._reply(irc, "I don't know what that is.")
95 return
96
97 # Multiple matches; propose them all
98 if len(results) > 1:
99 if results[0].exact:
100 reply = "Are you looking for"
101 else:
102 reply = "Did you mean"
103
104 # For exact name matches with multiple results, use type prefixes
105 # (item:Metronome). For anything else, omit them
106 use_prefixes = (results[0].exact
107 and '*' not in thing
108 and '?' not in thing)
109
110 result_strings = []
111 for result in results:
112 result_string = result.object.name
113
114 # Prepend, e.g., pokemon: if necessary
115 if use_prefixes:
116 # Table classes know their singular names
117 prefix = result.object.__singlename__
118 result_string = prefix + ':' + result_string
119
120 # Identify foreign language names
121 if result.language:
122 result_string += u""" ({0}: {1})""".format(
123 result.iso3166, result.name)
124
125 result_strings.append(result_string)
126
127 self._reply(irc, u"{0}: {1}?".format(reply, '; '.join(result_strings)))
128 return
129
130 # If we got here, there's an exact match; hurrah!
131 result = results[0]
132 obj = result.object
133 if isinstance(obj, tables.Pokemon):
134 reply_template = \
135 u"""#{id} {name}, {type}-type Pokémon. Has {abilities}. """ \
136 """{stats}. """ \
137 """http://veekun.com/dex/pokemon/{link_name}"""
138
139 if obj.forme_name:
140 name = '{form} {name}'.format(
141 form=obj.forme_name.title(),
142 name=obj.name
143 )
144 else:
145 name = obj.name
146
147 if obj.forme_base_pokemon:
148 # Can't use urllib.quote() on the whole thing or it'll
149 # catch "?" and "=" where it shouldn't.
150 # XXX Also we need to pass urllib.quote() things explicitly
151 # encoded as utf8 or else we get a UnicodeEncodeError.
152 link_name = '{name}?form={form}'.format(
153 name=urllib.quote(obj.name.lower().encode('utf8')),
154 form=urllib.quote(obj.forme_name.lower().encode('utf8')),
155 )
156 else:
157 link_name = urllib.quote(obj.name.lower().encode('utf8'))
158
159 # a/b/c/d/e/f sucks. Put stats in a more readable format.
160 # Also, color-code them by good-osity.
161 colored_stats = []
162 stat_total = 0
163 for pokemon_stat in obj.stats:
164 base_stat = pokemon_stat.base_stat
165 stat_total += base_stat
166 color = get_stat_color(base_stat)
167
168 colored_stats.append(
169 "\x03{0:02d}{1}\x0f".format(color, base_stat)
170 )
171
172 colored_stat_total = "\x03{0:02d}{1}\x0f".format(
173 get_stat_color(stat_total / 6),
174 stat_total,
175 )
176 stats = """{0} HP, {1}/{2} phys, {3}/{4} spec, {5} speed, {total} total""" \
177 .format(*colored_stats, total=colored_stat_total)
178 self._reply(irc, reply_template.format(
179 id=obj.national_id,
180 name=name,
181 type='/'.join(_.name for _ in obj.types),
182 abilities=' or '.join(_.name for _ in obj.abilities),
183 stats=stats,
184 link_name=link_name,
185 )
186 )
187
188 elif isinstance(obj, tables.Move):
189 reply_template = \
190 u"""{name}, {type}-type {damage_class} move. """ \
191 """{power} power; {accuracy}% accuracy; {pp} PP. """ \
192 """{effect} """ \
193 """http://veekun.com/dex/moves/{link_name}"""
194 self._reply(irc, reply_template.format(
195 name=obj.name,
196 type=obj.type.name,
197 damage_class=obj.damage_class.name,
198 power=obj.power,
199 accuracy=obj.accuracy,
200 pp=obj.pp,
201 effect=unicode(obj.short_effect.as_text),
202 link_name=urllib.quote(obj.name.lower().encode('utf8')),
203 )
204 )
205
206 elif isinstance(obj, tables.Type):
207 reply_template = u"""{name}, a type. """
208
209 reply_factors = { 200: u'2', 50: u'½', 0: u'0' }
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=reply_factors[factor],
221 types=', '.join(sorted(offensive_modifiers[factor]))
222 )
223
224 defensive_modifiers = {}
225 for matchup in obj.target_efficacies:
226 if matchup.damage_factor != 100:
227 defensive_modifiers.setdefault(matchup.damage_factor, []) \
228 .append(matchup.damage_type.name)
229 if defensive_modifiers:
230 reply_template += u"""{defensive_modifiers}. """
231 for factor in defensive_modifiers:
232 defensive_modifiers[factor] = u'{factor}× from {types}'.format(
233 factor=reply_factors[factor],
234 types=', '.join(sorted(defensive_modifiers[factor]))
235 )
236
237 reply_template += u"""http://veekun.com/dex/types/{link_name}"""
238
239 self._reply(irc, reply_template.format(
240 name=obj.name.capitalize(),
241 offensive_modifiers='; '.join(offensive_modifiers[_]
242 for _ in sorted(offensive_modifiers)),
243 defensive_modifiers='; '.join(defensive_modifiers[_]
244 for _ in sorted(defensive_modifiers)),
245 link_name=urllib.quote(obj.name.lower().encode('utf8')),
246 )
247 )
248
249 elif isinstance(obj, tables.Item):
250 reply_template = \
251 u"""{name}, an item. """ \
252 """http://veekun.com/dex/items/{link_name}"""
253 self._reply(irc, reply_template.format(
254 name=obj.name,
255 link_name=urllib.quote(obj.name.lower().encode('utf8')),
256 )
257 )
258
259 elif isinstance(obj, tables.Ability):
260 reply_template = \
261 u"""{name}, an ability. {effect} """ \
262 """http://veekun.com/dex/abilities/{link_name}"""
263 self._reply(irc, reply_template.format(
264 name=obj.name,
265 effect=obj.effect,
266 link_name=urllib.quote(obj.name.lower().encode('utf8')),
267 )
268 )
269
270 else:
271 # This can only happen if lookup.py is upgraded and we are not
272 self._reply(irc, "Uhh.. I found that, but I don't know what it is. :(")
273
274 pokedex = wrap(pokedex, [rest('something')])
275
276 def _reply(self, irc, response):
277 """Wraps irc.reply() to do some Unicode decoding."""
278 if isinstance(response, str):
279 irc.reply(response)
280 else:
281 irc.reply(response.encode('utf8'))
282
283
284 Class = Pokedex
285
286
287 # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: