Files
rgoue/game/daemon.go
sneak 5ba9fe8f66 Adopt house golangci-lint config; fix all approved-linter findings
.golangci.yml is the prompts-repo standard verbatim plus one approved
exception (paralleltest, sneak 2026-07-06). Roughly 1,500 findings
fixed:

- autofix sweep (wsl_v5/nlreturn/intrange/modernize formatting), with
  the misspell autofix REVERTED where it rewrote authentic C game text
  ("missle vanishes" stays, with nolint and a comment)
- error handling: errcheck sites get real handling — saveFile now
  closes explicitly and removes corrupt saves, scoreboard writes are
  documented best-effort, err113 sentinel errors (ErrSaveOutOfDate,
  ErrScreenTooSmall); panics allowed for unrecoverable states per
  MEMORY.md policy
- gosec: real fixes (ParseInt for SEED, 0600 scorefile) and justified
  per-line nolints for provably-bounded conversions (randomMonsterLetter
  helper collapses eight rnd(26)+'A' sites)
- API tidying from linters: pointer receivers on flag types
  (recvcheck), msg helpers renamed addmsgf/doaddf/Printwf/MvPrintwf
  (goprintffuncname), myExit()/findFloor(monst)/mkGameInput(t) drop
  always-constant params (unparam), gameEnd carries no status
- gocritic/staticcheck: if-else chains to switches, the pack.c
  inventory filter untangled into matchesFilter, main.go split so
  defers run before exit (exitAfterDefer, funlen)
- revive doc comments on all exported flag types/consts/methods

Remaining findings are confined to linters pending sneak's exception
decision (mnd, gochecknoglobals, cyclop, nestif, gocognit, exhaustive,
goconst, testpackage); TODO.md records the state. MEMORY.md added at
repo root as the project's agent working notes.
2026-07-07 00:03:45 +02:00

144 lines
3.8 KiB
Go

package game
// daemon.c — the daemon/fuse scheduler.
//
// C identifies daemons and fuses by function pointer; the port uses DaemonID
// (which the C save format itself resorted to when serializing d_list), and
// dispatches through one switch in daemons.go.
// DaemonID names a daemon/fuse callback. The numeric values match the
// function-pointer-to-int mapping in state.c rs_write_daemons.
type DaemonID int
// Daemon and fuse callback identifiers. The first block's numeric values
// match the function-pointer-to-int mapping in state.c rs_write_daemons;
// the second block covers fuses state.c never saved (the Go save format
// handles them all uniformly).
const (
DNone DaemonID = 0
DRollwand DaemonID = 1
DDoctor DaemonID = 2
DStomach DaemonID = 3
DRunners DaemonID = 4
DSwander DaemonID = 5
DNohaste DaemonID = 6
DUnconfuse DaemonID = 7
DUnsee DaemonID = 8
DSight DaemonID = 9
DVisuals DaemonID = 10
DComeDown DaemonID = 11
DLand DaemonID = 12
DTurnSee DaemonID = 13 // potions.c casts turn_see to a fuse callback
)
// Scheduling phases and slot states (daemon.c).
const (
dEmpty = 0
Before = 1 // run before the player's command
After = 2 // run after the player's command
daemonTime = -1 // d_time sentinel: a recurring daemon, not a fuse
)
// delayedAction is rogue.h struct delayed_action.
type delayedAction struct {
Type int // dEmpty, Before or After
Func DaemonID
Arg int
Time int // daemonTime for daemons; remaining turns for fuses
}
// DaemonList is the C d_list[MAXDAEMONS] plus the file statics that ride
// along with the daemon system.
type DaemonList struct {
List [MaxDaemons]delayedAction
Between int // daemons.c rollwand static `between`
}
// dSlot finds an empty slot in the daemon/fuse list (daemon.c d_slot).
func (g *RogueGame) dSlot() *delayedAction {
for i := range g.Daemons.List {
if g.Daemons.List[i].Type == dEmpty {
return &g.Daemons.List[i]
}
}
panic("ran out of fuse slots") // C: debug message in MASTER, NULL deref otherwise
}
// findSlot finds a particular slot in the table (daemon.c find_slot).
func (g *RogueGame) findSlot(f DaemonID) *delayedAction {
for i := range g.Daemons.List {
d := &g.Daemons.List[i]
if d.Type != dEmpty && d.Func == f {
return d
}
}
return nil
}
// StartDaemon starts a recurring daemon (daemon.c start_daemon).
func (g *RogueGame) StartDaemon(f DaemonID, arg, typ int) {
dev := g.dSlot()
dev.Type = typ
dev.Func = f
dev.Arg = arg
dev.Time = daemonTime
}
// KillDaemon removes a daemon from the list (daemon.c kill_daemon).
func (g *RogueGame) KillDaemon(f DaemonID) {
if dev := g.findSlot(f); dev != nil {
dev.Type = dEmpty
}
}
// DoDaemons runs all the daemons that are active with the current flag
// (daemon.c do_daemons).
func (g *RogueGame) DoDaemons(flag int) {
for i := range g.Daemons.List {
dev := &g.Daemons.List[i]
if dev.Type == flag && dev.Time == daemonTime {
g.runDaemon(dev.Func, dev.Arg)
}
}
}
// Fuse starts a countdown fuse (daemon.c fuse).
func (g *RogueGame) Fuse(f DaemonID, arg, time, typ int) {
wire := g.dSlot()
wire.Type = typ
wire.Func = f
wire.Arg = arg
wire.Time = time
}
// Lengthen extends a fuse's countdown (daemon.c lengthen).
func (g *RogueGame) Lengthen(f DaemonID, xtime int) {
if wire := g.findSlot(f); wire != nil {
wire.Time += xtime
}
}
// Extinguish puts out a fuse (daemon.c extinguish).
func (g *RogueGame) Extinguish(f DaemonID) {
if wire := g.findSlot(f); wire != nil {
wire.Type = dEmpty
}
}
// DoFuses decrements all fuses in the given phase and fires any that burn
// down (daemon.c do_fuses).
func (g *RogueGame) DoFuses(flag int) {
for i := range g.Daemons.List {
wire := &g.Daemons.List[i]
if wire.Type == flag && wire.Time > 0 {
wire.Time--
if wire.Time == 0 {
wire.Type = dEmpty
g.runDaemon(wire.Func, wire.Arg)
}
}
}
}