mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-15 08:32:41 +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
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -191,14 +192,33 @@ func matrixWorker(exe string, in <-chan matrixJob, out chan<- *plugin.SimResult,
|
|||||||
args = append(args, "-trace")
|
args = append(args, "-trace")
|
||||||
}
|
}
|
||||||
cmd := exec.Command(exe, args...)
|
cmd := exec.Command(exe, args...)
|
||||||
|
var stderrBuf bytes.Buffer
|
||||||
|
cmd.Stderr = &stderrBuf
|
||||||
stdout, runErr := cmd.Output()
|
stdout, runErr := cmd.Output()
|
||||||
var res plugin.SimResult
|
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"}
|
res = plugin.SimResult{UserID: uid, Class: j.class, Level: j.level, Zone: j.zone, Outcome: "halted"}
|
||||||
}
|
}
|
||||||
if runErr != nil && res.Outcome == "" {
|
if runErr != nil && res.Outcome == "" {
|
||||||
res.Outcome = "halted"
|
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 {
|
if !includeLog {
|
||||||
res.Log = nil
|
res.Log = nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -572,11 +572,17 @@ func (p *AdventurePlugin) advanceOnceWithOpts(ctx MessageContext, compact, inlin
|
|||||||
// autoResolveCombat / simPickCombatAction). Surface the
|
// autoResolveCombat / simPickCombatAction). Surface the
|
||||||
// doorway like the foreground path does, after the safety
|
// doorway like the foreground path does, after the safety
|
||||||
// gate has had a chance to defer the engagement.
|
// gate has had a chance to defer the engagement.
|
||||||
|
kind := "Elite"
|
||||||
r := stopElite
|
r := stopElite
|
||||||
if prev == RoomBoss {
|
if prev == RoomBoss {
|
||||||
|
kind = "Boss"
|
||||||
r = stopBoss
|
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
|
// Compact-mode elite/boss auto-resolve. resolveCombatRoom
|
||||||
// selects monster + label by run.CurrentRoomType().
|
// selects monster + label by run.CurrentRoomType().
|
||||||
|
|||||||
@@ -894,13 +894,18 @@ func simMartialFirstClass(class DnDClass) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// simPickSpiritualWeapon returns "spiritual_weapon" when a cleric should
|
// simPickSpiritualWeapon returns a !cast argument for Spiritual Weapon
|
||||||
// open the fight with it: the buff is not already active on the session,
|
// when a cleric should open the fight with it: the buff is not already
|
||||||
// the spell is prepared, and an L2 slot is available. Returns "" otherwise.
|
// active on the session, the spell is prepared, and some slot ≥ L2 is
|
||||||
// The buff path in combat_cmd.go folds into BuffSpiritProc/Dmg via
|
// available. Returns "" otherwise. The buff path in combat_cmd.go folds
|
||||||
// applyBuffDelta, which the turn engine fires each round via
|
// into BuffSpiritProc/Dmg via applyBuffDelta, which the turn engine fires
|
||||||
// spiritWeaponStrike — so one cast is worth more than a single L2 damage
|
// each round via spiritWeaponStrike — so one cast is worth more than a
|
||||||
// spell across a multi-round fight.
|
// 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 {
|
func simPickSpiritualWeapon(c *DnDCharacter, uid id.UserID, sess *CombatSession) string {
|
||||||
if c == nil || c.Class != ClassCleric || sess == nil {
|
if c == nil || c.Class != ClassCleric || sess == nil {
|
||||||
return ""
|
return ""
|
||||||
@@ -923,11 +928,18 @@ func simPickSpiritualWeapon(c *DnDCharacter, uid id.UserID, sess *CombatSession)
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
slots, _ := getSpellSlots(uid)
|
slots, _ := getSpellSlots(uid)
|
||||||
pair, ok := slots[2]
|
const simMaxSlot = 5
|
||||||
if !ok || pair[0]-pair[1] <= 0 {
|
for sl := 2; sl <= simMaxSlot; sl++ {
|
||||||
return ""
|
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
|
// simPickSpell returns the spell argument a competent player would pass
|
||||||
|
|||||||
Reference in New Issue
Block a user