mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-16 00:52:40 +00:00
N4/E1: T3 housing payoffs + a home-rest "well-rested" buff
Turn the dead top housing tiers into something worth buying. All three
land without a schema bump, and TestCombatCharacterization stays
byte-identical (the balance corpus never sets the new fields).
T3 trophy room: treasure cap 3->4 at HouseTier>=3 (maxTreasuresForTier).
Enforced at the save gate only -- HouseTier is never written downward,
so a held 4th treasure below tier 3 is unreachable and a load-time cap in
computeAdvBonuses would just add a query for an impossible state. The
all-irreplaceable manual discard prompt now lists the 4th slot too.
T3 workshop: +5% craft success at HouseTier>=3, lifting the cap 0.95->0.98
so a maxed forager still gains. craftingSuccessRate takes a workshopBonus,
threaded through autoCraftConsumables and renderRecipesKnown.
Well-rested buff (replaces the plan's T4 "inn-quality rest", a verified
no-op -- home rest already equals inn rest). Home-only (the inn and a
tier-1 shack grant nothing), starts at T2 and grows through T4, expires at
the next long rest:
- Temp HP cushion (8/12/16% of MaxHP). TempHP was a dormant field; wired
into applyDnDHPScaling as MaxHP headroom -- additive and golden-safe.
- Bonus spell slots (+1/+2/+3 at the caster's highest slot level), the
real reward, lifting casters who trail on spell-pool richness.
applyLongRestSpellSlots resets the pool to base then folds in the
bonus; expiry is stateless (next rest's reset drops it).
Magnitudes are tunable defaults; revisit against the post-parties
re-baseline.
Claude-Session: https://claude.ai/code/session_017mEwUmmS7aQTP2NQXj6rUa
This commit is contained in:
@@ -564,6 +564,48 @@ func refreshSpellSlots(userID id.UserID) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// applyLongRestSpellSlots resets a caster's slot pool to its base (class +
|
||||
// subclass) total with used=0, then folds in any well-rested bonus slots for a
|
||||
// home rest of the given tier. The bonus lives in `total`, so it is spent like
|
||||
// an ordinary slot and is wiped by the *next* long rest's reset — expiry needs
|
||||
// no separate bookkeeping. It is safe to call on every long rest because
|
||||
// ensureSpellsForCharacter already keeps `total` == slotsForCharacter(c) on
|
||||
// load, so the base reset is a no-op except for clearing a prior bonus.
|
||||
//
|
||||
// Non-casters have an empty pool; the function falls back to the plain
|
||||
// used=0 refresh and grants nothing. Returns the bonus actually granted (nil if
|
||||
// none) so the caller can name it in the rest message.
|
||||
func applyLongRestSpellSlots(c *DnDCharacter, restTier int) (map[int]int, error) {
|
||||
pool := slotsForCharacter(c)
|
||||
if len(pool) == 0 {
|
||||
return nil, refreshSpellSlots(c.UserID)
|
||||
}
|
||||
bonus := wellRestedSlotBonus(restTier, pool)
|
||||
tx, err := db.Get().Begin()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
if _, err := tx.Exec(`DELETE FROM dnd_spell_slots WHERE user_id = ?`, string(c.UserID)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for lvl, total := range pool {
|
||||
if _, err := tx.Exec(`
|
||||
INSERT INTO dnd_spell_slots (user_id, slot_level, total, used)
|
||||
VALUES (?, ?, ?, 0)`,
|
||||
string(c.UserID), lvl, total+bonus[lvl]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if err := tx.Commit(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(bonus) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return bonus, nil
|
||||
}
|
||||
|
||||
// partialRefreshSpellSlots restores spell slots on short rest. All L1 slots
|
||||
// come back, plus floor(charLevel/4) additional slots distributed
|
||||
// lowest-first across tiers ≥2. Returns slot_level→count restored so the
|
||||
|
||||
Reference in New Issue
Block a user