Add blacksmith repair system, community lottery, and audit fixes

Blacksmith: equipment repair with tier-based pricing, DM confirmation flow,
masterwork/arena surcharges, full flavor text pools. Added to main adventure
menu as option 6 (rest bumped to 7).

Lottery: weekly draw (Friday 23:59 UTC), ticket purchases with 100/week cap,
Fisher-Yates number generation, fixed+jackpot prize tiers, community pot
funding, Thursday reminders, draw history.

Audit fixes: TOCTOU in blacksmith repair costs (recompute from fresh equipment),
user lock in DM slot selection, partial repair refund tracking, error logging
on save failures, Fisher-Yates bias correction, communityPotDebit return value
checks in draw execution.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
prosolis
2026-04-04 09:37:21 -07:00
parent ad6e652755
commit 6c6d74fb1b
10 changed files with 1300 additions and 4 deletions

View File

@@ -204,6 +204,12 @@ func (p *AdventurePlugin) dispatchCommand(ctx MessageContext) error {
return p.handleRivalsCmd(ctx)
case lower == "babysit" || strings.HasPrefix(lower, "babysit "):
return p.handleBabysitCmd(ctx, strings.TrimSpace(strings.TrimPrefix(lower, "babysit")))
case lower == "blacksmith" || lower == "repair":
return p.handleBlacksmithCmd(ctx)
case lower == "repair all":
return p.handleRepairAllCmd(ctx)
case strings.HasPrefix(lower, "repair "):
return p.handleRepairSlotCmd(ctx, strings.TrimSpace(args[7:]))
}
return p.SendDM(ctx.Sender, "Unknown command. Type `!adventure help` to see available commands.")
@@ -223,6 +229,9 @@ const advHelpText = `**Adventure Commands**
` + "`!adventure respond`" + ` — Respond to a mid-day event
` + "`!adventure rivals`" + ` — View rival duel records
` + "`!adventure babysit`" + ` — Adventurer Babysitting Service
` + "`!adventure blacksmith`" + ` — Visit the blacksmith (view repair costs)
` + "`!adventure repair all`" + ` — Repair all damaged equipment
` + "`!adventure repair <slot>`" + ` — Repair a specific slot
` + "`!adventure help`" + ` — This message
**Arena:**
@@ -496,6 +505,10 @@ func (p *AdventurePlugin) resolvePendingInteraction(ctx MessageContext, interact
return p.handleMasterworkEquipConfirm(ctx, interaction)
case "rival_rps":
return p.resolveRivalRPSRound(ctx, interaction)
case "blacksmith_slot":
return p.resolveBlacksmithSlotChoice(ctx, interaction)
case "blacksmith_confirm":
return p.resolveBlacksmithConfirm(ctx, interaction)
}
return nil
}
@@ -585,11 +598,16 @@ func (p *AdventurePlugin) parseAndResolveChoice(ctx MessageContext, body string)
lower := strings.ToLower(body)
// Parse "6" or "rest"
if lower == "6" || lower == "rest" {
// Parse "7" or "rest"
if lower == "7" || lower == "rest" {
return p.resolveRest(ctx, char)
}
// Parse "6" or "blacksmith"
if lower == "6" || lower == "blacksmith" {
return p.handleBlacksmithCmd(ctx)
}
// Parse "5" or "shop"
if lower == "5" || lower == "shop" {
equip, _ := loadAdvEquipment(ctx.Sender)