X-Git-Url: http://git.veekun.com/zzz-pokedex.git/blobdiff_plain/16021ce8fdc613edd017b3866aa89d67ac19464e..28e6b68b0aa5e9488a20c8ec2666c2b8cafebc76:/pokedex/db/tables.py?ds=inline diff --git a/pokedex/db/tables.py b/pokedex/db/tables.py index 0fb02ec..63b2efc 100644 --- a/pokedex/db/tables.py +++ b/pokedex/db/tables.py @@ -53,6 +53,16 @@ class Ability(TableBase): short_effect = Column(markdown.MarkdownColumn(255), nullable=False, info=dict(description="Short summary of this ability's effect", format='markdown')) +class AbilityChangelog(TableBase): + """History of changes to abilities across main game versions.""" + __tablename__ = 'ability_changelog' + ability_id = Column(Integer, ForeignKey('abilities.id'), primary_key=True, nullable=False, + info=dict(description="The ID of the ability that changed")) + changed_in_version_group_id = Column(Integer, ForeignKey('version_groups.id'), primary_key=True, nullable=False, + info=dict(description="The ID of the version group in which the ability changed")) + effect = Column(markdown.MarkdownColumn(255), nullable=False, + info=dict(description="A description of the old behavior", format='markdown')) + class AbilityFlavorText(TableBase): u"""In-game flavor text of an ability """ @@ -563,7 +573,7 @@ class MoveDamageClass(TableBase): __tablename__ = 'move_damage_classes' id = Column(Integer, primary_key=True, nullable=False, info=dict(description="A numeric ID")) - name = Column(Unicode(8), nullable=False, + name = Column(Unicode(16), nullable=False, info=dict(description="An English name of the class", format='plaintext')) description = Column(Unicode(64), nullable=False, info=dict(description="An English description of the class", format='plaintext')) @@ -579,6 +589,16 @@ class MoveEffect(TableBase): effect = Column(Unicode(5120), nullable=False, info=dict(description="A detailed description of the effect", format='plaintext')) +class MoveEffectChangelog(TableBase): + """History of changes to move effects across main game versions.""" + __tablename__ = 'move_effect_changelog' + effect_id = Column(Integer, ForeignKey('move_effects.id'), primary_key=True, nullable=False, + info=dict(description="The ID of the effect that changed")) + changed_in_version_group_id = Column(Integer, ForeignKey('version_groups.id'), primary_key=True, nullable=False, + info=dict(description="The ID of the version group in which the effect changed")) + effect = Column(markdown.MarkdownColumn(255), nullable=False, + info=dict(description="A description of the old behavior", format='markdown')) + class MoveFlag(TableBase): u"""Maps a move flag to a move """ @@ -663,7 +683,7 @@ class Move(TableBase): info=dict(description="ID of the damage class (physical/special) of the move")) effect_id = Column(Integer, ForeignKey('move_effects.id'), nullable=False, info=dict(description="ID of the move's effect")) - effect_chance = Column(Integer, + effect_chance = Column(Integer, nullable=True, info=dict(description="The chance for a secondary effect. What this is a chance of is specified by the move's effect.")) contest_type_id = Column(Integer, ForeignKey('contest_types.id'), nullable=True, info=dict(description="ID of the move's Contest type (e.g. cool or smart)")) @@ -672,6 +692,26 @@ class Move(TableBase): super_contest_effect_id = Column(Integer, ForeignKey('super_contest_effects.id'), nullable=True, info=dict(description="ID of the move's Super Contest effect")) +class MoveChangelog(TableBase): + """History of changes to moves across main game versions.""" + __tablename__ = 'move_changelog' + move_id = Column(Integer, ForeignKey('moves.id'), primary_key=True, nullable=False, + info=dict(description="ID of the move that changed")) + changed_in_version_group_id = Column(Integer, ForeignKey('version_groups.id'), primary_key=True, nullable=False, + info=dict(description="ID of the version group in which the move changed")) + type_id = Column(Integer, ForeignKey('types.id'), nullable=True, + info=dict(description="Prior type of the move, or NULL if unchanged")) + power = Column(SmallInteger, nullable=True, + info=dict(description="Prior base power of the move, or NULL if unchanged")) + pp = Column(SmallInteger, nullable=True, + info=dict(description="Prior base PP of the move, or NULL if unchanged")) + accuracy = Column(SmallInteger, nullable=True, + info=dict(description="Prior accuracy of the move, or NULL if unchanged")) + effect_id = Column(Integer, ForeignKey('move_effects.id'), nullable=True, + info=dict(description="Prior ID of the effect, or NULL if unchanged")) + effect_chance = Column(Integer, nullable=True, + info=dict(description="Prior effect chance, or NULL if unchanged")) + class Nature(TableBase): u"""A nature a pokémon can have, such as Calm or Brave """ @@ -757,36 +797,15 @@ class Pokedex(TableBase): description = Column(Unicode(512), info=dict(description=u"A longer description of the pokédex", format='plaintext')) -class PokedexVersionGroup(TableBase): - u"""Maps a pokédex to the version group that uses it - """ - __tablename__ = 'pokedex_version_groups' - pokedex_id = Column(Integer, ForeignKey('pokedexes.id'), primary_key=True, nullable=False, autoincrement=False, - info=dict(description=u"ID of the pokédex")) - version_group_id = Column(Integer, ForeignKey('version_groups.id'), primary_key=True, nullable=False, autoincrement=False, - info=dict(description=u"ID of the version group")) - class Pokemon(TableBase): - u"""A species of pokémon. The core to this whole mess. - - Note that I use both 'forme' and 'form' in both code and the database. I - only use 'forme' when specifically referring to Pokémon that have multiple - distinct species as forms—i.e., different stats or movesets. 'Form' is a - more general term referring to any variation within a species, including - purely cosmetic forms like Unown. + u"""A species of Pokémon. The core to this whole mess. """ - # XXX: Refine the form-specific docs - # XXX: Update form/forme discussion when #179 is dealt with. __tablename__ = 'pokemon' __singlename__ = 'pokemon' id = Column(Integer, primary_key=True, nullable=False, info=dict(description=u"A numeric ID")) name = Column(Unicode(20), nullable=False, - info=dict(description=u"The English name of the pokémon", official=True, format='plaintext')) - forme_name = Column(Unicode(16), - info=dict(description=u"The name of this form, if the species has forms", format='plaintext')) - forme_base_pokemon_id = Column(Integer, ForeignKey('pokemon.id'), - info=dict(description=u"ID for the base form, if this species has one")) # XXX: ? + info=dict(description=u"The English name of the Pokémon", official=True, format='plaintext')) generation_id = Column(Integer, ForeignKey('generations.id'), info=dict(description=u"ID of the generation this species first appeared in")) evolution_chain_id = Column(Integer, ForeignKey('evolution_chains.id'), @@ -824,23 +843,32 @@ class Pokemon(TableBase): ### Stuff to handle alternate Pokémon forms @property - def national_id(self): - u"""Returns the National Pokédex number for this Pokémon. Use this - instead of the id directly; alternate formes may make the id incorrect. + def is_base_form(self): + u"""Returns True iff the Pokémon is the base form for its species, + e.g. Land Shaymin. """ - if self.forme_base_pokemon_id: - return self.forme_base_pokemon_id - return self.id + return self.unique_form is None or self.unique_form.is_default @property - def full_name(self): - u"""Returns the name of this Pokémon, including its Forme, if any. + def form_name(self): + u"""Returns the Pokémon's form name if it represents a particular form + and that form has a name, or None otherwise. """ - if self.forme_name: - return "%s %s" % (self.forme_name.title(), self.name) - return self.name + # If self.unique_form is None, the short-circuit "and" will go ahead + # and return that. Otherwise, it'll return the form's name, which may + # also be None. + return self.unique_form and self.unique_form.name + + @property + def full_name(self): + u"""Returns the Pokémon's name, including its form if applicable.""" + + if self.form_name: + return '{0} {1}'.format(self.form_name, self.name) + else: + return self.name @property def normal_form(self): @@ -848,9 +876,8 @@ class Pokemon(TableBase): regular Deoxys when called on any Deoxys form. """ - if self.forme_base_pokemon: - return self.forme_base_pokemon - + if self.unique_form: + return self.unique_form.form_base_pokemon return self ### Not forms! @@ -897,6 +924,11 @@ class PokemonAbility(TableBase): info=dict(description=u"ID of the pokémon")) ability_id = Column(Integer, ForeignKey('abilities.id'), nullable=False, info=dict(description=u"ID of the ability")) + # XXX having both a method and a slot is kind of gross. "slot" is a + # misnomer, anyway: duplicate abilities don't appear in slot 2. + # Probably should replace that with "order". + is_dream = Column(Boolean, nullable=False, index=True, + info=dict(description=u"Whether this is a Dream World ability")) slot = Column(Integer, primary_key=True, nullable=False, autoincrement=False, info=dict(description=u"The ability slot, i.e. 1 or 2 for gen. IV")) @@ -973,29 +1005,76 @@ class PokemonFlavorText(TableBase): flavor_text = Column(Unicode(255), nullable=False, info=dict(description=u"ID of the version that has this flavor text", official=True, format='gametext')) +class PokemonForm(TableBase): + u"""An individual form of a Pokémon. + + Pokémon that do not have separate forms are still given a single row to + represent their single form. + """ + __tablename__ = 'pokemon_forms' + __singlename__ = 'pokemon_form' + id = Column(Integer, primary_key=True, nullable=False, + info=dict(description=u'A unique ID for this form.')) + name = Column(Unicode(16), nullable=True, + info=dict(description=u"This form's name, e.g. \"Plant\" for Plant Cloak Burmy.", official=True, format='plaintext')) + form_base_pokemon_id = Column(Integer, ForeignKey('pokemon.id'), nullable=False, autoincrement=False, + info=dict(description=u'The ID of the base Pokémon for this form.')) + unique_pokemon_id = Column(Integer, ForeignKey('pokemon.id'), autoincrement=False, + info=dict(description=u'The ID of a Pokémon that represents specifically this form, for Pokémon with functionally-different forms like Wormadam.')) + introduced_in_version_group_id = Column(Integer, ForeignKey('version_groups.id'), autoincrement=False, + info=dict(description=u'The ID of the version group in which this form first appeared.')) + is_default = Column(Boolean, nullable=False, + info=dict(description=u'Set for exactly one form used as the default for each species.')) + order = Column(Integer, nullable=False, autoincrement=False, + info=dict(description=u'The order in which forms should be sorted. Multiple forms may have equal order, in which case they should fall back on sorting by name.')) + + @property + def full_name(self): + u"""Returns the full name of this form, e.g. "Plant Cloak".""" + + if not self.name: + return None + elif self.form_group and self.form_group.term: + return '{0} {1}'.format(self.name, self.form_group.term) + else: + return self.name + + @property + def pokemon_name(self): + u"""Returns the name of this Pokémon with this form, e.g. "Plant + Burmy". + """ + + if self.name: + return '{0} {1}'.format(self.name, self.form_base_pokemon.name) + else: + return self.form_base_pokemon.name + class PokemonFormGroup(TableBase): - # XXX: Give the docstring here & check column descriptions + u"""Information about a Pokémon's forms as a group.""" __tablename__ = 'pokemon_form_groups' pokemon_id = Column(Integer, ForeignKey('pokemon.id'), primary_key=True, nullable=False, autoincrement=False, - info=dict(description=u"ID of the base form pokémon")) + info=dict(description=u"ID of the base form Pokémon")) + term = Column(Unicode(16), nullable=True, + info=dict(description=u"The term for this Pokémon's forms, e.g. \"Cloak\" for Burmy or \"Forme\" for Deoxys.", official=True, format='plaintext')) is_battle_only = Column(Boolean, nullable=False, info=dict(description=u"Set iff the forms only change in battle")) description = Column(markdown.MarkdownColumn(1024), nullable=False, info=dict(description=u"English description of how the forms work", format='markdown')) -class PokemonFormSprite(TableBase): - # XXX: Give the docstring here & check column descriptions - __tablename__ = 'pokemon_form_sprites' - id = Column(Integer, primary_key=True, nullable=False, - info=dict(description=u"A numeric ID")) - pokemon_id = Column(Integer, ForeignKey('pokemon.id'), primary_key=True, nullable=False, autoincrement=False, - info=dict(description=u"ID of the pokémon")) - introduced_in_version_group_id = Column(Integer, ForeignKey('version_groups.id'), primary_key=True, nullable=False, autoincrement=False, - info=dict(description=u"ID of version group the form was introduced in")) - name = Column(Unicode(16), nullable=True, - info=dict(description=u"English name of the form", format='plaintext')) - is_default = Column(Boolean, nullable=True, - info=dict(description=u'Set iff the form is the base, normal, usual, or otherwise default form')) +class PokemonFormPokeathlonStat(TableBase): + u"""A Pokémon form's performance in one Pokéathlon stat.""" + __tablename__ = 'pokemon_form_pokeathlon_stats' + pokemon_form_id = Column(Integer, ForeignKey('pokemon_forms.id'), primary_key=True, nullable=False, autoincrement=False, + info=dict(description=u'The ID of the Pokémon form.')) + pokeathlon_stat_id = Column(Integer, ForeignKey('pokeathlon_stats.id'), primary_key=True, nullable=False, autoincrement=False, + info=dict(description=u'The ID of the Pokéathlon stat.')) + minimum_stat = Column(Integer, nullable=False, autoincrement=False, + info=dict(description=u'The minimum value for this stat for this Pokémon form.')) + base_stat = Column(Integer, nullable=False, autoincrement=False, + info=dict(description=u'The default value for this stat for this Pokémon form.')) + maximum_stat = Column(Integer, nullable=False, autoincrement=False, + info=dict(description=u'The maximum value for this stat for this Pokémon form.')) class PokemonHabitat(TableBase): u"""The habitat of a pokémon, as given in the FireRed/LeafGreen version pokédex @@ -1195,6 +1274,8 @@ class VersionGroup(TableBase): info=dict(description=u"A numeric ID")) generation_id = Column(Integer, ForeignKey('generations.id'), nullable=False, info=dict(description=u"ID of the generation the games of this group belong to")) + pokedex_id = Column(Integer, ForeignKey('pokedexes.id'), nullable=False, + info=dict(description=u"ID of the regional Pokédex used in this version group.")) class VersionGroupRegion(TableBase): u"""Maps a region to a game version group that features it @@ -1206,21 +1287,30 @@ class VersionGroupRegion(TableBase): info=dict(description=u"ID of the region")) class Version(TableBase): - u"""A version of a mainline pokémon game + u"""An individual main-series Pokémon game """ __tablename__ = 'versions' id = Column(Integer, primary_key=True, nullable=False, - info=dict(description=u"A numeric ID")) + info=dict(description=u"A unique ID for this version")) version_group_id = Column(Integer, ForeignKey('version_groups.id'), nullable=False, - info=dict(description=u"ID of the version group this game belongs to")) + info=dict(description=u"The ID of the version group this game belongs to")) name = Column(Unicode(32), nullable=False, info=dict(description=u'The English name of the game, without the "Pokémon" prefix', official=True, format='plaintext')) ### Relations down here, to avoid ordering problems +Ability.changelog = relation(AbilityChangelog, + order_by=AbilityChangelog.changed_in_version_group_id.desc(), + backref='ability', +) Ability.flavor_text = relation(AbilityFlavorText, order_by=AbilityFlavorText.version_group_id, backref='ability') Ability.foreign_names = relation(AbilityName, backref='ability') Ability.generation = relation(Generation, backref='abilities') +Ability.pokemon = relation(Pokemon, + secondary=PokemonAbility.__table__, +) + +AbilityChangelog.changed_in = relation(VersionGroup, backref='ability_changelog') AbilityFlavorText.version_group = relation(VersionGroup) @@ -1300,6 +1390,10 @@ LocationInternalID.generation = relation(Generation) Machine.item = relation(Item) Machine.version_group = relation(VersionGroup) +Move.changelog = relation(MoveChangelog, + order_by=MoveChangelog.changed_in_version_group_id.desc(), + backref='move', +) Move.contest_effect = relation(ContestEffect, backref='moves') Move.contest_combo_next = association_proxy('contest_combo_first', 'second') Move.contest_combo_prev = association_proxy('contest_combo_second', 'first') @@ -1321,6 +1415,8 @@ Move.type = relation(Type, backref='moves') Move.effect = markdown.MoveEffectProperty('effect') Move.short_effect = markdown.MoveEffectProperty('short_effect') +MoveChangelog.changed_in = relation(VersionGroup, backref='move_changelog') + MoveEffect.category_map = relation(MoveEffectCategoryMap) MoveEffect.categories = association_proxy('category_map', 'category') MoveEffectCategoryMap.category = relation(MoveEffectCategory) @@ -1352,23 +1448,31 @@ NatureName.language = relation(Language) NaturePokeathlonStat.pokeathlon_stat = relation(PokeathlonStat, backref='nature_effects') Pokedex.region = relation(Region, backref='pokedexes') -Pokedex.version_groups = relation(VersionGroup, secondary=PokedexVersionGroup.__table__, backref='pokedexes') - -Pokemon.abilities = relation(Ability, secondary=PokemonAbility.__table__, - order_by=PokemonAbility.slot, - backref='pokemon') -Pokemon.formes = relation(Pokemon, primaryjoin=Pokemon.id==Pokemon.forme_base_pokemon_id, - backref=backref('forme_base_pokemon', - remote_side=[Pokemon.id])) +Pokedex.version_groups = relation(VersionGroup, order_by=VersionGroup.id, back_populates='pokedex') + +Pokemon.all_abilities = relation(Ability, + secondary=PokemonAbility.__table__, + order_by=PokemonAbility.slot, +) +Pokemon.abilities = relation(Ability, + secondary=PokemonAbility.__table__, + primaryjoin=and_( + Pokemon.id == PokemonAbility.pokemon_id, + PokemonAbility.is_dream == False, + ), + order_by=PokemonAbility.slot, +) +Pokemon.dream_ability = relation(Ability, + secondary=PokemonAbility.__table__, + primaryjoin=and_( + Pokemon.id == PokemonAbility.pokemon_id, + PokemonAbility.is_dream == True, + ), + uselist=False, +) Pokemon.pokemon_color = relation(PokemonColor, backref='pokemon') Pokemon.color = association_proxy('pokemon_color', 'name') Pokemon.dex_numbers = relation(PokemonDexNumber, order_by=PokemonDexNumber.pokedex_id.asc(), backref='pokemon') -Pokemon.default_form_sprite = relation(PokemonFormSprite, - primaryjoin=and_( - Pokemon.id==PokemonFormSprite.pokemon_id, - PokemonFormSprite.is_default==True, - ), - uselist=False) Pokemon.egg_groups = relation(EggGroup, secondary=PokemonEggGroup.__table__, order_by=PokemonEggGroup.egg_group_id, backref='pokemon') @@ -1381,13 +1485,19 @@ Pokemon.child_pokemon = relation(Pokemon, ) Pokemon.flavor_text = relation(PokemonFlavorText, order_by=PokemonFlavorText.version_id.asc(), backref='pokemon') Pokemon.foreign_names = relation(PokemonName, backref='pokemon') +Pokemon.forms = relation(PokemonForm, primaryjoin=Pokemon.id==PokemonForm.form_base_pokemon_id, + order_by=(PokemonForm.order.asc(), PokemonForm.name.asc())) +Pokemon.default_form = relation(PokemonForm, + primaryjoin=and_(Pokemon.id==PokemonForm.form_base_pokemon_id, PokemonForm.is_default==True), + uselist=False, +) Pokemon.pokemon_habitat = relation(PokemonHabitat, backref='pokemon') Pokemon.habitat = association_proxy('pokemon_habitat', 'name') Pokemon.items = relation(PokemonItem, backref='pokemon') Pokemon.generation = relation(Generation, backref='pokemon') Pokemon.shape = relation(PokemonShape, backref='pokemon') Pokemon.stats = relation(PokemonStat, backref='pokemon', order_by=PokemonStat.stat_id.asc()) -Pokemon.types = relation(Type, secondary=PokemonType.__table__, order_by=PokemonType.slot.asc()) +Pokemon.types = relation(Type, secondary=PokemonType.__table__, order_by=PokemonType.slot.asc(), backref='pokemon') PokemonDexNumber.pokedex = relation(Pokedex) @@ -1425,13 +1535,22 @@ PokemonEvolution.party_pokemon = relation(Pokemon, PokemonFlavorText.version = relation(Version) -PokemonItem.item = relation(Item, backref='pokemon') -PokemonItem.version = relation(Version) +PokemonForm.form_base_pokemon = relation(Pokemon, primaryjoin=PokemonForm.form_base_pokemon_id==Pokemon.id) +PokemonForm.unique_pokemon = relation(Pokemon, backref=backref('unique_form', uselist=False), + primaryjoin=PokemonForm.unique_pokemon_id==Pokemon.id) +PokemonForm.version_group = relation(VersionGroup) +PokemonForm.form_group = association_proxy('form_base_pokemon', 'form_group') +PokemonForm.pokeathlon_stats = relation(PokemonFormPokeathlonStat, + order_by=PokemonFormPokeathlonStat.pokeathlon_stat_id, + backref='pokemon_form') PokemonFormGroup.pokemon = relation(Pokemon, backref=backref('form_group', uselist=False)) -PokemonFormSprite.pokemon = relation(Pokemon, backref='form_sprites') -PokemonFormSprite.introduced_in = relation(VersionGroup) + +PokemonFormPokeathlonStat.pokeathlon_stat = relation(PokeathlonStat) + +PokemonItem.item = relation(Item, backref='pokemon') +PokemonItem.version = relation(Version) PokemonMove.pokemon = relation(Pokemon, backref='pokemon_moves') PokemonMove.version_group = relation(VersionGroup) @@ -1475,9 +1594,11 @@ Type.foreign_names = relation(TypeName, backref='type') TypeName.language = relation(Language) -Version.version_group = relation(VersionGroup, backref='versions') +Version.version_group = relation(VersionGroup, back_populates='versions') Version.generation = association_proxy('version_group', 'generation') +VersionGroup.versions = relation(Version, order_by=Version.id, back_populates='version_group') VersionGroup.generation = relation(Generation, backref='version_groups') VersionGroup.version_group_regions = relation(VersionGroupRegion, backref='version_group') VersionGroup.regions = association_proxy('version_group_regions', 'region') +VersionGroup.pokedex = relation(Pokedex, back_populates='version_groups')