Convert doZap to a per-wand handler table (refactor step 7)
The do_zap switch becomes gameData.zapHandlers, indexed by WandKind; the shared monster-ray preamble is zapRayMonster/zapVictim, teleport away/to and the three bolt wands share handlers, and a false return aborts the zap without spending a charge (drain life on a too-weak hero, as in C). Effect order and RNG call sequence unchanged.
This commit is contained in:
408
game/sticks.go
408
game/sticks.go
@@ -12,8 +12,6 @@ const (
|
||||
|
||||
// doZap performs a zap with a wand (sticks.c do_zap).
|
||||
func (g *RogueGame) doZap() {
|
||||
p := &g.Player
|
||||
|
||||
obj, ok := g.promptPackItem("zap with", KindWand)
|
||||
if !ok {
|
||||
return
|
||||
@@ -32,182 +30,248 @@ func (g *RogueGame) doZap() {
|
||||
return
|
||||
}
|
||||
|
||||
switch obj.WandKind() {
|
||||
case WandLight:
|
||||
// Reddy Kilowatt wand. Light up the room
|
||||
g.Items.Sticks[WandLight].Know = true
|
||||
if p.Room.Flags.Has(Gone) {
|
||||
g.msg("the corridor glows and then fades")
|
||||
} else {
|
||||
p.Room.Flags.Clear(Dark)
|
||||
// Light the room and put the player back up
|
||||
g.enterRoom(p.Pos)
|
||||
g.addmsgf("the room is lit")
|
||||
|
||||
if !g.Options.Terse {
|
||||
g.addmsgf(" by a shimmering %s light", g.pickColor("blue"))
|
||||
}
|
||||
|
||||
g.endmsg()
|
||||
if h := g.data.zapHandlers[obj.WandKind()]; h != nil {
|
||||
if !h(g, obj) {
|
||||
return // the zap aborted; no charge is used
|
||||
}
|
||||
case WandDrainLife:
|
||||
// take away 1/2 of hero's hit points, then take it away evenly
|
||||
// from the monsters in the room (or next to hero if he is in a
|
||||
// passage)
|
||||
if p.Stats.HP < 2 {
|
||||
g.msg("you are too weak to use it")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
g.drain()
|
||||
case WandInvisibility, WandPolymorph, WandTeleportAway, WandTeleportTo, WandCancellation:
|
||||
y := p.Pos.Y
|
||||
|
||||
x := p.Pos.X
|
||||
for stepOk(g.Level.VisibleChar(y, x)) {
|
||||
y += g.Delta.Y
|
||||
x += g.Delta.X
|
||||
}
|
||||
|
||||
if tp := g.Level.MonsterAt(y, x); tp != nil {
|
||||
monster := tp.Type
|
||||
if monster == 'F' {
|
||||
p.Flags.Clear(Held)
|
||||
}
|
||||
|
||||
switch obj.WandKind() {
|
||||
case WandInvisibility:
|
||||
tp.Flags.Set(Invisible)
|
||||
|
||||
if g.canSee(y, x) {
|
||||
g.mvaddch(y, x, tp.OldCh)
|
||||
}
|
||||
case WandPolymorph:
|
||||
pp := tp.Pack
|
||||
g.Level.RemoveMonster(tp)
|
||||
|
||||
if g.seeMonst(tp) {
|
||||
g.mvaddch(y, x, g.Level.Char(y, x))
|
||||
}
|
||||
|
||||
oldch := tp.OldCh
|
||||
g.Delta.Y = y
|
||||
g.Delta.X = x
|
||||
monster = g.randomMonsterLetter()
|
||||
g.newMonster(tp, monster, g.Delta)
|
||||
|
||||
if g.seeMonst(tp) {
|
||||
g.mvaddch(y, x, monster)
|
||||
}
|
||||
|
||||
tp.OldCh = oldch
|
||||
|
||||
tp.Pack = pp
|
||||
if g.seeMonst(tp) {
|
||||
g.Items.Sticks[WandPolymorph].Know = true
|
||||
}
|
||||
case WandCancellation:
|
||||
tp.Flags.Set(Cancelled)
|
||||
tp.Flags.Clear(Invisible | CanConfuse)
|
||||
|
||||
tp.Disguise = tp.Type
|
||||
if g.seeMonst(tp) {
|
||||
g.mvaddch(y, x, tp.Disguise)
|
||||
}
|
||||
case WandTeleportAway, WandTeleportTo:
|
||||
var newPos Coord
|
||||
|
||||
if obj.WandKind() == WandTeleportAway {
|
||||
for {
|
||||
newPos, _ = g.findFloor(true)
|
||||
if newPos != p.Pos {
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
newPos.Y = p.Pos.Y + g.Delta.Y
|
||||
newPos.X = p.Pos.X + g.Delta.X
|
||||
}
|
||||
|
||||
tp.Dest = &p.Pos
|
||||
tp.Flags.Set(Awake)
|
||||
g.relocate(tp, newPos)
|
||||
}
|
||||
}
|
||||
case WandMagicMissile:
|
||||
g.Items.Sticks[WandMagicMissile].Know = true
|
||||
bolt := newObject()
|
||||
bolt.Kind = KindGold // C set o_type='*': draws a '*' and is not a weapon
|
||||
bolt.HurlDmg = dice("1x4")
|
||||
bolt.HPlus = 100
|
||||
bolt.DPlus = 1
|
||||
|
||||
bolt.Flags = Missile
|
||||
if p.CurWeapon != nil {
|
||||
bolt.Launch = WeaponKind(p.CurWeapon.Which)
|
||||
}
|
||||
|
||||
g.doMotion(bolt, g.Delta.Y, g.Delta.X)
|
||||
|
||||
if tp := g.Level.MonsterAt(bolt.Pos.Y, bolt.Pos.X); tp != nil &&
|
||||
!g.saveThrow(VsMagic, &tp.Stats) {
|
||||
g.hitMonster(bolt.Pos, bolt)
|
||||
} else if g.Options.Terse {
|
||||
g.msg("missle vanishes") //nolint:misspell // C's spelling, kept faithfully
|
||||
} else {
|
||||
g.msg("the missle vanishes with a puff of smoke") //nolint:misspell // C's spelling
|
||||
}
|
||||
case WandHasteMonster, WandSlowMonster:
|
||||
y := p.Pos.Y
|
||||
|
||||
x := p.Pos.X
|
||||
for stepOk(g.Level.VisibleChar(y, x)) {
|
||||
y += g.Delta.Y
|
||||
x += g.Delta.X
|
||||
}
|
||||
|
||||
if tp := g.Level.MonsterAt(y, x); tp != nil {
|
||||
if obj.WandKind() == WandHasteMonster {
|
||||
if tp.On(Slowed) {
|
||||
tp.Flags.Clear(Slowed)
|
||||
} else {
|
||||
tp.Flags.Set(Hasted)
|
||||
}
|
||||
} else {
|
||||
if tp.On(Hasted) {
|
||||
tp.Flags.Clear(Hasted)
|
||||
} else {
|
||||
tp.Flags.Set(Slowed)
|
||||
}
|
||||
|
||||
tp.Turn = true
|
||||
}
|
||||
|
||||
g.Delta.Y = y
|
||||
g.Delta.X = x
|
||||
g.runTo(g.Delta)
|
||||
}
|
||||
case WandLightning, WandFire, WandCold:
|
||||
var name string
|
||||
|
||||
switch obj.WandKind() {
|
||||
case WandLightning:
|
||||
name = "bolt"
|
||||
case WandFire:
|
||||
name = "flame"
|
||||
default:
|
||||
name = "ice"
|
||||
}
|
||||
|
||||
g.fireBolt(p.Pos, &g.Delta, name)
|
||||
g.Items.Sticks[obj.Which].Know = true
|
||||
case WandNothing:
|
||||
}
|
||||
|
||||
obj.Charges--
|
||||
}
|
||||
|
||||
// zapRayMonster walks the zap ray from the hero to the first blocking
|
||||
// spot and returns the monster standing there, if any (the shared
|
||||
// preamble of the C monster-affecting zap cases).
|
||||
func (g *RogueGame) zapRayMonster() *Monster {
|
||||
p := &g.Player
|
||||
|
||||
y := p.Pos.Y
|
||||
|
||||
x := p.Pos.X
|
||||
for stepOk(g.Level.VisibleChar(y, x)) {
|
||||
y += g.Delta.Y
|
||||
x += g.Delta.X
|
||||
}
|
||||
|
||||
return g.Level.MonsterAt(y, x)
|
||||
}
|
||||
|
||||
// zapVictim is zapRayMonster plus the flytrap release the C code does
|
||||
// before the invisibility-family effects.
|
||||
func (g *RogueGame) zapVictim() *Monster {
|
||||
tp := g.zapRayMonster()
|
||||
if tp != nil && tp.Type == 'F' {
|
||||
g.Player.Flags.Clear(Held)
|
||||
}
|
||||
|
||||
return tp
|
||||
}
|
||||
|
||||
// The per-wand effect handlers, dispatched through gameData.zapHandlers.
|
||||
// Each is one case of the C do_zap switch; returning false aborts the
|
||||
// zap without using a charge.
|
||||
|
||||
func (g *RogueGame) zapLight(*Object) bool {
|
||||
// Reddy Kilowatt wand. Light up the room
|
||||
p := &g.Player
|
||||
|
||||
g.Items.Sticks[WandLight].Know = true
|
||||
if p.Room.Flags.Has(Gone) {
|
||||
g.msg("the corridor glows and then fades")
|
||||
} else {
|
||||
p.Room.Flags.Clear(Dark)
|
||||
// Light the room and put the player back up
|
||||
g.enterRoom(p.Pos)
|
||||
g.addmsgf("the room is lit")
|
||||
|
||||
if !g.Options.Terse {
|
||||
g.addmsgf(" by a shimmering %s light", g.pickColor("blue"))
|
||||
}
|
||||
|
||||
g.endmsg()
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (g *RogueGame) zapDrainLife(*Object) bool {
|
||||
// take away 1/2 of hero's hit points, then take it away evenly
|
||||
// from the monsters in the room (or next to hero if he is in a
|
||||
// passage)
|
||||
if g.Player.Stats.HP < 2 {
|
||||
g.msg("you are too weak to use it")
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
g.drain()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (g *RogueGame) zapInvisibility(*Object) bool {
|
||||
if tp := g.zapVictim(); tp != nil {
|
||||
tp.Flags.Set(Invisible)
|
||||
|
||||
if g.canSee(tp.Pos.Y, tp.Pos.X) {
|
||||
g.mvaddch(tp.Pos.Y, tp.Pos.X, tp.OldCh)
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (g *RogueGame) zapPolymorph(*Object) bool {
|
||||
tp := g.zapVictim()
|
||||
if tp == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
y, x := tp.Pos.Y, tp.Pos.X
|
||||
|
||||
pp := tp.Pack
|
||||
g.Level.RemoveMonster(tp)
|
||||
|
||||
if g.seeMonst(tp) {
|
||||
g.mvaddch(y, x, g.Level.Char(y, x))
|
||||
}
|
||||
|
||||
oldch := tp.OldCh
|
||||
g.Delta.Y = y
|
||||
g.Delta.X = x
|
||||
monster := g.randomMonsterLetter()
|
||||
g.newMonster(tp, monster, g.Delta)
|
||||
|
||||
if g.seeMonst(tp) {
|
||||
g.mvaddch(y, x, monster)
|
||||
}
|
||||
|
||||
tp.OldCh = oldch
|
||||
|
||||
tp.Pack = pp
|
||||
if g.seeMonst(tp) {
|
||||
g.Items.Sticks[WandPolymorph].Know = true
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (g *RogueGame) zapCancellation(*Object) bool {
|
||||
if tp := g.zapVictim(); tp != nil {
|
||||
tp.Flags.Set(Cancelled)
|
||||
tp.Flags.Clear(Invisible | CanConfuse)
|
||||
|
||||
tp.Disguise = tp.Type
|
||||
if g.seeMonst(tp) {
|
||||
g.mvaddch(tp.Pos.Y, tp.Pos.X, tp.Disguise)
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (g *RogueGame) zapTeleport(obj *Object) bool {
|
||||
p := &g.Player
|
||||
|
||||
tp := g.zapVictim()
|
||||
if tp == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
var newPos Coord
|
||||
|
||||
if obj.WandKind() == WandTeleportAway {
|
||||
for {
|
||||
newPos, _ = g.findFloor(true)
|
||||
if newPos != p.Pos {
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
newPos.Y = p.Pos.Y + g.Delta.Y
|
||||
newPos.X = p.Pos.X + g.Delta.X
|
||||
}
|
||||
|
||||
tp.Dest = &p.Pos
|
||||
tp.Flags.Set(Awake)
|
||||
g.relocate(tp, newPos)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (g *RogueGame) zapMagicMissile(*Object) bool {
|
||||
p := &g.Player
|
||||
|
||||
g.Items.Sticks[WandMagicMissile].Know = true
|
||||
bolt := newObject()
|
||||
bolt.Kind = KindGold // C set o_type='*': draws a '*' and is not a weapon
|
||||
bolt.HurlDmg = dice("1x4")
|
||||
bolt.HPlus = 100
|
||||
bolt.DPlus = 1
|
||||
|
||||
bolt.Flags = Missile
|
||||
if p.CurWeapon != nil {
|
||||
bolt.Launch = WeaponKind(p.CurWeapon.Which)
|
||||
}
|
||||
|
||||
g.doMotion(bolt, g.Delta.Y, g.Delta.X)
|
||||
|
||||
if tp := g.Level.MonsterAt(bolt.Pos.Y, bolt.Pos.X); tp != nil &&
|
||||
!g.saveThrow(VsMagic, &tp.Stats) {
|
||||
g.hitMonster(bolt.Pos, bolt)
|
||||
} else if g.Options.Terse {
|
||||
g.msg("missle vanishes") //nolint:misspell // C's spelling, kept faithfully
|
||||
} else {
|
||||
g.msg("the missle vanishes with a puff of smoke") //nolint:misspell // C's spelling
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (g *RogueGame) zapSpeed(obj *Object) bool {
|
||||
if tp := g.zapRayMonster(); tp != nil {
|
||||
if obj.WandKind() == WandHasteMonster {
|
||||
if tp.On(Slowed) {
|
||||
tp.Flags.Clear(Slowed)
|
||||
} else {
|
||||
tp.Flags.Set(Hasted)
|
||||
}
|
||||
} else {
|
||||
if tp.On(Hasted) {
|
||||
tp.Flags.Clear(Hasted)
|
||||
} else {
|
||||
tp.Flags.Set(Slowed)
|
||||
}
|
||||
|
||||
tp.Turn = true
|
||||
}
|
||||
|
||||
g.Delta.Y = tp.Pos.Y
|
||||
g.Delta.X = tp.Pos.X
|
||||
g.runTo(g.Delta)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (g *RogueGame) zapBolt(obj *Object) bool {
|
||||
var name string
|
||||
|
||||
switch obj.WandKind() {
|
||||
case WandLightning:
|
||||
name = "bolt"
|
||||
case WandFire:
|
||||
name = "flame"
|
||||
default:
|
||||
name = "ice"
|
||||
}
|
||||
|
||||
g.fireBolt(g.Player.Pos, &g.Delta, name)
|
||||
g.Items.Sticks[obj.Which].Know = true
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// drain does the drain-hit-points-from-player schtick (sticks.c drain).
|
||||
func (g *RogueGame) drain() {
|
||||
p := &g.Player
|
||||
|
||||
@@ -132,6 +132,11 @@ type gameData struct {
|
||||
// the cases of the scrolls.c read_scroll switch. The handler gets
|
||||
// the scroll being read (the identify family needs its subtype).
|
||||
readHandlers [NumScrollTypes]func(g *RogueGame, obj *Object)
|
||||
|
||||
// zapHandlers dispatches each wand kind to its effect method: the
|
||||
// cases of the sticks.c do_zap switch. A false return aborts the
|
||||
// zap without using a charge (drain life on a too-weak hero).
|
||||
zapHandlers [NumWandTypes]func(g *RogueGame, obj *Object) bool
|
||||
}
|
||||
|
||||
// ripWall is the repeated blank wall line of the tombstone art.
|
||||
@@ -635,6 +640,22 @@ func newGameData() *gameData {
|
||||
ScrollAggravateMonsters: (*RogueGame).readAggravateMonsters,
|
||||
ScrollProtectArmor: (*RogueGame).readProtectArmor,
|
||||
},
|
||||
|
||||
zapHandlers: [NumWandTypes]func(*RogueGame, *Object) bool{
|
||||
WandLight: (*RogueGame).zapLight,
|
||||
WandInvisibility: (*RogueGame).zapInvisibility,
|
||||
WandLightning: (*RogueGame).zapBolt,
|
||||
WandFire: (*RogueGame).zapBolt,
|
||||
WandCold: (*RogueGame).zapBolt,
|
||||
WandPolymorph: (*RogueGame).zapPolymorph,
|
||||
WandMagicMissile: (*RogueGame).zapMagicMissile,
|
||||
WandHasteMonster: (*RogueGame).zapSpeed,
|
||||
WandSlowMonster: (*RogueGame).zapSpeed,
|
||||
WandDrainLife: (*RogueGame).zapDrainLife,
|
||||
WandTeleportAway: (*RogueGame).zapTeleport,
|
||||
WandTeleportTo: (*RogueGame).zapTeleport,
|
||||
WandCancellation: (*RogueGame).zapCancellation,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user