Files
rgoue/game/potions.go
sneak cdf9bf73a9 go: port item effects — potions, scrolls, options, call_it
- potions.c completed: quaff with all 14 potion effects, the PACT
  standard-fuse table (do_pot), raise_level; the see-invisible fruit-
  juice message is computed at quaff time as in C
- scrolls.c in full: read_scroll with all 18 scroll effects including
  the magic-mapping map rewrite and identify dispatch, uncurse
- options.c in full: the interactive options screen, get_bool/get_str/
  get_inv_t/get_sf editors, ROGUEOPTS parsing (prefix matching, no-
  prefixed booleans, ~ expansion), strucpy
- misc.c call_it via the get_str line editor
- turn_see gets a DaemonID (C casts it to a fuse callback for the
  monster-detection potion)

Tests: healing/confusion potions with fuse burn-down, enchant armor,
hold monster, slow-monster zap, ROGUEOPTS parsing, and a regression
test documenting the C wake_monster ISGREED/ISHELD quirk the port
keeps faithfully.
2026-07-06 19:34:36 +02:00

291 lines
7.1 KiB
Go

package game
import "fmt"
// potions.c — functions for dealing with potions.
// pact describes a standard fuse-based potion (potions.c PACT).
type pact struct {
flags CreatureFlags
daemon DaemonID
time int
high string // message when hallucinating
straight string
}
// pActions is potions.c p_actions[]. The P_SEEINVIS message is dynamic
// (it names the fruit) and is computed in doPot.
var pActions = [MaxPotions]pact{
PConfuse: {IsHuh, DUnconfuse, HuhDuration,
"what a tripy feeling!",
"wait, what's going on here. Huh? What? Who?"},
PLSD: {IsHalu, DComeDown, SeeDuration,
"Oh, wow! Everything seems so cosmic!",
"Oh, wow! Everything seems so cosmic!"},
PSeeInvis: {CanSee, DUnsee, SeeDuration, "", ""},
PBlind: {IsBlind, DSight, SeeDuration,
"oh, bummer! Everything is dark! Help!",
"a cloak of darkness falls around you"},
PLevit: {IsLevit, DLand, HealTime,
"oh, wow! You're floating in the air!",
"you start to float in the air"},
}
// quaff drinks a potion from the pack (potions.c quaff).
func (g *RogueGame) quaff() {
p := &g.Player
obj := g.getItem("quaff", int(Potion))
// Make certain that it is something that we want to drink
if obj == nil {
return
}
if obj.Type != Potion {
if !g.Options.Terse {
g.msg("yuk! Why would you want to drink that?")
} else {
g.msg("that's undrinkable")
}
return
}
if obj == p.CurWeapon {
p.CurWeapon = nil
}
// Calculate the effect it has on the poor guy.
trip := p.On(IsHalu)
g.leavePack(obj, false, false)
switch obj.Which {
case PConfuse:
g.doPot(PConfuse, !trip)
case PPoison:
g.Items.Potions[PPoison].Know = true
if p.IsWearing(RSustStr) {
g.msg("you feel momentarily sick")
} else {
g.chgStr(-(g.rnd(3) + 1))
g.msg("you feel very sick now")
g.comeDown(0)
}
case PHealing:
g.Items.Potions[PHealing].Know = true
if p.Stats.HP += g.roll(p.Stats.Lvl, 4); p.Stats.HP > p.Stats.MaxHP {
p.Stats.MaxHP++
p.Stats.HP = p.Stats.MaxHP
}
g.sight(0)
g.msg("you begin to feel better")
case PStrength:
g.Items.Potions[PStrength].Know = true
g.chgStr(1)
g.msg("you feel stronger, now. What bulging muscles!")
case PMFind:
p.Flags.Set(SeeMonst)
g.Fuse(DTurnSee, 1, HuhDuration, After)
if !g.turnSee(false) {
g.msg("you have a %s feeling for a moment, then it passes",
g.chooseStr("normal", "strange"))
}
case PTFind:
// Potion of magic detection. Show the potions and scrolls
show := false
if len(g.Level.Objects) > 0 {
g.scr.Hw.Clear()
for _, tp := range g.Level.Objects {
if tp.isMagic() {
show = true
g.scr.Hw.MvAddCh(tp.Pos.Y, tp.Pos.X, Magic)
g.Items.Potions[PTFind].Know = true
}
}
for _, mp := range g.Level.Monsters {
for _, tp := range mp.Pack {
if tp.isMagic() {
show = true
g.scr.Hw.MvAddCh(mp.Pos.Y, mp.Pos.X, Magic)
}
}
}
}
if show {
g.Items.Potions[PTFind].Know = true
g.showWin("You sense the presence of magic on this level.--More--")
} else {
g.msg("you have a %s feeling for a moment, then it passes",
g.chooseStr("normal", "strange"))
}
case PLSD:
if !trip {
if p.On(SeeMonst) {
g.turnSee(false)
}
g.StartDaemon(DVisuals, 0, Before)
g.SeenStairs = g.seenStairs()
}
g.doPot(PLSD, true)
case PSeeInvis:
show := p.On(CanSee)
g.doPot(PSeeInvis, false)
if !show {
g.invisOn()
}
g.sight(0)
case PRaise:
g.Items.Potions[PRaise].Know = true
g.msg("you suddenly feel much more skillful")
g.raiseLevel()
case PXHeal:
g.Items.Potions[PXHeal].Know = true
if p.Stats.HP += g.roll(p.Stats.Lvl, 8); p.Stats.HP > p.Stats.MaxHP {
if p.Stats.HP > p.Stats.MaxHP+p.Stats.Lvl+1 {
p.Stats.MaxHP++
}
p.Stats.MaxHP++
p.Stats.HP = p.Stats.MaxHP
}
g.sight(0)
g.comeDown(0)
g.msg("you begin to feel much better")
case PHaste:
g.Items.Potions[PHaste].Know = true
g.After = false
if g.addHaste(true) {
g.msg("you feel yourself moving much faster")
}
case PRestore:
if p.IsRing(Left, RAddStr) {
addStr(&p.Stats.Str, -p.CurRing[Left].Arm)
}
if p.IsRing(Right, RAddStr) {
addStr(&p.Stats.Str, -p.CurRing[Right].Arm)
}
if p.Stats.Str < p.MaxStats.Str {
p.Stats.Str = p.MaxStats.Str
}
if p.IsRing(Left, RAddStr) {
addStr(&p.Stats.Str, p.CurRing[Left].Arm)
}
if p.IsRing(Right, RAddStr) {
addStr(&p.Stats.Str, p.CurRing[Right].Arm)
}
g.msg("hey, this tastes great. It make you feel warm all over")
case PBlind:
g.doPot(PBlind, true)
case PLevit:
g.doPot(PLevit, true)
}
g.status()
// Throw the item away
g.callIt(&g.Items.Potions[obj.Which])
}
// raiseLevel: the guy just magically went up a level (potions.c
// raise_level).
func (g *RogueGame) raiseLevel() {
g.Player.Stats.Exp = eLevels[g.Player.Stats.Lvl-1] + 1
g.checkLevel()
}
// doPot does a potion with standard setup: it uses a fuse and turns on a
// flag (potions.c do_pot).
func (g *RogueGame) doPot(typ int, knowit bool) {
pp := &pActions[typ]
if !g.Items.Potions[typ].Know {
g.Items.Potions[typ].Know = knowit
}
t := g.spread(pp.time)
if !g.Player.On(pp.flags) {
g.Player.Flags.Set(pp.flags)
g.Fuse(pp.daemon, 0, t, After)
g.look(false)
} else {
g.Lengthen(pp.daemon, t)
}
high, straight := pp.high, pp.straight
if typ == PSeeInvis {
s := fmt.Sprintf("this potion tastes like %s juice", g.Fruit)
high, straight = s, s
}
g.msg("%s", g.chooseStr(high, straight))
}
// isMagic reports whether an object radiates magic (potions.c is_magic).
func (o *Object) isMagic() bool {
switch o.Type {
case Armor:
return o.Flags.Has(IsProt) || o.Arm != aClass[o.Which]
case Weapon:
return o.HPlus != 0 || o.DPlus != 0
case Potion, Scroll, Stick, Ring, Amulet:
return true
}
return false
}
// invisOn turns on the ability to see invisible monsters (potions.c
// invis_on).
func (g *RogueGame) invisOn() {
g.Player.Flags.Set(CanSee)
for _, mp := range g.Level.Monsters {
if mp.On(IsInvis) && g.seeMonst(mp) && !g.Player.On(IsHalu) {
g.mvaddch(mp.Pos.Y, mp.Pos.X, mp.Disguise)
}
}
}
// turnSee puts on or off seeing monsters on this level (potions.c
// turn_see).
func (g *RogueGame) turnSee(turnOff bool) bool {
addNew := false
for _, mp := range g.Level.Monsters {
g.move(mp.Pos.Y, mp.Pos.X)
canSee := g.seeMonst(mp)
if turnOff {
if !canSee {
g.addch(mp.OldCh)
}
} else {
if !canSee {
g.standout()
}
if !g.Player.On(IsHalu) {
g.addch(mp.Type)
} else {
g.addch(byte(g.rnd(26) + 'A'))
}
if !canSee {
g.standend()
addNew = true
}
}
}
if turnOff {
g.Player.Flags.Clear(SeeMonst)
} else {
g.Player.Flags.Set(SeeMonst)
}
return addNew
}
// seenStairs reports whether the player has seen the stairs (potions.c
// seen_stairs).
func (g *RogueGame) seenStairs() bool {
st := g.Level.Stairs
g.move(st.Y, st.X)
if g.inch() == Stairs { // it's on the map
return true
}
if g.Player.Pos == st { // it's under him
return true
}
// if a monster is on the stairs, this gets hairy
if tp := g.Level.MonsterAt(st.Y, st.X); tp != nil {
if g.seeMonst(tp) && tp.On(IsRun) { // visible and awake:
return true // it must have moved there
}
if g.Player.On(SeeMonst) && tp.OldCh == Stairs {
return true
}
}
return false
}