package game // monsters.c — monster creation and saving throws. // lvlMons and wandMons list monsters in rough order of vorpalness; zero // entries in wandMons never wander (monsters.c). var lvlMons = [26]byte{ 'K', 'E', 'B', 'S', 'H', 'I', 'R', 'O', 'Z', 'L', 'C', 'Q', 'A', 'N', 'Y', 'F', 'T', 'W', 'P', 'X', 'U', 'M', 'V', 'G', 'J', 'D', } var wandMons = [26]byte{ 'K', 'E', 'B', 'S', 'H', 0, 'R', 'O', 'Z', 0, 'C', 'Q', 'A', 0, 'Y', 0, 'T', 'W', 'P', 0, 'U', 'M', 'V', 'G', 'J', 0, } // randMonster picks a monster to show up; the lower the level, the meaner // the monster (monsters.c randmonster). func (g *RogueGame) randMonster(wander bool) byte { mons := &lvlMons if wander { mons = &wandMons } for { d := g.Depth + (g.rnd(10) - 6) if d < 0 { d = g.rnd(5) } if d > 25 { d = g.rnd(5) + 21 } if mons[d] != 0 { return mons[d] } } } // newMonster picks a new monster and adds it to the list (monsters.c // new_monster). func (g *RogueGame) newMonster(tp *Monster, typ byte, cp Coord) { levAdd := max(g.Depth-AmuletLevel, 0) attachMon(&g.Level.Monsters, tp) tp.Type = typ tp.Disguise = typ tp.Pos = cp g.move(cp.Y, cp.X) tp.OldCh = g.inch() tp.Room = g.roomin(cp) g.Level.SetMonsterAt(cp.Y, cp.X, tp) mp := &g.Monsters[tp.Type-'A'] tp.Stats.Lvl = mp.Stats.Lvl + levAdd tp.Stats.MaxHP = g.roll(tp.Stats.Lvl, 8) tp.Stats.HP = tp.Stats.MaxHP tp.Stats.ArmorClass = mp.Stats.ArmorClass - levAdd tp.Stats.Dmg = mp.Stats.Dmg tp.Stats.Str = mp.Stats.Str tp.Stats.Exp = mp.Stats.Exp + levAdd*10 + expAdd(tp) tp.Flags = mp.Flags if g.Depth > 29 { tp.Flags.Set(Hasted) } tp.Turn = true tp.Pack = nil if g.Player.IsWearing(RingAggravateMonsters) { g.runto(cp) } if typ == 'X' { tp.Disguise = g.rndThing() } } // expAdd is the experience to add for this monster's level/hit points // (monsters.c exp_add). func expAdd(tp *Monster) int { var mod int if tp.Stats.Lvl == 1 { mod = tp.Stats.MaxHP / 8 } else { mod = tp.Stats.MaxHP / 6 } if tp.Stats.Lvl > 9 { mod *= 20 } else if tp.Stats.Lvl > 6 { mod *= 4 } return mod } // wanderer creates a new wandering monster and aims it at the player // (monsters.c wanderer). func (g *RogueGame) wanderer() { tp := &Monster{} var cp Coord for { cp, _ = g.findFloor(true) if g.roomin(cp) != g.Player.Room { break } } g.newMonster(tp, g.randMonster(true), cp) if g.Player.On(SenseMonsters) { g.standout() if !g.Player.On(Hallucinating) { g.addch(tp.Type) } else { g.addch(g.randomMonsterLetter()) } g.standend() } g.runto(tp.Pos) } // wakeMonster is what to do when the hero steps next to a monster // (monsters.c wake_monster). func (g *RogueGame) wakeMonster(y, x int) *Monster { p := &g.Player tp := g.Level.MonsterAt(y, x) if tp == nil { panic("can't find monster in wake_monster") } ch := tp.Type // Every time he sees a mean monster, it might start chasing him if !tp.On(Awake) && g.rnd(3) != 0 && tp.On(Mean) && !tp.On(Held) && !p.IsWearing(RingStealth) && !p.On(Levitating) { tp.Dest = &p.Pos tp.Flags.Set(Awake) } if ch == 'M' && !p.On(Blind) && !p.On(Hallucinating) && !tp.On(Found) && !tp.On(Cancelled) && tp.On(Awake) { rp := p.Room if (rp != nil && !rp.Flags.Has(Dark)) || distance(y, x, p.Pos.Y, p.Pos.X) < LampDist { tp.Flags.Set(Found) if !g.save(VsMagic) { if p.On(Confused) { g.Lengthen(DUnconfuse, g.spread(HuhDuration)) } else { g.Fuse(DUnconfuse, 0, g.spread(HuhDuration), After) } p.Flags.Set(Confused) mname := g.setMname(tp) g.addmsgf("%s", mname) if mname != "it" { g.addmsgf("'") } g.msg("s gaze has confused you") } } } // Let greedy ones guard gold if tp.On(Greedy) && !tp.On(Awake) { tp.Flags.Set(Awake) if p.Room.GoldVal != 0 { tp.Dest = &p.Room.Gold } else { tp.Dest = &p.Pos } } return tp } // givePack gives a pack to a monster if it deserves one (monsters.c // give_pack). func (g *RogueGame) givePack(tp *Monster) { if g.Depth >= g.MaxDepth && g.rnd(100) < g.Monsters[tp.Type-'A'].Carry { attachObj(&tp.Pack, g.newThing()) } } // saveThrow sees if a creature saves against something (monsters.c // save_throw). func (g *RogueGame) saveThrow(which int, st *Stats) bool { need := 14 + which - st.Lvl/2 return g.roll(1, 20) >= need } // save sees if the hero saves against various nasty things (monsters.c // save). func (g *RogueGame) save(which int) bool { p := &g.Player if which == VsMagic { if p.IsRing(Left, RingProtection) { which -= p.CurRing[Left].Bonus } if p.IsRing(Right, RingProtection) { which -= p.CurRing[Right].Bonus } } return g.saveThrow(which, &p.Stats) } // randomMonsterLetter picks a random monster display letter, used by the // hallucination effects (the C rnd(26)+'A' idiom). func (g *RogueGame) randomMonsterLetter() byte { return byte(g.rnd(26) + 'A') //nolint:gosec // G115: 'A'..'Z' fits a byte }