mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-15 08:32:41 +00:00
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.
74 lines
2.6 KiB
Go
74 lines
2.6 KiB
Go
package plugin
|
|
|
|
// Phase 10 SUB2a — subclass combat hooks.
|
|
//
|
|
// applySubclassPassives layers subclass-driven flags onto CombatModifiers
|
|
// the same way applyClassPassives does. Called from combat_bridge after
|
|
// applyClassPassives so subclass effects can stack on top of (or override)
|
|
// class baseline.
|
|
//
|
|
// Subclass active abilities (currently just Berserker's rage) are
|
|
// registered via init() into dndActiveAbilities below, gated by
|
|
// DnDAbility.Subclass.
|
|
|
|
func applySubclassPassives(stats *CombatStats, mods *CombatModifiers, c *DnDCharacter) {
|
|
if c == nil || c.Subclass == "" {
|
|
return
|
|
}
|
|
switch c.Subclass {
|
|
case SubclassChampion:
|
|
// L5 Improved Critical: crit on nat 19+. SUB3 Superior Critical
|
|
// (L15) will lower this to 18.
|
|
if c.Level >= 5 {
|
|
mods.CritThreshold = 19
|
|
}
|
|
}
|
|
}
|
|
|
|
func init() {
|
|
// Berserker rage — active ability that piggybacks on the Fighter
|
|
// stamina pool. 5e specs 3 uses/long-rest as a separate pool, but
|
|
// stamina is already sized at 3 for Fighter and the design's
|
|
// "approachability" constraint preferred reusing the existing pool
|
|
// over introducing per-subclass resource types in SUB2a.
|
|
//
|
|
// While armed and fired:
|
|
// - +2 flat damage per hit (RageMeleeDmg)
|
|
// - incoming weapon damage halved (PhysicalResistRage)
|
|
// - +50% damage multiplier for Frenzy approximation
|
|
// (one-shot combat can't model "1 bonus attack per turn" literally)
|
|
//
|
|
// Post-combat exhaustion increment lives in
|
|
// persistDnDPostCombatSubclass — Frenzy specs +1 exhaustion after rage
|
|
// ends, and in our model rage spans the whole one-shot combat so we
|
|
// always tick exhaustion when rage fired.
|
|
dndActiveAbilities["rage"] = DnDAbility{
|
|
ID: "rage",
|
|
Name: "Rage",
|
|
Class: ClassFighter,
|
|
Subclass: SubclassBerserker,
|
|
Resource: "stamina",
|
|
Description: "Enter rage for the next combat: +2 damage per hit, halve incoming weapon damage, Frenzy bonus attack approximation. +1 exhaustion after.",
|
|
Apply: func(c *DnDCharacter, mods *CombatModifiers) {
|
|
mods.BerserkerRage = true
|
|
mods.RageMeleeDmg = 2
|
|
mods.PhysicalResistRage = true
|
|
mods.FrenzyDmgBonus = 0.5
|
|
},
|
|
}
|
|
}
|
|
|
|
// persistDnDPostCombatSubclass handles end-of-combat subclass bookkeeping.
|
|
// Currently: increment Exhaustion if Berserker rage fired. Called from
|
|
// combat_bridge after the combat result is computed.
|
|
//
|
|
// raged is computed as (mods.BerserkerRage at combat-start time) — pass it
|
|
// through from the caller where the mods value is still in scope.
|
|
func persistDnDPostCombatSubclass(c *DnDCharacter, raged bool) error {
|
|
if c == nil || !raged {
|
|
return nil
|
|
}
|
|
c.Exhaustion++
|
|
return SaveDnDCharacter(c)
|
|
}
|