diff --git a/.beans/nuzlocke-tracker-yfi8--fetch-boss-battle-sprites-from-bulbapedia.md b/.beans/nuzlocke-tracker-yfi8--fetch-boss-battle-sprites-from-bulbapedia.md new file mode 100644 index 0000000..a75639c --- /dev/null +++ b/.beans/nuzlocke-tracker-yfi8--fetch-boss-battle-sprites-from-bulbapedia.md @@ -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 \ No newline at end of file diff --git a/.beans/nuzlocke-tracker-zmvy--add-game-id-field-to-bossbattle-for-version-exclus.md b/.beans/nuzlocke-tracker-zmvy--add-game-id-field-to-bossbattle-for-version-exclus.md new file mode 100644 index 0000000..3166750 --- /dev/null +++ b/.beans/nuzlocke-tracker-zmvy--add-game-id-field-to-bossbattle-for-version-exclus.md @@ -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. \ No newline at end of file diff --git a/backend/src/app/alembic/versions/f7a8b9c0d1e2_add_game_id_to_boss_battles.py b/backend/src/app/alembic/versions/f7a8b9c0d1e2_add_game_id_to_boss_battles.py new file mode 100644 index 0000000..488551c --- /dev/null +++ b/backend/src/app/alembic/versions/f7a8b9c0d1e2_add_game_id_to_boss_battles.py @@ -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") diff --git a/backend/src/app/api/bosses.py b/backend/src/app/api/bosses.py index 069f234..187203a 100644 --- a/backend/src/app/api/bosses.py +++ b/backend/src/app/api/bosses.py @@ -1,7 +1,7 @@ from datetime import UTC, datetime from fastapi import APIRouter, Depends, HTTPException, Response -from sqlalchemy import select +from sqlalchemy import or_, select from sqlalchemy.ext.asyncio import AsyncSession 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]) -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) - result = await session.execute( + query = ( select(BossBattle) .where(BossBattle.version_group_id == vg_id) .options(selectinload(BossBattle.pokemon).selectinload(BossPokemon.pokemon)) .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() @@ -106,6 +117,14 @@ async def create_boss( ): 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()) session.add(boss) await session.commit() @@ -128,6 +147,14 @@ async def update_boss( ): 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( select(BossBattle) .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} + # 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] try: 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: raise HTTPException( diff --git a/backend/src/app/api/export.py b/backend/src/app/api/export.py index ab14e1c..e620a22 100644 --- a/backend/src/app/api/export.py +++ b/backend/src/app/api/export.py @@ -126,6 +126,7 @@ async def export_game_bosses( .options( selectinload(BossBattle.pokemon).selectinload(BossPokemon.pokemon), selectinload(BossBattle.after_route), + selectinload(BossBattle.game), ) .order_by(BossBattle.order) ) @@ -146,6 +147,7 @@ async def export_game_bosses( "location": b.location, "section": b.section, "sprite_url": b.sprite_url, + **({"game_slug": b.game.slug} if b.game_id else {}), "pokemon": [ { "pokeapi_id": bp.pokemon.pokeapi_id, diff --git a/backend/src/app/models/boss_battle.py b/backend/src/app/models/boss_battle.py index 5d4c405..4049079 100644 --- a/backend/src/app/models/boss_battle.py +++ b/backend/src/app/models/boss_battle.py @@ -33,9 +33,13 @@ class BossBattle(Base): location: Mapped[str] = mapped_column(String(200)) section: Mapped[str | None] = mapped_column(String(100), default=None) 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") after_route: Mapped["Route | None"] = relationship() + game: Mapped["Game | None"] = relationship() pokemon: Mapped[list["BossPokemon"]] = relationship( back_populates="boss_battle", cascade="all, delete-orphan" ) diff --git a/backend/src/app/schemas/boss.py b/backend/src/app/schemas/boss.py index 61b2b17..bc581e2 100644 --- a/backend/src/app/schemas/boss.py +++ b/backend/src/app/schemas/boss.py @@ -27,6 +27,7 @@ class BossBattleResponse(CamelModel): location: str section: str | None sprite_url: str | None + game_id: int | None pokemon: list[BossPokemonResponse] = [] @@ -54,6 +55,7 @@ class BossBattleCreate(CamelModel): location: str section: str | None = None sprite_url: str | None = None + game_id: int | None = None class BossBattleUpdate(CamelModel): @@ -68,6 +70,7 @@ class BossBattleUpdate(CamelModel): location: str | None = None section: str | None = None sprite_url: str | None = None + game_id: int | None = None class BossPokemonInput(CamelModel): diff --git a/backend/src/app/schemas/pokemon.py b/backend/src/app/schemas/pokemon.py index c6dce95..94ca898 100644 --- a/backend/src/app/schemas/pokemon.py +++ b/backend/src/app/schemas/pokemon.py @@ -215,4 +215,5 @@ class BulkBossItem(BaseModel): location: str section: str | None = None sprite_url: str | None = None + game_slug: str | None = None pokemon: list[BulkBossPokemonItem] = [] diff --git a/backend/src/app/seeds/data/black-bosses.json b/backend/src/app/seeds/data/black-bosses.json index 427a2c0..2fb81f6 100644 --- a/backend/src/app/seeds/data/black-bosses.json +++ b/backend/src/app/seeds/data/black-bosses.json @@ -107,8 +107,9 @@ "order": 8, "after_route_name": null, "location": "Opelucid Gym", - "section": "Black", + "section": null, "sprite_url": "/boss-sprites/black/drayden.png", + "game_slug": "black", "pokemon": [] }, { @@ -121,8 +122,9 @@ "order": 9, "after_route_name": null, "location": "Opelucid Gym", - "section": "White", + "section": null, "sprite_url": "/boss-sprites/black/iris.png", + "game_slug": "white", "pokemon": [] }, { diff --git a/backend/src/app/seeds/data/crystal-bosses.json b/backend/src/app/seeds/data/crystal-bosses.json index 878df3d..3bbf644 100644 --- a/backend/src/app/seeds/data/crystal-bosses.json +++ b/backend/src/app/seeds/data/crystal-bosses.json @@ -10,7 +10,7 @@ "after_route_name": "Violet City", "location": "Violet Gym", "section": "Main Story", - "sprite_url": "/boss-sprites/gold/falkner.png", + "sprite_url": "/boss-sprites/crystal/falkner.png", "pokemon": [] }, { @@ -24,7 +24,7 @@ "after_route_name": "Slowpoke Well", "location": "Azalea Gym", "section": "Main Story", - "sprite_url": "/boss-sprites/gold/bugsy.png", + "sprite_url": "/boss-sprites/crystal/bugsy.png", "pokemon": [] }, { @@ -38,7 +38,7 @@ "after_route_name": "Goldenrod City", "location": "Goldenrod Gym", "section": "Main Story", - "sprite_url": "/boss-sprites/gold/whitney.png", + "sprite_url": "/boss-sprites/crystal/whitney.png", "pokemon": [] }, { @@ -52,7 +52,7 @@ "after_route_name": "Ecruteak City", "location": "Ecruteak Gym", "section": "Main Story", - "sprite_url": "/boss-sprites/gold/morty.png", + "sprite_url": "/boss-sprites/crystal/morty.png", "pokemon": [] }, { @@ -66,7 +66,7 @@ "after_route_name": "Cianwood City", "location": "Cianwood Gym", "section": "Main Story", - "sprite_url": "/boss-sprites/gold/chuck.png", + "sprite_url": "/boss-sprites/crystal/chuck.png", "pokemon": [] }, { @@ -80,7 +80,7 @@ "after_route_name": "Cianwood City", "location": "Olivine Gym", "section": "Main Story", - "sprite_url": "/boss-sprites/gold/jasmine.png", + "sprite_url": "/boss-sprites/crystal/jasmine.png", "pokemon": [] }, { @@ -94,7 +94,7 @@ "after_route_name": "Lake of Rage", "location": "Mahogany Gym", "section": "Main Story", - "sprite_url": "/boss-sprites/gold/pryce.png", + "sprite_url": "/boss-sprites/crystal/pryce.png", "pokemon": [] }, { @@ -108,7 +108,7 @@ "after_route_name": "Blackthorn City", "location": "Blackthorn Gym", "section": "Main Story", - "sprite_url": "/boss-sprites/gold/clair.png", + "sprite_url": "/boss-sprites/crystal/clair.png", "pokemon": [] }, { @@ -122,7 +122,7 @@ "after_route_name": "Victory Road (Kanto)", "location": "Indigo Plateau", "section": "Main Story", - "sprite_url": "/boss-sprites/gold/will.png", + "sprite_url": "/boss-sprites/crystal/will.png", "pokemon": [] }, { @@ -136,7 +136,7 @@ "after_route_name": "Victory Road (Kanto)", "location": "Indigo Plateau", "section": "Main Story", - "sprite_url": "/boss-sprites/gold/koga.png", + "sprite_url": "/boss-sprites/crystal/koga.png", "pokemon": [] }, { @@ -150,7 +150,7 @@ "after_route_name": "Victory Road (Kanto)", "location": "Indigo Plateau", "section": "Main Story", - "sprite_url": "/boss-sprites/gold/bruno.png", + "sprite_url": "/boss-sprites/crystal/bruno.png", "pokemon": [] }, { @@ -164,7 +164,7 @@ "after_route_name": "Victory Road (Kanto)", "location": "Indigo Plateau", "section": "Main Story", - "sprite_url": "/boss-sprites/gold/karen.png", + "sprite_url": "/boss-sprites/crystal/karen.png", "pokemon": [] }, { @@ -178,7 +178,7 @@ "after_route_name": "Victory Road (Kanto)", "location": "Indigo Plateau", "section": "Main Story", - "sprite_url": "/boss-sprites/gold/lance.png", + "sprite_url": "/boss-sprites/crystal/lance.png", "pokemon": [] }, { @@ -192,7 +192,7 @@ "after_route_name": "Vermilion City", "location": "Vermilion Gym", "section": "Endgame", - "sprite_url": "/boss-sprites/gold/lt-surge.png", + "sprite_url": "/boss-sprites/crystal/lt-surge.png", "pokemon": [] }, { @@ -206,7 +206,7 @@ "after_route_name": "Route 05 (Kanto)", "location": "Saffron Gym", "section": "Endgame", - "sprite_url": "/boss-sprites/gold/sabrina.png", + "sprite_url": "/boss-sprites/crystal/sabrina.png", "pokemon": [] }, { @@ -220,7 +220,7 @@ "after_route_name": "Cerulean Cave (B1F)", "location": "Cerulean Gym", "section": "Endgame", - "sprite_url": "/boss-sprites/gold/misty.png", + "sprite_url": "/boss-sprites/crystal/misty.png", "pokemon": [] }, { @@ -234,7 +234,7 @@ "after_route_name": "Celadon City", "location": "Celadon Gym", "section": "Endgame", - "sprite_url": "/boss-sprites/gold/erika.png", + "sprite_url": "/boss-sprites/crystal/erika.png", "pokemon": [] }, { @@ -248,7 +248,7 @@ "after_route_name": "Fuchsia City", "location": "Fuchsia Gym", "section": "Endgame", - "sprite_url": "/boss-sprites/gold/janine.png", + "sprite_url": "/boss-sprites/crystal/janine.png", "pokemon": [] }, { @@ -262,7 +262,7 @@ "after_route_name": "Pewter City", "location": "Pewter Gym", "section": "Endgame", - "sprite_url": "/boss-sprites/gold/brock.png", + "sprite_url": "/boss-sprites/crystal/brock.png", "pokemon": [] }, { @@ -276,7 +276,7 @@ "after_route_name": "Cinnabar Island", "location": "Cinnabar Gym", "section": "Endgame", - "sprite_url": "/boss-sprites/gold/blaine.png", + "sprite_url": "/boss-sprites/crystal/blaine.png", "pokemon": [] }, { @@ -290,7 +290,7 @@ "after_route_name": "Viridian City", "location": "Viridian Gym", "section": "Endgame", - "sprite_url": "/boss-sprites/gold/blue.png", + "sprite_url": "/boss-sprites/crystal/blue.png", "pokemon": [] }, { @@ -304,7 +304,7 @@ "after_route_name": "Mt. Silver (Cave Full Restore Chamber)", "location": "Silver Cave", "section": "Endgame", - "sprite_url": "/boss-sprites/gold/red.png", + "sprite_url": "/boss-sprites/crystal/red.png", "pokemon": [] } ] diff --git a/backend/src/app/seeds/data/emerald-bosses.json b/backend/src/app/seeds/data/emerald-bosses.json index 8183b76..f1b46f3 100644 --- a/backend/src/app/seeds/data/emerald-bosses.json +++ b/backend/src/app/seeds/data/emerald-bosses.json @@ -10,7 +10,7 @@ "after_route_name": "Rustboro City", "location": "Rustboro Gym", "section": "Main Story", - "sprite_url": "/boss-sprites/ruby/roxanne.png", + "sprite_url": "/boss-sprites/emerald/roxanne.png", "pokemon": [] }, { @@ -24,7 +24,7 @@ "after_route_name": "Dewford Town", "location": "Dewford Gym", "section": "Main Story", - "sprite_url": "/boss-sprites/ruby/brawly.png", + "sprite_url": "/boss-sprites/emerald/brawly.png", "pokemon": [] }, { @@ -38,7 +38,7 @@ "after_route_name": "Hoenn Route 110", "location": "Mauville Gym", "section": "Main Story", - "sprite_url": "/boss-sprites/ruby/wattson.png", + "sprite_url": "/boss-sprites/emerald/wattson.png", "pokemon": [] }, { @@ -52,7 +52,7 @@ "after_route_name": "Lavaridge Town", "location": "Lavaridge Gym", "section": "Main Story", - "sprite_url": "/boss-sprites/ruby/flannery.png", + "sprite_url": "/boss-sprites/emerald/flannery.png", "pokemon": [] }, { @@ -66,7 +66,7 @@ "after_route_name": "Desert Ruins", "location": "Petalburg Gym", "section": "Main Story", - "sprite_url": "/boss-sprites/ruby/norman.png", + "sprite_url": "/boss-sprites/emerald/norman.png", "pokemon": [] }, { @@ -80,7 +80,7 @@ "after_route_name": "Fortree City", "location": "Foretree Gym", "section": "Main Story", - "sprite_url": "/boss-sprites/ruby/winona.png", + "sprite_url": "/boss-sprites/emerald/winona.png", "pokemon": [] }, { @@ -94,7 +94,7 @@ "after_route_name": "Mossdeep City", "location": "Mossdeep Gym", "section": "Main Story", - "sprite_url": "/boss-sprites/ruby/tate--lisa.png", + "sprite_url": "/boss-sprites/emerald/tate--lisa.png", "pokemon": [] }, { @@ -108,7 +108,7 @@ "after_route_name": "Sootopolis City", "location": "Sootopolis Gym", "section": "Main Story", - "sprite_url": "/boss-sprites/ruby/juan.png", + "sprite_url": "/boss-sprites/emerald/juan.png", "pokemon": [] }, { @@ -122,7 +122,7 @@ "after_route_name": "Victory Road (Hoenn)", "location": "Ever Grande City", "section": "Main Story", - "sprite_url": "/boss-sprites/ruby/sydney.png", + "sprite_url": "/boss-sprites/emerald/sydney.png", "pokemon": [] }, { @@ -136,7 +136,7 @@ "after_route_name": "Victory Road (Hoenn)", "location": "Ever Grande City", "section": "Main Story", - "sprite_url": "/boss-sprites/ruby/phoebe.png", + "sprite_url": "/boss-sprites/emerald/phoebe.png", "pokemon": [] }, { @@ -150,7 +150,7 @@ "after_route_name": "Victory Road (Hoenn)", "location": "Ever Grande City", "section": "Main Story", - "sprite_url": "/boss-sprites/ruby/glacia.png", + "sprite_url": "/boss-sprites/emerald/glacia.png", "pokemon": [] }, { @@ -164,7 +164,7 @@ "after_route_name": "Victory Road (Hoenn)", "location": "Ever Grande City", "section": "Main Story", - "sprite_url": "/boss-sprites/ruby/drake.png", + "sprite_url": "/boss-sprites/emerald/drake.png", "pokemon": [] }, { diff --git a/backend/src/app/seeds/data/firered-bosses.json b/backend/src/app/seeds/data/firered-bosses.json index 3f452a3..517a245 100644 --- a/backend/src/app/seeds/data/firered-bosses.json +++ b/backend/src/app/seeds/data/firered-bosses.json @@ -145,9 +145,9 @@ { "name": "Giovanni", "boss_type": "gym_leader", - "specialty_type": null, - "badge_name": "50", - "badge_image_url": "/badges/50.png", + "specialty_type": "ground", + "badge_name": "Earth Badge", + "badge_image_url": "/badges/earth-badge.png", "level_cap": 50, "order": 8, "after_route_name": null, @@ -167,7 +167,7 @@ "after_route_name": null, "location": "Indigo Plateau", "section": null, - "sprite_url": null, + "sprite_url": "/boss-sprites/firered/lorelei.png", "pokemon": [] }, { @@ -181,7 +181,7 @@ "after_route_name": null, "location": "Indigo Plateau", "section": null, - "sprite_url": null, + "sprite_url": "/boss-sprites/firered/bruno.png", "pokemon": [] }, { @@ -195,7 +195,7 @@ "after_route_name": null, "location": "Indigo Plateau", "section": null, - "sprite_url": null, + "sprite_url": "/boss-sprites/firered/agatha.png", "pokemon": [] }, { @@ -209,7 +209,7 @@ "after_route_name": null, "location": "Indigo Plateau", "section": null, - "sprite_url": null, + "sprite_url": "/boss-sprites/firered/lance.png", "pokemon": [] }, { @@ -223,7 +223,7 @@ "after_route_name": null, "location": "Indigo Plateau", "section": null, - "sprite_url": null, + "sprite_url": "/boss-sprites/firered/blue.png", "pokemon": [ { "pokeapi_id": 18, diff --git a/backend/src/app/seeds/data/heartgold-bosses.json b/backend/src/app/seeds/data/heartgold-bosses.json index 4663874..cdf02c0 100644 --- a/backend/src/app/seeds/data/heartgold-bosses.json +++ b/backend/src/app/seeds/data/heartgold-bosses.json @@ -122,7 +122,7 @@ "after_route_name": null, "location": "Indigo Plateau", "section": "Main Story", - "sprite_url": null, + "sprite_url": "/boss-sprites/heartgold/will.png", "pokemon": [] }, { @@ -136,7 +136,7 @@ "after_route_name": null, "location": "Indigo Plateau", "section": "Main Story", - "sprite_url": null, + "sprite_url": "/boss-sprites/heartgold/koga.png", "pokemon": [] }, { @@ -150,7 +150,7 @@ "after_route_name": null, "location": "Indigo Plateau", "section": "Main Story", - "sprite_url": null, + "sprite_url": "/boss-sprites/heartgold/bruno.png", "pokemon": [] }, { @@ -164,7 +164,7 @@ "after_route_name": null, "location": "Indigo Plateau", "section": "Main Story", - "sprite_url": null, + "sprite_url": "/boss-sprites/heartgold/karen.png", "pokemon": [] }, { @@ -178,7 +178,7 @@ "after_route_name": null, "location": "Indigo Plateau", "section": "Main Story", - "sprite_url": null, + "sprite_url": "/boss-sprites/heartgold/lance.png", "pokemon": [] }, { @@ -304,7 +304,7 @@ "after_route_name": null, "location": "Silver Cave", "section": "Endgame", - "sprite_url": null, + "sprite_url": "/boss-sprites/heartgold/red.png", "pokemon": [] } ] diff --git a/backend/src/app/seeds/data/legends-arceus-bosses.json b/backend/src/app/seeds/data/legends-arceus-bosses.json index 7acc41d..7764a82 100644 --- a/backend/src/app/seeds/data/legends-arceus-bosses.json +++ b/backend/src/app/seeds/data/legends-arceus-bosses.json @@ -10,7 +10,7 @@ "after_route_name": null, "location": "Grandtree Arena", "section": "Main Story", - "sprite_url": "/boss-sprites/legends-arceus/lord-kleavor.png", + "sprite_url": "/sprites/900.webp", "pokemon": [] }, { @@ -24,7 +24,7 @@ "after_route_name": null, "location": "Brava Arena", "section": "Main Story", - "sprite_url": "/boss-sprites/legends-arceus/lady-lilligant.png", + "sprite_url": "/sprites/10237.webp", "pokemon": [] }, { @@ -38,7 +38,7 @@ "after_route_name": null, "location": "Molten Arena", "section": "Main Story", - "sprite_url": "/boss-sprites/legends-arceus/lord-arcanine.png", + "sprite_url": "/sprites/10230.webp", "pokemon": [] }, { @@ -52,7 +52,7 @@ "after_route_name": null, "location": "Moonview Arena", "section": "Main Story", - "sprite_url": "/boss-sprites/legends-arceus/lord-electrode.png", + "sprite_url": "/sprites/10232.webp", "pokemon": [] }, { @@ -66,7 +66,7 @@ "after_route_name": null, "location": "Icepeak Arena", "section": "Main Story", - "sprite_url": "/boss-sprites/legends-arceus/lord-avalugg.png", + "sprite_url": "/sprites/10243.webp", "pokemon": [] }, { @@ -80,7 +80,7 @@ "after_route_name": null, "location": "Temple of Sinnoh", "section": "Main Story", - "sprite_url": "/boss-sprites/legends-arceus/origin-dialga--palkia.png", + "sprite_url": "/sprites/10245.webp", "pokemon": [] }, { @@ -94,7 +94,7 @@ "after_route_name": null, "location": "Temple of Sinnoh", "section": "Main Story", - "sprite_url": "/boss-sprites/legends-arceus/arceus.png", + "sprite_url": "/sprites/493.webp", "pokemon": [] } ] diff --git a/backend/src/app/seeds/data/scarlet-bosses.json b/backend/src/app/seeds/data/scarlet-bosses.json index f369e18..f8d904b 100644 --- a/backend/src/app/seeds/data/scarlet-bosses.json +++ b/backend/src/app/seeds/data/scarlet-bosses.json @@ -276,7 +276,7 @@ "after_route_name": null, "location": "South Province (Area Three)", "section": "Path of Legends", - "sprite_url": "/boss-sprites/scarlet/stony-cliff-titan.png", + "sprite_url": "/sprites/950.webp", "pokemon": [] }, { @@ -290,7 +290,7 @@ "after_route_name": null, "location": "West Province (Area One)", "section": "Path of Legends", - "sprite_url": "/boss-sprites/scarlet/open-sky-titan.png", + "sprite_url": "/sprites/962.webp", "pokemon": [] }, { @@ -304,7 +304,7 @@ "after_route_name": null, "location": "East Province (Area Three)", "section": "Path of Legends", - "sprite_url": "/boss-sprites/scarlet/lurking-steel-titan.png", + "sprite_url": "/sprites/968.webp", "pokemon": [] }, { @@ -318,7 +318,7 @@ "after_route_name": null, "location": "Asado Desert", "section": "Path of Legends", - "sprite_url": "/boss-sprites/scarlet/quaking-earth-titan.png", + "sprite_url": "/sprites/984.webp", "pokemon": [] }, { @@ -332,7 +332,7 @@ "after_route_name": null, "location": "Casseroya Lake", "section": "Path of Legends", - "sprite_url": "/boss-sprites/scarlet/false-dragon-titan.png", + "sprite_url": "/sprites/977.webp", "pokemon": [] } ] diff --git a/backend/src/app/seeds/data/sun-bosses.json b/backend/src/app/seeds/data/sun-bosses.json index 34579bb..8f70001 100644 --- a/backend/src/app/seeds/data/sun-bosses.json +++ b/backend/src/app/seeds/data/sun-bosses.json @@ -10,7 +10,7 @@ "after_route_name": null, "location": "Verdant Cavern", "section": "Melemele Island", - "sprite_url": "/boss-sprites/sun/totem-gumshoos.png", + "sprite_url": "/sprites/735.webp", "pokemon": [] }, { @@ -38,7 +38,7 @@ "after_route_name": null, "location": "Brooklet Hill", "section": "Akala Island", - "sprite_url": "/boss-sprites/sun/totem-wishiwashi.png", + "sprite_url": "/sprites/746.webp", "pokemon": [] }, { @@ -52,7 +52,7 @@ "after_route_name": null, "location": "Wela Volcano Park", "section": "Akala Island", - "sprite_url": "/boss-sprites/sun/totem-salazzle.png", + "sprite_url": "/sprites/758.webp", "pokemon": [] }, { @@ -66,7 +66,7 @@ "after_route_name": null, "location": "Lush Jungle", "section": "Akala Island", - "sprite_url": "/boss-sprites/sun/totem-lurantis.png", + "sprite_url": "/sprites/754.webp", "pokemon": [] }, { @@ -94,7 +94,7 @@ "after_route_name": null, "location": "Hokulani Observatory", "section": "Ula'ula Island", - "sprite_url": "/boss-sprites/sun/totem-vikavolt.png", + "sprite_url": "/sprites/738.webp", "pokemon": [] }, { @@ -108,7 +108,7 @@ "after_route_name": null, "location": "Thrifty Megamart", "section": "Ula'ula Island", - "sprite_url": "/boss-sprites/sun/totem-mimikyu.png", + "sprite_url": "/sprites/778.webp", "pokemon": [] }, { @@ -136,7 +136,7 @@ "after_route_name": null, "location": "Vast Poni Canyon", "section": "Poni Island", - "sprite_url": "/boss-sprites/sun/totem-kommo-o.png", + "sprite_url": "/sprites/784.webp", "pokemon": [] }, { diff --git a/backend/src/app/seeds/data/sword-bosses.json b/backend/src/app/seeds/data/sword-bosses.json index aab881c..b3ca2b6 100644 --- a/backend/src/app/seeds/data/sword-bosses.json +++ b/backend/src/app/seeds/data/sword-bosses.json @@ -51,8 +51,9 @@ "order": 4, "after_route_name": null, "location": "Stow-on-Side Stadium", - "section": "Sword", + "section": null, "sprite_url": "/boss-sprites/sword/bea.png", + "game_slug": "sword", "pokemon": [] }, { @@ -65,8 +66,9 @@ "order": 5, "after_route_name": null, "location": "Stow-on-Side Stadium", - "section": "Shield", + "section": null, "sprite_url": "/boss-sprites/sword/allister.png", + "game_slug": "shield", "pokemon": [] }, { @@ -93,8 +95,9 @@ "order": 7, "after_route_name": null, "location": "Circhester Stadium", - "section": "Sword", + "section": null, "sprite_url": "/boss-sprites/sword/gordie.png", + "game_slug": "sword", "pokemon": [] }, { @@ -107,8 +110,9 @@ "order": 8, "after_route_name": null, "location": "Circhester Stadium", - "section": "Shield", + "section": null, "sprite_url": "/boss-sprites/sword/melony.png", + "game_slug": "shield", "pokemon": [] }, { diff --git a/backend/src/app/seeds/data/ultra-sun-bosses.json b/backend/src/app/seeds/data/ultra-sun-bosses.json index daee58f..119ea6c 100644 --- a/backend/src/app/seeds/data/ultra-sun-bosses.json +++ b/backend/src/app/seeds/data/ultra-sun-bosses.json @@ -10,7 +10,7 @@ "after_route_name": null, "location": "Verdant Cavern", "section": "Melemele Island", - "sprite_url": "/boss-sprites/ultra-sun/totem-gumshoos.png", + "sprite_url": "/sprites/735.webp", "pokemon": [] }, { @@ -38,7 +38,7 @@ "after_route_name": null, "location": "Brooklet Hill", "section": "Akala Island", - "sprite_url": "/boss-sprites/ultra-sun/totem-araquanid.png", + "sprite_url": "/sprites/752.webp", "pokemon": [] }, { @@ -52,7 +52,7 @@ "after_route_name": null, "location": "Wela Volcano Park", "section": "Akala Island", - "sprite_url": "/boss-sprites/ultra-sun/totem-salazzle.png", + "sprite_url": "/sprites/758.webp", "pokemon": [] }, { @@ -66,7 +66,7 @@ "after_route_name": null, "location": "Lush Jungle", "section": "Akala Island", - "sprite_url": "/boss-sprites/ultra-sun/totem-lurantis.png", + "sprite_url": "/sprites/754.webp", "pokemon": [] }, { @@ -94,7 +94,7 @@ "after_route_name": null, "location": "Hokulani Observatory", "section": "Ula'ula Island", - "sprite_url": "/boss-sprites/ultra-sun/totem-vikavolt.png", + "sprite_url": "/sprites/738.webp", "pokemon": [] }, { @@ -108,7 +108,7 @@ "after_route_name": null, "location": "Thrifty Megamart", "section": "Ula'ula Island", - "sprite_url": "/boss-sprites/ultra-sun/totem-mimikyu.png", + "sprite_url": "/sprites/778.webp", "pokemon": [] }, { @@ -122,7 +122,7 @@ "after_route_name": null, "location": "Hokulani Observatory", "section": "Ula'ula Island", - "sprite_url": "/boss-sprites/ultra-sun/totem-togedemaru.png", + "sprite_url": "/sprites/777.webp", "pokemon": [] }, { @@ -150,7 +150,7 @@ "after_route_name": null, "location": "Vast Poni Canyon", "section": "Poni Island", - "sprite_url": "/boss-sprites/ultra-sun/totem-kommo-o.png", + "sprite_url": "/sprites/784.webp", "pokemon": [] }, { @@ -164,7 +164,7 @@ "after_route_name": null, "location": "Seafolk Village", "section": "Poni Island", - "sprite_url": "/boss-sprites/ultra-sun/totem-ribombee.png", + "sprite_url": "/sprites/743.webp", "pokemon": [] }, { diff --git a/backend/src/app/seeds/loader.py b/backend/src/app/seeds/loader.py index 2b56e8d..bd334d1 100644 --- a/backend/src/app/seeds/loader.py +++ b/backend/src/app/seeds/loader.py @@ -239,6 +239,7 @@ async def upsert_bosses( bosses: list[dict], dex_to_id: dict[int, int], route_name_to_id: dict[str, int] | None = None, + slug_to_game_id: dict[str, int] | None = None, ) -> int: """Upsert boss battles for a version group, return count of bosses upserted.""" count = 0 @@ -253,6 +254,16 @@ async def upsert_bosses( 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 stmt = ( insert(BossBattle) @@ -269,6 +280,7 @@ async def upsert_bosses( location=boss["location"], section=boss.get("section"), sprite_url=boss.get("sprite_url"), + game_id=game_id, ) .on_conflict_do_update( constraint="uq_boss_battles_version_group_order", @@ -283,6 +295,7 @@ async def upsert_bosses( "location": boss["location"], "section": boss.get("section"), "sprite_url": boss.get("sprite_url"), + "game_id": game_id, }, ) .returning(BossBattle.id) diff --git a/backend/src/app/seeds/run.py b/backend/src/app/seeds/run.py index 9b96ac5..2ec8852 100644 --- a/backend/src/app/seeds/run.py +++ b/backend/src/app/seeds/run.py @@ -160,7 +160,7 @@ async def seed(): route_name_to_id = route_maps_by_vg.get(vg_id, {}) 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 print(f" {vg_slug}: {boss_count} bosses") @@ -491,6 +491,7 @@ async def _export_bosses(session: AsyncSession, vg_data: dict): .options( selectinload(BossBattle.pokemon).selectinload(BossPokemon.pokemon), selectinload(BossBattle.after_route), + selectinload(BossBattle.game), ) .order_by(BossBattle.order) ) @@ -525,30 +526,31 @@ async def _export_bosses(session: AsyncSession, vg_data: dict): downloaded_sprites, ) - data.append( - { - "name": b.name, - "boss_type": b.boss_type, - "specialty_type": b.specialty_type, - "badge_name": b.badge_name, - "badge_image_url": badge_image_url, - "level_cap": b.level_cap, - "order": b.order, - "after_route_name": b.after_route.name if b.after_route else None, - "location": b.location, - "section": b.section, - "sprite_url": sprite_url, - "pokemon": [ - { - "pokeapi_id": bp.pokemon.pokeapi_id, - "pokemon_name": bp.pokemon.name, - "level": bp.level, - "order": bp.order, - } - for bp in sorted(b.pokemon, key=lambda p: p.order) - ], - } - ) + boss_dict: dict = { + "name": b.name, + "boss_type": b.boss_type, + "specialty_type": b.specialty_type, + "badge_name": b.badge_name, + "badge_image_url": badge_image_url, + "level_cap": b.level_cap, + "order": b.order, + "after_route_name": b.after_route.name if b.after_route else None, + "location": b.location, + "section": b.section, + "sprite_url": sprite_url, + "pokemon": [ + { + "pokeapi_id": bp.pokemon.pokeapi_id, + "pokemon_name": bp.pokemon.name, + "level": bp.level, + "order": bp.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) exported += 1 diff --git a/frontend/public/badges/basic-badge.png b/frontend/public/badges/basic-badge.png new file mode 100644 index 0000000..1f0e469 Binary files /dev/null and b/frontend/public/badges/basic-badge.png differ diff --git a/frontend/public/badges/beacon-badge.png b/frontend/public/badges/beacon-badge.png new file mode 100644 index 0000000..9515e42 Binary files /dev/null and b/frontend/public/badges/beacon-badge.png differ diff --git a/frontend/public/badges/bolt-badge.png b/frontend/public/badges/bolt-badge.png new file mode 100644 index 0000000..e06caca Binary files /dev/null and b/frontend/public/badges/bolt-badge.png differ diff --git a/frontend/public/badges/bug-badge.png b/frontend/public/badges/bug-badge.png new file mode 100644 index 0000000..11cb93e Binary files /dev/null and b/frontend/public/badges/bug-badge.png differ diff --git a/frontend/public/badges/cliff-badge.png b/frontend/public/badges/cliff-badge.png new file mode 100644 index 0000000..075ff45 Binary files /dev/null and b/frontend/public/badges/cliff-badge.png differ diff --git a/frontend/public/badges/cobble-badge.png b/frontend/public/badges/cobble-badge.png new file mode 100644 index 0000000..4466c94 Binary files /dev/null and b/frontend/public/badges/cobble-badge.png differ diff --git a/frontend/public/badges/dark-badge.png b/frontend/public/badges/dark-badge.png new file mode 100644 index 0000000..ab6174c Binary files /dev/null and b/frontend/public/badges/dark-badge.png differ diff --git a/frontend/public/badges/dragon-badge.png b/frontend/public/badges/dragon-badge.png new file mode 100644 index 0000000..38918d0 Binary files /dev/null and b/frontend/public/badges/dragon-badge.png differ diff --git a/frontend/public/badges/electric-badge.png b/frontend/public/badges/electric-badge.png new file mode 100644 index 0000000..0f33968 Binary files /dev/null and b/frontend/public/badges/electric-badge.png differ diff --git a/frontend/public/badges/fairy-badge.png b/frontend/public/badges/fairy-badge.png new file mode 100644 index 0000000..1ea44c9 Binary files /dev/null and b/frontend/public/badges/fairy-badge.png differ diff --git a/frontend/public/badges/fen-badge.png b/frontend/public/badges/fen-badge.png new file mode 100644 index 0000000..a191c4d Binary files /dev/null and b/frontend/public/badges/fen-badge.png differ diff --git a/frontend/public/badges/fighting-badge.png b/frontend/public/badges/fighting-badge.png new file mode 100644 index 0000000..4de056f Binary files /dev/null and b/frontend/public/badges/fighting-badge.png differ diff --git a/frontend/public/badges/fire-badge.png b/frontend/public/badges/fire-badge.png new file mode 100644 index 0000000..6009db9 Binary files /dev/null and b/frontend/public/badges/fire-badge.png differ diff --git a/frontend/public/badges/forest-badge.png b/frontend/public/badges/forest-badge.png new file mode 100644 index 0000000..a7e450b Binary files /dev/null and b/frontend/public/badges/forest-badge.png differ diff --git a/frontend/public/badges/freeze-badge.png b/frontend/public/badges/freeze-badge.png new file mode 100644 index 0000000..60ecd03 Binary files /dev/null and b/frontend/public/badges/freeze-badge.png differ diff --git a/frontend/public/badges/ghost-badge.png b/frontend/public/badges/ghost-badge.png new file mode 100644 index 0000000..4eab3b4 Binary files /dev/null and b/frontend/public/badges/ghost-badge.png differ diff --git a/frontend/public/badges/grass-badge.png b/frontend/public/badges/grass-badge.png new file mode 100644 index 0000000..97606d0 Binary files /dev/null and b/frontend/public/badges/grass-badge.png differ diff --git a/frontend/public/badges/ice-badge.png b/frontend/public/badges/ice-badge.png new file mode 100644 index 0000000..ebc7eed Binary files /dev/null and b/frontend/public/badges/ice-badge.png differ diff --git a/frontend/public/badges/iceberg-badge.png b/frontend/public/badges/iceberg-badge.png new file mode 100644 index 0000000..d6bdf67 Binary files /dev/null and b/frontend/public/badges/iceberg-badge.png differ diff --git a/frontend/public/badges/icicle-badge.png b/frontend/public/badges/icicle-badge.png new file mode 100644 index 0000000..544e891 Binary files /dev/null and b/frontend/public/badges/icicle-badge.png differ diff --git a/frontend/public/badges/insect-badge.png b/frontend/public/badges/insect-badge.png new file mode 100644 index 0000000..301875f Binary files /dev/null and b/frontend/public/badges/insect-badge.png differ diff --git a/frontend/public/badges/jet-badge.png b/frontend/public/badges/jet-badge.png new file mode 100644 index 0000000..1290f26 Binary files /dev/null and b/frontend/public/badges/jet-badge.png differ diff --git a/frontend/public/badges/legend-badge.png b/frontend/public/badges/legend-badge.png new file mode 100644 index 0000000..b36a996 Binary files /dev/null and b/frontend/public/badges/legend-badge.png differ diff --git a/frontend/public/badges/mine-badge.png b/frontend/public/badges/mine-badge.png new file mode 100644 index 0000000..3eecb9a Binary files /dev/null and b/frontend/public/badges/mine-badge.png differ diff --git a/frontend/public/badges/normal-badge.png b/frontend/public/badges/normal-badge.png new file mode 100644 index 0000000..682d25d Binary files /dev/null and b/frontend/public/badges/normal-badge.png differ diff --git a/frontend/public/badges/plant-badge.png b/frontend/public/badges/plant-badge.png new file mode 100644 index 0000000..a267cd5 Binary files /dev/null and b/frontend/public/badges/plant-badge.png differ diff --git a/frontend/public/badges/psychic-badge.png b/frontend/public/badges/psychic-badge.png new file mode 100644 index 0000000..7c8c9e4 Binary files /dev/null and b/frontend/public/badges/psychic-badge.png differ diff --git a/frontend/public/badges/quake-badge.png b/frontend/public/badges/quake-badge.png new file mode 100644 index 0000000..304e1f5 Binary files /dev/null and b/frontend/public/badges/quake-badge.png differ diff --git a/frontend/public/badges/relic-badge.png b/frontend/public/badges/relic-badge.png new file mode 100644 index 0000000..5c3ab0a Binary files /dev/null and b/frontend/public/badges/relic-badge.png differ diff --git a/frontend/public/badges/rock-badge.png b/frontend/public/badges/rock-badge.png new file mode 100644 index 0000000..31a4d26 Binary files /dev/null and b/frontend/public/badges/rock-badge.png differ diff --git a/frontend/public/badges/rumble-badge.png b/frontend/public/badges/rumble-badge.png new file mode 100644 index 0000000..2106a44 Binary files /dev/null and b/frontend/public/badges/rumble-badge.png differ diff --git a/frontend/public/badges/toxic-badge.png b/frontend/public/badges/toxic-badge.png new file mode 100644 index 0000000..028fde4 Binary files /dev/null and b/frontend/public/badges/toxic-badge.png differ diff --git a/frontend/public/badges/trio-badge.png b/frontend/public/badges/trio-badge.png new file mode 100644 index 0000000..174b442 Binary files /dev/null and b/frontend/public/badges/trio-badge.png differ diff --git a/frontend/public/badges/voltage-badge.png b/frontend/public/badges/voltage-badge.png new file mode 100644 index 0000000..4f4393b Binary files /dev/null and b/frontend/public/badges/voltage-badge.png differ diff --git a/frontend/public/badges/water-badge.png b/frontend/public/badges/water-badge.png new file mode 100644 index 0000000..fd9b565 Binary files /dev/null and b/frontend/public/badges/water-badge.png differ diff --git a/frontend/public/badges/wave-badge.png b/frontend/public/badges/wave-badge.png new file mode 100644 index 0000000..39d2335 Binary files /dev/null and b/frontend/public/badges/wave-badge.png differ diff --git a/frontend/public/boss-sprites/black-2/burgh.png b/frontend/public/boss-sprites/black-2/burgh.png new file mode 100644 index 0000000..56a1b09 Binary files /dev/null and b/frontend/public/boss-sprites/black-2/burgh.png differ diff --git a/frontend/public/boss-sprites/black-2/caitlin.png b/frontend/public/boss-sprites/black-2/caitlin.png new file mode 100644 index 0000000..5095a65 Binary files /dev/null and b/frontend/public/boss-sprites/black-2/caitlin.png differ diff --git a/frontend/public/boss-sprites/black-2/cheren.png b/frontend/public/boss-sprites/black-2/cheren.png new file mode 100644 index 0000000..ab149bc Binary files /dev/null and b/frontend/public/boss-sprites/black-2/cheren.png differ diff --git a/frontend/public/boss-sprites/black-2/clay.png b/frontend/public/boss-sprites/black-2/clay.png new file mode 100644 index 0000000..c55a936 Binary files /dev/null and b/frontend/public/boss-sprites/black-2/clay.png differ diff --git a/frontend/public/boss-sprites/black-2/drayden.png b/frontend/public/boss-sprites/black-2/drayden.png new file mode 100644 index 0000000..0fd3f26 Binary files /dev/null and b/frontend/public/boss-sprites/black-2/drayden.png differ diff --git a/frontend/public/boss-sprites/black-2/elesa.png b/frontend/public/boss-sprites/black-2/elesa.png new file mode 100644 index 0000000..6a4c9b5 Binary files /dev/null and b/frontend/public/boss-sprites/black-2/elesa.png differ diff --git a/frontend/public/boss-sprites/black-2/grimsley.png b/frontend/public/boss-sprites/black-2/grimsley.png new file mode 100644 index 0000000..5582438 Binary files /dev/null and b/frontend/public/boss-sprites/black-2/grimsley.png differ diff --git a/frontend/public/boss-sprites/black-2/iris.png b/frontend/public/boss-sprites/black-2/iris.png new file mode 100644 index 0000000..6229238 Binary files /dev/null and b/frontend/public/boss-sprites/black-2/iris.png differ diff --git a/frontend/public/boss-sprites/black-2/marlon.png b/frontend/public/boss-sprites/black-2/marlon.png new file mode 100644 index 0000000..3ee453b Binary files /dev/null and b/frontend/public/boss-sprites/black-2/marlon.png differ diff --git a/frontend/public/boss-sprites/black-2/marshal.png b/frontend/public/boss-sprites/black-2/marshal.png new file mode 100644 index 0000000..18fbaa9 Binary files /dev/null and b/frontend/public/boss-sprites/black-2/marshal.png differ diff --git a/frontend/public/boss-sprites/black-2/roxie.png b/frontend/public/boss-sprites/black-2/roxie.png new file mode 100644 index 0000000..44c93a5 Binary files /dev/null and b/frontend/public/boss-sprites/black-2/roxie.png differ diff --git a/frontend/public/boss-sprites/black-2/shauntal.png b/frontend/public/boss-sprites/black-2/shauntal.png new file mode 100644 index 0000000..39791b7 Binary files /dev/null and b/frontend/public/boss-sprites/black-2/shauntal.png differ diff --git a/frontend/public/boss-sprites/black-2/skyla.png b/frontend/public/boss-sprites/black-2/skyla.png new file mode 100644 index 0000000..69b3df3 Binary files /dev/null and b/frontend/public/boss-sprites/black-2/skyla.png differ diff --git a/frontend/public/boss-sprites/black/brycen.png b/frontend/public/boss-sprites/black/brycen.png new file mode 100644 index 0000000..76e9c7e Binary files /dev/null and b/frontend/public/boss-sprites/black/brycen.png differ diff --git a/frontend/public/boss-sprites/black/burgh.png b/frontend/public/boss-sprites/black/burgh.png new file mode 100644 index 0000000..ffdcae6 Binary files /dev/null and b/frontend/public/boss-sprites/black/burgh.png differ diff --git a/frontend/public/boss-sprites/black/caitlin.png b/frontend/public/boss-sprites/black/caitlin.png new file mode 100644 index 0000000..28507bc Binary files /dev/null and b/frontend/public/boss-sprites/black/caitlin.png differ diff --git a/frontend/public/boss-sprites/black/cilan--chili--cress.png b/frontend/public/boss-sprites/black/cilan--chili--cress.png new file mode 100644 index 0000000..cad9919 Binary files /dev/null and b/frontend/public/boss-sprites/black/cilan--chili--cress.png differ diff --git a/frontend/public/boss-sprites/black/clay.png b/frontend/public/boss-sprites/black/clay.png new file mode 100644 index 0000000..5d367cb Binary files /dev/null and b/frontend/public/boss-sprites/black/clay.png differ diff --git a/frontend/public/boss-sprites/black/drayden.png b/frontend/public/boss-sprites/black/drayden.png new file mode 100644 index 0000000..8168f34 Binary files /dev/null and b/frontend/public/boss-sprites/black/drayden.png differ diff --git a/frontend/public/boss-sprites/black/elesa.png b/frontend/public/boss-sprites/black/elesa.png new file mode 100644 index 0000000..ceeb9c8 Binary files /dev/null and b/frontend/public/boss-sprites/black/elesa.png differ diff --git a/frontend/public/boss-sprites/black/ghetsis.png b/frontend/public/boss-sprites/black/ghetsis.png new file mode 100644 index 0000000..dbf4827 Binary files /dev/null and b/frontend/public/boss-sprites/black/ghetsis.png differ diff --git a/frontend/public/boss-sprites/black/grimsley.png b/frontend/public/boss-sprites/black/grimsley.png new file mode 100644 index 0000000..ae30fd0 Binary files /dev/null and b/frontend/public/boss-sprites/black/grimsley.png differ diff --git a/frontend/public/boss-sprites/black/iris.png b/frontend/public/boss-sprites/black/iris.png new file mode 100644 index 0000000..891a804 Binary files /dev/null and b/frontend/public/boss-sprites/black/iris.png differ diff --git a/frontend/public/boss-sprites/black/lenora.png b/frontend/public/boss-sprites/black/lenora.png new file mode 100644 index 0000000..4b9f1be Binary files /dev/null and b/frontend/public/boss-sprites/black/lenora.png differ diff --git a/frontend/public/boss-sprites/black/marshal.png b/frontend/public/boss-sprites/black/marshal.png new file mode 100644 index 0000000..43dbbd0 Binary files /dev/null and b/frontend/public/boss-sprites/black/marshal.png differ diff --git a/frontend/public/boss-sprites/black/n.png b/frontend/public/boss-sprites/black/n.png new file mode 100644 index 0000000..e89d626 Binary files /dev/null and b/frontend/public/boss-sprites/black/n.png differ diff --git a/frontend/public/boss-sprites/black/shauntal.png b/frontend/public/boss-sprites/black/shauntal.png new file mode 100644 index 0000000..fdf9e68 Binary files /dev/null and b/frontend/public/boss-sprites/black/shauntal.png differ diff --git a/frontend/public/boss-sprites/black/skyla.png b/frontend/public/boss-sprites/black/skyla.png new file mode 100644 index 0000000..4b2a1e5 Binary files /dev/null and b/frontend/public/boss-sprites/black/skyla.png differ diff --git a/frontend/public/boss-sprites/brilliant-diamond/aaron.png b/frontend/public/boss-sprites/brilliant-diamond/aaron.png new file mode 100644 index 0000000..b6b9b5d Binary files /dev/null and b/frontend/public/boss-sprites/brilliant-diamond/aaron.png differ diff --git a/frontend/public/boss-sprites/brilliant-diamond/bertha.png b/frontend/public/boss-sprites/brilliant-diamond/bertha.png new file mode 100644 index 0000000..674c77a Binary files /dev/null and b/frontend/public/boss-sprites/brilliant-diamond/bertha.png differ diff --git a/frontend/public/boss-sprites/brilliant-diamond/byron.png b/frontend/public/boss-sprites/brilliant-diamond/byron.png new file mode 100644 index 0000000..b676748 Binary files /dev/null and b/frontend/public/boss-sprites/brilliant-diamond/byron.png differ diff --git a/frontend/public/boss-sprites/brilliant-diamond/candice.png b/frontend/public/boss-sprites/brilliant-diamond/candice.png new file mode 100644 index 0000000..90ac706 Binary files /dev/null and b/frontend/public/boss-sprites/brilliant-diamond/candice.png differ diff --git a/frontend/public/boss-sprites/brilliant-diamond/crasher-wake.png b/frontend/public/boss-sprites/brilliant-diamond/crasher-wake.png new file mode 100644 index 0000000..638606a Binary files /dev/null and b/frontend/public/boss-sprites/brilliant-diamond/crasher-wake.png differ diff --git a/frontend/public/boss-sprites/brilliant-diamond/cynthia.png b/frontend/public/boss-sprites/brilliant-diamond/cynthia.png new file mode 100644 index 0000000..cab73bb Binary files /dev/null and b/frontend/public/boss-sprites/brilliant-diamond/cynthia.png differ diff --git a/frontend/public/boss-sprites/brilliant-diamond/fantina.png b/frontend/public/boss-sprites/brilliant-diamond/fantina.png new file mode 100644 index 0000000..a84b5c3 Binary files /dev/null and b/frontend/public/boss-sprites/brilliant-diamond/fantina.png differ diff --git a/frontend/public/boss-sprites/brilliant-diamond/flint.png b/frontend/public/boss-sprites/brilliant-diamond/flint.png new file mode 100644 index 0000000..a43fd2d Binary files /dev/null and b/frontend/public/boss-sprites/brilliant-diamond/flint.png differ diff --git a/frontend/public/boss-sprites/brilliant-diamond/gardenia.png b/frontend/public/boss-sprites/brilliant-diamond/gardenia.png new file mode 100644 index 0000000..da97197 Binary files /dev/null and b/frontend/public/boss-sprites/brilliant-diamond/gardenia.png differ diff --git a/frontend/public/boss-sprites/brilliant-diamond/lucian.png b/frontend/public/boss-sprites/brilliant-diamond/lucian.png new file mode 100644 index 0000000..40f8b62 Binary files /dev/null and b/frontend/public/boss-sprites/brilliant-diamond/lucian.png differ diff --git a/frontend/public/boss-sprites/brilliant-diamond/maylene.png b/frontend/public/boss-sprites/brilliant-diamond/maylene.png new file mode 100644 index 0000000..56a7992 Binary files /dev/null and b/frontend/public/boss-sprites/brilliant-diamond/maylene.png differ diff --git a/frontend/public/boss-sprites/brilliant-diamond/roark.png b/frontend/public/boss-sprites/brilliant-diamond/roark.png new file mode 100644 index 0000000..0ffdff9 Binary files /dev/null and b/frontend/public/boss-sprites/brilliant-diamond/roark.png differ diff --git a/frontend/public/boss-sprites/brilliant-diamond/volkner.png b/frontend/public/boss-sprites/brilliant-diamond/volkner.png new file mode 100644 index 0000000..7329ba5 Binary files /dev/null and b/frontend/public/boss-sprites/brilliant-diamond/volkner.png differ diff --git a/frontend/public/boss-sprites/diamond/aaron.png b/frontend/public/boss-sprites/diamond/aaron.png new file mode 100644 index 0000000..5f4617f Binary files /dev/null and b/frontend/public/boss-sprites/diamond/aaron.png differ diff --git a/frontend/public/boss-sprites/diamond/bertha.png b/frontend/public/boss-sprites/diamond/bertha.png new file mode 100644 index 0000000..be3b7b1 Binary files /dev/null and b/frontend/public/boss-sprites/diamond/bertha.png differ diff --git a/frontend/public/boss-sprites/diamond/byron.png b/frontend/public/boss-sprites/diamond/byron.png new file mode 100644 index 0000000..80aae2e Binary files /dev/null and b/frontend/public/boss-sprites/diamond/byron.png differ diff --git a/frontend/public/boss-sprites/diamond/candice.png b/frontend/public/boss-sprites/diamond/candice.png new file mode 100644 index 0000000..1630674 Binary files /dev/null and b/frontend/public/boss-sprites/diamond/candice.png differ diff --git a/frontend/public/boss-sprites/diamond/crasher-wake.png b/frontend/public/boss-sprites/diamond/crasher-wake.png new file mode 100644 index 0000000..7c8c709 Binary files /dev/null and b/frontend/public/boss-sprites/diamond/crasher-wake.png differ diff --git a/frontend/public/boss-sprites/diamond/cynthia.png b/frontend/public/boss-sprites/diamond/cynthia.png new file mode 100644 index 0000000..5101c75 Binary files /dev/null and b/frontend/public/boss-sprites/diamond/cynthia.png differ diff --git a/frontend/public/boss-sprites/diamond/fantina.png b/frontend/public/boss-sprites/diamond/fantina.png new file mode 100644 index 0000000..1d83e21 Binary files /dev/null and b/frontend/public/boss-sprites/diamond/fantina.png differ diff --git a/frontend/public/boss-sprites/diamond/flint.png b/frontend/public/boss-sprites/diamond/flint.png new file mode 100644 index 0000000..22b1c8d Binary files /dev/null and b/frontend/public/boss-sprites/diamond/flint.png differ diff --git a/frontend/public/boss-sprites/diamond/gardenia.png b/frontend/public/boss-sprites/diamond/gardenia.png new file mode 100644 index 0000000..33fa748 Binary files /dev/null and b/frontend/public/boss-sprites/diamond/gardenia.png differ diff --git a/frontend/public/boss-sprites/diamond/lucian.png b/frontend/public/boss-sprites/diamond/lucian.png new file mode 100644 index 0000000..0a3e017 Binary files /dev/null and b/frontend/public/boss-sprites/diamond/lucian.png differ diff --git a/frontend/public/boss-sprites/diamond/maylene.png b/frontend/public/boss-sprites/diamond/maylene.png new file mode 100644 index 0000000..281829e Binary files /dev/null and b/frontend/public/boss-sprites/diamond/maylene.png differ diff --git a/frontend/public/boss-sprites/diamond/volkner.png b/frontend/public/boss-sprites/diamond/volkner.png new file mode 100644 index 0000000..fa08ac3 Binary files /dev/null and b/frontend/public/boss-sprites/diamond/volkner.png differ diff --git a/frontend/public/boss-sprites/firered/agatha.png b/frontend/public/boss-sprites/firered/agatha.png new file mode 100644 index 0000000..7dac517 Binary files /dev/null and b/frontend/public/boss-sprites/firered/agatha.png differ diff --git a/frontend/public/boss-sprites/firered/blue.png b/frontend/public/boss-sprites/firered/blue.png new file mode 100644 index 0000000..5800be8 Binary files /dev/null and b/frontend/public/boss-sprites/firered/blue.png differ diff --git a/frontend/public/boss-sprites/firered/bruno.png b/frontend/public/boss-sprites/firered/bruno.png new file mode 100644 index 0000000..86a8a1f Binary files /dev/null and b/frontend/public/boss-sprites/firered/bruno.png differ diff --git a/frontend/public/boss-sprites/firered/lance.png b/frontend/public/boss-sprites/firered/lance.png new file mode 100644 index 0000000..318d46c Binary files /dev/null and b/frontend/public/boss-sprites/firered/lance.png differ diff --git a/frontend/public/boss-sprites/firered/lorelei.png b/frontend/public/boss-sprites/firered/lorelei.png new file mode 100644 index 0000000..6ecb2c2 Binary files /dev/null and b/frontend/public/boss-sprites/firered/lorelei.png differ diff --git a/frontend/public/boss-sprites/heartgold/bruno.png b/frontend/public/boss-sprites/heartgold/bruno.png new file mode 100644 index 0000000..5c33e7e Binary files /dev/null and b/frontend/public/boss-sprites/heartgold/bruno.png differ diff --git a/frontend/public/boss-sprites/heartgold/karen.png b/frontend/public/boss-sprites/heartgold/karen.png new file mode 100644 index 0000000..9117ceb Binary files /dev/null and b/frontend/public/boss-sprites/heartgold/karen.png differ diff --git a/frontend/public/boss-sprites/heartgold/koga.png b/frontend/public/boss-sprites/heartgold/koga.png new file mode 100644 index 0000000..e2b48b8 Binary files /dev/null and b/frontend/public/boss-sprites/heartgold/koga.png differ diff --git a/frontend/public/boss-sprites/heartgold/lance.png b/frontend/public/boss-sprites/heartgold/lance.png new file mode 100644 index 0000000..0e22a2c Binary files /dev/null and b/frontend/public/boss-sprites/heartgold/lance.png differ diff --git a/frontend/public/boss-sprites/heartgold/red.png b/frontend/public/boss-sprites/heartgold/red.png new file mode 100644 index 0000000..fab42f8 Binary files /dev/null and b/frontend/public/boss-sprites/heartgold/red.png differ diff --git a/frontend/public/boss-sprites/heartgold/will.png b/frontend/public/boss-sprites/heartgold/will.png new file mode 100644 index 0000000..f030c51 Binary files /dev/null and b/frontend/public/boss-sprites/heartgold/will.png differ diff --git a/frontend/public/boss-sprites/lets-go-pikachu/agatha.png b/frontend/public/boss-sprites/lets-go-pikachu/agatha.png new file mode 100644 index 0000000..d5b687d Binary files /dev/null and b/frontend/public/boss-sprites/lets-go-pikachu/agatha.png differ diff --git a/frontend/public/boss-sprites/lets-go-pikachu/blaine.png b/frontend/public/boss-sprites/lets-go-pikachu/blaine.png new file mode 100644 index 0000000..e644b5c Binary files /dev/null and b/frontend/public/boss-sprites/lets-go-pikachu/blaine.png differ diff --git a/frontend/public/boss-sprites/lets-go-pikachu/blue.png b/frontend/public/boss-sprites/lets-go-pikachu/blue.png new file mode 100644 index 0000000..2336042 Binary files /dev/null and b/frontend/public/boss-sprites/lets-go-pikachu/blue.png differ diff --git a/frontend/public/boss-sprites/lets-go-pikachu/brock.png b/frontend/public/boss-sprites/lets-go-pikachu/brock.png new file mode 100644 index 0000000..2957b66 Binary files /dev/null and b/frontend/public/boss-sprites/lets-go-pikachu/brock.png differ diff --git a/frontend/public/boss-sprites/lets-go-pikachu/bruno.png b/frontend/public/boss-sprites/lets-go-pikachu/bruno.png new file mode 100644 index 0000000..55a8ab9 Binary files /dev/null and b/frontend/public/boss-sprites/lets-go-pikachu/bruno.png differ diff --git a/frontend/public/boss-sprites/lets-go-pikachu/erika.png b/frontend/public/boss-sprites/lets-go-pikachu/erika.png new file mode 100644 index 0000000..5ba9f6f Binary files /dev/null and b/frontend/public/boss-sprites/lets-go-pikachu/erika.png differ diff --git a/frontend/public/boss-sprites/lets-go-pikachu/giovanni.png b/frontend/public/boss-sprites/lets-go-pikachu/giovanni.png new file mode 100644 index 0000000..6e3177f Binary files /dev/null and b/frontend/public/boss-sprites/lets-go-pikachu/giovanni.png differ diff --git a/frontend/public/boss-sprites/lets-go-pikachu/koga.png b/frontend/public/boss-sprites/lets-go-pikachu/koga.png new file mode 100644 index 0000000..39c3649 Binary files /dev/null and b/frontend/public/boss-sprites/lets-go-pikachu/koga.png differ diff --git a/frontend/public/boss-sprites/lets-go-pikachu/lance.png b/frontend/public/boss-sprites/lets-go-pikachu/lance.png new file mode 100644 index 0000000..a3ed526 Binary files /dev/null and b/frontend/public/boss-sprites/lets-go-pikachu/lance.png differ diff --git a/frontend/public/boss-sprites/lets-go-pikachu/lorelei.png b/frontend/public/boss-sprites/lets-go-pikachu/lorelei.png new file mode 100644 index 0000000..3f487cb Binary files /dev/null and b/frontend/public/boss-sprites/lets-go-pikachu/lorelei.png differ diff --git a/frontend/public/boss-sprites/lets-go-pikachu/lt-surge.png b/frontend/public/boss-sprites/lets-go-pikachu/lt-surge.png new file mode 100644 index 0000000..d0a67bc Binary files /dev/null and b/frontend/public/boss-sprites/lets-go-pikachu/lt-surge.png differ diff --git a/frontend/public/boss-sprites/lets-go-pikachu/misty.png b/frontend/public/boss-sprites/lets-go-pikachu/misty.png new file mode 100644 index 0000000..e6da287 Binary files /dev/null and b/frontend/public/boss-sprites/lets-go-pikachu/misty.png differ diff --git a/frontend/public/boss-sprites/lets-go-pikachu/sabrina.png b/frontend/public/boss-sprites/lets-go-pikachu/sabrina.png new file mode 100644 index 0000000..307b363 Binary files /dev/null and b/frontend/public/boss-sprites/lets-go-pikachu/sabrina.png differ diff --git a/frontend/public/boss-sprites/omega-ruby/brawly.png b/frontend/public/boss-sprites/omega-ruby/brawly.png new file mode 100644 index 0000000..03d00f1 Binary files /dev/null and b/frontend/public/boss-sprites/omega-ruby/brawly.png differ diff --git a/frontend/public/boss-sprites/omega-ruby/drake.png b/frontend/public/boss-sprites/omega-ruby/drake.png new file mode 100644 index 0000000..8b3308b Binary files /dev/null and b/frontend/public/boss-sprites/omega-ruby/drake.png differ diff --git a/frontend/public/boss-sprites/omega-ruby/flannery.png b/frontend/public/boss-sprites/omega-ruby/flannery.png new file mode 100644 index 0000000..0ed8e91 Binary files /dev/null and b/frontend/public/boss-sprites/omega-ruby/flannery.png differ diff --git a/frontend/public/boss-sprites/omega-ruby/glacia.png b/frontend/public/boss-sprites/omega-ruby/glacia.png new file mode 100644 index 0000000..c90c974 Binary files /dev/null and b/frontend/public/boss-sprites/omega-ruby/glacia.png differ diff --git a/frontend/public/boss-sprites/omega-ruby/norman.png b/frontend/public/boss-sprites/omega-ruby/norman.png new file mode 100644 index 0000000..8e81146 Binary files /dev/null and b/frontend/public/boss-sprites/omega-ruby/norman.png differ diff --git a/frontend/public/boss-sprites/omega-ruby/phoebe.png b/frontend/public/boss-sprites/omega-ruby/phoebe.png new file mode 100644 index 0000000..ca8155f Binary files /dev/null and b/frontend/public/boss-sprites/omega-ruby/phoebe.png differ diff --git a/frontend/public/boss-sprites/omega-ruby/roxanne.png b/frontend/public/boss-sprites/omega-ruby/roxanne.png new file mode 100644 index 0000000..2acab8c Binary files /dev/null and b/frontend/public/boss-sprites/omega-ruby/roxanne.png differ diff --git a/frontend/public/boss-sprites/omega-ruby/sidney.png b/frontend/public/boss-sprites/omega-ruby/sidney.png new file mode 100644 index 0000000..4397ba4 Binary files /dev/null and b/frontend/public/boss-sprites/omega-ruby/sidney.png differ diff --git a/frontend/public/boss-sprites/omega-ruby/steven.png b/frontend/public/boss-sprites/omega-ruby/steven.png new file mode 100644 index 0000000..2a1e298 Binary files /dev/null and b/frontend/public/boss-sprites/omega-ruby/steven.png differ diff --git a/frontend/public/boss-sprites/omega-ruby/tate--liza.png b/frontend/public/boss-sprites/omega-ruby/tate--liza.png new file mode 100644 index 0000000..76094ce Binary files /dev/null and b/frontend/public/boss-sprites/omega-ruby/tate--liza.png differ diff --git a/frontend/public/boss-sprites/omega-ruby/wallace.png b/frontend/public/boss-sprites/omega-ruby/wallace.png new file mode 100644 index 0000000..6858334 Binary files /dev/null and b/frontend/public/boss-sprites/omega-ruby/wallace.png differ diff --git a/frontend/public/boss-sprites/omega-ruby/wattson.png b/frontend/public/boss-sprites/omega-ruby/wattson.png index 6fef85b..943c57b 100644 Binary files a/frontend/public/boss-sprites/omega-ruby/wattson.png and b/frontend/public/boss-sprites/omega-ruby/wattson.png differ diff --git a/frontend/public/boss-sprites/omega-ruby/winona.png b/frontend/public/boss-sprites/omega-ruby/winona.png new file mode 100644 index 0000000..5a23efc Binary files /dev/null and b/frontend/public/boss-sprites/omega-ruby/winona.png differ diff --git a/frontend/public/boss-sprites/platinum/aaron.png b/frontend/public/boss-sprites/platinum/aaron.png new file mode 100644 index 0000000..f32e6da Binary files /dev/null and b/frontend/public/boss-sprites/platinum/aaron.png differ diff --git a/frontend/public/boss-sprites/platinum/bertha.png b/frontend/public/boss-sprites/platinum/bertha.png new file mode 100644 index 0000000..93fe426 Binary files /dev/null and b/frontend/public/boss-sprites/platinum/bertha.png differ diff --git a/frontend/public/boss-sprites/platinum/byron.png b/frontend/public/boss-sprites/platinum/byron.png new file mode 100644 index 0000000..8b9e203 Binary files /dev/null and b/frontend/public/boss-sprites/platinum/byron.png differ diff --git a/frontend/public/boss-sprites/platinum/candice.png b/frontend/public/boss-sprites/platinum/candice.png new file mode 100644 index 0000000..d493795 Binary files /dev/null and b/frontend/public/boss-sprites/platinum/candice.png differ diff --git a/frontend/public/boss-sprites/platinum/crasher-wake.png b/frontend/public/boss-sprites/platinum/crasher-wake.png new file mode 100644 index 0000000..e69d7ac Binary files /dev/null and b/frontend/public/boss-sprites/platinum/crasher-wake.png differ diff --git a/frontend/public/boss-sprites/platinum/cynthia.png b/frontend/public/boss-sprites/platinum/cynthia.png new file mode 100644 index 0000000..954f6ee Binary files /dev/null and b/frontend/public/boss-sprites/platinum/cynthia.png differ diff --git a/frontend/public/boss-sprites/platinum/fantina.png b/frontend/public/boss-sprites/platinum/fantina.png new file mode 100644 index 0000000..8492286 Binary files /dev/null and b/frontend/public/boss-sprites/platinum/fantina.png differ diff --git a/frontend/public/boss-sprites/platinum/flint.png b/frontend/public/boss-sprites/platinum/flint.png new file mode 100644 index 0000000..48dc8c1 Binary files /dev/null and b/frontend/public/boss-sprites/platinum/flint.png differ diff --git a/frontend/public/boss-sprites/platinum/gardenia.png b/frontend/public/boss-sprites/platinum/gardenia.png new file mode 100644 index 0000000..0928989 Binary files /dev/null and b/frontend/public/boss-sprites/platinum/gardenia.png differ diff --git a/frontend/public/boss-sprites/platinum/lucian.png b/frontend/public/boss-sprites/platinum/lucian.png new file mode 100644 index 0000000..973ecaf Binary files /dev/null and b/frontend/public/boss-sprites/platinum/lucian.png differ diff --git a/frontend/public/boss-sprites/platinum/maylene.png b/frontend/public/boss-sprites/platinum/maylene.png new file mode 100644 index 0000000..c73f838 Binary files /dev/null and b/frontend/public/boss-sprites/platinum/maylene.png differ diff --git a/frontend/public/boss-sprites/platinum/roark.png b/frontend/public/boss-sprites/platinum/roark.png new file mode 100644 index 0000000..1bb47d8 Binary files /dev/null and b/frontend/public/boss-sprites/platinum/roark.png differ diff --git a/frontend/public/boss-sprites/platinum/volkner.png b/frontend/public/boss-sprites/platinum/volkner.png new file mode 100644 index 0000000..b2fac66 Binary files /dev/null and b/frontend/public/boss-sprites/platinum/volkner.png differ diff --git a/frontend/public/boss-sprites/scarlet/atticus.png b/frontend/public/boss-sprites/scarlet/atticus.png new file mode 100644 index 0000000..fb01009 Binary files /dev/null and b/frontend/public/boss-sprites/scarlet/atticus.png differ diff --git a/frontend/public/boss-sprites/scarlet/brassius.png b/frontend/public/boss-sprites/scarlet/brassius.png new file mode 100644 index 0000000..065981a Binary files /dev/null and b/frontend/public/boss-sprites/scarlet/brassius.png differ diff --git a/frontend/public/boss-sprites/scarlet/champion-nemona.png b/frontend/public/boss-sprites/scarlet/champion-nemona.png new file mode 100644 index 0000000..9ae94fc Binary files /dev/null and b/frontend/public/boss-sprites/scarlet/champion-nemona.png differ diff --git a/frontend/public/boss-sprites/scarlet/eri.png b/frontend/public/boss-sprites/scarlet/eri.png new file mode 100644 index 0000000..90b34ae Binary files /dev/null and b/frontend/public/boss-sprites/scarlet/eri.png differ diff --git a/frontend/public/boss-sprites/scarlet/giacomo.png b/frontend/public/boss-sprites/scarlet/giacomo.png new file mode 100644 index 0000000..b88385d Binary files /dev/null and b/frontend/public/boss-sprites/scarlet/giacomo.png differ diff --git a/frontend/public/boss-sprites/scarlet/grusha.png b/frontend/public/boss-sprites/scarlet/grusha.png new file mode 100644 index 0000000..a31b2c5 Binary files /dev/null and b/frontend/public/boss-sprites/scarlet/grusha.png differ diff --git a/frontend/public/boss-sprites/scarlet/hassel.png b/frontend/public/boss-sprites/scarlet/hassel.png new file mode 100644 index 0000000..4193531 Binary files /dev/null and b/frontend/public/boss-sprites/scarlet/hassel.png differ diff --git a/frontend/public/boss-sprites/scarlet/iono.png b/frontend/public/boss-sprites/scarlet/iono.png new file mode 100644 index 0000000..dc5f070 Binary files /dev/null and b/frontend/public/boss-sprites/scarlet/iono.png differ diff --git a/frontend/public/boss-sprites/scarlet/katy.png b/frontend/public/boss-sprites/scarlet/katy.png new file mode 100644 index 0000000..c49ac8c Binary files /dev/null and b/frontend/public/boss-sprites/scarlet/katy.png differ diff --git a/frontend/public/boss-sprites/scarlet/kofu.png b/frontend/public/boss-sprites/scarlet/kofu.png new file mode 100644 index 0000000..dd66ef7 Binary files /dev/null and b/frontend/public/boss-sprites/scarlet/kofu.png differ diff --git a/frontend/public/boss-sprites/scarlet/larry.png b/frontend/public/boss-sprites/scarlet/larry.png new file mode 100644 index 0000000..f2514c6 Binary files /dev/null and b/frontend/public/boss-sprites/scarlet/larry.png differ diff --git a/frontend/public/boss-sprites/scarlet/mela.png b/frontend/public/boss-sprites/scarlet/mela.png new file mode 100644 index 0000000..ae67111 Binary files /dev/null and b/frontend/public/boss-sprites/scarlet/mela.png differ diff --git a/frontend/public/boss-sprites/scarlet/ortega.png b/frontend/public/boss-sprites/scarlet/ortega.png new file mode 100644 index 0000000..0359f34 Binary files /dev/null and b/frontend/public/boss-sprites/scarlet/ortega.png differ diff --git a/frontend/public/boss-sprites/scarlet/poppy.png b/frontend/public/boss-sprites/scarlet/poppy.png new file mode 100644 index 0000000..f4d288a Binary files /dev/null and b/frontend/public/boss-sprites/scarlet/poppy.png differ diff --git a/frontend/public/boss-sprites/scarlet/rika.png b/frontend/public/boss-sprites/scarlet/rika.png new file mode 100644 index 0000000..32c0694 Binary files /dev/null and b/frontend/public/boss-sprites/scarlet/rika.png differ diff --git a/frontend/public/boss-sprites/scarlet/ryme.png b/frontend/public/boss-sprites/scarlet/ryme.png new file mode 100644 index 0000000..966b17e Binary files /dev/null and b/frontend/public/boss-sprites/scarlet/ryme.png differ diff --git a/frontend/public/boss-sprites/scarlet/top-champion-geeta.png b/frontend/public/boss-sprites/scarlet/top-champion-geeta.png new file mode 100644 index 0000000..4fd491c Binary files /dev/null and b/frontend/public/boss-sprites/scarlet/top-champion-geeta.png differ diff --git a/frontend/public/boss-sprites/scarlet/tulip.png b/frontend/public/boss-sprites/scarlet/tulip.png new file mode 100644 index 0000000..1993e8b Binary files /dev/null and b/frontend/public/boss-sprites/scarlet/tulip.png differ diff --git a/frontend/public/boss-sprites/sun/acerola.png b/frontend/public/boss-sprites/sun/acerola.png new file mode 100644 index 0000000..bcd2635 Binary files /dev/null and b/frontend/public/boss-sprites/sun/acerola.png differ diff --git a/frontend/public/boss-sprites/sun/hala.png b/frontend/public/boss-sprites/sun/hala.png new file mode 100644 index 0000000..b8f8003 Binary files /dev/null and b/frontend/public/boss-sprites/sun/hala.png differ diff --git a/frontend/public/boss-sprites/sun/hapu.png b/frontend/public/boss-sprites/sun/hapu.png new file mode 100644 index 0000000..fe433fc Binary files /dev/null and b/frontend/public/boss-sprites/sun/hapu.png differ diff --git a/frontend/public/boss-sprites/sun/kahili.png b/frontend/public/boss-sprites/sun/kahili.png new file mode 100644 index 0000000..29a8b4a Binary files /dev/null and b/frontend/public/boss-sprites/sun/kahili.png differ diff --git a/frontend/public/boss-sprites/sun/nanu.png b/frontend/public/boss-sprites/sun/nanu.png new file mode 100644 index 0000000..41034b6 Binary files /dev/null and b/frontend/public/boss-sprites/sun/nanu.png differ diff --git a/frontend/public/boss-sprites/sun/olivia.png b/frontend/public/boss-sprites/sun/olivia.png new file mode 100644 index 0000000..1915578 Binary files /dev/null and b/frontend/public/boss-sprites/sun/olivia.png differ diff --git a/frontend/public/boss-sprites/sun/professor-kukui.png b/frontend/public/boss-sprites/sun/professor-kukui.png new file mode 100644 index 0000000..b606772 Binary files /dev/null and b/frontend/public/boss-sprites/sun/professor-kukui.png differ diff --git a/frontend/public/boss-sprites/sword/allister.png b/frontend/public/boss-sprites/sword/allister.png new file mode 100644 index 0000000..eaa2c48 Binary files /dev/null and b/frontend/public/boss-sprites/sword/allister.png differ diff --git a/frontend/public/boss-sprites/sword/bea.png b/frontend/public/boss-sprites/sword/bea.png new file mode 100644 index 0000000..c43423e Binary files /dev/null and b/frontend/public/boss-sprites/sword/bea.png differ diff --git a/frontend/public/boss-sprites/sword/gordie.png b/frontend/public/boss-sprites/sword/gordie.png new file mode 100644 index 0000000..d9a630b Binary files /dev/null and b/frontend/public/boss-sprites/sword/gordie.png differ diff --git a/frontend/public/boss-sprites/sword/kabu.png b/frontend/public/boss-sprites/sword/kabu.png new file mode 100644 index 0000000..56c4ed9 Binary files /dev/null and b/frontend/public/boss-sprites/sword/kabu.png differ diff --git a/frontend/public/boss-sprites/sword/leon.png b/frontend/public/boss-sprites/sword/leon.png new file mode 100644 index 0000000..e5b1cee Binary files /dev/null and b/frontend/public/boss-sprites/sword/leon.png differ diff --git a/frontend/public/boss-sprites/sword/melony.png b/frontend/public/boss-sprites/sword/melony.png new file mode 100644 index 0000000..f1a2bbe Binary files /dev/null and b/frontend/public/boss-sprites/sword/melony.png differ diff --git a/frontend/public/boss-sprites/sword/milo.png b/frontend/public/boss-sprites/sword/milo.png new file mode 100644 index 0000000..9fb87db Binary files /dev/null and b/frontend/public/boss-sprites/sword/milo.png differ diff --git a/frontend/public/boss-sprites/sword/nessa.png b/frontend/public/boss-sprites/sword/nessa.png new file mode 100644 index 0000000..8bdc2e1 Binary files /dev/null and b/frontend/public/boss-sprites/sword/nessa.png differ diff --git a/frontend/public/boss-sprites/sword/opal.png b/frontend/public/boss-sprites/sword/opal.png new file mode 100644 index 0000000..e468645 Binary files /dev/null and b/frontend/public/boss-sprites/sword/opal.png differ diff --git a/frontend/public/boss-sprites/sword/piers.png b/frontend/public/boss-sprites/sword/piers.png new file mode 100644 index 0000000..eb3192e Binary files /dev/null and b/frontend/public/boss-sprites/sword/piers.png differ diff --git a/frontend/public/boss-sprites/sword/raihan.png b/frontend/public/boss-sprites/sword/raihan.png new file mode 100644 index 0000000..7bf61f2 Binary files /dev/null and b/frontend/public/boss-sprites/sword/raihan.png differ diff --git a/frontend/public/boss-sprites/ultra-sun/acerola.png b/frontend/public/boss-sprites/ultra-sun/acerola.png new file mode 100644 index 0000000..bcd2635 Binary files /dev/null and b/frontend/public/boss-sprites/ultra-sun/acerola.png differ diff --git a/frontend/public/boss-sprites/ultra-sun/hala.png b/frontend/public/boss-sprites/ultra-sun/hala.png new file mode 100644 index 0000000..2c9f8ec Binary files /dev/null and b/frontend/public/boss-sprites/ultra-sun/hala.png differ diff --git a/frontend/public/boss-sprites/ultra-sun/hapu.png b/frontend/public/boss-sprites/ultra-sun/hapu.png new file mode 100644 index 0000000..fe433fc Binary files /dev/null and b/frontend/public/boss-sprites/ultra-sun/hapu.png differ diff --git a/frontend/public/boss-sprites/ultra-sun/hau.png b/frontend/public/boss-sprites/ultra-sun/hau.png new file mode 100644 index 0000000..8ef35cb Binary files /dev/null and b/frontend/public/boss-sprites/ultra-sun/hau.png differ diff --git a/frontend/public/boss-sprites/ultra-sun/kahili.png b/frontend/public/boss-sprites/ultra-sun/kahili.png new file mode 100644 index 0000000..058b4e8 Binary files /dev/null and b/frontend/public/boss-sprites/ultra-sun/kahili.png differ diff --git a/frontend/public/boss-sprites/ultra-sun/molayne.png b/frontend/public/boss-sprites/ultra-sun/molayne.png new file mode 100644 index 0000000..d41e09a Binary files /dev/null and b/frontend/public/boss-sprites/ultra-sun/molayne.png differ diff --git a/frontend/public/boss-sprites/ultra-sun/nanu.png b/frontend/public/boss-sprites/ultra-sun/nanu.png new file mode 100644 index 0000000..6aed6e2 Binary files /dev/null and b/frontend/public/boss-sprites/ultra-sun/nanu.png differ diff --git a/frontend/public/boss-sprites/ultra-sun/olivia.png b/frontend/public/boss-sprites/ultra-sun/olivia.png new file mode 100644 index 0000000..283aa91 Binary files /dev/null and b/frontend/public/boss-sprites/ultra-sun/olivia.png differ diff --git a/frontend/public/boss-sprites/x/clemont.png b/frontend/public/boss-sprites/x/clemont.png new file mode 100644 index 0000000..ec6b2b9 Binary files /dev/null and b/frontend/public/boss-sprites/x/clemont.png differ diff --git a/frontend/public/boss-sprites/x/diantha.png b/frontend/public/boss-sprites/x/diantha.png new file mode 100644 index 0000000..496de60 Binary files /dev/null and b/frontend/public/boss-sprites/x/diantha.png differ diff --git a/frontend/public/boss-sprites/x/drasna.png b/frontend/public/boss-sprites/x/drasna.png new file mode 100644 index 0000000..f2eeb66 Binary files /dev/null and b/frontend/public/boss-sprites/x/drasna.png differ diff --git a/frontend/public/boss-sprites/x/grant.png b/frontend/public/boss-sprites/x/grant.png new file mode 100644 index 0000000..6eac553 Binary files /dev/null and b/frontend/public/boss-sprites/x/grant.png differ diff --git a/frontend/public/boss-sprites/x/korrina.png b/frontend/public/boss-sprites/x/korrina.png new file mode 100644 index 0000000..373c384 Binary files /dev/null and b/frontend/public/boss-sprites/x/korrina.png differ diff --git a/frontend/public/boss-sprites/x/malva.png b/frontend/public/boss-sprites/x/malva.png new file mode 100644 index 0000000..f1afe62 Binary files /dev/null and b/frontend/public/boss-sprites/x/malva.png differ diff --git a/frontend/public/boss-sprites/x/olympia.png b/frontend/public/boss-sprites/x/olympia.png new file mode 100644 index 0000000..9dd4b93 Binary files /dev/null and b/frontend/public/boss-sprites/x/olympia.png differ diff --git a/frontend/public/boss-sprites/x/ramos.png b/frontend/public/boss-sprites/x/ramos.png new file mode 100644 index 0000000..4f440da Binary files /dev/null and b/frontend/public/boss-sprites/x/ramos.png differ diff --git a/frontend/public/boss-sprites/x/siebold.png b/frontend/public/boss-sprites/x/siebold.png new file mode 100644 index 0000000..79a61be Binary files /dev/null and b/frontend/public/boss-sprites/x/siebold.png differ diff --git a/frontend/public/boss-sprites/x/valerie.png b/frontend/public/boss-sprites/x/valerie.png new file mode 100644 index 0000000..5d58b21 Binary files /dev/null and b/frontend/public/boss-sprites/x/valerie.png differ diff --git a/frontend/public/boss-sprites/x/viola.png b/frontend/public/boss-sprites/x/viola.png new file mode 100644 index 0000000..35c11d6 Binary files /dev/null and b/frontend/public/boss-sprites/x/viola.png differ diff --git a/frontend/public/boss-sprites/x/wikstrom.png b/frontend/public/boss-sprites/x/wikstrom.png new file mode 100644 index 0000000..1c2fe9a Binary files /dev/null and b/frontend/public/boss-sprites/x/wikstrom.png differ diff --git a/frontend/public/boss-sprites/x/wulfric.png b/frontend/public/boss-sprites/x/wulfric.png new file mode 100644 index 0000000..ee1b8b9 Binary files /dev/null and b/frontend/public/boss-sprites/x/wulfric.png differ diff --git a/frontend/src/api/bosses.ts b/frontend/src/api/bosses.ts index 1f4f971..c212ea9 100644 --- a/frontend/src/api/bosses.ts +++ b/frontend/src/api/bosses.ts @@ -1,8 +1,9 @@ import { api } from './client' import type { BossBattle, BossResult, CreateBossResultInput } from '../types/game' -export function getGameBosses(gameId: number): Promise { - return api.get(`/games/${gameId}/bosses`) +export function getGameBosses(gameId: number, all?: boolean): Promise { + const params = all ? '?all=true' : '' + return api.get(`/games/${gameId}/bosses${params}`) } export function getBossResults(runId: number): Promise { diff --git a/frontend/src/components/admin/BossBattleFormModal.tsx b/frontend/src/components/admin/BossBattleFormModal.tsx index ca81465..399a259 100644 --- a/frontend/src/components/admin/BossBattleFormModal.tsx +++ b/frontend/src/components/admin/BossBattleFormModal.tsx @@ -1,11 +1,12 @@ import { type FormEvent, useState } from 'react' import { FormModal } from './FormModal' -import type { BossBattle, Route } from '../../types/game' +import type { BossBattle, Game, Route } from '../../types/game' import type { CreateBossBattleInput, UpdateBossBattleInput } from '../../types/admin' interface BossBattleFormModalProps { boss?: BossBattle routes: Route[] + games?: Game[] nextOrder: number onSubmit: (data: CreateBossBattleInput | UpdateBossBattleInput) => void onClose: () => void @@ -35,6 +36,7 @@ const BOSS_TYPES = [ export function BossBattleFormModal({ boss, routes, + games, nextOrder, onSubmit, onClose, @@ -54,6 +56,7 @@ export function BossBattleFormModal({ const [location, setLocation] = useState(boss?.location ?? '') const [section, setSection] = useState(boss?.section ?? '') const [spriteUrl, setSpriteUrl] = useState(boss?.spriteUrl ?? '') + const [gameId, setGameId] = useState(String(boss?.gameId ?? '')) const handleSubmit = (e: FormEvent) => { e.preventDefault() @@ -69,6 +72,7 @@ export function BossBattleFormModal({ location, section: section || null, spriteUrl: spriteUrl || null, + gameId: gameId ? Number(gameId) : null, }) } @@ -173,15 +177,34 @@ export function BossBattleFormModal({ -
- - setSection(e.target.value)} - placeholder="e.g. Main Story, Endgame" - className="w-full px-3 py-2 border rounded-md dark:bg-gray-700 dark:border-gray-600" - /> +
+
+ + setSection(e.target.value)} + placeholder="e.g. Main Story, Endgame" + className="w-full px-3 py-2 border rounded-md dark:bg-gray-700 dark:border-gray-600" + /> +
+ {games && games.length > 1 && ( +
+ + +
+ )}
diff --git a/frontend/src/hooks/useBosses.ts b/frontend/src/hooks/useBosses.ts index 17f52c9..fe814e8 100644 --- a/frontend/src/hooks/useBosses.ts +++ b/frontend/src/hooks/useBosses.ts @@ -3,10 +3,10 @@ import { toast } from 'sonner' import { getGameBosses, getBossResults, createBossResult, deleteBossResult } from '../api/bosses' import type { CreateBossResultInput } from '../types/game' -export function useGameBosses(gameId: number | null) { +export function useGameBosses(gameId: number | null, all?: boolean) { return useQuery({ - queryKey: ['games', gameId, 'bosses'], - queryFn: () => getGameBosses(gameId!), + queryKey: ['games', gameId, 'bosses', { all }], + queryFn: () => getGameBosses(gameId!, all), enabled: gameId != null, }) } diff --git a/frontend/src/pages/RunEncounters.tsx b/frontend/src/pages/RunEncounters.tsx index 1585129..2693a95 100644 --- a/frontend/src/pages/RunEncounters.tsx +++ b/frontend/src/pages/RunEncounters.tsx @@ -1376,7 +1376,7 @@ export function RunEncounters() { {boss.spriteUrl && ( - {boss.name} + {boss.name} )}
diff --git a/frontend/src/pages/admin/AdminGameDetail.tsx b/frontend/src/pages/admin/AdminGameDetail.tsx index 3fe58a5..18a8bad 100644 --- a/frontend/src/pages/admin/AdminGameDetail.tsx +++ b/frontend/src/pages/admin/AdminGameDetail.tsx @@ -21,7 +21,7 @@ import { RouteFormModal } from '../../components/admin/RouteFormModal' import { BossBattleFormModal } from '../../components/admin/BossBattleFormModal' import { BossTeamEditor } from '../../components/admin/BossTeamEditor' import { TypeBadge } from '../../components/TypeBadge' -import { useGame } from '../../hooks/useGames' +import { useGame, useGames } from '../../hooks/useGames' import { useGameBosses } from '../../hooks/useBosses' import { useCreateRoute, @@ -162,11 +162,13 @@ function SortableRouteGroup({ function SortableBossRow({ boss, routes, + games, onPositionChange, onClick, }: { boss: BossBattle routes: GameRoute[] + games: import('../../types/game').Game[] onPositionChange: (bossId: number, afterRouteId: number | null) => void onClick: (b: BossBattle) => void }) { @@ -204,7 +206,17 @@ function SortableBossRow({ {boss.order} - {boss.name} + + {boss.name} + {boss.gameId != null && (() => { + const g = games.find((g) => g.id === boss.gameId) + return g ? ( + + {g.name} + + ) : null + })()} + {boss.bossType.replace('_', ' ')} @@ -247,7 +259,8 @@ export function AdminGameDetail() { const deleteRoute = useDeleteRoute(id) const reorderRoutes = useReorderRoutes(id) const bulkImportRoutes = useBulkImportRoutes(id) - const { data: bosses } = useGameBosses(id) + const { data: bosses } = useGameBosses(id, true) + const { data: allGames } = useGames() const createBoss = useCreateBossBattle(id) const updateBoss = useUpdateBossBattle(id) const deleteBoss = useDeleteBossBattle(id) @@ -273,6 +286,9 @@ export function AdminGameDetail() { const routes = game.routes ?? [] const routeGroups = organizeRoutes(routes) + const versionGroupGames = (allGames ?? []).filter( + (g) => g.versionGroupId === game.versionGroupId, + ) const handleDragEnd = (event: DragEndEvent) => { const { active, over } = event @@ -573,6 +589,7 @@ export function AdminGameDetail() { key={boss.id} boss={boss} routes={routes} + games={versionGroupGames} onPositionChange={(bossId, afterRouteId) => updateBoss.mutate({ bossId, @@ -596,6 +613,7 @@ export function AdminGameDetail() { {showCreateBoss && ( b.order)) + 1 : 1} onSubmit={(data) => createBoss.mutate(data as CreateBossBattleInput, { @@ -611,6 +629,7 @@ export function AdminGameDetail() { updateBoss.mutate( diff --git a/frontend/src/types/admin.ts b/frontend/src/types/admin.ts index 4535c77..ada4707 100644 --- a/frontend/src/types/admin.ts +++ b/frontend/src/types/admin.ts @@ -151,6 +151,7 @@ export interface CreateBossBattleInput { location: string section?: string | null spriteUrl?: string | null + gameId?: number | null } export interface UpdateBossBattleInput { @@ -165,6 +166,7 @@ export interface UpdateBossBattleInput { location?: string section?: string | null spriteUrl?: string | null + gameId?: number | null } export interface BossReorderItem { diff --git a/frontend/src/types/game.ts b/frontend/src/types/game.ts index 7c4d6d5..1753498 100644 --- a/frontend/src/types/game.ts +++ b/frontend/src/types/game.ts @@ -188,6 +188,7 @@ export interface BossBattle { location: string section: string | null spriteUrl: string | null + gameId: number | null pokemon: BossPokemon[] } diff --git a/scripts/fetch_badges.py b/scripts/fetch_badges.py new file mode 100644 index 0000000..45273fb --- /dev/null +++ b/scripts/fetch_badges.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python3 +"""One-time script to download badge images from Bulbapedia.""" + +import json +import re +import subprocess +import sys +from pathlib import Path + +BADGES_DIR = Path(__file__).resolve().parent.parent / "frontend" / "public" / "badges" +SEEDS_DIR = ( + Path(__file__).resolve().parent.parent + / "backend" + / "src" + / "app" + / "seeds" + / "data" +) + +MEDIAWIKI_API = "https://archives.bulbagarden.net/w/api.php" + + +def get_referenced_badges() -> set[str]: + """Extract all unique non-null badge_image_url from seed files.""" + badges = set() + for f in SEEDS_DIR.glob("*-bosses.json"): + data = json.loads(f.read_text()) + for boss in data: + url = boss.get("badge_image_url") + if url: + badges.add(url) + return badges + + +def get_missing_badges() -> list[str]: + """Return badge paths that are referenced but don't exist on disk.""" + referenced = get_referenced_badges() + missing = [] + for badge_path in sorted(referenced): + full_path = BADGES_DIR / Path(badge_path).name + if not full_path.exists(): + missing.append(badge_path) + return missing + + +def badge_path_to_bulbapedia_filename(badge_path: str) -> str: + """Convert /badges/coal-badge.png -> Coal_Badge.png""" + name = Path(badge_path).stem # e.g. "coal-badge" + parts = name.split("-") # ["coal", "badge"] + title_parts = [p.capitalize() for p in parts] + return "_".join(title_parts) + ".png" + + +def resolve_image_urls(filenames: list[str]) -> dict[str, str | None]: + """Use MediaWiki API to resolve image filenames to direct URLs.""" + results = {} + # Process in batches of 50 + for i in range(0, len(filenames), 50): + batch = filenames[i : i + 50] + titles = "|".join(f"File:{fn}" for fn in batch) + cmd = [ + "curl", + "-s", + f"{MEDIAWIKI_API}?action=query&titles={titles}" + "&prop=imageinfo&iiprop=url&format=json", + ] + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + data = json.loads(result.stdout) + + # Build normalization map (API normalizes underscores to spaces) + norm_map = {} + for entry in data.get("query", {}).get("normalized", []): + norm_map[entry["to"]] = entry["from"] + + pages = data.get("query", {}).get("pages", {}) + for page in pages.values(): + title = page.get("title", "").replace("File:", "") + # Map back to original underscore form + original = norm_map.get(f"File:{title}", f"File:{title}").replace( + "File:", "" + ) + imageinfo = page.get("imageinfo", []) + if imageinfo: + results[original] = imageinfo[0]["url"] + else: + results[original] = None + return results + + +def download_file(url: str, dest: Path) -> bool: + """Download a file using curl.""" + dest.parent.mkdir(parents=True, exist_ok=True) + result = subprocess.run( + ["curl", "-sL", "-o", str(dest), url], + capture_output=True, + text=True, + ) + return result.returncode == 0 and dest.exists() and dest.stat().st_size > 0 + + +def main(): + missing = get_missing_badges() + if not missing: + print("All badge images already exist!") + return + + print(f"Missing {len(missing)} badge images:") + for b in missing: + print(f" {b}") + + # Build mapping: badge_path -> bulbapedia_filename + path_to_filename = {} + for badge_path in missing: + path_to_filename[badge_path] = badge_path_to_bulbapedia_filename(badge_path) + + print(f"\nResolving {len(path_to_filename)} image URLs from Bulbapedia...") + filenames = list(set(path_to_filename.values())) + url_map = resolve_image_urls(filenames) + + # Download + success = 0 + failed = [] + for badge_path, bp_filename in sorted(path_to_filename.items()): + url = url_map.get(bp_filename) + if not url: + print(f" FAILED: {badge_path} (no URL for {bp_filename})") + failed.append((badge_path, bp_filename)) + continue + + dest = BADGES_DIR / Path(badge_path).name + if download_file(url, dest): + print(f" OK: {badge_path}") + success += 1 + else: + print(f" FAILED: {badge_path} (download error)") + failed.append((badge_path, bp_filename)) + + print(f"\nDownloaded: {success}/{len(missing)}") + if failed: + print(f"Failed ({len(failed)}):") + for badge_path, bp_filename in failed: + print(f" {badge_path} -> {bp_filename}") + + +if __name__ == "__main__": + main() diff --git a/scripts/fetch_boss_sprites.py b/scripts/fetch_boss_sprites.py new file mode 100644 index 0000000..c45ad1f --- /dev/null +++ b/scripts/fetch_boss_sprites.py @@ -0,0 +1,380 @@ +#!/usr/bin/env python3 +"""One-time script to fetch boss battle sprites from Bulbapedia archives. + +For trainer bosses (gym leaders, elite four, champions, kahunas): + Downloads VS portraits or battle sprites from archives.bulbagarden.net. + +For totem/noble pokemon bosses: + Links to existing pokemon sprites already in the project. + +Usage: + python scripts/fetch_boss_sprites.py +""" + +import json +import re +import sys +import time +import urllib.request +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +SEED_DIR = ROOT / "backend" / "src" / "app" / "seeds" / "data" +SPRITE_DIR = ROOT / "frontend" / "public" / "boss-sprites" + +BULBA_API = "https://archives.bulbagarden.net/w/api.php" +USER_AGENT = "nuzlocke-tracker-sprite-fetch/1.0" + +# ── Game slug → Bulbapedia sprite naming conventions ── +# For Gen 1-5: Spr_{CODE}_{Name}.png (pixel battle sprites) +# For Gen 6+: VS{Name}.png or VS{Name}_{CODE}.png (VS portraits) +GAME_SPRITE_CONFIG: dict[str, dict] = { + "red": {"prefix": "Spr", "code": "RG", "fmt": "spr"}, + "yellow": {"prefix": "Spr", "code": "Y", "fmt": "spr"}, + "gold": {"prefix": "Spr", "code": "GS", "fmt": "spr"}, + "crystal": {"prefix": "Spr", "code": "C", "fmt": "spr"}, + "ruby": {"prefix": "Spr", "code": "RS", "fmt": "spr"}, + "emerald": {"prefix": "Spr", "code": "E", "fmt": "spr"}, + "firered": {"prefix": "Spr", "code": "FRLG", "fmt": "spr"}, + "diamond": {"prefix": "Spr", "code": "DP", "fmt": "spr"}, + "platinum": {"prefix": "Spr", "code": "Pt", "fmt": "spr"}, + "heartgold": {"prefix": "Spr", "code": "HGSS", "fmt": "spr"}, + "black": {"prefix": "Spr", "code": "BW", "fmt": "spr"}, + "black-2": {"prefix": "Spr", "code": "B2W2", "fmt": "spr"}, + "x": {"suffix": "", "fmt": "vs"}, + "omega-ruby": {"suffix": "", "fmt": "vs"}, + "sun": {"suffix": "", "fmt": "vs"}, + "ultra-sun": {"suffix": "USUM", "fmt": "vs"}, + "lets-go-pikachu": {"suffix": "PE", "fmt": "vs"}, + "sword": {"suffix": "", "fmt": "vs"}, + "brilliant-diamond": {"suffix": "BDSP", "fmt": "vs"}, + "legends-arceus": {"suffix": "LA", "fmt": "vs"}, + "scarlet": {"suffix": "", "fmt": "vs"}, +} + +# ── Boss name → Bulbapedia filename overrides ── +# For names that don't map cleanly to Bulbapedia filenames +NAME_OVERRIDES: dict[str, str] = { + "Lt. Surge": "Lt_Surge", + "Crasher Wake": "Crasher_Wake", + "Professor Kukui": "Professor_Kukui", + "Tate & Lisa": "Tate_and_Liza", + "Tate & Liza": "Tate_and_Liza", + "Top Champion Geeta": "Geeta", +} + +# ── Totem/Noble pokemon → pokemon name in seed data for sprite lookup ── +TOTEM_POKEMON: dict[str, str] = { + "Totem Gumshoos": "Gumshoos", + "Totem Wishiwashi": "Wishiwashi Solo", + "Totem Salazzle": "Salazzle", + "Totem Lurantis": "Lurantis", + "Totem Vikavolt": "Vikavolt", + "Totem Mimikyu": "Mimikyu Disguised", + "Totem Kommo-o": "Kommo O", + "Totem Araquanid": "Araquanid", + "Totem Togedemaru": "Togedemaru", + "Totem Ribombee": "Ribombee", + # Legends: Arceus nobles + "Lord Kleavor": "Kleavor", + "Lady Lilligant": "Lilligant (Hisui)", + "Lord Arcanine": "Arcanine (Hisui)", + "Lord Electrode": "Electrode (Hisui)", + "Lord Avalugg": "Avalugg (Hisui)", + "Origin Dialga / Palkia": "Dialga (Origin)", + "Arceus": "Arceus", + # Scarlet/Violet Titan pokemon + "Stony Cliff Titan": "Klawf", + "Open Sky Titan": "Bombirdier", + "Lurking Steel Titan": "Orthworm", + "Quaking Earth Titan": "Great Tusk", + "False Dragon Titan": "Dondozo", +} + +# Multi-boss entries: use the first trainer's name +MULTI_BOSS_PRIMARY: dict[str, str] = { + "Cilan / Chili / Cress": "Cilan", +} + + +def slugify(name: str) -> str: + slug = name.lower().replace(" ", "-") + return re.sub(r"[^a-z0-9-]", "", slug) + + +def bulba_filename(boss_name: str, config: dict) -> str | None: + """Construct the Bulbapedia filename for a trainer boss.""" + name = MULTI_BOSS_PRIMARY.get(boss_name, boss_name) + name = NAME_OVERRIDES.get(name, name) + + # Replace spaces with underscores for Bulbapedia + bulba_name = name.replace(" ", "_") + + if config["fmt"] == "spr": + return f"Spr_{config['code']}_{bulba_name}.png" + else: + suffix = config.get("suffix", "") + if suffix: + return f"VS{bulba_name}_{suffix}.png" + else: + return f"VS{bulba_name}.png" + + +def resolve_image_urls(filenames: list[str]) -> dict[str, str | None]: + """Batch-resolve Bulbapedia filenames to direct download URLs via the MediaWiki API.""" + result: dict[str, str | None] = {} + batch_size = 50 + + for i in range(0, len(filenames), batch_size): + batch = filenames[i : i + batch_size] + titles = "|".join(f"File:{fn}" for fn in batch) + url = f"{BULBA_API}?action=query&titles={urllib.request.quote(titles)}&prop=imageinfo&iiprop=url&format=json" + + req = urllib.request.Request(url, headers={"User-Agent": USER_AGENT}) + try: + with urllib.request.urlopen(req, timeout=30) as resp: + data = json.loads(resp.read()) + except (urllib.error.URLError, OSError) as exc: + print(f" API error: {exc}") + for fn in batch: + result[fn] = None + continue + + pages = data.get("query", {}).get("pages", {}) + # Build title → url mapping + page_urls: dict[str, str] = {} + for page in pages.values(): + info = page.get("imageinfo", []) + if info: + # Normalize title: "File:Spr BW Cilan.png" → "Spr_BW_Cilan.png" + title = page["title"].removeprefix("File:").replace(" ", "_") + page_urls[title] = info[0]["url"] + + for fn in batch: + result[fn] = page_urls.get(fn) + + if i + batch_size < len(filenames): + time.sleep(0.5) # be polite + + return result + + +def download_image(url: str, dest: Path) -> bool: + """Download an image file.""" + dest.parent.mkdir(parents=True, exist_ok=True) + req = urllib.request.Request(url, headers={"User-Agent": USER_AGENT}) + try: + with urllib.request.urlopen(req, timeout=30) as resp: + dest.write_bytes(resp.read()) + return True + except (urllib.error.URLError, OSError) as exc: + print(f" FAILED to download {url}: {exc}") + return False + + +def load_pokemon_sprites() -> dict[str, str]: + """Load pokemon name → sprite_url mapping from the pokemon seed data.""" + pokemon_file = SEED_DIR / "pokemon.json" + if not pokemon_file.exists(): + return {} + with open(pokemon_file) as f: + pokemon_list = json.load(f) + return {p["name"]: p.get("sprite_url", "") for p in pokemon_list if p.get("sprite_url")} + + +def main(): + pokemon_sprites = load_pokemon_sprites() + if not pokemon_sprites: + print("Warning: Could not load pokemon sprites for totem/noble mapping") + + # Collect all filenames we need to resolve + all_filenames: list[str] = [] + # Track: (game_slug, boss_index, bulba_filename, dest_path, is_pokemon) + tasks: list[tuple[str, int, str | None, Path, bool]] = [] + + boss_files = sorted(SEED_DIR.glob("*-bosses.json")) + + for boss_file in boss_files: + game_slug = boss_file.name.removesuffix("-bosses.json") + config = GAME_SPRITE_CONFIG.get(game_slug) + if config is None: + print(f"Skipping {game_slug}: no sprite config defined") + continue + + with open(boss_file) as f: + bosses = json.load(f) + + sprite_subdir = SPRITE_DIR / game_slug + for idx, boss in enumerate(bosses): + boss_name = boss["name"] + boss_slug = slugify(boss_name) + dest = sprite_subdir / f"{boss_slug}.png" + local_path = f"/boss-sprites/{game_slug}/{boss_slug}.png" + + # Check if sprite already exists on disk + if dest.exists(): + # Ensure seed file has the path + if boss.get("sprite_url") != local_path: + boss["sprite_url"] = local_path + tasks.append((game_slug, idx, None, dest, False)) + continue + + # Check if this is a totem/noble pokemon boss + if boss_name in TOTEM_POKEMON: + pokemon_name = TOTEM_POKEMON[boss_name] + sprite = pokemon_sprites.get(pokemon_name) + if sprite: + boss["sprite_url"] = sprite + tasks.append((game_slug, idx, None, dest, True)) + continue + else: + print(f" Warning: No pokemon sprite found for {boss_name} ({pokemon_name})") + + # Construct Bulbapedia filename + fn = bulba_filename(boss_name, config) + if fn: + all_filenames.append(fn) + tasks.append((game_slug, idx, fn, dest, False)) + else: + print(f" Could not construct filename for {boss_name}") + tasks.append((game_slug, idx, None, dest, False)) + + # Write back any sprite_url fixes + with open(boss_file, "w") as f: + json.dump(bosses, f, indent=2, ensure_ascii=False) + f.write("\n") + + if not all_filenames: + print("No sprites to download!") + return + + # Deduplicate filenames (some bosses appear across games with same Bulba file) + unique_filenames = list(dict.fromkeys(all_filenames)) + print(f"\nResolving {len(unique_filenames)} unique Bulbapedia filenames...") + url_map = resolve_image_urls(unique_filenames) + + resolved = sum(1 for v in url_map.values() if v) + print(f"Resolved {resolved}/{len(unique_filenames)} URLs") + + # Report missing + missing = [fn for fn, url in url_map.items() if not url] + if missing: + print(f"\nCould not resolve {len(missing)} filenames:") + for fn in missing: + print(f" - {fn}") + + # Download sprites and update seed files + downloaded = 0 + skipped = 0 + failed = 0 + + # Re-read seed files for updating + seed_data: dict[str, list] = {} + for boss_file in boss_files: + game_slug = boss_file.name.removesuffix("-bosses.json") + with open(boss_file) as f: + seed_data[game_slug] = json.load(f) + + for game_slug, idx, bulba_fn, dest, is_pokemon in tasks: + if is_pokemon or dest.exists(): + skipped += 1 + continue + + if bulba_fn is None: + continue + + url = url_map.get(bulba_fn) + if not url: + failed += 1 + continue + + boss_slug = dest.stem + local_path = f"/boss-sprites/{game_slug}/{boss_slug}.png" + + if download_image(url, dest): + seed_data[game_slug][idx]["sprite_url"] = local_path + downloaded += 1 + print(f" {game_slug}/{boss_slug}.png") + else: + failed += 1 + + time.sleep(0.3) # rate limit + + # Write updated seed files + for game_slug, bosses in seed_data.items(): + boss_file = SEED_DIR / f"{game_slug}-bosses.json" + with open(boss_file, "w") as f: + json.dump(bosses, f, indent=2, ensure_ascii=False) + f.write("\n") + + print(f"\nDone! Downloaded: {downloaded}, Skipped (existing): {skipped}, Failed: {failed}") + + # Fallback attempts for failed sprites + if missing: + print("\n--- Attempting fallback filenames for missing sprites ---") + _attempt_fallbacks(missing, url_map, seed_data, tasks) + + +def _attempt_fallbacks( + missing_filenames: list[str], + url_map: dict[str, str | None], + seed_data: dict[str, list], + tasks: list, +): + """Try alternate Bulbapedia filename patterns for sprites that weren't found.""" + fallback_filenames: list[str] = [] + fallback_map: dict[str, str] = {} # fallback_fn → original_fn + + for orig_fn in missing_filenames: + # Try VS ↔ Spr swaps + if orig_fn.startswith("Spr_"): + # Spr_CODE_Name.png → VSName.png + parts = orig_fn.removeprefix("Spr_").removesuffix(".png").split("_", 1) + if len(parts) == 2: + name = parts[1] + alt = f"VS{name}.png" + fallback_filenames.append(alt) + fallback_map[alt] = orig_fn + elif orig_fn.startswith("VS"): + # VSName.png → VSName_2.png (alternate appearance) + base = orig_fn.removesuffix(".png") + alt = f"{base}_2.png" + fallback_filenames.append(alt) + fallback_map[alt] = orig_fn + + if not fallback_filenames: + return + + print(f"Trying {len(fallback_filenames)} fallback filenames...") + fallback_urls = resolve_image_urls(fallback_filenames) + + found = 0 + for fb_fn, fb_url in fallback_urls.items(): + if fb_url: + orig_fn = fallback_map[fb_fn] + url_map[orig_fn] = fb_url + found += 1 + + # Find and download for matching tasks + for game_slug, idx, bulba_fn, dest, is_pokemon in tasks: + if bulba_fn == orig_fn and not dest.exists(): + boss_slug = dest.stem + local_path = f"/boss-sprites/{game_slug}/{boss_slug}.png" + if download_image(fb_url, dest): + seed_data[game_slug][idx]["sprite_url"] = local_path + print(f" {game_slug}/{boss_slug}.png (fallback: {fb_fn})") + time.sleep(0.3) + + if found: + # Re-save seed files + for game_slug, bosses in seed_data.items(): + boss_file = SEED_DIR / f"{game_slug}-bosses.json" + with open(boss_file, "w") as f: + json.dump(bosses, f, indent=2, ensure_ascii=False) + f.write("\n") + + print(f"Fallback resolved: {found}/{len(fallback_filenames)}") + + +if __name__ == "__main__": + main()