mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-15 16:42:41 +00:00
Adv 2.0 Phase L2 step 1: extract renderBossOutcome
Factors the staged-narration body of resolveBossRoom into a shared
helper renderBossOutcome(BossOutcomeInputs) so the upcoming arena
boss flow (resolveArenaBoss) can reuse the same Nat20/Nat1 mood lines,
phase-two transition barb, BossDeath/PlayerDeath flavor, and trailing
roll-summary line.
Inputs include caller-supplied DefeatHeadline / VictoryHeadline so
arena and zone read in their own voice ("Run ended." vs the future
arena equivalent). Helper is pure: side effects (abandonZoneRun, loot
drops, threat ticks, kill records) stay at the call site.
No behavior change for zone bosses — same strings, same ordering.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -650,50 +650,28 @@ func (p *AdventurePlugin) resolveBossRoom(userID id.UserID, run *DungeonRun, zon
|
||||
}
|
||||
phases = RenderCombatLog(result, playerName, monster.Name)
|
||||
|
||||
var ob strings.Builder
|
||||
if nat20s > 0 {
|
||||
if line := twinBeeLine(zone.ID, DMNat20, run.RunID, run.CurrentRoom); line != "" {
|
||||
ob.WriteString(line)
|
||||
ob.WriteString("\n")
|
||||
}
|
||||
} else if nat1s > 0 {
|
||||
if line := twinBeeLine(zone.ID, DMNat1, run.RunID, run.CurrentRoom); line != "" {
|
||||
ob.WriteString(line)
|
||||
ob.WriteString("\n")
|
||||
}
|
||||
}
|
||||
if phaseTwoCrossedInEvents(result.Events, result.EnemyStartHP, zone.Boss.PhaseTwoAt) {
|
||||
if line := bossPhaseTwoLine(zone.ID, run.RunID, run.CurrentRoom); line != "" {
|
||||
ob.WriteString(line)
|
||||
ob.WriteString("\n")
|
||||
}
|
||||
}
|
||||
outcome = renderBossOutcome(BossOutcomeInputs{
|
||||
ZoneID: zone.ID,
|
||||
RunID: run.RunID,
|
||||
RoomIdx: run.CurrentRoom,
|
||||
Monster: monster,
|
||||
Result: result,
|
||||
PreHP: preHP,
|
||||
PostHP: postHP,
|
||||
MaxHP: maxHP,
|
||||
PhaseTwoAt: zone.Boss.PhaseTwoAt,
|
||||
Nat20s: nat20s,
|
||||
Nat1s: nat1s,
|
||||
DefeatHeadline: fmt.Sprintf("💀 **%s** stands over your body. Run ended.", monster.Name),
|
||||
VictoryHeadline: fmt.Sprintf("🏆 **%s** falls (HP %d→%d / %d).", monster.Name, preHP, postHP, maxHP),
|
||||
})
|
||||
if !result.PlayerWon {
|
||||
_, _ = applyMoodEvent(run.RunID, MoodEventPlayerDeath)
|
||||
_ = abandonZoneRun(userID)
|
||||
markAdventureDead(userID, "zone", zone.Display)
|
||||
if line := twinBeeLine(zone.ID, DMPlayerDeath, run.RunID, run.CurrentRoom); line != "" {
|
||||
ob.WriteString(line)
|
||||
ob.WriteString("\n")
|
||||
}
|
||||
ob.WriteString(fmt.Sprintf("💀 **%s** stands over your body. Run ended.", monster.Name))
|
||||
if rollLine := dndRollSummaryLine(result); rollLine != "" {
|
||||
ob.WriteString("\n")
|
||||
ob.WriteString(rollLine)
|
||||
}
|
||||
outcome = ob.String()
|
||||
ended = true
|
||||
return
|
||||
}
|
||||
if line := twinBeeLine(zone.ID, DMBossDeath, run.RunID, run.CurrentRoom); line != "" {
|
||||
ob.WriteString(line)
|
||||
ob.WriteString("\n")
|
||||
}
|
||||
ob.WriteString(fmt.Sprintf("🏆 **%s** falls (HP %d→%d / %d).", monster.Name, preHP, postHP, maxHP))
|
||||
if rollLine := dndRollSummaryLine(result); rollLine != "" {
|
||||
ob.WriteString("\n")
|
||||
ob.WriteString(rollLine)
|
||||
}
|
||||
recordZoneKillForUser(userID, monster.ID)
|
||||
// §8.1: zone boss defeat drops expedition threat by 20. Silent
|
||||
// no-op for standalone zone runs (no active expedition).
|
||||
@@ -701,13 +679,83 @@ func (p *AdventurePlugin) resolveBossRoom(userID id.UserID, run *DungeonRun, zon
|
||||
_ = applyBossDefeatThreat(exp.ID)
|
||||
}
|
||||
if drop := p.dropZoneLoot(userID, zone.ID, monster, true); drop != "" {
|
||||
ob.WriteString("\n")
|
||||
ob.WriteString(drop)
|
||||
outcome = outcome + "\n" + drop
|
||||
}
|
||||
outcome = ob.String()
|
||||
return
|
||||
}
|
||||
|
||||
// BossOutcomeInputs is the carrier struct for renderBossOutcome — the
|
||||
// shared staged-narration body used by zone bosses (resolveBossRoom)
|
||||
// and arena bosses (resolveArenaBoss, post-L2). Pure inputs: every
|
||||
// field is read-only and the helper performs no DB writes.
|
||||
type BossOutcomeInputs struct {
|
||||
ZoneID ZoneID // routes twinBeeLine (use ZoneArena for arena fights)
|
||||
RunID string // deterministic seed for twinBeeLine pickers
|
||||
RoomIdx int // ditto
|
||||
|
||||
Monster DnDMonsterTemplate
|
||||
Result CombatResult
|
||||
PreHP, PostHP, MaxHP int
|
||||
PhaseTwoAt float64 // fraction of MaxHP; 0 disables phase-two narration
|
||||
Nat20s, Nat1s int // pre-counted by scanMoodEventsFromCombat
|
||||
|
||||
// Caller-supplied headlines so arena and zone read in their own voice.
|
||||
// DefeatHeadline is the full sentence shown after the PlayerDeath
|
||||
// TwinBee line; VictoryHeadline is the full sentence shown after
|
||||
// BossDeath. The roll-summary line is appended automatically.
|
||||
DefeatHeadline string
|
||||
VictoryHeadline string
|
||||
}
|
||||
|
||||
// renderBossOutcome composes the outcome block for a boss-shaped
|
||||
// encounter: optional Nat20/Nat1 mood line, optional phase-two barb if
|
||||
// the enemy crossed PhaseTwoAt mid-fight, BossDeath/PlayerDeath flavor,
|
||||
// the caller-supplied victory/defeat headline, and a trailing dice-roll
|
||||
// summary. Side-effect free — callers handle abandonZoneRun, loot,
|
||||
// kill records, payout, etc.
|
||||
func renderBossOutcome(in BossOutcomeInputs) string {
|
||||
var ob strings.Builder
|
||||
if in.Nat20s > 0 {
|
||||
if line := twinBeeLine(in.ZoneID, DMNat20, in.RunID, in.RoomIdx); line != "" {
|
||||
ob.WriteString(line)
|
||||
ob.WriteString("\n")
|
||||
}
|
||||
} else if in.Nat1s > 0 {
|
||||
if line := twinBeeLine(in.ZoneID, DMNat1, in.RunID, in.RoomIdx); line != "" {
|
||||
ob.WriteString(line)
|
||||
ob.WriteString("\n")
|
||||
}
|
||||
}
|
||||
if phaseTwoCrossedInEvents(in.Result.Events, in.Result.EnemyStartHP, in.PhaseTwoAt) {
|
||||
if line := bossPhaseTwoLine(in.ZoneID, in.RunID, in.RoomIdx); line != "" {
|
||||
ob.WriteString(line)
|
||||
ob.WriteString("\n")
|
||||
}
|
||||
}
|
||||
if !in.Result.PlayerWon {
|
||||
if line := twinBeeLine(in.ZoneID, DMPlayerDeath, in.RunID, in.RoomIdx); line != "" {
|
||||
ob.WriteString(line)
|
||||
ob.WriteString("\n")
|
||||
}
|
||||
ob.WriteString(in.DefeatHeadline)
|
||||
if rollLine := dndRollSummaryLine(in.Result); rollLine != "" {
|
||||
ob.WriteString("\n")
|
||||
ob.WriteString(rollLine)
|
||||
}
|
||||
return ob.String()
|
||||
}
|
||||
if line := twinBeeLine(in.ZoneID, DMBossDeath, in.RunID, in.RoomIdx); line != "" {
|
||||
ob.WriteString(line)
|
||||
ob.WriteString("\n")
|
||||
}
|
||||
ob.WriteString(in.VictoryHeadline)
|
||||
if rollLine := dndRollSummaryLine(in.Result); rollLine != "" {
|
||||
ob.WriteString("\n")
|
||||
ob.WriteString(rollLine)
|
||||
}
|
||||
return ob.String()
|
||||
}
|
||||
|
||||
// ── taunt / compliment ──────────────────────────────────────────────────────
|
||||
|
||||
func (p *AdventurePlugin) zoneCmdTaunt(ctx MessageContext) error {
|
||||
|
||||
Reference in New Issue
Block a user