.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.
231 lines
5.3 KiB
Go
231 lines
5.3 KiB
Go
package game
|
|
|
|
import "fmt"
|
|
|
|
// weapons.c — functions for dealing with problems brought about by weapons.
|
|
|
|
const noWeapon WeaponKind = -1
|
|
|
|
// missile fires a missile in a given direction (weapons.c missile).
|
|
func (g *RogueGame) missile(ydelta, xdelta int) {
|
|
// Get which thing we are hurling
|
|
obj := g.getItem("throw", KindWeapon)
|
|
if obj == nil {
|
|
return
|
|
}
|
|
|
|
if !g.dropCheck(obj) || g.isCurrent(obj) {
|
|
return
|
|
}
|
|
|
|
obj = g.leavePack(obj, true, false)
|
|
g.doMotion(obj, ydelta, xdelta)
|
|
// AHA! Here it has hit something. If it is a wall or a door, or if
|
|
// it misses (combat) the monster, put it on the floor
|
|
if g.Level.MonsterAt(obj.Pos.Y, obj.Pos.X) == nil ||
|
|
!g.hitMonster(obj.Pos, obj) {
|
|
g.fall(obj, true)
|
|
}
|
|
}
|
|
|
|
// doMotion does the actual motion on the screen done by an object
|
|
// traveling across the room (weapons.c do_motion).
|
|
func (g *RogueGame) doMotion(obj *Object, ydelta, xdelta int) {
|
|
p := &g.Player
|
|
// Come fly with us ...
|
|
obj.Pos = p.Pos
|
|
for {
|
|
// Erase the old one
|
|
if obj.Pos != p.Pos && g.cansee(obj.Pos.Y, obj.Pos.X) && !g.Options.Terse {
|
|
ch := g.Level.Char(obj.Pos.Y, obj.Pos.X)
|
|
if ch == Floor && !g.showFloor() {
|
|
ch = ' '
|
|
}
|
|
|
|
g.mvaddch(obj.Pos.Y, obj.Pos.X, ch)
|
|
}
|
|
// Get the new position
|
|
obj.Pos.Y += ydelta
|
|
obj.Pos.X += xdelta
|
|
|
|
ch := g.Level.VisibleChar(obj.Pos.Y, obj.Pos.X)
|
|
if stepOk(ch) && ch != Door {
|
|
// It hasn't hit anything yet, so display it if it's alright.
|
|
if g.cansee(obj.Pos.Y, obj.Pos.X) && !g.Options.Terse {
|
|
g.mvaddch(obj.Pos.Y, obj.Pos.X, obj.Kind.Glyph())
|
|
g.refresh()
|
|
}
|
|
|
|
continue
|
|
}
|
|
|
|
break
|
|
}
|
|
}
|
|
|
|
// fall drops an item someplace around here (weapons.c fall).
|
|
func (g *RogueGame) fall(obj *Object, pr bool) {
|
|
if fpos, ok := g.fallpos(obj.Pos); ok {
|
|
pp := g.Level.At(fpos.Y, fpos.X)
|
|
pp.Ch = obj.Kind.Glyph()
|
|
|
|
obj.Pos = fpos
|
|
if g.cansee(fpos.Y, fpos.X) {
|
|
if pp.Monst != nil {
|
|
pp.Monst.OldCh = obj.Kind.Glyph()
|
|
} else {
|
|
g.mvaddch(fpos.Y, fpos.X, obj.Kind.Glyph())
|
|
}
|
|
}
|
|
|
|
attachObj(&g.Level.Objects, obj)
|
|
|
|
return
|
|
}
|
|
|
|
if pr {
|
|
if g.HasHit {
|
|
g.endmsg()
|
|
g.HasHit = false
|
|
}
|
|
|
|
g.msg("the %s vanishes as it hits the ground",
|
|
g.Items.Weapons[obj.Which].Name)
|
|
}
|
|
}
|
|
|
|
// hitMonster checks if the missile hits the monster (weapons.c
|
|
// hit_monster).
|
|
func (g *RogueGame) hitMonster(mp Coord, obj *Object) bool {
|
|
return g.fight(mp, obj, true)
|
|
}
|
|
|
|
// wield pulls out a certain weapon (weapons.c wield).
|
|
func (g *RogueGame) wield() {
|
|
p := &g.Player
|
|
|
|
oweapon := p.CurWeapon
|
|
if !g.dropCheck(p.CurWeapon) {
|
|
p.CurWeapon = oweapon
|
|
|
|
return
|
|
}
|
|
|
|
p.CurWeapon = oweapon
|
|
|
|
obj := g.getItem("wield", KindWeapon)
|
|
if obj == nil {
|
|
g.After = false
|
|
|
|
return
|
|
}
|
|
|
|
if obj.Kind == KindArmor {
|
|
g.msg("you can't wield armor")
|
|
g.After = false
|
|
|
|
return
|
|
}
|
|
|
|
if g.isCurrent(obj) {
|
|
g.After = false
|
|
|
|
return
|
|
}
|
|
|
|
sp := g.invName(obj, true)
|
|
p.CurWeapon = obj
|
|
|
|
if !g.Options.Terse {
|
|
g.addmsgf("you are now ")
|
|
}
|
|
|
|
g.msg("wielding %s (%c)", sp, obj.PackCh)
|
|
}
|
|
|
|
// initWeaps is the weapons.c init_dam[] table.
|
|
var initWeaps = [NumWeaponTypes]struct {
|
|
dam DiceSpec // damage when wielded
|
|
hrl DiceSpec // damage when thrown
|
|
launch WeaponKind // launching weapon
|
|
flags ObjFlags
|
|
}{
|
|
{dice("2x4"), dice("1x3"), noWeapon, 0}, // WeaponMace
|
|
{dice("3x4"), dice("1x2"), noWeapon, 0}, // Long sword
|
|
{dice("1x1"), dice("1x1"), noWeapon, 0}, // WeaponBow
|
|
{dice("1x1"), dice("2x3"), WeaponBow, Stackable | Missile}, // WeaponArrow
|
|
{dice("1x6"), dice("1x4"), noWeapon, Missile}, // WeaponDagger
|
|
{dice("4x4"), dice("1x2"), noWeapon, 0}, // 2h sword
|
|
{dice("1x1"), dice("1x3"), noWeapon, Stackable | Missile}, // WeaponDart
|
|
{dice("1x2"), dice("2x4"), noWeapon, Stackable | Missile}, // Shuriken
|
|
{dice("2x3"), dice("1x6"), noWeapon, Missile}, // WeaponSpear
|
|
}
|
|
|
|
// initWeapon sets up a new weapon (weapons.c init_weapon).
|
|
func (g *RogueGame) initWeapon(weap *Object, which WeaponKind) {
|
|
iwp := &initWeaps[which]
|
|
weap.Kind = KindWeapon
|
|
weap.Which = int(which)
|
|
weap.Damage = iwp.dam
|
|
weap.HurlDmg = iwp.hrl
|
|
weap.Launch = iwp.launch
|
|
weap.Flags = iwp.flags
|
|
weap.HPlus = 0
|
|
|
|
weap.DPlus = 0
|
|
|
|
switch {
|
|
case which == WeaponDagger:
|
|
weap.Count = g.rnd(4) + 2
|
|
weap.Group = g.Items.Group
|
|
g.Items.Group++
|
|
case weap.Flags.Has(Stackable):
|
|
weap.Count = g.rnd(8) + 8
|
|
weap.Group = g.Items.Group
|
|
g.Items.Group++
|
|
default:
|
|
weap.Count = 1
|
|
weap.Group = 0
|
|
}
|
|
}
|
|
|
|
// num formats a hit/damage or armor bonus string (weapons.c num).
|
|
func num(n1, n2 int, typ byte) string {
|
|
out := fmt.Sprintf("%+d", n1)
|
|
if typ == Weapon {
|
|
out += fmt.Sprintf(",%+d", n2)
|
|
}
|
|
|
|
return out
|
|
}
|
|
|
|
// fallpos picks a random empty position around (pos) for a dropped item
|
|
// (weapons.c fallpos).
|
|
func (g *RogueGame) fallpos(pos Coord) (Coord, bool) {
|
|
var newpos Coord
|
|
|
|
cnt := 0
|
|
|
|
for y := pos.Y - 1; y <= pos.Y+1; y++ {
|
|
for x := pos.X - 1; x <= pos.X+1; x++ {
|
|
// check to make certain the spot is empty, if it is, put the
|
|
// object there, set it in the level list and re-draw the room
|
|
// if he can see it
|
|
if (y == g.Player.Pos.Y && x == g.Player.Pos.X) ||
|
|
y < 0 || x < 0 {
|
|
continue
|
|
}
|
|
|
|
ch := g.Level.Char(y, x)
|
|
if ch == Floor || ch == Passage {
|
|
if cnt++; g.rnd(cnt) == 0 {
|
|
newpos.Y = y
|
|
newpos.X = x
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return newpos, cnt != 0
|
|
}
|