Add boss battles, level caps, and badge tracking
Introduces full boss battle system: data models (BossBattle, BossPokemon, BossResult), API endpoints for CRUD and per-run defeat tracking, and frontend UI including a sticky level cap bar with badge display on the run page, interleaved boss battle cards in the encounter list, and an admin panel section for managing boss battles and their pokemon teams. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,3 +1,6 @@
|
||||
from app.models.boss_battle import BossBattle
|
||||
from app.models.boss_pokemon import BossPokemon
|
||||
from app.models.boss_result import BossResult
|
||||
from app.models.encounter import Encounter
|
||||
from app.models.evolution import Evolution
|
||||
from app.models.game import Game
|
||||
@@ -7,6 +10,9 @@ from app.models.route import Route
|
||||
from app.models.route_encounter import RouteEncounter
|
||||
|
||||
__all__ = [
|
||||
"BossBattle",
|
||||
"BossPokemon",
|
||||
"BossResult",
|
||||
"Encounter",
|
||||
"Evolution",
|
||||
"Game",
|
||||
|
||||
31
backend/src/app/models/boss_battle.py
Normal file
31
backend/src/app/models/boss_battle.py
Normal file
@@ -0,0 +1,31 @@
|
||||
from sqlalchemy import ForeignKey, SmallInteger, String
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from app.core.database import Base
|
||||
|
||||
|
||||
class BossBattle(Base):
|
||||
__tablename__ = "boss_battles"
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
game_id: Mapped[int] = mapped_column(ForeignKey("games.id"), index=True)
|
||||
name: Mapped[str] = mapped_column(String(100))
|
||||
boss_type: Mapped[str] = mapped_column(String(20)) # gym_leader, elite_four, champion, rival, evil_team, other
|
||||
badge_name: Mapped[str | None] = mapped_column(String(100))
|
||||
badge_image_url: Mapped[str | None] = mapped_column(String(500))
|
||||
level_cap: Mapped[int] = mapped_column(SmallInteger)
|
||||
order: Mapped[int] = mapped_column(SmallInteger)
|
||||
after_route_id: Mapped[int | None] = mapped_column(
|
||||
ForeignKey("routes.id"), index=True, default=None
|
||||
)
|
||||
location: Mapped[str] = mapped_column(String(200))
|
||||
sprite_url: Mapped[str | None] = mapped_column(String(500))
|
||||
|
||||
game: Mapped["Game"] = relationship(back_populates="boss_battles")
|
||||
after_route: Mapped["Route | None"] = relationship()
|
||||
pokemon: Mapped[list["BossPokemon"]] = relationship(
|
||||
back_populates="boss_battle", cascade="all, delete-orphan"
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<BossBattle(id={self.id}, name='{self.name}', type='{self.boss_type}')>"
|
||||
22
backend/src/app/models/boss_pokemon.py
Normal file
22
backend/src/app/models/boss_pokemon.py
Normal file
@@ -0,0 +1,22 @@
|
||||
from sqlalchemy import ForeignKey, SmallInteger
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from app.core.database import Base
|
||||
|
||||
|
||||
class BossPokemon(Base):
|
||||
__tablename__ = "boss_pokemon"
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
boss_battle_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("boss_battles.id", ondelete="CASCADE"), index=True
|
||||
)
|
||||
pokemon_id: Mapped[int] = mapped_column(ForeignKey("pokemon.id"), index=True)
|
||||
level: Mapped[int] = mapped_column(SmallInteger)
|
||||
order: Mapped[int] = mapped_column(SmallInteger)
|
||||
|
||||
boss_battle: Mapped["BossBattle"] = relationship(back_populates="pokemon")
|
||||
pokemon: Mapped["Pokemon"] = relationship()
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<BossPokemon(id={self.id}, boss_battle_id={self.boss_battle_id}, pokemon_id={self.pokemon_id})>"
|
||||
30
backend/src/app/models/boss_result.py
Normal file
30
backend/src/app/models/boss_result.py
Normal file
@@ -0,0 +1,30 @@
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import DateTime, ForeignKey, SmallInteger, String, UniqueConstraint
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from app.core.database import Base
|
||||
|
||||
|
||||
class BossResult(Base):
|
||||
__tablename__ = "boss_results"
|
||||
__table_args__ = (
|
||||
UniqueConstraint("run_id", "boss_battle_id", name="uq_boss_results_run_boss"),
|
||||
)
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
run_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("nuzlocke_runs.id", ondelete="CASCADE"), index=True
|
||||
)
|
||||
boss_battle_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("boss_battles.id"), index=True
|
||||
)
|
||||
result: Mapped[str] = mapped_column(String(10)) # won, lost
|
||||
attempts: Mapped[int] = mapped_column(SmallInteger, default=1)
|
||||
completed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
||||
|
||||
run: Mapped["NuzlockeRun"] = relationship(back_populates="boss_results")
|
||||
boss_battle: Mapped["BossBattle"] = relationship()
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<BossResult(id={self.id}, run_id={self.run_id}, boss_battle_id={self.boss_battle_id}, result='{self.result}')>"
|
||||
@@ -18,6 +18,7 @@ class Game(Base):
|
||||
|
||||
routes: Mapped[list["Route"]] = relationship(back_populates="game")
|
||||
runs: Mapped[list["NuzlockeRun"]] = relationship(back_populates="game")
|
||||
boss_battles: Mapped[list["BossBattle"]] = relationship(back_populates="game")
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<Game(id={self.id}, name='{self.name}')>"
|
||||
|
||||
@@ -22,6 +22,7 @@ class NuzlockeRun(Base):
|
||||
|
||||
game: Mapped["Game"] = relationship(back_populates="runs")
|
||||
encounters: Mapped[list["Encounter"]] = relationship(back_populates="run")
|
||||
boss_results: Mapped[list["BossResult"]] = relationship(back_populates="run")
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<NuzlockeRun(id={self.id}, name='{self.name}', status='{self.status}')>"
|
||||
|
||||
Reference in New Issue
Block a user