mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-15 08:32:41 +00:00
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>
This commit is contained in:
117
internal/plugin/dnd_flavor.go
Normal file
117
internal/plugin/dnd_flavor.go
Normal file
@@ -0,0 +1,117 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"gogobee/internal/flavor"
|
||||
)
|
||||
|
||||
// D&D-layer flavor wire-in. Helpers here pull from the protected pools in
|
||||
// internal/flavor and are called at the relevant integration points
|
||||
// (combat outputs, level-up DM, rest commands, NPC encounters, loot drops).
|
||||
//
|
||||
// Each helper returns "" when the corresponding pool is empty so callers can
|
||||
// safely embed the result without conditional-noise everywhere.
|
||||
|
||||
// ── Combat narrative ─────────────────────────────────────────────────────────
|
||||
|
||||
// dndCombatOpeningLine returns a single CombatStart pool line for use at
|
||||
// the top of a fight's combat-log render. Caller decides where to inject it
|
||||
// (typically prepended to the first phase message).
|
||||
func dndCombatOpeningLine() string {
|
||||
return flavor.Pick(flavor.CombatStart)
|
||||
}
|
||||
|
||||
// dndCombatClosingLine returns a single line appropriate for the fight's
|
||||
// end state: CombatVictory on a non-boss win, BossDeath on a boss kill,
|
||||
// PlayerDeath on a loss.
|
||||
func dndCombatClosingLine(playerWon, isBoss bool) string {
|
||||
switch {
|
||||
case playerWon && isBoss:
|
||||
return flavor.Pick(flavor.BossDeath)
|
||||
case playerWon:
|
||||
return flavor.Pick(flavor.CombatVictory)
|
||||
default:
|
||||
return flavor.Pick(flavor.PlayerDeath)
|
||||
}
|
||||
}
|
||||
|
||||
// dndZoneCompleteLine fires on dungeon completion (only on victorious clear).
|
||||
func dndZoneCompleteLine() string {
|
||||
return flavor.Pick(flavor.ZoneComplete)
|
||||
}
|
||||
|
||||
// dndNat20Line / dndNat1Line — appended to the fight's roll summary when
|
||||
// at least one nat 20 / nat 1 was rolled by the player. A single line per
|
||||
// fight (not per roll) to avoid spam.
|
||||
func dndNat20Line() string {
|
||||
return flavor.Pick(flavor.Nat20)
|
||||
}
|
||||
|
||||
func dndNat1Line() string {
|
||||
return flavor.Pick(flavor.Nat1)
|
||||
}
|
||||
|
||||
// ── Level-up + items ────────────────────────────────────────────────────────
|
||||
|
||||
// dndLevelUpFlavorLine — random LevelUp pool entry for the level-up DM.
|
||||
func dndLevelUpFlavorLine() string {
|
||||
return flavor.Pick(flavor.LevelUp)
|
||||
}
|
||||
|
||||
// dndItemFoundLine — used when loot drops in dungeon resolution.
|
||||
func dndItemFoundLine() string {
|
||||
return flavor.Pick(flavor.ItemFound)
|
||||
}
|
||||
|
||||
// ── Rest ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
// dndRestShortFlavorLine — for !rest short.
|
||||
func dndRestShortFlavorLine() string {
|
||||
return flavor.Pick(flavor.RestShort)
|
||||
}
|
||||
|
||||
// dndRestLongFlavorLine — for !rest long. The HomeLongRest pool is
|
||||
// preferred when the player is at home; falls back to generic RestLong.
|
||||
func dndRestLongFlavorLine(atHome bool) string {
|
||||
if atHome {
|
||||
if line := flavor.Pick(flavor.HomeLongRest); line != "" {
|
||||
return line
|
||||
}
|
||||
}
|
||||
return flavor.Pick(flavor.RestLong)
|
||||
}
|
||||
|
||||
// ── NPC greetings ───────────────────────────────────────────────────────────
|
||||
//
|
||||
// The NPC encounter system already has legacy `mistyOpenings` and
|
||||
// `arinaOpenings` slices used for prompt openings. We don't replace those —
|
||||
// we union them with the new D&D-flavored greetings so players see ~2x
|
||||
// variety in the openings. Helpers below return the combined pool.
|
||||
|
||||
// dndMistyGreetingPool returns the union of legacy and D&D pools for Misty.
|
||||
// Used by the encounter-fire path to vary opening lines.
|
||||
func dndMistyGreetingPool() []string {
|
||||
combined := make([]string, 0, len(flavor.MistyGreeting)+len(mistyOpenings))
|
||||
combined = append(combined, flavor.MistyGreeting...)
|
||||
combined = append(combined, mistyOpenings...)
|
||||
return combined
|
||||
}
|
||||
|
||||
func dndArinaGreetingPool() []string {
|
||||
combined := make([]string, 0, len(flavor.ArinaGreeting)+len(arinaOpenings))
|
||||
combined = append(combined, flavor.ArinaGreeting...)
|
||||
combined = append(combined, arinaOpenings...)
|
||||
return combined
|
||||
}
|
||||
|
||||
// ── Helpers ──────────────────────────────────────────────────────────────────
|
||||
|
||||
// dndItalicize wraps a non-empty line in markdown italics with leading
|
||||
// blank line so callers can append narrative cleanly.
|
||||
func dndItalicize(line string) string {
|
||||
if strings.TrimSpace(line) == "" {
|
||||
return ""
|
||||
}
|
||||
return "\n\n_" + line + "_"
|
||||
}
|
||||
Reference in New Issue
Block a user