mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-16 00:52:40 +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)
|
phases = RenderCombatLog(result, playerName, monster.Name)
|
||||||
|
|
||||||
var ob strings.Builder
|
outcome = renderBossOutcome(BossOutcomeInputs{
|
||||||
if nat20s > 0 {
|
ZoneID: zone.ID,
|
||||||
if line := twinBeeLine(zone.ID, DMNat20, run.RunID, run.CurrentRoom); line != "" {
|
RunID: run.RunID,
|
||||||
ob.WriteString(line)
|
RoomIdx: run.CurrentRoom,
|
||||||
ob.WriteString("\n")
|
Monster: monster,
|
||||||
}
|
Result: result,
|
||||||
} else if nat1s > 0 {
|
PreHP: preHP,
|
||||||
if line := twinBeeLine(zone.ID, DMNat1, run.RunID, run.CurrentRoom); line != "" {
|
PostHP: postHP,
|
||||||
ob.WriteString(line)
|
MaxHP: maxHP,
|
||||||
ob.WriteString("\n")
|
PhaseTwoAt: zone.Boss.PhaseTwoAt,
|
||||||
}
|
Nat20s: nat20s,
|
||||||
}
|
Nat1s: nat1s,
|
||||||
if phaseTwoCrossedInEvents(result.Events, result.EnemyStartHP, zone.Boss.PhaseTwoAt) {
|
DefeatHeadline: fmt.Sprintf("💀 **%s** stands over your body. Run ended.", monster.Name),
|
||||||
if line := bossPhaseTwoLine(zone.ID, run.RunID, run.CurrentRoom); line != "" {
|
VictoryHeadline: fmt.Sprintf("🏆 **%s** falls (HP %d→%d / %d).", monster.Name, preHP, postHP, maxHP),
|
||||||
ob.WriteString(line)
|
})
|
||||||
ob.WriteString("\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !result.PlayerWon {
|
if !result.PlayerWon {
|
||||||
_, _ = applyMoodEvent(run.RunID, MoodEventPlayerDeath)
|
_, _ = applyMoodEvent(run.RunID, MoodEventPlayerDeath)
|
||||||
_ = abandonZoneRun(userID)
|
_ = abandonZoneRun(userID)
|
||||||
markAdventureDead(userID, "zone", zone.Display)
|
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
|
ended = true
|
||||||
return
|
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)
|
recordZoneKillForUser(userID, monster.ID)
|
||||||
// §8.1: zone boss defeat drops expedition threat by 20. Silent
|
// §8.1: zone boss defeat drops expedition threat by 20. Silent
|
||||||
// no-op for standalone zone runs (no active expedition).
|
// 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)
|
_ = applyBossDefeatThreat(exp.ID)
|
||||||
}
|
}
|
||||||
if drop := p.dropZoneLoot(userID, zone.ID, monster, true); drop != "" {
|
if drop := p.dropZoneLoot(userID, zone.ID, monster, true); drop != "" {
|
||||||
ob.WriteString("\n")
|
outcome = outcome + "\n" + drop
|
||||||
ob.WriteString(drop)
|
|
||||||
}
|
}
|
||||||
outcome = ob.String()
|
|
||||||
return
|
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 ──────────────────────────────────────────────────────
|
// ── taunt / compliment ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
func (p *AdventurePlugin) zoneCmdTaunt(ctx MessageContext) error {
|
func (p *AdventurePlugin) zoneCmdTaunt(ctx MessageContext) error {
|
||||||
|
|||||||
Reference in New Issue
Block a user