mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-15 08:32:41 +00:00
Adv 2.0 D&D Phase 10 SUB2a-i: Champion + Berserker + Thief L5/L7
First mechanical slice of the subclass system. Each subclass gets its L5 and L7 abilities wired through the existing combat engine + skill check substrate; no new resource pools introduced. Champion (L5/L7): - Improved Critical: new Mods.CritThreshold lowers crit floor to nat 19+ in resolvePlayerAttack. Default 20 preserved for non-Champions. - Remarkable Athlete: +½ proficiency bonus to STR/DEX/CON skill checks via subclassSkillBonus, layered on raceSkillBonus. Berserker (L5/L7): - Rage: new !arm-able active ability gated to Berserker subclass via DnDAbility.Subclass field. Reuses the Fighter stamina pool (3/long-rest matches 5e's rage uses). On fire: +2 flat damage per hit, halve incoming weapon damage, +50% damage approximation for Frenzy's bonus-attack-per- turn (one-shot combat can't model that literally). - Frenzy exhaustion: new exhaustion column on dnd_character; incremented by persistDnDPostCombatSubclass after any rage'd combat. Decremented by one per long rest. - Mindless Rage: documented; charm/frighten conditions don't exist in the combat engine yet, so no functional effect until P11 zone bosses land. Thief (L5/L7): - Sleight of Hand added to dndSkillTable. - Fast Hands: +5 to Sleight of Hand checks via subclassSkillBonus. - Supreme Sneak: advantage on Stealth (best of two d20s) via new subclassSkillAdvantage hook in performSkillCheck. 5e's half-speed gate dropped — we don't track movement speed. Plumbing: - applySubclassPassives layered after applyClassPassives in both arena and dungeon combat bridges. - DnDAbility.Subclass enables per-subclass gating; characterActiveAbilities filters !arm and !abilities lists by both class and subclass. - Existing !respec full-wipe also clears Exhaustion. Tests: Champion CritThreshold gating across L4/L5/no-subclass; rage Apply flag-set; rage probabilistic win-rate lift vs tougher enemy; exhaustion increment only on raged combats; long-rest decrement; Champion bonus on STR/DEX/CON only and only at L7+; Thief Fast Hands SoH-only; Thief Supreme Sneak L7+ Stealth-only; statistical advantage roll lifts Thief stealth average by ≥2 vs plain Rogue; !arm rage subclass gating; schema roundtrip; characterActiveAbilities visibility.
This commit is contained in:
@@ -182,6 +182,11 @@ type DnDCharacter struct {
|
||||
// cooldown (LastSubclassRespecAt) and 500-coin cost.
|
||||
Subclass DnDSubclass
|
||||
LastSubclassRespecAt *time.Time
|
||||
// Phase 10 SUB2a — Berserker Frenzy increments after each rage'd
|
||||
// combat; future class abilities will also tick this. Cleared on
|
||||
// long rest. 5e caps at 6 (death); we let it grow and let consumers
|
||||
// clamp where needed.
|
||||
Exhaustion int
|
||||
LastRespecAt *time.Time
|
||||
LastShortRestAt *time.Time
|
||||
LastLongRestAt *time.Time
|
||||
@@ -210,7 +215,7 @@ func LoadDnDCharacter(userID id.UserID) (*DnDCharacter, error) {
|
||||
hp_current, hp_max, temp_hp, armor_class,
|
||||
pending_setup, auto_migrated, onboarding_sent, armed_ability,
|
||||
pending_cast, concentration_spell, concentration_expires_at,
|
||||
subclass, last_subclass_respec_at,
|
||||
subclass, last_subclass_respec_at, exhaustion,
|
||||
last_respec_at, last_short_rest_at, last_long_rest_at,
|
||||
created_at, updated_at
|
||||
FROM dnd_character WHERE user_id = ?`, string(userID))
|
||||
@@ -223,7 +228,7 @@ func LoadDnDCharacter(userID id.UserID) (*DnDCharacter, error) {
|
||||
&c.HPCurrent, &c.HPMax, &c.TempHP, &c.ArmorClass,
|
||||
&pending, &autoMig, &onboard, &c.ArmedAbility,
|
||||
&c.PendingCast, &c.ConcentrationSpell, &c.ConcentrationExpiresAt,
|
||||
&subclassStr, &c.LastSubclassRespecAt,
|
||||
&subclassStr, &c.LastSubclassRespecAt, &c.Exhaustion,
|
||||
&c.LastRespecAt, &c.LastShortRestAt, &c.LastLongRestAt,
|
||||
&c.CreatedAt, &c.UpdatedAt)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
@@ -262,9 +267,9 @@ func SaveDnDCharacter(c *DnDCharacter) error {
|
||||
hp_current, hp_max, temp_hp, armor_class,
|
||||
pending_setup, auto_migrated, onboarding_sent, armed_ability,
|
||||
pending_cast, concentration_spell, concentration_expires_at,
|
||||
subclass, last_subclass_respec_at,
|
||||
subclass, last_subclass_respec_at, exhaustion,
|
||||
last_respec_at, last_short_rest_at, last_long_rest_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
||||
ON CONFLICT(user_id) DO UPDATE SET
|
||||
race=excluded.race, class=excluded.class,
|
||||
dnd_level=excluded.dnd_level, dnd_xp=excluded.dnd_xp,
|
||||
@@ -282,6 +287,7 @@ func SaveDnDCharacter(c *DnDCharacter) error {
|
||||
concentration_expires_at=excluded.concentration_expires_at,
|
||||
subclass=excluded.subclass,
|
||||
last_subclass_respec_at=excluded.last_subclass_respec_at,
|
||||
exhaustion=excluded.exhaustion,
|
||||
last_respec_at=excluded.last_respec_at,
|
||||
last_short_rest_at=excluded.last_short_rest_at,
|
||||
last_long_rest_at=excluded.last_long_rest_at,
|
||||
@@ -291,7 +297,7 @@ func SaveDnDCharacter(c *DnDCharacter) error {
|
||||
c.HPCurrent, c.HPMax, c.TempHP, c.ArmorClass,
|
||||
pending, autoMig, onboard, c.ArmedAbility,
|
||||
c.PendingCast, c.ConcentrationSpell, c.ConcentrationExpiresAt,
|
||||
string(c.Subclass), c.LastSubclassRespecAt,
|
||||
string(c.Subclass), c.LastSubclassRespecAt, c.Exhaustion,
|
||||
c.LastRespecAt, c.LastShortRestAt, c.LastLongRestAt)
|
||||
if err != nil {
|
||||
return fmt.Errorf("save dnd_character: %w", err)
|
||||
|
||||
Reference in New Issue
Block a user