feat: Add universal victory celebration modal, chamber item requirements, and fix broom & cauldron displays

This commit is contained in:
Maxi
2026-09-22 21:10:08 +02:00
parent cb8851b7a4
commit 8c3e60c1c4
19 changed files with 1149 additions and 92 deletions
+103 -11
View File
@@ -48,6 +48,14 @@ ALL_ITEMS = {
"action": "broom_polish",
"action_label": "Besen polieren ✨"
},
"winged_key": {
"id": "winged_key",
"name": "Geflügelter Schlüssel",
"icon": "🗝️",
"desc": "Ein alter Silberschlüssel mit zarten Libellenflügeln. Er sperrt die Eichentür zur Duellier-Kammer auf!",
"action": "flutter_key",
"action_label": "Flügel surren lassen 🪽"
},
"duel_badge": {
"id": "duel_badge",
"name": "Duellanten-Abzeichen",
@@ -130,6 +138,8 @@ def get_session_state(request: Request) -> dict:
state = request.session["escape"]
if "inventory" not in state:
state["inventory"] = ["wand", "broom", "snape_scroll"]
if state.get("broom_solved") and "winged_key" not in state["inventory"]:
state["inventory"].append("winged_key")
if state.get("duel_solved") and "duel_badge" not in state["inventory"]:
state["inventory"].append("duel_badge")
if state.get("brew_solved") and "felix_felicis" not in state["inventory"]:
@@ -153,17 +163,94 @@ def get_session_state(request: Request) -> dict:
request.session["escape"] = state
return request.session["escape"]
def get_template_context(request: Request, state: dict, lang: str) -> dict:
CHAMBER_REQUIREMENTS = {
"duel": {
"item_id": "winged_key",
"item_name": "Geflügelter Schlüssel",
"item_icon": "🗝️",
"from_chamber": "Kammer I: Der Ring-Flug",
"from_url": "/broom",
"reason": "Die schwere Eichentür zur Duellier-Kammer ist mit einem verzauberten Flügelschloss versiegelt. Fange zuerst den geflügelten Schlüssel im Besenflug!"
},
"brew": {
"item_id": "duel_badge",
"item_name": "Duellanten-Abzeichen & Kessel-Rezept",
"item_icon": "⚡",
"from_chamber": "Kammer II: Das Spruch-Duell",
"from_url": "/duel",
"reason": "Snapes Kupferkessel bleibt kalt. Du benötigst die geheime Rezeptur aus der bezwungenen Trainingsrüstung des Duellier-Clubs!"
},
"cryptex": {
"item_id": "felix_felicis",
"item_name": "Felix Felicis",
"item_icon": "🧪",
"from_chamber": "Kammer III: Die Brauküche",
"from_url": "/brew",
"reason": "Ein uralter Verwirrungsbann verschleiert die Runen des Gründerschlosses. Braue 'Felix Felicis', um die Symbole der vier Gründer zu durchschauen!"
},
"pixies": {
"item_id": "cryptex_key",
"item_name": "Schlüssel der Gründer",
"item_icon": "🗝️",
"from_chamber": "Kammer IV: Das Runen-Drehschloss",
"from_url": "/cryptex",
"reason": "Das Verteidigungs-Klassenzimmer ist mit dem uralten Siegel der vier Gründer verriegelt. Knacke das Drehschloss in Kammer IV!"
},
"bottles": {
"item_id": "cryptex_key",
"item_name": "Schlüssel der Gründer",
"item_icon": "🗝️",
"from_chamber": "Kammer V: Wichtel-Panik",
"from_url": "/pixies",
"reason": "Tödliche Flammen und schwere Ketten versperren den Eingang. Schließe zuerst die Wichtel-Prüfung ab und banne die Plagegeister!"
},
"finale": {
"item_id": "philosophers_stone",
"item_name": "Stein der Weisen",
"item_icon": "💎",
"from_chamber": "Kammer VI: Snapes Flaschen",
"from_url": "/bottles",
"reason": "Der Spiegel Nerhegeb offenbart sich nur jenem Zauberer, der Snapes schwarzes Feuer erfolgreich durchquert hat!"
}
}
def get_template_context(request: Request, state: dict, lang: str, chamber_name: str = None) -> dict:
inv_ids = state.get("inventory", ["wand", "broom", "snape_scroll"])
inv_items = [ALL_ITEMS[i] for i in inv_ids if i in ALL_ITEMS]
house_info = HOUSES.get(state.get("house", "gryffindor"), HOUSES["gryffindor"])
is_locked = False
lock_info = None
has_alohomora = request.query_params.get("alohomora") == "1" or state.get("alohomora_mode")
if request.query_params.get("alohomora") == "1":
state["alohomora_mode"] = True
request.session["escape"] = state
req = CHAMBER_REQUIREMENTS.get(chamber_name)
if req and not has_alohomora:
has_item = req["item_id"] in inv_ids
solved_bypass = {
"duel": state.get("broom_solved"),
"brew": state.get("duel_solved"),
"cryptex": state.get("brew_solved"),
"pixies": state.get("cryptex_solved"),
"bottles": state.get("pixies_solved"),
"finale": state.get("bottles_solved")
}
if not has_item and not solved_bypass.get(chamber_name, False):
is_locked = True
lock_info = req
return {
"state": state,
"lang": lang,
"inventory_items": inv_items,
"all_items": ALL_ITEMS,
"house": house_info,
"houses": HOUSES
"houses": HOUSES,
"is_locked": is_locked,
"lock_info": lock_info,
"chamber_requirements": CHAMBER_REQUIREMENTS
}
@app.get("/", response_class=HTMLResponse)
@@ -187,7 +274,7 @@ async def page_broom(request: Request):
return templates.TemplateResponse(
request=request,
name="broom.html",
context=get_template_context(request, state, lang)
context=get_template_context(request, state, lang, chamber_name="broom")
)
@app.get("/duel", response_class=HTMLResponse)
@@ -199,7 +286,7 @@ async def page_duel(request: Request):
return templates.TemplateResponse(
request=request,
name="duel.html",
context=get_template_context(request, state, lang)
context=get_template_context(request, state, lang, chamber_name="duel")
)
@app.get("/brew", response_class=HTMLResponse)
@@ -211,7 +298,7 @@ async def page_brew(request: Request):
return templates.TemplateResponse(
request=request,
name="brew.html",
context=get_template_context(request, state, lang)
context=get_template_context(request, state, lang, chamber_name="brew")
)
@app.get("/cryptex", response_class=HTMLResponse)
@@ -223,7 +310,7 @@ async def page_cryptex(request: Request):
return templates.TemplateResponse(
request=request,
name="cryptex.html",
context=get_template_context(request, state, lang)
context=get_template_context(request, state, lang, chamber_name="cryptex")
)
@app.get("/pixies", response_class=HTMLResponse)
@@ -235,7 +322,7 @@ async def page_pixies(request: Request):
return templates.TemplateResponse(
request=request,
name="pixies.html",
context=get_template_context(request, state, lang)
context=get_template_context(request, state, lang, chamber_name="pixies")
)
@app.get("/chess")
@@ -252,7 +339,7 @@ async def page_bottles(request: Request):
return templates.TemplateResponse(
request=request,
name="bottles.html",
context=get_template_context(request, state, lang)
context=get_template_context(request, state, lang, chamber_name="bottles")
)
@app.get("/sliding", response_class=HTMLResponse)
@@ -264,7 +351,7 @@ async def page_sliding(request: Request):
return templates.TemplateResponse(
request=request,
name="sliding.html",
context=get_template_context(request, state, lang)
context=get_template_context(request, state, lang, chamber_name="sliding")
)
@app.get("/finale", response_class=HTMLResponse)
@@ -276,7 +363,7 @@ async def page_finale(request: Request):
return templates.TemplateResponse(
request=request,
name="finale.html",
context=get_template_context(request, state, lang)
context=get_template_context(request, state, lang, chamber_name="finale")
)
@app.get("/api/state")
@@ -348,10 +435,15 @@ async def api_broom_complete(request: Request):
state["broom_solved"] = True
state["broom_score"] = int(data.get("score", 100))
state["broom_time"] = int(data.get("time_taken", 30))
if "inventory" not in state:
state["inventory"] = ["wand", "broom", "snape_scroll"]
if "winged_key" not in state["inventory"]:
state["inventory"].append("winged_key")
request.session["escape"] = state
return JSONResponse({
"success": True,
"message": "Parcours gemeistert! Die Tür zum Zauberspruch-Duell steht offen!",
"message": "Parcours gemeistert! Der geflügelte Schlüssel öffnet die Tür zum Zauberspruch-Duell!",
"new_item": ALL_ITEMS["winged_key"],
"state": state
})