From 90edb271471ae124c314dfa165f2f14aed5c1eb8 Mon Sep 17 00:00:00 2001 From: prosolis <5590409+prosolis@users.noreply.github.com> Date: Mon, 27 Apr 2026 18:15:26 -0700 Subject: [PATCH] Coop: add !coop admgift admin debug path Admin-only command that creates a gift without the party-member or harvest-action checks. Lets a solo tester exercise the basket/mimic mechanic without recruiting a non-party spectator. Usage: !coop admgift [sender_user_id] (sender defaults to the caller) Co-Authored-By: Claude Opus 4.7 (1M context) --- internal/plugin/coop_dungeon.go | 58 +++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/internal/plugin/coop_dungeon.go b/internal/plugin/coop_dungeon.go index cfcbb4a..d4a1dcf 100644 --- a/internal/plugin/coop_dungeon.go +++ b/internal/plugin/coop_dungeon.go @@ -104,6 +104,8 @@ func (p *AdventurePlugin) handleCoopCmd(ctx MessageContext) error { return p.handleCoopGift(ctx, strings.TrimSpace(args[5:])) case strings.HasPrefix(lower, "giftvote "): return p.handleCoopGiftVote(ctx, strings.TrimSpace(args[9:])) + case strings.HasPrefix(lower, "admgift "): + return p.handleCoopAdmGift(ctx, strings.TrimSpace(args[8:])) } return p.SendDM(ctx.Sender, "Unknown coop command. Try `!coop help`.") @@ -365,6 +367,62 @@ func (p *AdventurePlugin) handleCoopCancel(ctx MessageContext) error { return p.SendDM(ctx.Sender, fmt.Sprintf("Run #%d cancelled.", run.ID)) } +// handleCoopAdmGift is an admin-only debug path that creates a gift on +// behalf of the caller (or a specified sender) without the harvest-action +// or party-membership checks. Useful for testing the gift mechanic without +// recruiting a non-party spectator. +// +// Usage: !coop admgift [sender_user_id] +func (p *AdventurePlugin) handleCoopAdmGift(ctx MessageContext, args string) error { + if !p.IsAdmin(ctx.Sender) { + return nil + } + parts := strings.Fields(args) + if len(parts) < 2 { + return p.SendDM(ctx.Sender, "Usage: `!coop admgift [sender_user_id]`. Admin-only — bypasses party-member and harvest checks.") + } + runID, err := strconv.Atoi(parts[0]) + if err != nil { + return p.SendDM(ctx.Sender, "Run ID must be a number.") + } + giftType := strings.ToLower(parts[1]) + if giftType != coopGiftBasket && giftType != coopGiftMimic { + return p.SendDM(ctx.Sender, "Gift type must be `basket` or `mimic`.") + } + sender := ctx.Sender + if len(parts) >= 3 { + sender = id.UserID(parts[2]) + } + + run, err := loadCoopRun(runID) + if err != nil || run == nil { + return p.SendDM(ctx.Sender, "Run not found.") + } + if run.Status != "active" { + return p.SendDM(ctx.Sender, fmt.Sprintf("Run #%d is %s — gifts only land during active runs.", runID, run.Status)) + } + + giftID, err := createCoopGift(runID, sender, run.CurrentDay, giftType) + if err != nil { + return p.SendDM(ctx.Sender, "Couldn't create gift.") + } + + gr := gamesRoom() + if gr != "" { + gift, _ := loadCoopGift(giftID) + members, _ := loadCoopMembers(runID) + if gift != nil { + postID, perr := p.SendMessageID(gr, renderCoopGiftPost(run, gift, members)) + if perr == nil { + _ = saveCoopGiftPostID(giftID, postID) + } + } + } + + return p.SendDM(ctx.Sender, fmt.Sprintf("📦 Admin gift dispatched: %s from %s to run #%d (day %d). Gift id #%d. No harvest action consumed.", + giftType, sender, runID, run.CurrentDay, giftID)) +} + func (p *AdventurePlugin) handleCoopVote(ctx MessageContext, voteArg string) error { userMu := p.advUserLock(ctx.Sender) userMu.Lock()