Adv 2.0 D&D layer: Phases 1-8 + Phase 9 SP1+SP2

Combines all uncommitted D&D work into one checkpoint:

Phases 1-8 (per gogobee_dnd_session_summary.md):
- Race/class/stats system, !setup flow, !sheet, !respec
- d20-vs-AC combat with race/class passives, auto-migration
- XP/level-up, skill checks, NPC refund hooks
- Equipment slot mapping, rarity, !rest short/long
- Pre-arm active abilities, resource pools
- Bestiary, !roll/!stats/!level, onboarding DM
- Audit fixes A-I, flavor wiring under internal/flavor/
- Phase 8 weapon dice + armor AC tables (gogobee_equipment_appendix)

Phase 9 SP1 — spell system foundations:
- 79 SpellDefinitions (76 in-scope + 3 reaction-deferred)
- dnd_known_spells, dnd_spell_slots tables
- pending_cast / concentration_* columns on dnd_character
- Slot tables for Mage/Cleric/Ranger, DC + attack-bonus math
- Long-rest refreshes slots; respec wipes spell state
- Auto-grant default known list on !setup confirm and auto-migration

Phase 9 SP2 — !cast / !spells / !prepare commands:
- Out-of-combat HEAL and UTILITY resolve immediately
- Damage/control/buff queue as pending_cast for next combat
- Audit-style save-then-debit, slot refund on save failure
- !cast --drop, concentration supersession, prep-cap enforcement
- Reaction spells refused with Phase 11 note

SP3 (combat-time resolution of pending_cast) and SP4 (full prep/learn
tests) are next. Build clean, full test suite green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
prosolis
2026-05-08 08:21:44 -07:00
parent 8e0fe0230c
commit 9e1a1f606c
80 changed files with 19088 additions and 210 deletions

View File

@@ -74,16 +74,57 @@ func (p *AdventurePlugin) handleBabysitCmd(ctx MessageContext, args string) erro
return p.handleBabysitPurchase(ctx, 30)
case lower == "auto":
return p.handleBabysitAutoToggle(ctx)
case strings.HasPrefix(lower, "focus"):
return p.handleBabysitFocus(ctx, strings.TrimSpace(strings.TrimPrefix(lower, "focus")))
default:
return p.SendDM(ctx.Sender, "🍼 **Adventurer Babysitting Service**\n\n"+
"`!adventure babysit week` — 7 days of service\n"+
"`!adventure babysit month` — 30 days of service\n"+
"`!adventure babysit auto` — toggle auto-babysit on missed days\n"+
"`!adventure babysit focus <mining|fishing|foraging>` — pick the skill auto-babysit trains\n"+
"`!adventure babysit status` — check service status\n"+
"`!adventure babysit cancel` — cancel early (no refund)")
}
}
func (p *AdventurePlugin) handleBabysitFocus(ctx MessageContext, arg string) error {
userMu := p.advUserLock(ctx.Sender)
userMu.Lock()
defer userMu.Unlock()
char, err := loadAdvCharacter(ctx.Sender)
if err != nil {
return p.SendDM(ctx.Sender, "No adventurer found. Type `!adventure` to create one.")
}
switch arg {
case "":
current := char.AutoBabysitFocus
if current == "" {
current = "weakest skill (default)"
}
return p.SendDM(ctx.Sender, fmt.Sprintf(
"🎯 **Auto-babysit focus:** %s\n\nSet with `!adventure babysit focus <mining|fishing|foraging>`. Use `!adventure babysit focus clear` to revert to the weakest-skill default.",
current))
case "clear", "default", "weakest", "off":
char.AutoBabysitFocus = ""
case "mining", "fishing", "foraging":
char.AutoBabysitFocus = arg
default:
return p.SendDM(ctx.Sender, "Pick one of `mining`, `fishing`, `foraging`, or `clear`.")
}
if err := saveAdvCharacter(char); err != nil {
slog.Error("babysit: failed to save focus", "user", ctx.Sender, "err", err)
return p.SendDM(ctx.Sender, "Something went wrong. Try again.")
}
if char.AutoBabysitFocus == "" {
return p.SendDM(ctx.Sender, "🎯 Auto-babysit focus cleared. The babysitter will train your weakest skill.")
}
return p.SendDM(ctx.Sender, fmt.Sprintf("🎯 Auto-babysit focus set to **%s**. The babysitter will train this on missed days.", char.AutoBabysitFocus))
}
func (p *AdventurePlugin) handleBabysitAutoToggle(ctx MessageContext) error {
userMu := p.advUserLock(ctx.Sender)
userMu.Lock()
@@ -348,13 +389,24 @@ func (p *AdventurePlugin) runBabysitAction(char *AdventureCharacter, equip map[E
return int(result.TotalLootValue), result.XPGained, items
}
// AutoBabysitDayResult summarizes one day of auto-babysit work, surfaced in
// the morning DM so the babysitter feels like a companion that did
// something rather than a silent insurance product.
type AutoBabysitDayResult struct {
Skill string
Gold int
XP int
Items []string
Highlight bool // true if the day produced an unusually large single haul or multiple item finds
}
// runAutoBabysitDay runs a single day of babysit actions for auto-babysit.
// Called by the scheduler when a player with auto-babysit enabled misses a day.
func (p *AdventurePlugin) runAutoBabysitDay(char *AdventureCharacter) {
func (p *AdventurePlugin) runAutoBabysitDay(char *AdventureCharacter) AutoBabysitDayResult {
equip, err := loadAdvEquipment(char.UserID)
if err != nil {
slog.Error("auto-babysit: failed to load equipment", "user", char.UserID, "err", err)
return
return AutoBabysitDayResult{}
}
isHol, _ := isHolidayToday()
@@ -364,17 +416,29 @@ func (p *AdventurePlugin) runAutoBabysitDay(char *AdventureCharacter) {
}
bonuses := &AdvBonusSummary{}
skill := babysitWeakestSkill(char)
// Honor a player-set focus if it's a valid harvest skill; otherwise
// fall back to the weakest-skill default.
skill := char.AutoBabysitFocus
switch skill {
case "mining", "fishing", "foraging":
// player preference
default:
skill = babysitWeakestSkill(char)
}
activity := skillToActivity(skill)
var totalGold int
var totalXP int
var allItems []string
bestSingleHaul := 0
for char.HarvestActionsUsed < harvestMax {
gold, xp, items := p.runBabysitAction(char, equip, activity, bonuses)
totalGold += gold
totalXP += xp
allItems = append(allItems, items...)
if gold > bestSingleHaul {
bestSingleHaul = gold
}
char.HarvestActionsUsed++
}
@@ -387,6 +451,14 @@ func (p *AdventurePlugin) runAutoBabysitDay(char *AdventureCharacter) {
}
logBabysitActivity(char.UserID, string(activity), "auto_babysit_daily",
totalGold, totalXP, itemsJSON)
return AutoBabysitDayResult{
Skill: skill,
Gold: totalGold,
XP: totalXP,
Items: allItems,
Highlight: bestSingleHaul >= 200 || len(allItems) >= 2,
}
}
// ── Expiry Check ────────────────────────────────────────────────────────────