mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-16 00:52:40 +00:00
WIP: in-flight combat/expedition/flavor changes
Bundle of uncommitted working-tree edits across combat engine, expedition cycle, flavor pools, and TwinBee/zone narration. Includes new files: combat_debug.go, dnd_boss_consumables.go, dnd_dex_floor.go, plus CHANGES_24H.md and REBALANCE_NOTES.md scratch notes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -7,24 +7,43 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Phase 6 — !rest short / !rest long.
|
||||
// !rest short / !rest long.
|
||||
//
|
||||
// Short rest: 1h cooldown, no daily-action cost. Recovers 1d6+CON HP at L1-4
|
||||
// and 2*(1d6+CON) at L5+ (matching v1.0 §10.1's "x2 at level 5+" line).
|
||||
// Short rest: spend 1 hit-dice charge (max charges = character level,
|
||||
// restored on long rest). Heals 1d6+CON HP, x2 at L5+. Sets a 1h activity
|
||||
// lockout — the character is *actually resting* for that hour and can't
|
||||
// !zone enter / !expedition start until the timer expires.
|
||||
//
|
||||
// Long rest: 24h cooldown. Requires housing (HouseTier > 0) OR pays the
|
||||
// Thom Krooke inn (200 euros). Full HP recovery; resources reset (none yet
|
||||
// in Phase 6, but the wiring is in place for Phase 7+).
|
||||
// Long rest: 24h cooldown. Full HP recovery, restores all short rest
|
||||
// charges, sets an 8h activity lockout. Requires housing (HouseTier > 0)
|
||||
// OR pays the Thom Krooke inn (200 euros). Slot/spell refresh runs here.
|
||||
//
|
||||
// These commands operate on the D&D layer only. The legacy `!adventure` menu
|
||||
// "rest" choice is unchanged — it remains the daily-action narrative day-skip.
|
||||
// These commands operate on the D&D layer only. The legacy `!adventure`
|
||||
// menu "rest" choice is unchanged — it remains the daily-action narrative
|
||||
// day-skip.
|
||||
|
||||
const (
|
||||
dndShortRestCooldown = 1 * time.Hour
|
||||
dndLongRestCooldown = 24 * time.Hour
|
||||
dndInnCost = 200
|
||||
dndLongRestCooldown = 24 * time.Hour
|
||||
dndShortRestLockoutHours = 1
|
||||
dndLongRestLockoutHours = 8
|
||||
dndInnCost = 200
|
||||
)
|
||||
|
||||
// restingLockoutRemaining returns the time left on a character's rest
|
||||
// lockout (zero if not currently resting). Callers gate !zone enter and
|
||||
// !expedition start on this so a freshly-rested character can't
|
||||
// immediately jump back into combat.
|
||||
func restingLockoutRemaining(c *DnDCharacter) time.Duration {
|
||||
if c == nil || c.RestingUntil == nil {
|
||||
return 0
|
||||
}
|
||||
remaining := time.Until(*c.RestingUntil)
|
||||
if remaining <= 0 {
|
||||
return 0
|
||||
}
|
||||
return remaining
|
||||
}
|
||||
|
||||
func (p *AdventurePlugin) handleDnDRestCmd(ctx MessageContext, args string) error {
|
||||
args = strings.TrimSpace(strings.ToLower(args))
|
||||
switch args {
|
||||
@@ -40,8 +59,8 @@ func (p *AdventurePlugin) handleDnDRestCmd(ctx MessageContext, args string) erro
|
||||
|
||||
func dndRestHelpText() string {
|
||||
return "🛌 **Adv 2.0 Rest**\n\n" +
|
||||
"`!rest short` — 1h cooldown. Recovers 1d6 + CON HP. No action cost.\n" +
|
||||
"`!rest long` — 24h cooldown. Full HP recovery. Requires housing or pays the inn (€200)."
|
||||
"`!rest short` — spend 1 hit-dice charge. Heals 1d6 + CON HP (x2 at L5+). Locks zone/expedition for **1 hour**.\n" +
|
||||
"`!rest long` — once per 24h. Full HP, restores hit-dice charges. Locks zone/expedition for **8 hours**. Requires housing or pays the inn (€200)."
|
||||
}
|
||||
|
||||
// ── Short rest ───────────────────────────────────────────────────────────────
|
||||
@@ -57,17 +76,12 @@ func (p *AdventurePlugin) handleDnDShortRest(ctx MessageContext) error {
|
||||
return p.SendDM(ctx.Sender, "Couldn't load your character.")
|
||||
}
|
||||
|
||||
if c.LastShortRestAt != nil {
|
||||
elapsed := time.Since(*c.LastShortRestAt)
|
||||
if elapsed < dndShortRestCooldown {
|
||||
remaining := dndShortRestCooldown - elapsed
|
||||
return p.SendDM(ctx.Sender, fmt.Sprintf(
|
||||
"Short rest on cooldown — %s remaining.", formatRespecDuration(remaining)))
|
||||
}
|
||||
if c.ShortRestCharges <= 0 {
|
||||
return p.SendDM(ctx.Sender,
|
||||
"You're out of short rest charges. Take a `!rest long` to restore them.")
|
||||
}
|
||||
|
||||
if c.HPCurrent >= c.HPMax {
|
||||
return p.SendDM(ctx.Sender, "You're already at full HP. Save the rest for when you need it.")
|
||||
return p.SendDM(ctx.Sender, "You're already at full HP. Save the charge for when you need it.")
|
||||
}
|
||||
|
||||
conMod := abilityModifier(c.CON)
|
||||
@@ -85,16 +99,19 @@ func (p *AdventurePlugin) handleDnDShortRest(ctx MessageContext) error {
|
||||
if c.HPCurrent > c.HPMax {
|
||||
c.HPCurrent = c.HPMax
|
||||
}
|
||||
c.ShortRestCharges--
|
||||
now := time.Now().UTC()
|
||||
c.LastShortRestAt = &now
|
||||
lockoutEnd := now.Add(dndShortRestLockoutHours * time.Hour)
|
||||
c.RestingUntil = &lockoutEnd
|
||||
|
||||
if err := SaveDnDCharacter(c); err != nil {
|
||||
return p.SendDM(ctx.Sender, "Couldn't save rest state: "+err.Error())
|
||||
}
|
||||
|
||||
msg := fmt.Sprintf(
|
||||
"🛌 **Short rest.** You recover **%d HP** (%d→%d / %d).\n_Next short rest available in 1 hour._",
|
||||
c.HPCurrent-before, before, c.HPCurrent, c.HPMax)
|
||||
"🛌 **Short rest.** You recover **%d HP** (%d→%d / %d).\n_Charges remaining: %d. You're resting — `!zone` and `!expedition` locked for 1 hour._",
|
||||
c.HPCurrent-before, before, c.HPCurrent, c.HPMax, c.ShortRestCharges)
|
||||
if line := dndRestShortFlavorLine(); line != "" {
|
||||
msg += "\n\n_" + line + "_"
|
||||
}
|
||||
@@ -141,6 +158,7 @@ func (p *AdventurePlugin) handleDnDLongRest(ctx MessageContext) error {
|
||||
|
||||
c.HPCurrent = c.HPMax
|
||||
c.TempHP = 0
|
||||
c.ShortRestCharges = c.Level
|
||||
// Phase 10 SUB2a — long rest clears one level of exhaustion (5e: a
|
||||
// long rest clears one). For Berserker who racks up exhaustion via
|
||||
// Frenzy, this is the recovery cadence.
|
||||
@@ -149,6 +167,8 @@ func (p *AdventurePlugin) handleDnDLongRest(ctx MessageContext) error {
|
||||
}
|
||||
now := time.Now().UTC()
|
||||
c.LastLongRestAt = &now
|
||||
lockoutEnd := now.Add(dndLongRestLockoutHours * time.Hour)
|
||||
c.RestingUntil = &lockoutEnd
|
||||
|
||||
if err := SaveDnDCharacter(c); err != nil {
|
||||
return p.SendDM(ctx.Sender, "Couldn't save rest state: "+err.Error())
|
||||
@@ -170,8 +190,8 @@ func (p *AdventurePlugin) handleDnDLongRest(ctx MessageContext) error {
|
||||
loc = fmt.Sprintf("the inn (€%d spent)", dndInnCost)
|
||||
}
|
||||
msg := fmt.Sprintf(
|
||||
"🌙 **Long rest** at %s. Full HP recovered (%d/%d).\n_Next long rest available in 24 hours._",
|
||||
loc, c.HPCurrent, c.HPMax)
|
||||
"🌙 **Long rest** at %s. Full HP recovered (%d/%d). Hit-dice charges restored: **%d**.\n_You're resting — `!zone` and `!expedition` locked for 8 hours. Next long rest in 24 hours._",
|
||||
loc, c.HPCurrent, c.HPMax, c.ShortRestCharges)
|
||||
// HomeLongRest pool when at home; generic RestLong otherwise.
|
||||
if line := dndRestLongFlavorLine(hasHousing); line != "" {
|
||||
msg += "\n\n_" + line + "_"
|
||||
|
||||
Reference in New Issue
Block a user