mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-16 08:52:42 +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:
@@ -21,16 +21,17 @@ import (
|
||||
type DnDSkill string
|
||||
|
||||
const (
|
||||
SkillAthletics DnDSkill = "athletics"
|
||||
SkillAcrobatics DnDSkill = "acrobatics"
|
||||
SkillStealth DnDSkill = "stealth"
|
||||
SkillArcana DnDSkill = "arcana"
|
||||
SkillInvestigation DnDSkill = "investigation"
|
||||
SkillPerception DnDSkill = "perception"
|
||||
SkillInsight DnDSkill = "insight"
|
||||
SkillPersuasion DnDSkill = "persuasion"
|
||||
SkillIntimidation DnDSkill = "intimidation"
|
||||
SkillDeception DnDSkill = "deception"
|
||||
SkillAthletics DnDSkill = "athletics"
|
||||
SkillAcrobatics DnDSkill = "acrobatics"
|
||||
SkillStealth DnDSkill = "stealth"
|
||||
SkillSleightOfHand DnDSkill = "sleight_of_hand"
|
||||
SkillArcana DnDSkill = "arcana"
|
||||
SkillInvestigation DnDSkill = "investigation"
|
||||
SkillPerception DnDSkill = "perception"
|
||||
SkillInsight DnDSkill = "insight"
|
||||
SkillPersuasion DnDSkill = "persuasion"
|
||||
SkillIntimidation DnDSkill = "intimidation"
|
||||
SkillDeception DnDSkill = "deception"
|
||||
)
|
||||
|
||||
type dndSkillInfo struct {
|
||||
@@ -43,6 +44,7 @@ var dndSkillTable = []dndSkillInfo{
|
||||
{SkillAthletics, "Athletics", "str"},
|
||||
{SkillAcrobatics, "Acrobatics", "dex"},
|
||||
{SkillStealth, "Stealth", "dex"},
|
||||
{SkillSleightOfHand, "Sleight of Hand", "dex"},
|
||||
{SkillArcana, "Arcana", "int"},
|
||||
{SkillInvestigation, "Investigation", "int"},
|
||||
{SkillPerception, "Perception", "wis"},
|
||||
@@ -136,6 +138,54 @@ func statValue(c *DnDCharacter, stat string) int {
|
||||
return 10
|
||||
}
|
||||
|
||||
// subclassSkillBonus returns the additive bonus from a subclass ability
|
||||
// for a given skill. Phase 10 SUB2a:
|
||||
//
|
||||
// Champion (L7+) Remarkable Athlete: +½ proficiency bonus (rounded down)
|
||||
// to STR/DEX/CON skill checks. Approximation of 5e's "+½ prof to
|
||||
// checks not already proficient"; we don't track per-skill prof yet.
|
||||
// Thief (L5+) Fast Hands: +5 to Sleight of Hand. Stand-in for 5e's
|
||||
// "bonus action: SoH check" — out-of-combat utility flattened into a
|
||||
// raw skill bonus.
|
||||
func subclassSkillBonus(c *DnDCharacter, info dndSkillInfo) int {
|
||||
if c == nil || c.Subclass == "" {
|
||||
return 0
|
||||
}
|
||||
switch c.Subclass {
|
||||
case SubclassChampion:
|
||||
if c.Level >= 7 {
|
||||
switch info.Stat {
|
||||
case "str", "dex", "con":
|
||||
return proficiencyBonus(c.Level) / 2
|
||||
}
|
||||
}
|
||||
case SubclassThief:
|
||||
if c.Level >= 5 && info.Key == SkillSleightOfHand {
|
||||
return 5
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// subclassSkillAdvantage reports whether the player rolls with advantage
|
||||
// (best of two d20s) on this skill check. Phase 10 SUB2a:
|
||||
//
|
||||
// Thief (L7+) Supreme Sneak: advantage on Stealth (5e gates this on
|
||||
// half-speed movement; we don't track movement speed, so we apply
|
||||
// unconditionally — approachability cut).
|
||||
func subclassSkillAdvantage(c *DnDCharacter, info dndSkillInfo) bool {
|
||||
if c == nil || c.Subclass == "" {
|
||||
return false
|
||||
}
|
||||
switch c.Subclass {
|
||||
case SubclassThief:
|
||||
if c.Level >= 7 && info.Key == SkillStealth {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// raceSkillBonus returns the per-skill bonus from a player's race.
|
||||
// Half-Elf gets +1 to every skill (rough mapping of "two bonus skill profs").
|
||||
// Tiefling gets +2 to CHA-based skills (matches the doc's "+bonus on CHA checks").
|
||||
@@ -151,11 +201,20 @@ func raceSkillBonus(c *DnDCharacter, info dndSkillInfo) int {
|
||||
return 0
|
||||
}
|
||||
|
||||
// performSkillCheck rolls the d20 and computes the result.
|
||||
// performSkillCheck rolls the d20 and computes the result. Subclass
|
||||
// effects (Phase 10 SUB2a) apply additive bonuses and may grant advantage
|
||||
// (best of two d20s).
|
||||
func performSkillCheck(c *DnDCharacter, skill DnDSkill, dc int) SkillCheckResult {
|
||||
info, _ := skillInfo(skill)
|
||||
mod := abilityModifier(statValue(c, info.Stat)) + raceSkillBonus(c, info)
|
||||
mod := abilityModifier(statValue(c, info.Stat)) +
|
||||
raceSkillBonus(c, info) +
|
||||
subclassSkillBonus(c, info)
|
||||
roll := 1 + rand.IntN(20)
|
||||
if subclassSkillAdvantage(c, info) {
|
||||
if alt := 1 + rand.IntN(20); alt > roll {
|
||||
roll = alt
|
||||
}
|
||||
}
|
||||
res := SkillCheckResult{Skill: skill, DC: dc, Roll: roll, Mod: mod}
|
||||
|
||||
switch roll {
|
||||
|
||||
Reference in New Issue
Block a user