mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-16 08:52:42 +00:00
Add rival duels, babysitting service, multilingual !define, economy tuning
Rival System: - RPS-based duels between combat level 5+ players (best of 3, ties re-prompt) - Stakes scale with level, split 50/50 between winner and community pot - 3-4 day randomized challenge interval, 7-day same-pair cooldown - 24h response window with auto-forfeit - Full flavor text pools, character sheet integration, !adventure rivals Babysitting Service: - Weekly/monthly auto-play service targeting weakest skill - Babysitter rerolls death, claims items, credits gold and XP - Rivals automatically declined during service - End-of-service summary with diaper report Revive fixes: - On-demand revive in handleMenu, parseAndResolveChoice, handleStatus - Morning DM respawn check now runs before babysit interception - Players no longer stuck dead after DeadUntil expires mid-day Other changes: - Multilingual !define searches all DreamDict languages by default - TwinBee empty haul now shows "jackshit" instead of blank - Wordle solve payouts increased (e.g. 1-guess: €100 -> €500) - Per-message euro payouts doubled across all tiers - Audit fixes: user locks on RPS/babysit handlers, TOCTOU on gold transfers, dead code removal, deprecated strings.Title replaced, error logging on silent failures Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -117,6 +117,7 @@ func (p *AdventurePlugin) Init() error {
|
||||
go p.midnightTicker()
|
||||
go p.eventTicker()
|
||||
go p.arenaAutoCashoutTicker()
|
||||
go p.rivalChallengeTicker()
|
||||
|
||||
// Auto-cashout any arena runs left in 'awaiting' from a prior restart
|
||||
p.arenaCleanupStaleRuns()
|
||||
@@ -199,6 +200,10 @@ func (p *AdventurePlugin) dispatchCommand(ctx MessageContext) error {
|
||||
return p.handleEventRespond(ctx)
|
||||
case lower == "help":
|
||||
return p.SendDM(ctx.Sender, advHelpText)
|
||||
case lower == "rivals":
|
||||
return p.handleRivalsCmd(ctx)
|
||||
case lower == "babysit" || strings.HasPrefix(lower, "babysit "):
|
||||
return p.handleBabysitCmd(ctx, strings.TrimSpace(strings.TrimPrefix(lower, "babysit")))
|
||||
}
|
||||
|
||||
return p.SendDM(ctx.Sender, "Unknown command. Type `!adventure help` to see available commands.")
|
||||
@@ -216,6 +221,8 @@ const advHelpText = `**Adventure Commands**
|
||||
` + "`!adventure inventory`" + ` — View your inventory
|
||||
` + "`!adventure leaderboard`" + ` — View the leaderboard
|
||||
` + "`!adventure respond`" + ` — Respond to a mid-day event
|
||||
` + "`!adventure rivals`" + ` — View rival duel records
|
||||
` + "`!adventure babysit`" + ` — Adventurer Babysitting Service
|
||||
` + "`!adventure help`" + ` — This message
|
||||
|
||||
**Arena:**
|
||||
@@ -238,8 +245,22 @@ func (p *AdventurePlugin) handleMenu(ctx MessageContext) error {
|
||||
}
|
||||
|
||||
if !char.Alive {
|
||||
text := renderAdvDeathStatusDM(char)
|
||||
return p.SendDM(ctx.Sender, text)
|
||||
// On-demand revive if death timer has expired
|
||||
if char.DeadUntil != nil && time.Now().UTC().After(*char.DeadUntil) {
|
||||
char.Alive = true
|
||||
char.DeadUntil = nil
|
||||
if err := saveAdvCharacter(char); err != nil {
|
||||
slog.Error("adventure: on-demand revive failed", "user", char.UserID, "err", err)
|
||||
} else {
|
||||
text := renderAdvRespawnDM(char)
|
||||
p.SendDM(ctx.Sender, text)
|
||||
// Fall through to show menu
|
||||
}
|
||||
}
|
||||
if !char.Alive {
|
||||
text := renderAdvDeathStatusDM(char)
|
||||
return p.SendDM(ctx.Sender, text)
|
||||
}
|
||||
}
|
||||
|
||||
if char.ActionTakenToday {
|
||||
@@ -284,9 +305,29 @@ func (p *AdventurePlugin) handleStatus(ctx MessageContext) error {
|
||||
return p.SendReply(ctx.RoomID, ctx.EventID, "No adventurer found. Type `!adventure` to create one.")
|
||||
}
|
||||
|
||||
equip, _ := loadAdvEquipment(ctx.Sender)
|
||||
items, _ := loadAdvInventory(ctx.Sender)
|
||||
treasures, _ := loadAdvTreasureBonuses(ctx.Sender)
|
||||
// On-demand revive if death timer has expired
|
||||
if !char.Alive && char.DeadUntil != nil && time.Now().UTC().After(*char.DeadUntil) {
|
||||
char.Alive = true
|
||||
char.DeadUntil = nil
|
||||
if err := saveAdvCharacter(char); err != nil {
|
||||
slog.Error("adventure: on-demand revive failed", "user", char.UserID, "err", err)
|
||||
} else {
|
||||
p.SendDM(ctx.Sender, renderAdvRespawnDM(char))
|
||||
}
|
||||
}
|
||||
|
||||
equip, err := loadAdvEquipment(ctx.Sender)
|
||||
if err != nil {
|
||||
slog.Error("adventure: failed to load equipment for status", "user", ctx.Sender, "err", err)
|
||||
}
|
||||
items, err := loadAdvInventory(ctx.Sender)
|
||||
if err != nil {
|
||||
slog.Error("adventure: failed to load inventory for status", "user", ctx.Sender, "err", err)
|
||||
}
|
||||
treasures, err := loadAdvTreasureBonuses(ctx.Sender)
|
||||
if err != nil {
|
||||
slog.Error("adventure: failed to load treasures for status", "user", ctx.Sender, "err", err)
|
||||
}
|
||||
balance := p.euro.GetBalance(ctx.Sender)
|
||||
|
||||
text := renderAdvCharacterSheet(char, equip, items, treasures, balance)
|
||||
@@ -453,6 +494,8 @@ func (p *AdventurePlugin) resolvePendingInteraction(ctx MessageContext, interact
|
||||
return p.handleMasterworkEquipReply(ctx, interaction)
|
||||
case "masterwork_equip_confirm":
|
||||
return p.handleMasterworkEquipConfirm(ctx, interaction)
|
||||
case "rival_rps":
|
||||
return p.resolveRivalRPSRound(ctx, interaction)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -499,7 +542,19 @@ func (p *AdventurePlugin) parseAndResolveChoice(ctx MessageContext, body string)
|
||||
}
|
||||
|
||||
if !char.Alive {
|
||||
return p.SendDM(ctx.Sender, renderAdvDeathStatusDM(char))
|
||||
// On-demand revive if death timer has expired
|
||||
if char.DeadUntil != nil && time.Now().UTC().After(*char.DeadUntil) {
|
||||
char.Alive = true
|
||||
char.DeadUntil = nil
|
||||
if err := saveAdvCharacter(char); err != nil {
|
||||
slog.Error("adventure: on-demand revive failed", "user", char.UserID, "err", err)
|
||||
} else {
|
||||
p.SendDM(ctx.Sender, renderAdvRespawnDM(char))
|
||||
}
|
||||
}
|
||||
if !char.Alive {
|
||||
return p.SendDM(ctx.Sender, renderAdvDeathStatusDM(char))
|
||||
}
|
||||
}
|
||||
|
||||
if char.ActionTakenToday {
|
||||
@@ -665,6 +720,9 @@ func (p *AdventurePlugin) resolveActivity(ctx MessageContext, char *AdventureCha
|
||||
|
||||
// Check level up
|
||||
result.LeveledUp, result.NewLevel = checkAdvLevelUp(char, result.XPSkill)
|
||||
if result.LeveledUp && result.XPSkill == "combat" {
|
||||
p.checkRivalPoolUnlock(char)
|
||||
}
|
||||
|
||||
// Handle death
|
||||
deathReprieved := false
|
||||
|
||||
Reference in New Issue
Block a user