Add Hall of Fame team selection for completed runs
After marking a run as completed, a modal prompts the player to select which Pokemon (up to 6) entered the Hall of Fame. The selection is stored as hof_encounter_ids on the run, displayed in the victory banner, and can be edited later. This lays the foundation for scoping genlocke retireHoF to only the actual HoF team. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
"""add hof_encounter_ids to nuzlocke_runs
|
||||
|
||||
Revision ID: d4e5f6a7b9c0
|
||||
Revises: c3d4e5f6a7b9
|
||||
Create Date: 2026-02-09 20:00:00.000000
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects.postgresql import JSONB
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = 'd4e5f6a7b9c0'
|
||||
down_revision: Union[str, Sequence[str], None] = 'c3d4e5f6a7b9'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.add_column(
|
||||
'nuzlocke_runs',
|
||||
sa.Column('hof_encounter_ids', JSONB(), nullable=True),
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_column('nuzlocke_runs', 'hof_encounter_ids')
|
||||
@@ -120,6 +120,38 @@ async def update_run(
|
||||
|
||||
update_data = data.model_dump(exclude_unset=True)
|
||||
|
||||
# Validate hof_encounter_ids if provided
|
||||
if "hof_encounter_ids" in update_data and update_data["hof_encounter_ids"] is not None:
|
||||
hof_ids = update_data["hof_encounter_ids"]
|
||||
if len(hof_ids) > 6:
|
||||
raise HTTPException(
|
||||
status_code=400, detail="HoF team cannot have more than 6 Pokemon"
|
||||
)
|
||||
if hof_ids:
|
||||
# Validate all encounter IDs belong to this run and are alive
|
||||
enc_result = await session.execute(
|
||||
select(Encounter).where(
|
||||
Encounter.id.in_(hof_ids),
|
||||
Encounter.run_id == run_id,
|
||||
)
|
||||
)
|
||||
found = {e.id: e for e in enc_result.scalars().all()}
|
||||
missing = [eid for eid in hof_ids if eid not in found]
|
||||
if missing:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"Encounters not found in this run: {missing}",
|
||||
)
|
||||
not_alive = [
|
||||
eid for eid, e in found.items()
|
||||
if e.status != "caught" or e.faint_level is not None
|
||||
]
|
||||
if not_alive:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"Encounters are not alive: {not_alive}",
|
||||
)
|
||||
|
||||
# Auto-set completed_at when ending a run
|
||||
if "status" in update_data and update_data["status"] in ("completed", "failed"):
|
||||
if run.status != "active":
|
||||
|
||||
@@ -19,6 +19,7 @@ class NuzlockeRun(Base):
|
||||
DateTime(timezone=True), server_default=func.now()
|
||||
)
|
||||
completed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
||||
hof_encounter_ids: Mapped[list[int] | None] = mapped_column(JSONB, default=None)
|
||||
|
||||
game: Mapped["Game"] = relationship(back_populates="runs")
|
||||
encounters: Mapped[list["Encounter"]] = relationship(back_populates="run")
|
||||
|
||||
@@ -15,6 +15,7 @@ class RunUpdate(CamelModel):
|
||||
name: str | None = None
|
||||
status: str | None = None
|
||||
rules: dict | None = None
|
||||
hof_encounter_ids: list[int] | None = None
|
||||
|
||||
|
||||
class RunResponse(CamelModel):
|
||||
@@ -23,6 +24,7 @@ class RunResponse(CamelModel):
|
||||
name: str
|
||||
status: str
|
||||
rules: dict
|
||||
hof_encounter_ids: list[int] | None = None
|
||||
started_at: datetime
|
||||
completed_at: datetime | None
|
||||
|
||||
|
||||
Reference in New Issue
Block a user