4 Most media accessor __init__s take an ORM object from the pokedex package.
5 Their various methods take a number of arguments specifying exactly which
6 file you want (such as the female sprite, backsprite, etc.).
7 ValueError is raised when the specified file cannot be found.
9 The accessors use fallbacks: for example Bulbasaur's males and females look the
10 same, so if you request Bulbasaur's female sprite, it will give you the common
11 image. Or for a Pokemon without individual form sprites, you will get the
12 common base sprite. Or for versions witout shiny Pokemon, you will always
13 get the non-shiny version (that's how shiny Pokemon looked there!).
14 However arguments such as `animated` don't use fallbacks.
15 You can set `strict` to True to disable these fallbacks and cause ValueError
16 to be raised when the exact specific file you asked for is not found. This is
17 useful for listing non-duplicate sprites, for example.
19 Use keyword arguments when calling the media-getting methods, unless noted
22 The returned "file" objects have useful attributes like relative_path,
25 All images are in the PNG format, except animations (GIF). All sounds are OGGs.
31 class MediaFile(object):
32 """Represents a file: picture, sound, etc.
35 relative_path: Filesystem path relative to the media directory
36 path: Absolute path to the file
38 exists: True if the file exists
42 def __init__(self
, *path_elements
):
43 self
.path_elements
= path_elements
44 self
._dexpath
= '/'.join(('data', 'media') + path_elements
)
47 def relative_path(self
):
48 return os
.path
.join(*self
.path_elements
)
52 return pkg_resources
.resource_filename('pokedex', self
._dexpath
)
55 """Open this file for reading, in the appropriate mode (i.e. binary)
57 return open(self
.path
, 'rb')
61 return pkg_resources
.resource_exists('pokedex', self
._dexpath
)
63 def __eq__(self
, other
):
64 return self
.path
== other
.path
66 def __ne__(self
, other
):
67 return self
.path
!= other
.path
70 return '<Pokedex file %s>' % self
.relative_path
72 class BaseMedia(object):
73 def from_path_elements(self
, path_elements
, basename
, extension
,
75 filename
= basename
+ extension
76 path_elements
= [self
.toplevel_dir
] + path_elements
+ [filename
]
77 mfile
= MediaFile(*path_elements
)
78 if surely_exists
or mfile
.exists
:
81 raise ValueError('File %s not found' % mfile
.relative_path
)
83 class _BasePokemonMedia(BaseMedia
):
84 toplevel_dir
= 'pokemon'
85 has_gender_differences
= False
89 # Info about of what's inside the pokemon main sprite directories, so we
90 # don't have to check directory existence all the time.
91 _pokemon_sprite_info
= {
92 'red-blue': (1, set('back gray'.split())),
93 'red-green': (1, set('back gray'.split())),
94 'yellow': (1, set('back gray gbc'.split())),
95 'gold': (2, set('back shiny'.split())),
96 'silver': (2, set('back shiny'.split())),
97 'crystal': (2, set('animated back shiny'.split())),
98 'ruby-sapphire': (3, set('back shiny'.split())),
99 'emerald': (3, set('animated back shiny frame2'.split())),
100 'firered-leafgreen': (3, set('back shiny'.split())),
101 'diamond-pearl': (4, set('back shiny female frame2'.split())),
102 'platinum': (4, set('back shiny female frame2'.split())),
103 'heartgold-soulsilver': (4, set('back shiny female frame2'.split())),
104 'black-white': (5, set('back shiny female'.split())),
107 def __init__(self
, pokemon_id
, form_postfix
=None):
108 BaseMedia
.__init__(self
)
109 self
.pokemon_id
= str(pokemon_id
)
110 self
.form_postfix
= form_postfix
112 def _get_file(self
, path_elements
, extension
, strict
, surely_exists
=False):
113 basename
= str(self
.pokemon_id
)
114 if self
.form_postfix
:
115 fullname
= basename
+ self
.form_postfix
117 return self
.from_path_elements(
118 path_elements
, fullname
, extension
,
119 surely_exists
=surely_exists
)
123 return self
.from_path_elements(path_elements
, basename
, extension
,
124 surely_exists
=surely_exists
)
127 version
='black-white',
129 # The media directories are in this order:
139 """Get a main sprite sprite for a pokemon.
141 Everything except version should be given as a keyword argument.
143 Either specify version as an ORM object, or give the version path as
144 a string (which is the only way to get 'red-green'). Leave the default
145 for the latest version.
147 animated: get a GIF animation (currently Crystal & Emerald only)
148 back: get a backsprite instead of a front one
149 color: can be 'color' (RGBY only) or 'gbc' (Yellow only)
150 shiny: get a shiny sprite. In old versions, gives a normal sprite unless
152 female: get a female sprite instead of male. For pokemon with no sexual
153 dimorphism, gets the common sprite unless `strict` is set.
154 frame: set to 2 to get the second frame of the animation
155 (Emerald, DPP, and HG/SS only)
157 If the sprite is not found, raise a ValueError.
159 if isinstance(version
, basestring
):
160 version_dir
= version
162 generation
, info
= self
._pokemon_sprite_info
[version_dir
]
164 raise ValueError('Version directory %s not found', version_dir
)
166 version_dir
= version
.identifier
168 generation
, info
= self
._pokemon_sprite_info
[version_dir
]
170 version_group
= version
.version_group
171 version_dir
= '-'.join(
172 v
.identifier
for v
in version_group
.versions
)
173 generation
, info
= self
._pokemon_sprite_info
[version_dir
]
174 if generation
< self
.introduced_in
:
175 raise ValueError("Pokemon %s didn't exist in %s" %
(
176 self
.pokemon_id
, version_dir
))
177 path_elements
= ['main-sprites', version_dir
]
179 if 'animated' not in info
:
180 raise ValueError("No animated sprites for %s" % version_dir
)
181 path_elements
.append('animated')
186 if version_dir
== 'emerald':
187 # Emerald backsprites are the same as ruby/sapphire
189 raise ValueError("Emerald uses R/S backsprites")
191 raise ValueError("No animated backsprites for Emerald")
192 path_elements
[1] = version_dir
= 'ruby-sapphire'
193 if version_dir
== 'crystal' and animated
:
194 raise ValueError("No animated backsprites for Crystal")
195 path_elements
.append('back')
197 if 'gray' not in info
:
198 raise ValueError("No grayscale sprites for %s" % version_dir
)
199 path_elements
.append('gray')
201 if 'gbc' not in info
:
202 raise ValueError("No GBC sprites for %s" % version_dir
)
203 path_elements
.append('gbc')
205 raise ValueError("Unknown color scheme: %s" % color
)
208 path_elements
.append('shiny')
210 raise ValueError("No shiny sprites for %s" % version_dir
)
212 female_sprite
= self
.has_gender_differences
213 # Chimecho's female back frame 2 sprite has one hand in
214 # a slightly different pose, in Platinum and HGSS
215 # (we have duplicate sprites frame 1, for convenience)
216 if self
.pokemon_id
== '358' and back
and version_dir
in (
217 'platinum', 'heartgold-soulsilver'):
219 female_sprite
= female_sprite
and 'female' in info
221 path_elements
.append('female')
224 'Pokemon %s has no gender differences' % self
.pokemon_id
)
225 if not frame
or frame
== 1:
229 path_elements
.append('frame%s' % frame
)
231 raise ValueError("No frame 2 for %s" % version_dir
)
233 raise ValueError("Bad frame %s" % frame
)
234 return self
._get_file(path_elements
, extension
, strict
=strict
,
235 # Avoid a stat in the common case
236 surely_exists
=(self
.form
and version_dir
== 'black-white'
237 and not back
and not female
238 and not self
.form_postfix
))
240 def _maybe_female(self
, path_elements
, female
, strict
):
242 if self
.has_gender_differences
:
243 elements
= path_elements
+ ['female']
245 return self
._get_file(elements
, '.png', strict
=strict
)
251 'Pokemon %s has no gender differences' % self
.pokemon_id
)
252 return self
._get_file(path_elements
, '.png', strict
=strict
)
254 def icon(self
, female
=False, strict
=False):
255 """Get the Pokemon's menu icon"""
256 return self
._maybe_female(['icons'], female
, strict
)
258 def sugimori(self
, female
=False, strict
=False):
259 """Get the Pokemon's official art, drawn by Ken Sugimori"""
260 return self
._maybe_female(['sugimori'], female
, strict
)
269 """Get an overworld sprite
271 direction: 'up', 'down', 'left', or 'right'
272 shiny: true for a shiny sprite
273 female: true for female sprite (or the common one for both M & F)
274 frame: 2 for the second animation frame
276 strict: disable fallback for `female`
278 path_elements
= ['overworld']
280 path_elements
.append('shiny')
282 if self
.has_gender_differences
:
283 path_elements
.append('female')
285 raise ValueError('No female overworld sprite')
288 path_elements
.append(direction
)
289 if frame
and frame
> 1:
290 path_elements
.append('frame%s' % frame
)
292 return self
._get_file(path_elements
, '.png', strict
=strict
)
294 if female
and not strict
:
295 path_elements
.remove('female')
296 return self
._get_file(path_elements
, '.png', strict
=strict
)
300 def footprint(self
, strict
=False):
301 """Get the Pokemon's footprint"""
302 return self
._get_file(['footprints'], '.png', strict
=strict
)
304 def trozei(self
, strict
=False):
305 """Get the Pokemon's animated Trozei sprite"""
306 return self
._get_file(['trozei'], '.gif', strict
=strict
)
308 def cry(self
, strict
=False):
309 """Get the Pokemon's cry"""
310 return self
._get_file(['cries'], '.ogg', strict
=strict
)
312 def cropped_sprite(self
, strict
=False):
313 """Get the Pokemon's cropped sprite"""
314 return self
._get_file(['cropped'], '.png', strict
=strict
)
316 class PokemonFormMedia(_BasePokemonMedia
):
317 """Media related to a Pokemon form
319 def __init__(self
, pokemon_form
):
320 pokemon_id
= pokemon_form
.form_base_pokemon_id
321 if pokemon_form
.identifier
:
322 form_postfix
= '-' + pokemon_form
.identifier
325 _BasePokemonMedia
.__init__(self
, pokemon_id
, form_postfix
)
326 self
.form
= pokemon_form
327 pokemon
= pokemon_form
.form_base_pokemon
328 self
.has_gender_differences
= pokemon
.has_gender_differences
329 self
.introduced_in
= pokemon
.generation_id
331 class PokemonMedia(_BasePokemonMedia
):
332 """Media related to a Pokemon
334 def __init__(self
, pokemon
):
335 _BasePokemonMedia
.__init__(self
, pokemon
.id)
336 self
.form
= pokemon
.default_form
337 self
.has_gender_differences
= (pokemon
.has_gender_differences
)
338 self
.introduced_in
= pokemon
.generation_id
340 class UnknownPokemonMedia(_BasePokemonMedia
):
341 """Media related to the unknown Pokemon ("?")
343 Note that not a lot of files are available for it.
346 _BasePokemonMedia
.__init__(self
, '0')
348 class EggMedia(_BasePokemonMedia
):
349 """Media related to a pokemon egg
351 Note that not a lot of files are available for these.
353 Give a Manaphy as `pokemon` to get the Manaphy egg.
355 def __init__(self
, pokemon
=None):
356 if pokemon
and pokemon
.identifier
== 'manaphy':
360 _BasePokemonMedia
.__init__(self
, 'egg', postfix
)
362 class SubstituteMedia(_BasePokemonMedia
):
363 """Media related to the Substitute sprite
365 Note that not a lot of files are available for Substitute.
368 _BasePokemonMedia
.__init__(self
, 'substitute')
370 class _BaseItemMedia(BaseMedia
):
371 toplevel_dir
= 'items'
372 def underground(self
, rotation
=0):
373 """Get the item's sprite as it appears in the Sinnoh underground
375 Rotation can be 0, 90, 180, or 270.
378 basename
= self
.identifier
+ '-%s' % rotation
380 basename
= self
.identifier
381 return self
.from_path_elements(['underground'], basename
, '.png')
383 class ItemMedia(_BaseItemMedia
):
384 """Media related to an item
386 def __init__(self
, item
):
388 self
.identifier
= item
.identifier
390 def sprite(self
, version
=None):
391 """Get the item's sprite
393 If version is not given, use the latest version.
395 identifier
= self
.identifier
397 # We check the identifier, so that we don't query the machine
398 # information for any item.
399 if identifier
.startswith(('tm', 'hm')):
406 machines
= self
.item
.machines
411 if m
.version_group
== version
.version_group
414 raise ValueError("%s doesn't exist in %s" %
(
415 identifier
, version
.identifier
))
417 # They're ordered, so get the last one
418 machine
= machines
[-1]
419 type_identifier
= machine
.move
.type.identifier
420 identifier
= identifier
[:2] + '-' + type_identifier
421 elif identifier
.startswith('data-card-'):
425 # Not a real data card???
428 identifier
= 'data-card'
429 if version
is not None:
430 generation_id
= version
.generation
.id
431 if generation_id
<= 3 and identifier
== 'dowsing-mchn':
432 identifier
= 'itemfinder'
434 gen
= 'gen%s' % generation_id
435 return self
.from_path_elements([gen
], identifier
, '.png')
438 return self
.from_path_elements([], identifier
, '.png',
441 def underground(self
, rotation
=0):
442 """Get the item's sprite as it appears in the Sinnoh underground
444 Rotation can be 0, 90, 180, or 270.
446 if not self
.item
.appears_underground
:
447 raise ValueError("%s doesn't appear underground" % self
.identifier
)
448 return super(ItemMedia
, self
).underground(rotation
=rotation
)
450 def berry_image(self
):
451 """Get a berry's big sprite
453 if not self
.item
.berry
:
454 raise ValueError("%s is not a berry" % self
.identifier
)
455 return self
.from_path_elements(['berries'], self
.identifier
, '.png')
457 class UndergroundRockMedia(_BaseItemMedia
):
458 """Media related to a rock in the Sinnoh underground
460 rock_type can be one of: i, ii, o, o-big, s, t, z
462 def __init__(self
, rock_type
):
463 self
.identifier
= 'rock-%s' % rock_type
465 class UndergroundSphereMedia(_BaseItemMedia
):
466 """Media related to a sphere in the Sinnoh underground
468 color can be one of: red, blue, green, pale, prism
470 def __init__(self
, color
, big
=False):
471 self
.identifier
= '%s-sphere' % color
473 self
.identifier
+= '-big'
475 class _SimpleIconMedia(BaseMedia
):
476 def __init__(self
, thing
):
477 self
.identifier
= thing
.identifier
480 return self
.from_path_elements([], self
.identifier
, '.png')
482 class DamageClassMedia(_SimpleIconMedia
):
483 toplevel_dir
= 'damage-classes'
485 class HabitatMedia(_SimpleIconMedia
):
486 toplevel_dir
= 'habitats'
488 class ShapeMedia(_SimpleIconMedia
):
489 toplevel_dir
= 'shapes'
491 class ItemPocketMedia(_SimpleIconMedia
):
492 toplevel_dir
= 'item-pockets'
493 def icon(self
, selected
=False):
495 return self
.from_path_elements(
496 ['selected'], self
.identifier
, '.png')
498 return self
.from_path_elements([], self
.identifier
, '.png')
500 class _LanguageIconMedia(_SimpleIconMedia
):
501 def icon(self
, lang
='en'):
502 return self
.from_path_elements([lang
], self
.identifier
, '.png')
504 class ContestTypeMedia(_LanguageIconMedia
):
505 toplevel_dir
= 'contest-types'
507 class TypeMedia(_LanguageIconMedia
):
508 toplevel_dir
= 'types'
510 ''' XXX: No accessors for: