develop #24

Merged
TheFurya merged 2 commits from develop into main 2026-02-14 11:05:21 +01:00
225 changed files with 879 additions and 130 deletions

View File

@@ -0,0 +1,26 @@
---
# nuzlocke-tracker-yfi8
title: Fetch boss battle sprites from Bulbapedia
status: in-progress
type: task
priority: normal
created_at: 2026-02-14T09:14:32Z
updated_at: 2026-02-14T09:24:28Z
---
## Overview
Write and run a one-time script to download boss battle trainer sprites from Bulbapedia's archives. For totem pokemon, link to existing pokemon sprites instead.
## Current State
- 10 game directories have sprites (122 files total): red, yellow, gold, crystal, ruby, emerald, firered, diamond, heartgold, omega-ruby
- 11 game directories are completely missing: black, black-2, platinum, x, sun, ultra-sun, lets-go-pikachu, sword, brilliant-diamond, legends-arceus, scarlet
- 11 boss entries have null sprite_url (firered Elite Four + heartgold Elite Four/Red)
## Checklist
- [x] Write download script with Bulbapedia image URL patterns
- [x] Download sprites for all missing games
- [x] Fill in null sprite_url entries for firered/heartgold
- [x] Handle totem pokemon by linking to existing pokemon sprites
- [x] Handle special cases (multi-boss entries, Legends Arceus nobles)
- [x] Update seed JSON files with correct local paths

View File

@@ -0,0 +1,11 @@
---
# nuzlocke-tracker-zmvy
title: Add game_id field to BossBattle for version-exclusive bosses
status: completed
type: feature
priority: normal
created_at: 2026-02-14T09:47:40Z
updated_at: 2026-02-14T09:52:59Z
---
Add a proper game_id FK to BossBattle so version-exclusive bosses can be filtered per game instead of overloading the section field.

View File

@@ -0,0 +1,76 @@
"""add game_id to boss battles
Revision ID: f7a8b9c0d1e2
Revises: e5f70a1ca323
Create Date: 2026-02-14 12:00:00.000000
"""
from collections.abc import Sequence
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision: str = "f7a8b9c0d1e2"
down_revision: str | Sequence[str] | None = "e5f70a1ca323"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
op.add_column(
"boss_battles",
sa.Column("game_id", sa.Integer(), nullable=True),
)
op.create_foreign_key(
"fk_boss_battles_game_id",
"boss_battles",
"games",
["game_id"],
["id"],
)
op.create_index("ix_boss_battles_game_id", "boss_battles", ["game_id"])
# Data migration: for bosses where section is a game name,
# look up the game ID, set game_id, and reset section to null.
conn = op.get_bind()
rows = conn.execute(
sa.text(
"SELECT bb.id, g.id AS gid "
"FROM boss_battles bb "
"JOIN games g ON LOWER(bb.section) = LOWER(g.name) "
"WHERE bb.section IS NOT NULL"
)
).fetchall()
for row in rows:
conn.execute(
sa.text(
"UPDATE boss_battles SET game_id = :gid, section = NULL WHERE id = :bid"
),
{"gid": row.gid, "bid": row.id},
)
def downgrade() -> None:
# Reverse data migration: restore section from game name
conn = op.get_bind()
rows = conn.execute(
sa.text(
"SELECT bb.id, g.name "
"FROM boss_battles bb "
"JOIN games g ON bb.game_id = g.id "
"WHERE bb.game_id IS NOT NULL"
)
).fetchall()
for row in rows:
conn.execute(
sa.text(
"UPDATE boss_battles SET section = :name, game_id = NULL WHERE id = :bid"
),
{"name": row.name, "bid": row.id},
)
op.drop_index("ix_boss_battles_game_id", table_name="boss_battles")
op.drop_constraint("fk_boss_battles_game_id", "boss_battles", type_="foreignkey")
op.drop_column("boss_battles", "game_id")

View File

@@ -1,7 +1,7 @@
from datetime import UTC, datetime from datetime import UTC, datetime
from fastapi import APIRouter, Depends, HTTPException, Response from fastapi import APIRouter, Depends, HTTPException, Response
from sqlalchemy import select from sqlalchemy import or_, select
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload from sqlalchemy.orm import selectinload
@@ -43,15 +43,26 @@ async def _get_version_group_id(session: AsyncSession, game_id: int) -> int:
@router.get("/games/{game_id}/bosses", response_model=list[BossBattleResponse]) @router.get("/games/{game_id}/bosses", response_model=list[BossBattleResponse])
async def list_bosses(game_id: int, session: AsyncSession = Depends(get_session)): async def list_bosses(
game_id: int,
all: bool = False,
session: AsyncSession = Depends(get_session),
):
vg_id = await _get_version_group_id(session, game_id) vg_id = await _get_version_group_id(session, game_id)
result = await session.execute( query = (
select(BossBattle) select(BossBattle)
.where(BossBattle.version_group_id == vg_id) .where(BossBattle.version_group_id == vg_id)
.options(selectinload(BossBattle.pokemon).selectinload(BossPokemon.pokemon)) .options(selectinload(BossBattle.pokemon).selectinload(BossPokemon.pokemon))
.order_by(BossBattle.order) .order_by(BossBattle.order)
) )
if not all:
query = query.where(
or_(BossBattle.game_id.is_(None), BossBattle.game_id == game_id)
)
result = await session.execute(query)
return result.scalars().all() return result.scalars().all()
@@ -106,6 +117,14 @@ async def create_boss(
): ):
vg_id = await _get_version_group_id(session, game_id) vg_id = await _get_version_group_id(session, game_id)
if data.game_id is not None:
game = await session.get(Game, data.game_id)
if game is None or game.version_group_id != vg_id:
raise HTTPException(
status_code=400,
detail="game_id does not belong to this version group",
)
boss = BossBattle(version_group_id=vg_id, **data.model_dump()) boss = BossBattle(version_group_id=vg_id, **data.model_dump())
session.add(boss) session.add(boss)
await session.commit() await session.commit()
@@ -128,6 +147,14 @@ async def update_boss(
): ):
vg_id = await _get_version_group_id(session, game_id) vg_id = await _get_version_group_id(session, game_id)
if data.game_id is not None:
game = await session.get(Game, data.game_id)
if game is None or game.version_group_id != vg_id:
raise HTTPException(
status_code=400,
detail="game_id does not belong to this version group",
)
result = await session.execute( result = await session.execute(
select(BossBattle) select(BossBattle)
.where(BossBattle.id == boss_id, BossBattle.version_group_id == vg_id) .where(BossBattle.id == boss_id, BossBattle.version_group_id == vg_id)
@@ -192,10 +219,16 @@ async def bulk_import_bosses(
) )
route_name_to_id = {row.name: row.id for row in result} route_name_to_id = {row.name: row.id for row in result}
# Build game slug -> id mapping for game_slug resolution
result = await session.execute(
select(Game.slug, Game.id).where(Game.version_group_id == vg_id)
)
slug_to_game_id = {row.slug: row.id for row in result}
bosses_data = [item.model_dump() for item in items] bosses_data = [item.model_dump() for item in items]
try: try:
count = await upsert_bosses( count = await upsert_bosses(
session, vg_id, bosses_data, dex_to_id, route_name_to_id session, vg_id, bosses_data, dex_to_id, route_name_to_id, slug_to_game_id
) )
except Exception as e: except Exception as e:
raise HTTPException( raise HTTPException(

View File

@@ -126,6 +126,7 @@ async def export_game_bosses(
.options( .options(
selectinload(BossBattle.pokemon).selectinload(BossPokemon.pokemon), selectinload(BossBattle.pokemon).selectinload(BossPokemon.pokemon),
selectinload(BossBattle.after_route), selectinload(BossBattle.after_route),
selectinload(BossBattle.game),
) )
.order_by(BossBattle.order) .order_by(BossBattle.order)
) )
@@ -146,6 +147,7 @@ async def export_game_bosses(
"location": b.location, "location": b.location,
"section": b.section, "section": b.section,
"sprite_url": b.sprite_url, "sprite_url": b.sprite_url,
**({"game_slug": b.game.slug} if b.game_id else {}),
"pokemon": [ "pokemon": [
{ {
"pokeapi_id": bp.pokemon.pokeapi_id, "pokeapi_id": bp.pokemon.pokeapi_id,

View File

@@ -33,9 +33,13 @@ class BossBattle(Base):
location: Mapped[str] = mapped_column(String(200)) location: Mapped[str] = mapped_column(String(200))
section: Mapped[str | None] = mapped_column(String(100), default=None) section: Mapped[str | None] = mapped_column(String(100), default=None)
sprite_url: Mapped[str | None] = mapped_column(String(500)) sprite_url: Mapped[str | None] = mapped_column(String(500))
game_id: Mapped[int | None] = mapped_column(
ForeignKey("games.id"), index=True, default=None
)
version_group: Mapped["VersionGroup"] = relationship(back_populates="boss_battles") version_group: Mapped["VersionGroup"] = relationship(back_populates="boss_battles")
after_route: Mapped["Route | None"] = relationship() after_route: Mapped["Route | None"] = relationship()
game: Mapped["Game | None"] = relationship()
pokemon: Mapped[list["BossPokemon"]] = relationship( pokemon: Mapped[list["BossPokemon"]] = relationship(
back_populates="boss_battle", cascade="all, delete-orphan" back_populates="boss_battle", cascade="all, delete-orphan"
) )

View File

@@ -27,6 +27,7 @@ class BossBattleResponse(CamelModel):
location: str location: str
section: str | None section: str | None
sprite_url: str | None sprite_url: str | None
game_id: int | None
pokemon: list[BossPokemonResponse] = [] pokemon: list[BossPokemonResponse] = []
@@ -54,6 +55,7 @@ class BossBattleCreate(CamelModel):
location: str location: str
section: str | None = None section: str | None = None
sprite_url: str | None = None sprite_url: str | None = None
game_id: int | None = None
class BossBattleUpdate(CamelModel): class BossBattleUpdate(CamelModel):
@@ -68,6 +70,7 @@ class BossBattleUpdate(CamelModel):
location: str | None = None location: str | None = None
section: str | None = None section: str | None = None
sprite_url: str | None = None sprite_url: str | None = None
game_id: int | None = None
class BossPokemonInput(CamelModel): class BossPokemonInput(CamelModel):

View File

@@ -215,4 +215,5 @@ class BulkBossItem(BaseModel):
location: str location: str
section: str | None = None section: str | None = None
sprite_url: str | None = None sprite_url: str | None = None
game_slug: str | None = None
pokemon: list[BulkBossPokemonItem] = [] pokemon: list[BulkBossPokemonItem] = []

View File

@@ -107,8 +107,9 @@
"order": 8, "order": 8,
"after_route_name": null, "after_route_name": null,
"location": "Opelucid Gym", "location": "Opelucid Gym",
"section": "Black", "section": null,
"sprite_url": "/boss-sprites/black/drayden.png", "sprite_url": "/boss-sprites/black/drayden.png",
"game_slug": "black",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -121,8 +122,9 @@
"order": 9, "order": 9,
"after_route_name": null, "after_route_name": null,
"location": "Opelucid Gym", "location": "Opelucid Gym",
"section": "White", "section": null,
"sprite_url": "/boss-sprites/black/iris.png", "sprite_url": "/boss-sprites/black/iris.png",
"game_slug": "white",
"pokemon": [] "pokemon": []
}, },
{ {

View File

@@ -10,7 +10,7 @@
"after_route_name": "Violet City", "after_route_name": "Violet City",
"location": "Violet Gym", "location": "Violet Gym",
"section": "Main Story", "section": "Main Story",
"sprite_url": "/boss-sprites/gold/falkner.png", "sprite_url": "/boss-sprites/crystal/falkner.png",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -24,7 +24,7 @@
"after_route_name": "Slowpoke Well", "after_route_name": "Slowpoke Well",
"location": "Azalea Gym", "location": "Azalea Gym",
"section": "Main Story", "section": "Main Story",
"sprite_url": "/boss-sprites/gold/bugsy.png", "sprite_url": "/boss-sprites/crystal/bugsy.png",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -38,7 +38,7 @@
"after_route_name": "Goldenrod City", "after_route_name": "Goldenrod City",
"location": "Goldenrod Gym", "location": "Goldenrod Gym",
"section": "Main Story", "section": "Main Story",
"sprite_url": "/boss-sprites/gold/whitney.png", "sprite_url": "/boss-sprites/crystal/whitney.png",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -52,7 +52,7 @@
"after_route_name": "Ecruteak City", "after_route_name": "Ecruteak City",
"location": "Ecruteak Gym", "location": "Ecruteak Gym",
"section": "Main Story", "section": "Main Story",
"sprite_url": "/boss-sprites/gold/morty.png", "sprite_url": "/boss-sprites/crystal/morty.png",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -66,7 +66,7 @@
"after_route_name": "Cianwood City", "after_route_name": "Cianwood City",
"location": "Cianwood Gym", "location": "Cianwood Gym",
"section": "Main Story", "section": "Main Story",
"sprite_url": "/boss-sprites/gold/chuck.png", "sprite_url": "/boss-sprites/crystal/chuck.png",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -80,7 +80,7 @@
"after_route_name": "Cianwood City", "after_route_name": "Cianwood City",
"location": "Olivine Gym", "location": "Olivine Gym",
"section": "Main Story", "section": "Main Story",
"sprite_url": "/boss-sprites/gold/jasmine.png", "sprite_url": "/boss-sprites/crystal/jasmine.png",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -94,7 +94,7 @@
"after_route_name": "Lake of Rage", "after_route_name": "Lake of Rage",
"location": "Mahogany Gym", "location": "Mahogany Gym",
"section": "Main Story", "section": "Main Story",
"sprite_url": "/boss-sprites/gold/pryce.png", "sprite_url": "/boss-sprites/crystal/pryce.png",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -108,7 +108,7 @@
"after_route_name": "Blackthorn City", "after_route_name": "Blackthorn City",
"location": "Blackthorn Gym", "location": "Blackthorn Gym",
"section": "Main Story", "section": "Main Story",
"sprite_url": "/boss-sprites/gold/clair.png", "sprite_url": "/boss-sprites/crystal/clair.png",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -122,7 +122,7 @@
"after_route_name": "Victory Road (Kanto)", "after_route_name": "Victory Road (Kanto)",
"location": "Indigo Plateau", "location": "Indigo Plateau",
"section": "Main Story", "section": "Main Story",
"sprite_url": "/boss-sprites/gold/will.png", "sprite_url": "/boss-sprites/crystal/will.png",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -136,7 +136,7 @@
"after_route_name": "Victory Road (Kanto)", "after_route_name": "Victory Road (Kanto)",
"location": "Indigo Plateau", "location": "Indigo Plateau",
"section": "Main Story", "section": "Main Story",
"sprite_url": "/boss-sprites/gold/koga.png", "sprite_url": "/boss-sprites/crystal/koga.png",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -150,7 +150,7 @@
"after_route_name": "Victory Road (Kanto)", "after_route_name": "Victory Road (Kanto)",
"location": "Indigo Plateau", "location": "Indigo Plateau",
"section": "Main Story", "section": "Main Story",
"sprite_url": "/boss-sprites/gold/bruno.png", "sprite_url": "/boss-sprites/crystal/bruno.png",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -164,7 +164,7 @@
"after_route_name": "Victory Road (Kanto)", "after_route_name": "Victory Road (Kanto)",
"location": "Indigo Plateau", "location": "Indigo Plateau",
"section": "Main Story", "section": "Main Story",
"sprite_url": "/boss-sprites/gold/karen.png", "sprite_url": "/boss-sprites/crystal/karen.png",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -178,7 +178,7 @@
"after_route_name": "Victory Road (Kanto)", "after_route_name": "Victory Road (Kanto)",
"location": "Indigo Plateau", "location": "Indigo Plateau",
"section": "Main Story", "section": "Main Story",
"sprite_url": "/boss-sprites/gold/lance.png", "sprite_url": "/boss-sprites/crystal/lance.png",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -192,7 +192,7 @@
"after_route_name": "Vermilion City", "after_route_name": "Vermilion City",
"location": "Vermilion Gym", "location": "Vermilion Gym",
"section": "Endgame", "section": "Endgame",
"sprite_url": "/boss-sprites/gold/lt-surge.png", "sprite_url": "/boss-sprites/crystal/lt-surge.png",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -206,7 +206,7 @@
"after_route_name": "Route 05 (Kanto)", "after_route_name": "Route 05 (Kanto)",
"location": "Saffron Gym", "location": "Saffron Gym",
"section": "Endgame", "section": "Endgame",
"sprite_url": "/boss-sprites/gold/sabrina.png", "sprite_url": "/boss-sprites/crystal/sabrina.png",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -220,7 +220,7 @@
"after_route_name": "Cerulean Cave (B1F)", "after_route_name": "Cerulean Cave (B1F)",
"location": "Cerulean Gym", "location": "Cerulean Gym",
"section": "Endgame", "section": "Endgame",
"sprite_url": "/boss-sprites/gold/misty.png", "sprite_url": "/boss-sprites/crystal/misty.png",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -234,7 +234,7 @@
"after_route_name": "Celadon City", "after_route_name": "Celadon City",
"location": "Celadon Gym", "location": "Celadon Gym",
"section": "Endgame", "section": "Endgame",
"sprite_url": "/boss-sprites/gold/erika.png", "sprite_url": "/boss-sprites/crystal/erika.png",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -248,7 +248,7 @@
"after_route_name": "Fuchsia City", "after_route_name": "Fuchsia City",
"location": "Fuchsia Gym", "location": "Fuchsia Gym",
"section": "Endgame", "section": "Endgame",
"sprite_url": "/boss-sprites/gold/janine.png", "sprite_url": "/boss-sprites/crystal/janine.png",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -262,7 +262,7 @@
"after_route_name": "Pewter City", "after_route_name": "Pewter City",
"location": "Pewter Gym", "location": "Pewter Gym",
"section": "Endgame", "section": "Endgame",
"sprite_url": "/boss-sprites/gold/brock.png", "sprite_url": "/boss-sprites/crystal/brock.png",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -276,7 +276,7 @@
"after_route_name": "Cinnabar Island", "after_route_name": "Cinnabar Island",
"location": "Cinnabar Gym", "location": "Cinnabar Gym",
"section": "Endgame", "section": "Endgame",
"sprite_url": "/boss-sprites/gold/blaine.png", "sprite_url": "/boss-sprites/crystal/blaine.png",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -290,7 +290,7 @@
"after_route_name": "Viridian City", "after_route_name": "Viridian City",
"location": "Viridian Gym", "location": "Viridian Gym",
"section": "Endgame", "section": "Endgame",
"sprite_url": "/boss-sprites/gold/blue.png", "sprite_url": "/boss-sprites/crystal/blue.png",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -304,7 +304,7 @@
"after_route_name": "Mt. Silver (Cave Full Restore Chamber)", "after_route_name": "Mt. Silver (Cave Full Restore Chamber)",
"location": "Silver Cave", "location": "Silver Cave",
"section": "Endgame", "section": "Endgame",
"sprite_url": "/boss-sprites/gold/red.png", "sprite_url": "/boss-sprites/crystal/red.png",
"pokemon": [] "pokemon": []
} }
] ]

View File

@@ -10,7 +10,7 @@
"after_route_name": "Rustboro City", "after_route_name": "Rustboro City",
"location": "Rustboro Gym", "location": "Rustboro Gym",
"section": "Main Story", "section": "Main Story",
"sprite_url": "/boss-sprites/ruby/roxanne.png", "sprite_url": "/boss-sprites/emerald/roxanne.png",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -24,7 +24,7 @@
"after_route_name": "Dewford Town", "after_route_name": "Dewford Town",
"location": "Dewford Gym", "location": "Dewford Gym",
"section": "Main Story", "section": "Main Story",
"sprite_url": "/boss-sprites/ruby/brawly.png", "sprite_url": "/boss-sprites/emerald/brawly.png",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -38,7 +38,7 @@
"after_route_name": "Hoenn Route 110", "after_route_name": "Hoenn Route 110",
"location": "Mauville Gym", "location": "Mauville Gym",
"section": "Main Story", "section": "Main Story",
"sprite_url": "/boss-sprites/ruby/wattson.png", "sprite_url": "/boss-sprites/emerald/wattson.png",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -52,7 +52,7 @@
"after_route_name": "Lavaridge Town", "after_route_name": "Lavaridge Town",
"location": "Lavaridge Gym", "location": "Lavaridge Gym",
"section": "Main Story", "section": "Main Story",
"sprite_url": "/boss-sprites/ruby/flannery.png", "sprite_url": "/boss-sprites/emerald/flannery.png",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -66,7 +66,7 @@
"after_route_name": "Desert Ruins", "after_route_name": "Desert Ruins",
"location": "Petalburg Gym", "location": "Petalburg Gym",
"section": "Main Story", "section": "Main Story",
"sprite_url": "/boss-sprites/ruby/norman.png", "sprite_url": "/boss-sprites/emerald/norman.png",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -80,7 +80,7 @@
"after_route_name": "Fortree City", "after_route_name": "Fortree City",
"location": "Foretree Gym", "location": "Foretree Gym",
"section": "Main Story", "section": "Main Story",
"sprite_url": "/boss-sprites/ruby/winona.png", "sprite_url": "/boss-sprites/emerald/winona.png",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -94,7 +94,7 @@
"after_route_name": "Mossdeep City", "after_route_name": "Mossdeep City",
"location": "Mossdeep Gym", "location": "Mossdeep Gym",
"section": "Main Story", "section": "Main Story",
"sprite_url": "/boss-sprites/ruby/tate--lisa.png", "sprite_url": "/boss-sprites/emerald/tate--lisa.png",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -108,7 +108,7 @@
"after_route_name": "Sootopolis City", "after_route_name": "Sootopolis City",
"location": "Sootopolis Gym", "location": "Sootopolis Gym",
"section": "Main Story", "section": "Main Story",
"sprite_url": "/boss-sprites/ruby/juan.png", "sprite_url": "/boss-sprites/emerald/juan.png",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -122,7 +122,7 @@
"after_route_name": "Victory Road (Hoenn)", "after_route_name": "Victory Road (Hoenn)",
"location": "Ever Grande City", "location": "Ever Grande City",
"section": "Main Story", "section": "Main Story",
"sprite_url": "/boss-sprites/ruby/sydney.png", "sprite_url": "/boss-sprites/emerald/sydney.png",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -136,7 +136,7 @@
"after_route_name": "Victory Road (Hoenn)", "after_route_name": "Victory Road (Hoenn)",
"location": "Ever Grande City", "location": "Ever Grande City",
"section": "Main Story", "section": "Main Story",
"sprite_url": "/boss-sprites/ruby/phoebe.png", "sprite_url": "/boss-sprites/emerald/phoebe.png",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -150,7 +150,7 @@
"after_route_name": "Victory Road (Hoenn)", "after_route_name": "Victory Road (Hoenn)",
"location": "Ever Grande City", "location": "Ever Grande City",
"section": "Main Story", "section": "Main Story",
"sprite_url": "/boss-sprites/ruby/glacia.png", "sprite_url": "/boss-sprites/emerald/glacia.png",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -164,7 +164,7 @@
"after_route_name": "Victory Road (Hoenn)", "after_route_name": "Victory Road (Hoenn)",
"location": "Ever Grande City", "location": "Ever Grande City",
"section": "Main Story", "section": "Main Story",
"sprite_url": "/boss-sprites/ruby/drake.png", "sprite_url": "/boss-sprites/emerald/drake.png",
"pokemon": [] "pokemon": []
}, },
{ {

View File

@@ -145,9 +145,9 @@
{ {
"name": "Giovanni", "name": "Giovanni",
"boss_type": "gym_leader", "boss_type": "gym_leader",
"specialty_type": null, "specialty_type": "ground",
"badge_name": "50", "badge_name": "Earth Badge",
"badge_image_url": "/badges/50.png", "badge_image_url": "/badges/earth-badge.png",
"level_cap": 50, "level_cap": 50,
"order": 8, "order": 8,
"after_route_name": null, "after_route_name": null,
@@ -167,7 +167,7 @@
"after_route_name": null, "after_route_name": null,
"location": "Indigo Plateau", "location": "Indigo Plateau",
"section": null, "section": null,
"sprite_url": null, "sprite_url": "/boss-sprites/firered/lorelei.png",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -181,7 +181,7 @@
"after_route_name": null, "after_route_name": null,
"location": "Indigo Plateau", "location": "Indigo Plateau",
"section": null, "section": null,
"sprite_url": null, "sprite_url": "/boss-sprites/firered/bruno.png",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -195,7 +195,7 @@
"after_route_name": null, "after_route_name": null,
"location": "Indigo Plateau", "location": "Indigo Plateau",
"section": null, "section": null,
"sprite_url": null, "sprite_url": "/boss-sprites/firered/agatha.png",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -209,7 +209,7 @@
"after_route_name": null, "after_route_name": null,
"location": "Indigo Plateau", "location": "Indigo Plateau",
"section": null, "section": null,
"sprite_url": null, "sprite_url": "/boss-sprites/firered/lance.png",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -223,7 +223,7 @@
"after_route_name": null, "after_route_name": null,
"location": "Indigo Plateau", "location": "Indigo Plateau",
"section": null, "section": null,
"sprite_url": null, "sprite_url": "/boss-sprites/firered/blue.png",
"pokemon": [ "pokemon": [
{ {
"pokeapi_id": 18, "pokeapi_id": 18,

View File

@@ -122,7 +122,7 @@
"after_route_name": null, "after_route_name": null,
"location": "Indigo Plateau", "location": "Indigo Plateau",
"section": "Main Story", "section": "Main Story",
"sprite_url": null, "sprite_url": "/boss-sprites/heartgold/will.png",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -136,7 +136,7 @@
"after_route_name": null, "after_route_name": null,
"location": "Indigo Plateau", "location": "Indigo Plateau",
"section": "Main Story", "section": "Main Story",
"sprite_url": null, "sprite_url": "/boss-sprites/heartgold/koga.png",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -150,7 +150,7 @@
"after_route_name": null, "after_route_name": null,
"location": "Indigo Plateau", "location": "Indigo Plateau",
"section": "Main Story", "section": "Main Story",
"sprite_url": null, "sprite_url": "/boss-sprites/heartgold/bruno.png",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -164,7 +164,7 @@
"after_route_name": null, "after_route_name": null,
"location": "Indigo Plateau", "location": "Indigo Plateau",
"section": "Main Story", "section": "Main Story",
"sprite_url": null, "sprite_url": "/boss-sprites/heartgold/karen.png",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -178,7 +178,7 @@
"after_route_name": null, "after_route_name": null,
"location": "Indigo Plateau", "location": "Indigo Plateau",
"section": "Main Story", "section": "Main Story",
"sprite_url": null, "sprite_url": "/boss-sprites/heartgold/lance.png",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -304,7 +304,7 @@
"after_route_name": null, "after_route_name": null,
"location": "Silver Cave", "location": "Silver Cave",
"section": "Endgame", "section": "Endgame",
"sprite_url": null, "sprite_url": "/boss-sprites/heartgold/red.png",
"pokemon": [] "pokemon": []
} }
] ]

View File

@@ -10,7 +10,7 @@
"after_route_name": null, "after_route_name": null,
"location": "Grandtree Arena", "location": "Grandtree Arena",
"section": "Main Story", "section": "Main Story",
"sprite_url": "/boss-sprites/legends-arceus/lord-kleavor.png", "sprite_url": "/sprites/900.webp",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -24,7 +24,7 @@
"after_route_name": null, "after_route_name": null,
"location": "Brava Arena", "location": "Brava Arena",
"section": "Main Story", "section": "Main Story",
"sprite_url": "/boss-sprites/legends-arceus/lady-lilligant.png", "sprite_url": "/sprites/10237.webp",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -38,7 +38,7 @@
"after_route_name": null, "after_route_name": null,
"location": "Molten Arena", "location": "Molten Arena",
"section": "Main Story", "section": "Main Story",
"sprite_url": "/boss-sprites/legends-arceus/lord-arcanine.png", "sprite_url": "/sprites/10230.webp",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -52,7 +52,7 @@
"after_route_name": null, "after_route_name": null,
"location": "Moonview Arena", "location": "Moonview Arena",
"section": "Main Story", "section": "Main Story",
"sprite_url": "/boss-sprites/legends-arceus/lord-electrode.png", "sprite_url": "/sprites/10232.webp",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -66,7 +66,7 @@
"after_route_name": null, "after_route_name": null,
"location": "Icepeak Arena", "location": "Icepeak Arena",
"section": "Main Story", "section": "Main Story",
"sprite_url": "/boss-sprites/legends-arceus/lord-avalugg.png", "sprite_url": "/sprites/10243.webp",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -80,7 +80,7 @@
"after_route_name": null, "after_route_name": null,
"location": "Temple of Sinnoh", "location": "Temple of Sinnoh",
"section": "Main Story", "section": "Main Story",
"sprite_url": "/boss-sprites/legends-arceus/origin-dialga--palkia.png", "sprite_url": "/sprites/10245.webp",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -94,7 +94,7 @@
"after_route_name": null, "after_route_name": null,
"location": "Temple of Sinnoh", "location": "Temple of Sinnoh",
"section": "Main Story", "section": "Main Story",
"sprite_url": "/boss-sprites/legends-arceus/arceus.png", "sprite_url": "/sprites/493.webp",
"pokemon": [] "pokemon": []
} }
] ]

View File

@@ -276,7 +276,7 @@
"after_route_name": null, "after_route_name": null,
"location": "South Province (Area Three)", "location": "South Province (Area Three)",
"section": "Path of Legends", "section": "Path of Legends",
"sprite_url": "/boss-sprites/scarlet/stony-cliff-titan.png", "sprite_url": "/sprites/950.webp",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -290,7 +290,7 @@
"after_route_name": null, "after_route_name": null,
"location": "West Province (Area One)", "location": "West Province (Area One)",
"section": "Path of Legends", "section": "Path of Legends",
"sprite_url": "/boss-sprites/scarlet/open-sky-titan.png", "sprite_url": "/sprites/962.webp",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -304,7 +304,7 @@
"after_route_name": null, "after_route_name": null,
"location": "East Province (Area Three)", "location": "East Province (Area Three)",
"section": "Path of Legends", "section": "Path of Legends",
"sprite_url": "/boss-sprites/scarlet/lurking-steel-titan.png", "sprite_url": "/sprites/968.webp",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -318,7 +318,7 @@
"after_route_name": null, "after_route_name": null,
"location": "Asado Desert", "location": "Asado Desert",
"section": "Path of Legends", "section": "Path of Legends",
"sprite_url": "/boss-sprites/scarlet/quaking-earth-titan.png", "sprite_url": "/sprites/984.webp",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -332,7 +332,7 @@
"after_route_name": null, "after_route_name": null,
"location": "Casseroya Lake", "location": "Casseroya Lake",
"section": "Path of Legends", "section": "Path of Legends",
"sprite_url": "/boss-sprites/scarlet/false-dragon-titan.png", "sprite_url": "/sprites/977.webp",
"pokemon": [] "pokemon": []
} }
] ]

View File

@@ -10,7 +10,7 @@
"after_route_name": null, "after_route_name": null,
"location": "Verdant Cavern", "location": "Verdant Cavern",
"section": "Melemele Island", "section": "Melemele Island",
"sprite_url": "/boss-sprites/sun/totem-gumshoos.png", "sprite_url": "/sprites/735.webp",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -38,7 +38,7 @@
"after_route_name": null, "after_route_name": null,
"location": "Brooklet Hill", "location": "Brooklet Hill",
"section": "Akala Island", "section": "Akala Island",
"sprite_url": "/boss-sprites/sun/totem-wishiwashi.png", "sprite_url": "/sprites/746.webp",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -52,7 +52,7 @@
"after_route_name": null, "after_route_name": null,
"location": "Wela Volcano Park", "location": "Wela Volcano Park",
"section": "Akala Island", "section": "Akala Island",
"sprite_url": "/boss-sprites/sun/totem-salazzle.png", "sprite_url": "/sprites/758.webp",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -66,7 +66,7 @@
"after_route_name": null, "after_route_name": null,
"location": "Lush Jungle", "location": "Lush Jungle",
"section": "Akala Island", "section": "Akala Island",
"sprite_url": "/boss-sprites/sun/totem-lurantis.png", "sprite_url": "/sprites/754.webp",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -94,7 +94,7 @@
"after_route_name": null, "after_route_name": null,
"location": "Hokulani Observatory", "location": "Hokulani Observatory",
"section": "Ula'ula Island", "section": "Ula'ula Island",
"sprite_url": "/boss-sprites/sun/totem-vikavolt.png", "sprite_url": "/sprites/738.webp",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -108,7 +108,7 @@
"after_route_name": null, "after_route_name": null,
"location": "Thrifty Megamart", "location": "Thrifty Megamart",
"section": "Ula'ula Island", "section": "Ula'ula Island",
"sprite_url": "/boss-sprites/sun/totem-mimikyu.png", "sprite_url": "/sprites/778.webp",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -136,7 +136,7 @@
"after_route_name": null, "after_route_name": null,
"location": "Vast Poni Canyon", "location": "Vast Poni Canyon",
"section": "Poni Island", "section": "Poni Island",
"sprite_url": "/boss-sprites/sun/totem-kommo-o.png", "sprite_url": "/sprites/784.webp",
"pokemon": [] "pokemon": []
}, },
{ {

View File

@@ -51,8 +51,9 @@
"order": 4, "order": 4,
"after_route_name": null, "after_route_name": null,
"location": "Stow-on-Side Stadium", "location": "Stow-on-Side Stadium",
"section": "Sword", "section": null,
"sprite_url": "/boss-sprites/sword/bea.png", "sprite_url": "/boss-sprites/sword/bea.png",
"game_slug": "sword",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -65,8 +66,9 @@
"order": 5, "order": 5,
"after_route_name": null, "after_route_name": null,
"location": "Stow-on-Side Stadium", "location": "Stow-on-Side Stadium",
"section": "Shield", "section": null,
"sprite_url": "/boss-sprites/sword/allister.png", "sprite_url": "/boss-sprites/sword/allister.png",
"game_slug": "shield",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -93,8 +95,9 @@
"order": 7, "order": 7,
"after_route_name": null, "after_route_name": null,
"location": "Circhester Stadium", "location": "Circhester Stadium",
"section": "Sword", "section": null,
"sprite_url": "/boss-sprites/sword/gordie.png", "sprite_url": "/boss-sprites/sword/gordie.png",
"game_slug": "sword",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -107,8 +110,9 @@
"order": 8, "order": 8,
"after_route_name": null, "after_route_name": null,
"location": "Circhester Stadium", "location": "Circhester Stadium",
"section": "Shield", "section": null,
"sprite_url": "/boss-sprites/sword/melony.png", "sprite_url": "/boss-sprites/sword/melony.png",
"game_slug": "shield",
"pokemon": [] "pokemon": []
}, },
{ {

View File

@@ -10,7 +10,7 @@
"after_route_name": null, "after_route_name": null,
"location": "Verdant Cavern", "location": "Verdant Cavern",
"section": "Melemele Island", "section": "Melemele Island",
"sprite_url": "/boss-sprites/ultra-sun/totem-gumshoos.png", "sprite_url": "/sprites/735.webp",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -38,7 +38,7 @@
"after_route_name": null, "after_route_name": null,
"location": "Brooklet Hill", "location": "Brooklet Hill",
"section": "Akala Island", "section": "Akala Island",
"sprite_url": "/boss-sprites/ultra-sun/totem-araquanid.png", "sprite_url": "/sprites/752.webp",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -52,7 +52,7 @@
"after_route_name": null, "after_route_name": null,
"location": "Wela Volcano Park", "location": "Wela Volcano Park",
"section": "Akala Island", "section": "Akala Island",
"sprite_url": "/boss-sprites/ultra-sun/totem-salazzle.png", "sprite_url": "/sprites/758.webp",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -66,7 +66,7 @@
"after_route_name": null, "after_route_name": null,
"location": "Lush Jungle", "location": "Lush Jungle",
"section": "Akala Island", "section": "Akala Island",
"sprite_url": "/boss-sprites/ultra-sun/totem-lurantis.png", "sprite_url": "/sprites/754.webp",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -94,7 +94,7 @@
"after_route_name": null, "after_route_name": null,
"location": "Hokulani Observatory", "location": "Hokulani Observatory",
"section": "Ula'ula Island", "section": "Ula'ula Island",
"sprite_url": "/boss-sprites/ultra-sun/totem-vikavolt.png", "sprite_url": "/sprites/738.webp",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -108,7 +108,7 @@
"after_route_name": null, "after_route_name": null,
"location": "Thrifty Megamart", "location": "Thrifty Megamart",
"section": "Ula'ula Island", "section": "Ula'ula Island",
"sprite_url": "/boss-sprites/ultra-sun/totem-mimikyu.png", "sprite_url": "/sprites/778.webp",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -122,7 +122,7 @@
"after_route_name": null, "after_route_name": null,
"location": "Hokulani Observatory", "location": "Hokulani Observatory",
"section": "Ula'ula Island", "section": "Ula'ula Island",
"sprite_url": "/boss-sprites/ultra-sun/totem-togedemaru.png", "sprite_url": "/sprites/777.webp",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -150,7 +150,7 @@
"after_route_name": null, "after_route_name": null,
"location": "Vast Poni Canyon", "location": "Vast Poni Canyon",
"section": "Poni Island", "section": "Poni Island",
"sprite_url": "/boss-sprites/ultra-sun/totem-kommo-o.png", "sprite_url": "/sprites/784.webp",
"pokemon": [] "pokemon": []
}, },
{ {
@@ -164,7 +164,7 @@
"after_route_name": null, "after_route_name": null,
"location": "Seafolk Village", "location": "Seafolk Village",
"section": "Poni Island", "section": "Poni Island",
"sprite_url": "/boss-sprites/ultra-sun/totem-ribombee.png", "sprite_url": "/sprites/743.webp",
"pokemon": [] "pokemon": []
}, },
{ {

View File

@@ -239,6 +239,7 @@ async def upsert_bosses(
bosses: list[dict], bosses: list[dict],
dex_to_id: dict[int, int], dex_to_id: dict[int, int],
route_name_to_id: dict[str, int] | None = None, route_name_to_id: dict[str, int] | None = None,
slug_to_game_id: dict[str, int] | None = None,
) -> int: ) -> int:
"""Upsert boss battles for a version group, return count of bosses upserted.""" """Upsert boss battles for a version group, return count of bosses upserted."""
count = 0 count = 0
@@ -253,6 +254,16 @@ async def upsert_bosses(
f" Warning: route '{after_route_name}' not found for boss '{boss['name']}'" f" Warning: route '{after_route_name}' not found for boss '{boss['name']}'"
) )
# Resolve game_slug to game_id
game_id = None
game_slug = boss.get("game_slug")
if game_slug and slug_to_game_id:
game_id = slug_to_game_id.get(game_slug)
if game_id is None:
print(
f" Warning: game '{game_slug}' not found for boss '{boss['name']}'"
)
# Upsert the boss battle on (version_group_id, order) conflict # Upsert the boss battle on (version_group_id, order) conflict
stmt = ( stmt = (
insert(BossBattle) insert(BossBattle)
@@ -269,6 +280,7 @@ async def upsert_bosses(
location=boss["location"], location=boss["location"],
section=boss.get("section"), section=boss.get("section"),
sprite_url=boss.get("sprite_url"), sprite_url=boss.get("sprite_url"),
game_id=game_id,
) )
.on_conflict_do_update( .on_conflict_do_update(
constraint="uq_boss_battles_version_group_order", constraint="uq_boss_battles_version_group_order",
@@ -283,6 +295,7 @@ async def upsert_bosses(
"location": boss["location"], "location": boss["location"],
"section": boss.get("section"), "section": boss.get("section"),
"sprite_url": boss.get("sprite_url"), "sprite_url": boss.get("sprite_url"),
"game_id": game_id,
}, },
) )
.returning(BossBattle.id) .returning(BossBattle.id)

