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).
|
// doZap performs a zap with a wand (sticks.c do_zap).
|
||||||
func (g *RogueGame) doZap() {
|
func (g *RogueGame) doZap() {
|
||||||
p := &g.Player
|
|
||||||
|
|
||||||
obj, ok := g.promptPackItem("zap with", KindWand)
|
obj, ok := g.promptPackItem("zap with", KindWand)
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
@@ -32,182 +30,248 @@ func (g *RogueGame) doZap() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch obj.WandKind() {
|
if h := g.data.zapHandlers[obj.WandKind()]; h != nil {
|
||||||
case WandLight:
|
if !h(g, obj) {
|
||||||
// Reddy Kilowatt wand. Light up the room
|
return // the zap aborted; no charge is used
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
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--
|
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).
|
// drain does the drain-hit-points-from-player schtick (sticks.c drain).
|
||||||
func (g *RogueGame) drain() {
|
func (g *RogueGame) drain() {
|
||||||
p := &g.Player
|
p := &g.Player
|
||||||
|
|||||||
@@ -132,6 +132,11 @@ type gameData struct {
|
|||||||
// the cases of the scrolls.c read_scroll switch. The handler gets
|
// the cases of the scrolls.c read_scroll switch. The handler gets
|
||||||
// the scroll being read (the identify family needs its subtype).
|
// the scroll being read (the identify family needs its subtype).
|
||||||
readHandlers [NumScrollTypes]func(g *RogueGame, obj *Object)
|
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.
|
// ripWall is the repeated blank wall line of the tombstone art.
|
||||||
@@ -635,6 +640,22 @@ func newGameData() *gameData {
|
|||||||
ScrollAggravateMonsters: (*RogueGame).readAggravateMonsters,
|
ScrollAggravateMonsters: (*RogueGame).readAggravateMonsters,
|
||||||
ScrollProtectArmor: (*RogueGame).readProtectArmor,
|
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