Files
rgoue/game/weapons.go
sneak e71a2ef3fd Rename constants to descriptive names (refactor step 1)
Pure rename, no behavior change; the full suite (RNG goldens,
generation invariants, scripted sessions, save round trip) is
unchanged and green.

- Creature flags: IsHuh→Confused, IsHalu→Hallucinating,
  CanHuh→CanConfuse, CanSee→CanSeeInvisible, IsRun→Awake,
  SeeMonst→SenseMonsters, IsCanc→Cancelled, IsLevit→Levitating,
  IsBlind/IsGreed/IsHaste/IsTarget/IsHeld/IsInvis/IsMean/IsRegen/
  IsFly/IsSlow → Blind/Greedy/Hasted/Targeted/Held/Invisible/Mean/
  Regenerates/Flying/Slowed, IsFound→Found
- Object flags: IsCursed→Cursed, IsKnow→Known, IsMissl→Missile,
  IsMany→Stackable, ObjIsFound→WasFound, IsProt→Protected
- Room flags: IsDark/IsGone/IsMaze → Dark/Gone/Maze; place flags:
  FPass→FPassage, FPNum→FPassNum, FTMask→FTrapMask
- Trap types: TDoor→TrapDoor ... TMyst→TrapMystery
- Item subtypes: P*→Potion* (PLSD→PotionLSD, PMFind→
  PotionDetectMonsters, ...), S*→Scroll* (SIDRorS→
  ScrollIdentifyRingOrStick, ...), R*→Ring* (RAddHit→RingDexterity,
  RNop→RingAdornment, ...), Ws*→Wand* (WsElect→WandLightning, ...),
  weapons (TwoSword→WeaponTwoHandedSword, Shiraken→WeaponShuriken,
  ...), armor (RingMail→ArmorRingMail, ...)
- Counts: Max{Potions,Scrolls,Rings,Sticks,Weapons,Armors} →
  Num{Potion,Scroll,Ring,Wand,Weapon,Armor}Types; NTraps→NumTrapTypes
- Level.NTraps field → TrapCount (also in the save snapshot)
- Original C constant names preserved as comment breadcrumbs in
  types.go so the lineage stays greppable

TODO.md: step 1 moved to Completed, step 2 (typed kinds) promoted to
Next Step.
2026-07-06 21:28:57 +02:00

201 lines
5.1 KiB
Go

package game
import "fmt"
// weapons.c — functions for dealing with problems brought about by weapons.
const noWeapon = -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", int(Weapon))
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.Type)
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.Type
obj.Pos = fpos
if g.cansee(fpos.Y, fpos.X) {
if pp.Monst != nil {
pp.Monst.OldCh = obj.Type
} else {
g.mvaddch(fpos.Y, fpos.X, obj.Type)
}
}
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", int(Weapon))
if obj == nil {
g.After = false
return
}
if obj.Type == Armor {
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.addmsg("you are now ")
}
g.msg("wielding %s (%c)", sp, obj.PackCh)
}
// initWeaps is the weapons.c init_dam[] table.
var initWeaps = [NumWeaponTypes]struct {
dam string // damage when wielded
hrl string // damage when thrown
launch int // launching weapon
flags ObjFlags
}{
{"2x4", "1x3", noWeapon, 0}, // WeaponMace
{"3x4", "1x2", noWeapon, 0}, // Long sword
{"1x1", "1x1", noWeapon, 0}, // WeaponBow
{"1x1", "2x3", WeaponBow, Stackable | Missile}, // WeaponArrow
{"1x6", "1x4", noWeapon, Missile}, // WeaponDagger
{"4x4", "1x2", noWeapon, 0}, // 2h sword
{"1x1", "1x3", noWeapon, Stackable | Missile}, // WeaponDart
{"1x2", "2x4", noWeapon, Stackable | Missile}, // Shuriken
{"2x3", "1x6", noWeapon, Missile}, // WeaponSpear
}
// initWeapon sets up a new weapon (weapons.c init_weapon).
func (g *RogueGame) initWeapon(weap *Object, which int) {
iwp := &initWeaps[which]
weap.Type = Weapon
weap.Which = which
weap.Damage = iwp.dam
weap.HurlDmg = iwp.hrl
weap.Launch = iwp.launch
weap.Flags = iwp.flags
weap.HPlus = 0
weap.DPlus = 0
if which == WeaponDagger {
weap.Count = g.rnd(4) + 2
weap.Group = g.Items.Group
g.Items.Group++
} else if weap.Flags.Has(Stackable) {
weap.Count = g.rnd(8) + 8
weap.Group = g.Items.Group
g.Items.Group++
} else {
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
}