feat: catch-pending flag + wounded state for encounter resolution
catch_pending: set immediately when /buddymon catch is invoked, suppresses auto-resolve while weakening Q&A is in progress. Cleared before catch roll (success clears encounter, failure leaves it without the flag so auto-resolve resumes naturally on the next clean Bash run). wounded: first clean Bash run without catch_pending drops the encounter to 5% strength and re-announces via UserPromptSubmit with a fleeing message. Second clean run auto-resolves it (it fled). UserPromptSubmit now shows distinct announcement text for wounded vs fresh encounters.
This commit is contained in:
parent
55747068e1
commit
85f53b1e83
3 changed files with 85 additions and 27 deletions
|
|
@ -169,6 +169,20 @@ def set_active_encounter(encounter: dict):
|
||||||
save_json(enc_file, data)
|
save_json(enc_file, data)
|
||||||
|
|
||||||
|
|
||||||
|
def wound_encounter() -> None:
|
||||||
|
"""Drop active encounter to minimum strength and flag for re-announcement."""
|
||||||
|
enc_file = BUDDYMON_DIR / "encounters.json"
|
||||||
|
data = load_json(enc_file)
|
||||||
|
enc = data.get("active_encounter")
|
||||||
|
if not enc:
|
||||||
|
return
|
||||||
|
enc["current_strength"] = 5
|
||||||
|
enc["wounded"] = True
|
||||||
|
enc["announced"] = False # triggers UserPromptSubmit re-announcement
|
||||||
|
data["active_encounter"] = enc
|
||||||
|
save_json(enc_file, data)
|
||||||
|
|
||||||
|
|
||||||
def match_bug_monster(output_text: str, catalog: dict) -> dict | None:
|
def match_bug_monster(output_text: str, catalog: dict) -> dict | None:
|
||||||
"""Return the first matching bug monster from the catalog, or None."""
|
"""Return the first matching bug monster from the catalog, or None."""
|
||||||
if not output_text:
|
if not output_text:
|
||||||
|
|
@ -416,14 +430,23 @@ def main():
|
||||||
existing = get_active_encounter()
|
existing = get_active_encounter()
|
||||||
|
|
||||||
if existing:
|
if existing:
|
||||||
# Auto-resolve if the monster's patterns no longer appear in output
|
# On a clean Bash run (monster patterns gone), respect catch_pending,
|
||||||
|
# wound a healthy monster, or auto-resolve a wounded one.
|
||||||
if output and not encounter_still_present(existing, output, catalog):
|
if output and not encounter_still_present(existing, output, catalog):
|
||||||
|
if existing.get("catch_pending"):
|
||||||
|
# User invoked /buddymon catch — hold the monster for them
|
||||||
|
pass
|
||||||
|
elif existing.get("wounded"):
|
||||||
|
# Already wounded on last clean run — auto-resolve (it fled)
|
||||||
xp, display = auto_resolve_encounter(existing, buddy_id)
|
xp, display = auto_resolve_encounter(existing, buddy_id)
|
||||||
messages.append(
|
messages.append(
|
||||||
f"\n⚔️ **{buddy_display} defeated {display}!** (auto-resolved)\n"
|
f"\n💨 **{display} fled!** (escaped while wounded)\n"
|
||||||
f" +{xp} XP\n"
|
f" {buddy_display} gets partial XP: +{xp}\n"
|
||||||
)
|
)
|
||||||
# else: monster persists, no message — don't spam every tool call
|
else:
|
||||||
|
# First clean run — wound it and re-announce so user can catch
|
||||||
|
wound_encounter()
|
||||||
|
# else: monster still present, no message — don't spam every tool call
|
||||||
elif output or command:
|
elif output or command:
|
||||||
# No active encounter — check for bug monster first, then event encounters
|
# No active encounter — check for bug monster first, then event encounters
|
||||||
session = load_json(BUDDYMON_DIR / "session.json")
|
session = load_json(BUDDYMON_DIR / "session.json")
|
||||||
|
|
|
||||||
|
|
@ -80,8 +80,20 @@ def main():
|
||||||
catchable = enc.get("catchable", True)
|
catchable = enc.get("catchable", True)
|
||||||
flavor = monster.get("flavor", "")
|
flavor = monster.get("flavor", "")
|
||||||
|
|
||||||
|
if enc.get("wounded"):
|
||||||
|
# Wounded re-announcement — urgent, catch-or-lose framing
|
||||||
|
lines = [
|
||||||
|
f"\n🩹 **{enc['display']} is wounded and fleeing!**",
|
||||||
|
f" Strength: {strength}% · This is your last chance to catch it.",
|
||||||
|
"",
|
||||||
|
f" **{buddy_display}** is ready — move fast!",
|
||||||
|
"",
|
||||||
|
" `[CATCH]` → `/buddymon catch` (near-guaranteed at 5% strength)",
|
||||||
|
" `[IGNORE]` → it flees on the next clean run",
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
# Normal first appearance
|
||||||
catchable_str = "[catchable · catch only]" if not defeatable else f"[{rarity} · {'catchable' if catchable else ''}]"
|
catchable_str = "[catchable · catch only]" if not defeatable else f"[{rarity} · {'catchable' if catchable else ''}]"
|
||||||
|
|
||||||
lines = [
|
lines = [
|
||||||
f"\n💀 **{enc['display']} appeared!** {catchable_str}",
|
f"\n💀 **{enc['display']} appeared!** {catchable_str}",
|
||||||
f" Strength: {strength}% · Rarity: {stars}",
|
f" Strength: {strength}% · Rarity: {stars}",
|
||||||
|
|
|
||||||
|
|
@ -175,14 +175,31 @@ ShadowBit (🔒) cannot be defeated — redirect to catch.
|
||||||
|
|
||||||
Read active encounter. If none: "No active encounter."
|
Read active encounter. If none: "No active encounter."
|
||||||
|
|
||||||
Show strength and weakening status. Explain weaken actions:
|
**Immediately set `catch_pending = True`** on the encounter to suppress auto-resolve
|
||||||
|
while the weakening Q&A is in progress:
|
||||||
|
|
||||||
|
```python
|
||||||
|
import json, os
|
||||||
|
BUDDYMON_DIR = os.path.expanduser("~/.claude/buddymon")
|
||||||
|
enc_file = f"{BUDDYMON_DIR}/encounters.json"
|
||||||
|
encounters = json.load(open(enc_file))
|
||||||
|
enc = encounters.get("active_encounter")
|
||||||
|
if enc:
|
||||||
|
enc["catch_pending"] = True
|
||||||
|
encounters["active_encounter"] = enc
|
||||||
|
json.dump(encounters, open(enc_file, "w"), indent=2)
|
||||||
|
```
|
||||||
|
|
||||||
|
Show strength and weakening status. If `enc.get("wounded")` is True, note that
|
||||||
|
it's already at 5% and a catch is near-guaranteed. Explain weaken actions:
|
||||||
- Write a failing test → -20% strength
|
- Write a failing test → -20% strength
|
||||||
- Isolate reproduction case → -20% strength
|
- Isolate reproduction case → -20% strength
|
||||||
- Add documenting comment → -10% strength
|
- Add documenting comment → -10% strength
|
||||||
|
|
||||||
Ask which weakening actions have been done. Apply reductions to `current_strength`.
|
Ask which weakening actions have been done. Apply reductions to `current_strength`.
|
||||||
|
|
||||||
Catch roll:
|
Catch roll (clear `catch_pending` before rolling — success clears encounter, failure
|
||||||
|
leaves it active without the flag so auto-resolve resumes naturally):
|
||||||
```python
|
```python
|
||||||
import json, os, random
|
import json, os, random
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
|
|
@ -202,6 +219,9 @@ roster = json.load(open(roster_file))
|
||||||
enc = encounters.get("active_encounter")
|
enc = encounters.get("active_encounter")
|
||||||
buddy_id = active.get("buddymon_id")
|
buddy_id = active.get("buddymon_id")
|
||||||
|
|
||||||
|
# Clear catch_pending before rolling (win or lose)
|
||||||
|
enc["catch_pending"] = False
|
||||||
|
|
||||||
buddy_data = (catalog.get("buddymon", {}).get(buddy_id)
|
buddy_data = (catalog.get("buddymon", {}).get(buddy_id)
|
||||||
or catalog.get("evolutions", {}).get(buddy_id) or {})
|
or catalog.get("evolutions", {}).get(buddy_id) or {})
|
||||||
buddy_level = roster.get("owned", {}).get(buddy_id, {}).get("level", 1)
|
buddy_level = roster.get("owned", {}).get(buddy_id, {}).get("level", 1)
|
||||||
|
|
@ -233,6 +253,9 @@ if success:
|
||||||
json.dump(encounters, open(enc_file, "w"), indent=2)
|
json.dump(encounters, open(enc_file, "w"), indent=2)
|
||||||
print(f"caught:{xp}")
|
print(f"caught:{xp}")
|
||||||
else:
|
else:
|
||||||
|
# Save cleared catch_pending back on failure
|
||||||
|
encounters["active_encounter"] = enc
|
||||||
|
json.dump(encounters, open(enc_file, "w"), indent=2)
|
||||||
print(f"failed:{int(catch_rate * 100)}")
|
print(f"failed:{int(catch_rate * 100)}")
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue