Files
gogobee/internal/plugin/dnd_bestiary.go
prosolis 9e1a1f606c Adv 2.0 D&D layer: Phases 1-8 + Phase 9 SP1+SP2
Combines all uncommitted D&D work into one checkpoint:

Phases 1-8 (per gogobee_dnd_session_summary.md):
- Race/class/stats system, !setup flow, !sheet, !respec
- d20-vs-AC combat with race/class passives, auto-migration
- XP/level-up, skill checks, NPC refund hooks
- Equipment slot mapping, rarity, !rest short/long
- Pre-arm active abilities, resource pools
- Bestiary, !roll/!stats/!level, onboarding DM
- Audit fixes A-I, flavor wiring under internal/flavor/
- Phase 8 weapon dice + armor AC tables (gogobee_equipment_appendix)

Phase 9 SP1 — spell system foundations:
- 79 SpellDefinitions (76 in-scope + 3 reaction-deferred)
- dnd_known_spells, dnd_spell_slots tables
- pending_cast / concentration_* columns on dnd_character
- Slot tables for Mage/Cleric/Ranger, DC + attack-bonus math
- Long-rest refreshes slots; respec wipes spell state
- Auto-grant default known list on !setup confirm and auto-migration

Phase 9 SP2 — !cast / !spells / !prepare commands:
- Out-of-combat HEAL and UTILITY resolve immediately
- Damage/control/buff queue as pending_cast for next combat
- Audit-style save-then-debit, slot refund on save failure
- !cast --drop, concentration supersession, prep-cap enforcement
- Reaction spells refused with Phase 11 note

SP3 (combat-time resolution of pending_cast) and SP4 (full prep/learn
tests) are next. Build clean, full test suite green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:25:21 -07:00

109 lines
4.1 KiB
Go

package plugin
// Phase 7 — D&D starter bestiary.
//
// These are named monster definitions ready to slot into encounter content.
// Phase 7 ships them as data only — wiring them into specific dungeons or
// arena rounds is a future content task. The combat engine consumes them
// via the existing DnDMonsterTemplate→CombatStats path (see toCombatStats).
// DnDMonsterTemplate is the bestiary record for a named creature. Mirrors
// v1.0 §8.1's stat block schema, simplified to the subset combat actually
// uses (HP, AC, attack, speed, ability proc, special tags).
type DnDMonsterTemplate struct {
ID string
Name string
CR float32 // challenge rating (display only for now)
HP int
AC int
Attack int // engine's "Attack" stat (raw damage value)
AttackBonus int // d20 to-hit bonus
Speed int
BlockRate float64
Ability *MonsterAbility
XPValue int
Notes string
}
// dndBestiary is the canonical lookup. Keyed by ID.
var dndBestiary = map[string]DnDMonsterTemplate{
"goblin": {
ID: "goblin", Name: "Goblin",
CR: 0.25, HP: 7, AC: 13, Attack: 6, AttackBonus: 4, Speed: 14,
BlockRate: 0.05,
Ability: &MonsterAbility{Name: "Nimble Escape", Phase: "any", ProcChance: 0.20, Effect: "stun"},
XPValue: 30,
Notes: "Fast and skittish. Escapes pin attempts; will retreat if obviously outmatched.",
},
"skeleton": {
ID: "skeleton", Name: "Skeleton",
CR: 0.25, HP: 13, AC: 13, Attack: 8, AttackBonus: 4, Speed: 10,
BlockRate: 0.10,
// Skeletons are immune to poison; we'd model that with a future
// CombatModifiers.PoisonImmunity flag. For Phase 7, no ability.
XPValue: 50,
Notes: "Bone-and-rust. Resistant to slashing weapons; vulnerable to bludgeoning.",
},
"orc_grunt": {
ID: "orc_grunt", Name: "Orc Grunt",
CR: 0.5, HP: 15, AC: 13, Attack: 10, AttackBonus: 5, Speed: 11,
BlockRate: 0.05,
Ability: &MonsterAbility{Name: "Aggressive", Phase: "opening", ProcChance: 0.30, Effect: "enrage"},
XPValue: 100,
Notes: "Charges. Bonus damage on its first hit if it goes first.",
},
"troll": {
ID: "troll", Name: "Troll",
CR: 5, HP: 84, AC: 15, Attack: 22, AttackBonus: 7, Speed: 8,
BlockRate: 0.10,
Ability: &MonsterAbility{Name: "Regeneration", Phase: "any", ProcChance: 0.50, Effect: "lifesteal"},
XPValue: 1800,
Notes: "Regenerates each round unless burned or acid-burned. Fire/acid damage stops the regen.",
},
"wyvern": {
ID: "wyvern", Name: "Wyvern",
CR: 6, HP: 110, AC: 13, Attack: 28, AttackBonus: 7, Speed: 16,
BlockRate: 0.05,
Ability: &MonsterAbility{Name: "Poison Sting", Phase: "decisive", ProcChance: 0.60, Effect: "poison"},
XPValue: 2300,
Notes: "Aerial; opens with dive attack. Tail sting injects poison (CON DC 15 in tabletop).",
},
"ancient_dragon": {
ID: "ancient_dragon", Name: "Ancient Dragon",
CR: 20, HP: 367, AC: 22, Attack: 65, AttackBonus: 14, Speed: 18,
BlockRate: 0.15,
Ability: &MonsterAbility{Name: "Frightful Presence", Phase: "opening", ProcChance: 0.80, Effect: "stun"},
XPValue: 25000,
Notes: "Legendary. Breath weapon (cleave-equivalent), frightful presence on opening, regenerates between phases. Tabletop equivalent has legendary actions; the engine approximates with elevated stats.",
},
}
// dndBestiaryByCR returns templates whose CR is at or below the given cap.
// Useful for procedurally selecting a monster appropriate to player level.
func dndBestiaryByCR(maxCR float32) []DnDMonsterTemplate {
var out []DnDMonsterTemplate
for _, m := range dndBestiary {
if m.CR <= maxCR {
out = append(out, m)
}
}
return out
}
// toCombatStats converts a bestiary entry into the engine's CombatStats +
// CombatModifiers shape. Future encounter content can use this to spawn
// named monsters in the existing combat pipeline.
func (m DnDMonsterTemplate) toCombatStats() (CombatStats, CombatModifiers) {
stats := CombatStats{
MaxHP: m.HP,
Attack: m.Attack,
Defense: 0,
Speed: m.Speed,
BlockRate: m.BlockRate,
AC: m.AC,
AttackBonus: m.AttackBonus,
}
mods := CombatModifiers{DamageReduct: 1.0}
return stats, mods
}