View File

@@ -160,7 +160,7 @@ async def seed():
route_name_to_id = route_maps_by_vg.get(vg_id, {}) route_name_to_id = route_maps_by_vg.get(vg_id, {})
boss_count = await upsert_bosses( boss_count = await upsert_bosses(
session, vg_id, bosses_data, dex_to_id, route_name_to_id session, vg_id, bosses_data, dex_to_id, route_name_to_id, slug_to_id
) )
total_bosses += boss_count total_bosses += boss_count
print(f" {vg_slug}: {boss_count} bosses") print(f" {vg_slug}: {boss_count} bosses")
@@ -491,6 +491,7 @@ async def _export_bosses(session: AsyncSession, vg_data: dict):
.options( .options(
selectinload(BossBattle.pokemon).selectinload(BossPokemon.pokemon), selectinload(BossBattle.pokemon).selectinload(BossPokemon.pokemon),
selectinload(BossBattle.after_route), selectinload(BossBattle.after_route),
selectinload(BossBattle.game),
) )
.order_by(BossBattle.order) .order_by(BossBattle.order)
) )
@@ -525,8 +526,7 @@ async def _export_bosses(session: AsyncSession, vg_data: dict):
downloaded_sprites, downloaded_sprites,
) )
data.append( boss_dict: dict = {
{
"name": b.name, "name": b.name,
"boss_type": b.boss_type, "boss_type": b.boss_type,
"specialty_type": b.specialty_type, "specialty_type": b.specialty_type,
@@ -548,7 +548,9 @@ async def _export_bosses(session: AsyncSession, vg_data: dict):
for bp in sorted(b.pokemon, key=lambda p: p.order) for bp in sorted(b.pokemon, key=lambda p: p.order)
], ],
} }
) if b.game_id:
boss_dict["game_slug"] = b.game.slug
data.append(boss_dict)
_write_json(f"{first_game_slug}-bosses.json", data) _write_json(f"{first_game_slug}-bosses.json", data)
exported += 1 exported += 1

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 561 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 493 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 585 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 566 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 604 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 683 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 524 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 821 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 745 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 973 B

Some files were not shown because too many files have changed in this diff Show More