mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-15 00:32:40 +00:00
J3 D8-review: surface sim subprocess errors + SW upcast + doorway msg
- expedition-sim matrix worker now captures child stderr and dumps runErr/stderr/stdout-snippet on failure so halted rows have a cause. - simPickSpiritualWeapon walks slots 2..5 and upcasts when L2 is spent instead of silently skipping the spell on high-level clerics. - advanceOnceWithOpts !inlineBossCombat branch now emits the same "Room X/Y — Boss/Elite. Type !fight to engage." line as foreground.
This commit is contained in:
@@ -13,6 +13,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
@@ -191,14 +192,33 @@ func matrixWorker(exe string, in <-chan matrixJob, out chan<- *plugin.SimResult,
|
||||
args = append(args, "-trace")
|
||||
}
|
||||
cmd := exec.Command(exe, args...)
|
||||
var stderrBuf bytes.Buffer
|
||||
cmd.Stderr = &stderrBuf
|
||||
stdout, runErr := cmd.Output()
|
||||
var res plugin.SimResult
|
||||
if jerr := json.Unmarshal(stdout, &res); jerr != nil {
|
||||
jerr := json.Unmarshal(stdout, &res)
|
||||
if jerr != nil {
|
||||
res = plugin.SimResult{UserID: uid, Class: j.class, Level: j.level, Zone: j.zone, Outcome: "halted"}
|
||||
}
|
||||
if runErr != nil && res.Outcome == "" {
|
||||
res.Outcome = "halted"
|
||||
}
|
||||
// Surface subprocess failures: parse error with non-empty stdout
|
||||
// (corrupted JSON) and non-zero exits both get a stderr dump so the
|
||||
// user sees the underlying cause instead of just a halted row.
|
||||
if jerr != nil || runErr != nil {
|
||||
fmt.Fprintf(os.Stderr, "sim cell %s halted: runErr=%v jerr=%v\n", uid, runErr, jerr)
|
||||
if stderrBuf.Len() > 0 {
|
||||
fmt.Fprintf(os.Stderr, " child stderr:\n%s\n", stderrBuf.String())
|
||||
}
|
||||
if jerr != nil && len(stdout) > 0 {
|
||||
snip := stdout
|
||||
if len(snip) > 200 {
|
||||
snip = snip[:200]
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, " child stdout (first 200B): %q\n", snip)
|
||||
}
|
||||
}
|
||||
if !includeLog {
|
||||
res.Log = nil
|
||||
}
|
||||
|
||||
@@ -572,11 +572,17 @@ func (p *AdventurePlugin) advanceOnceWithOpts(ctx MessageContext, compact, inlin
|
||||
// autoResolveCombat / simPickCombatAction). Surface the
|
||||
// doorway like the foreground path does, after the safety
|
||||
// gate has had a chance to defer the engagement.
|
||||
kind := "Elite"
|
||||
r := stopElite
|
||||
if prev == RoomBoss {
|
||||
kind = "Boss"
|
||||
r = stopBoss
|
||||
}
|
||||
return advanceResult{reason: r}, nil
|
||||
return advanceResult{
|
||||
final: fmt.Sprintf("**Room %d/%d — %s.** Type `!fight` to engage.",
|
||||
prevIdx+1, run.TotalRooms, kind),
|
||||
reason: r,
|
||||
}, nil
|
||||
}
|
||||
// Compact-mode elite/boss auto-resolve. resolveCombatRoom
|
||||
// selects monster + label by run.CurrentRoomType().
|
||||
|
||||
@@ -894,13 +894,18 @@ func simMartialFirstClass(class DnDClass) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// simPickSpiritualWeapon returns "spiritual_weapon" when a cleric should
|
||||
// open the fight with it: the buff is not already active on the session,
|
||||
// the spell is prepared, and an L2 slot is available. Returns "" otherwise.
|
||||
// The buff path in combat_cmd.go folds into BuffSpiritProc/Dmg via
|
||||
// applyBuffDelta, which the turn engine fires each round via
|
||||
// spiritWeaponStrike — so one cast is worth more than a single L2 damage
|
||||
// spell across a multi-round fight.
|
||||
// simPickSpiritualWeapon returns a !cast argument for Spiritual Weapon
|
||||
// when a cleric should open the fight with it: the buff is not already
|
||||
// active on the session, the spell is prepared, and some slot ≥ L2 is
|
||||
// available. Returns "" otherwise. The buff path in combat_cmd.go folds
|
||||
// into BuffSpiritProc/Dmg via applyBuffDelta, which the turn engine fires
|
||||
// each round via spiritWeaponStrike — so one cast is worth more than a
|
||||
// single L2 damage spell across a multi-round fight.
|
||||
//
|
||||
// Slot pick: lowest available slot ≥ 2. Upcasting is +1d8 per 2 slots
|
||||
// above 2nd, so spending a precious L5 to add a single d8 to the proc is
|
||||
// not worth burning the bigger slot's damage potential elsewhere; sim
|
||||
// behaves like a competent player and saves the high slot.
|
||||
func simPickSpiritualWeapon(c *DnDCharacter, uid id.UserID, sess *CombatSession) string {
|
||||
if c == nil || c.Class != ClassCleric || sess == nil {
|
||||
return ""
|
||||
@@ -923,11 +928,18 @@ func simPickSpiritualWeapon(c *DnDCharacter, uid id.UserID, sess *CombatSession)
|
||||
return ""
|
||||
}
|
||||
slots, _ := getSpellSlots(uid)
|
||||
pair, ok := slots[2]
|
||||
if !ok || pair[0]-pair[1] <= 0 {
|
||||
return ""
|
||||
const simMaxSlot = 5
|
||||
for sl := 2; sl <= simMaxSlot; sl++ {
|
||||
pair, ok := slots[sl]
|
||||
if !ok || pair[0]-pair[1] <= 0 {
|
||||
continue
|
||||
}
|
||||
if sl == 2 {
|
||||
return "spiritual_weapon"
|
||||
}
|
||||
return fmt.Sprintf("spiritual_weapon --upcast %d", sl)
|
||||
}
|
||||
return "spiritual_weapon"
|
||||
return ""
|
||||
}
|
||||
|
||||
// simPickSpell returns the spell argument a competent player would pass
|
||||
|
||||
Reference in New Issue
Block a user