Add type restriction rule (monolocke)
All checks were successful
CI / backend-lint (push) Successful in 10s
CI / actions-lint (push) Successful in 14s
CI / frontend-lint (push) Successful in 22s

Adds allowedTypes: string[] to NuzlockeRules. When set, the encounter
selector hides non-matching Pokemon and the routes endpoint filters out
routes with no matching encounters, so only eligible locations appear.

Type picker UI in RulesConfiguration; active restriction shown in
RuleBadges. Backend accepts allowed_types query param and joins through
RouteEncounter.pokemon to filter by type.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-21 12:22:05 +01:00
parent 85fef68dae
commit 993ad09d9c
11 changed files with 149 additions and 28 deletions

View File

@@ -1,7 +1,7 @@
import json
from pathlib import Path
from fastapi import APIRouter, Depends, HTTPException
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy import delete, select, update
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload
@@ -131,6 +131,7 @@ async def get_game(game_id: int, session: AsyncSession = Depends(get_session)):
async def list_game_routes(
game_id: int,
flat: bool = False,
allowed_types: list[str] | None = Query(None),
session: AsyncSession = Depends(get_session),
):
"""
@@ -138,13 +139,18 @@ async def list_game_routes(
By default, returns a hierarchical structure with top-level routes containing
nested children. Use `flat=True` to get a flat list of all routes.
When `allowed_types` is provided, routes with no encounters matching any of
those Pokemon types are excluded.
"""
vg_id = await _get_version_group_id(session, game_id)
result = await session.execute(
select(Route)
.where(Route.version_group_id == vg_id)
.options(selectinload(Route.route_encounters))
.options(
selectinload(Route.route_encounters).selectinload(RouteEncounter.pokemon)
)
.order_by(Route.order)
)
all_routes = result.scalars().all()
@@ -170,7 +176,14 @@ async def list_game_routes(
# Determine which routes have encounters for this game
def has_encounters(route: Route) -> bool:
return any(re.game_id == game_id for re in route.route_encounters)
encounters = [re for re in route.route_encounters if re.game_id == game_id]
if not encounters:
return False
if allowed_types:
return any(
t in allowed_types for re in encounters for t in re.pokemon.types
)
return True
# Collect IDs of parent routes that have at least one child with encounters
parents_with_children = set()

View File

@@ -154,6 +154,7 @@ DEFAULT_RULES = {
"egglocke": False,
"wonderlocke": False,
"randomizer": False,
"allowedTypes": [],
}