diff --git a/matrixbot/commands.py b/matrixbot/commands.py
index ef4f710..1bedbc4 100644
--- a/matrixbot/commands.py
+++ b/matrixbot/commands.py
@@ -233,6 +233,68 @@ async def cmd_8ball(client: AsyncClient, room_id: str, sender: str, args: str):
WYNTER_ID = "@wynter:mozilla.org"
JARED_ID = "@jared:matrix.lotusguild.org"
+ LEON_ID = "@stranger_danger:matrix.lotusguild.org"
+
+ _LEON_LORE = (
+ "Leon Scott Kennedy is a former Raccoon City rookie cop turned elite U.S. government special agent. "
+ "He survived the 1998 Raccoon City zombie outbreak on his first day on the job (caused by the Umbrella Corporation's T-virus). "
+ "He later rescued the President's daughter Ashley in rural Spain from a bioweapon cult (RE4). "
+ "He has a complicated, unresolved romantic history with Ada Wong, a spy/mercenary who keeps saving and betraying him. "
+ "Personality: dry wit, sarcastic quips under pressure, self-deprecating humor, but deeply committed to protecting civilians. "
+ "Speech style: cool one-liners, dark humor in dangerous situations, never panics. "
+ "Famous lines: 'Where's everyone going? Bingo?', 'What are ya buyin?', 'You're small-time.' "
+ "He is haunted by Raccoon City and distrustful of powerful organizations, but never loses his moral compass."
+ )
+
+ if sender == LEON_ID:
+ question = sanitize_input(args)
+ q_for_prompt = question
+ bio_context = _LEON_LORE + " "
+ prompt = (
+ bio_context +
+ "You are a magic 8-ball responding to Leon S. Kennedy from Resident Evil. "
+ "Answer his question in the tone and style of the Resident Evil universe — dramatic, slightly dark, "
+ "with the dry wit and cool-under-pressure attitude Leon is known for. "
+ "Reference his world (bioweapons, government ops, Ada Wong, survival) if relevant. "
+ "One sentence only. Give only your prediction — no questions back, no disclaimers.\n\n"
+ f"Question: {q_for_prompt}"
+ )
+ fallback_leon = random.choice([
+ "The signs point to danger ahead — but you've handled worse.",
+ "Outlook unclear. Better stock up on ammo just in case.",
+ "It is certain — but so was Raccoon City, and look how that turned out.",
+ "Signs point to yes. Ada probably already knew.",
+ "Don't count on it. You should know by now that nothing goes according to plan.",
+ "Definitely. Now stop standing around and move.",
+ ])
+ used_llm = False
+ try:
+ timeout = aiohttp.ClientTimeout(total=30)
+ async with aiohttp.ClientSession(timeout=timeout) as session:
+ async with session.post(
+ f"{OLLAMA_URL}/api/generate",
+ json={"model": BALL_MODEL, "prompt": prompt, "stream": False},
+ ) as response:
+ data = await response.json()
+ raw = _normalize_caps(data.get("response", "").strip())
+ if _is_valid_8ball_response(raw):
+ answer = raw
+ used_llm = True
+ else:
+ answer = fallback_leon
+ except Exception as e:
+ logger.error(f"8ball Ollama error (leon): {e}", exc_info=True)
+ answer = fallback_leon
+
+ plain = f"🎱 {answer}\n{args}"
+ html = (
+ f'🎱 {answer}
'
+ f'{args}'
+ + (f'
via {_model_label(BALL_MODEL)}' if used_llm else "")
+ + (f'
[debug] prompt: {q_for_prompt}' if debug else "")
+ )
+ await send_html(client, room_id, plain, html)
+ return
if sender in (JARED_ID, WYNTER_ID):
question = sanitize_input(args)
@@ -1840,12 +1902,21 @@ _NATCO_LORE = (
"Technical College covering SQL, .NET, and computer programming — which he now uses to rack tape drives."
)
+_LEON_ROAST_LORE = (
+ "Leon S. Kennedy is a U.S. government special agent and Resident Evil protagonist. "
+ "He survived the Raccoon City zombie outbreak on his first day as a cop, then spent his career "
+ "fighting bioweapon cults in rural Spain, getting betrayed by Ada Wong repeatedly, and making "
+ "action-hero one-liners while covered in blood. He has a bad haircut and even worse luck with women."
+)
+
_ROAST_LORE: dict[str, tuple[str, str]] = {
"jared": ("Jared", _JARED_LORE),
"wynter": ("Wynter", _WYNTER_LORE),
"lonely": ("Cole", _LONELY_LORE),
"natco": ("Nathan", _NATCO_LORE),
"natcofragomatic": ("Nathan", _NATCO_LORE),
+ "stranger_danger": ("Leon", _LEON_ROAST_LORE),
+ "leon": ("Leon", _LEON_ROAST_LORE),
}