8 Commits

Author SHA1 Message Date
3ed7931676 Merge refactor/object-fields (refactor step 3 + module rename) 2026-07-06 23:10:39 +02:00
43c59b7f82 Update module base path to git.eeqj.de/sneak/rgoue
go.mod, term/ and cmd/ imports, the ARCHITECTURE.md module references,
and the in-game version string. User request; the repo already lives
at this remote.
2026-07-06 23:10:39 +02:00
2eff377a73 Un-overload Object fields and pre-parse damage dice (refactor step 3)
Object.Arm carried four meanings in C (o_arm/o_charges/o_goldval plus
ring bonuses); it is now four fields: ArmorClass, Charges, GoldValue,
and Bonus. Stats.Arm becomes Stats.ArmorClass. The Charges()/GoldVal()
accessor pair is gone.

Damage dice strings ("1x4/1x2") are parsed once into DiceSpec — at
table definition for the bestiary and weapon tables, at creation for
items — instead of re-parsed with atoi on every swing as fight.c
roll_em did. ParseDice preserves the C parse semantics exactly,
including the junk-tolerant "%%%x0" bestiary placeholder and the
"000x0" flytrap reset (regression-tested in dice_test.go); the
flytrap's growing grip becomes DiceSpec{{VfHit, 1}}.

Save format bumps to 5.4.4-go3 (field renames and retypes would
silently zero under gob's match-by-name decoding).

No behavior change; full suite green.
2026-07-06 23:07:57 +02:00
c75af2ec22 Rotate TODO.md for completed refactor step 2
The rotation should have ridden the step-2 commit per the Workflow
section, but the file was edited concurrently and the update was lost;
recording it now. Step 3 (un-overload Object fields) is Next Step.
2026-07-06 22:01:33 +02:00
4b04ac08ea Merge refactor/typed-kinds (refactor step 2) 2026-07-06 22:00:43 +02:00
b940cfc41f Give item categories and subtypes real types (refactor step 2)
ObjectKind separates what an item is from how it draws: Object.Type
(a byte that doubled as the map character) becomes Kind ObjectKind,
with Glyph() producing the display character and objectKindForGlyph
converting back at the map boundary. KindWand covers the C 'stick'
category. The magic-missile bolt uses KindGold, matching C's literal
o_type='*' trick, with a comment.

PotionKind, ScrollKind, RingKind, WandKind, WeaponKind, ArmorKind and
TrapKind are now iota enums with Stringer (names come from the base
info tables); Object gains typed accessors (obj.RingKind(), ...) and
Launch is typed WeaponKind. beTrapped returns TrapKind. The
getItem/inventory/whatis prompt filters take ObjectKind, with
KindCallable/KindRingOrStick replacing the C CALLABLE/R_OR_S
sentinels; wizard.c's type_name table is subsumed by
ObjectKind.String(). IsRing/IsWearing/initWeapon/doPot signatures are
typed accordingly.

The save format version becomes 5.4.4-go2: the gob field rename would
otherwise silently zero Kind when reading old saves.

No behavior change; full suite green.
2026-07-06 22:00:43 +02:00
626f8c5a7d Merge refactor/descriptive-constants (refactor step 1) 2026-07-06 21:29:19 +02:00
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
41 changed files with 1252 additions and 1004 deletions

View File

@@ -6,7 +6,7 @@ This document has two parts:
codebase (every file, every function, every type, every global) as it exists
on the `modern-rogue` branch.
- **Part 2 — The Go Port Design**: the object-oriented architecture for the Go
port (`sneak.berlin/go/rogue`), including how every C construct maps onto Go
port (`git.eeqj.de/sneak/rgoue`), including how every C construct maps onto Go
types and methods.
---
@@ -887,7 +887,7 @@ Declares `rd_score(SCORE*)` / `wr_score(SCORE*)` (implemented in save.c).
# Part 2 — The Go Port Design
Module: **`sneak.berlin/go/rogue`**
Module: **`git.eeqj.de/sneak/rgoue`**
## 1. Goals and principles
@@ -929,7 +929,7 @@ branches; this branch is Go-only. The tcell wrapper ended up in its own
`term` package so that `game` itself has no third-party imports:
```
go.mod module sneak.berlin/go/rogue
go.mod module git.eeqj.de/sneak/rgoue
cmd/rogue/main.go package main — flag parsing, constructs RogueGame, calls Run
game/ package game — the port, one .go file per .c file
game.go RogueGame struct, NewGame, Run (main.c)

70
TODO.md
View File

@@ -29,19 +29,32 @@ Refactor ground rules:
# Next Step
Refactor step 1 (branch refactor/descriptive-constants): rename
constants to descriptive, less C-like names. Creature/object/room/place
flag bits (IsHuh→Confused, IsHalu→Hallucinating, CanHuh→CanConfuse,
SeeMonst→SenseMonsters, ObjIsFound→WasFound, IsCanc→Cancelled, ...),
trap types (TMyst→TrapMystery), item subtype constants
(PLSD→PotionLSD, SIDRorS→ScrollIdentifyRingOrStick,
RAddHit→RingDexterity, WsHasteM→WandHasteMonster,
TwoSword→WeaponTwoHandedSword, ...), Max* count constants →
Num*Types, and the Level.NTraps field → TrapCount. Pure rename; no
behavior change; tests unchanged.
Adopt the house Go linting standards: copy .golangci.yml from the
prompts repo and bring game/, term/, and cmd/ lint-clean (the port is
greenfield code, so no exemptions apply).
# Completed Steps
- 2026-07-06 Module base path updated to git.eeqj.de/sneak/rgoue
(go.mod, term/ and cmd/ imports, ARCHITECTURE.md, version string).
- 2026-07-06 Refactor step 3 (refactor/object-fields): Object.Arm split
into ArmorClass/Charges/GoldValue/Bonus (rings); Stats.Arm →
ArmorClass; damage strings parsed once into DiceSpec at table
definition (ParseDice keeps C roll_em parse semantics, incl. "%%%x0"
and "000x0" edge cases, regression-tested); save format 5.4.4-go3.
- 2026-07-06 Refactor step 2 (refactor/typed-kinds, b940cfc):
ObjectKind separates item category from map glyph (Object.Type byte
→ Kind ObjectKind with Glyph()); PotionKind/ScrollKind/RingKind/
WandKind/WeaponKind/ArmorKind/TrapKind typed iota enums with
Stringer; typed accessors on Object; getItem/inventory/whatis
filters take ObjectKind (KindCallable/KindRingOrStick replace
CALLABLE/R_OR_S); save format bumped to 5.4.4-go2. Suite green.
- 2026-07-06 Refactor step 1 (refactor/descriptive-constants): renamed
all flag bits, trap types, item subtype constants, and Max* counts to
descriptive names (IsHuh→Confused, SeeMonst→SenseMonsters,
WsHasteM→WandHasteMonster, MaxSticks→NumWandTypes, ...);
Level.NTraps→TrapCount; C names kept as comment breadcrumbs. Pure
rename, suite green.
- 2026-07-06 Made the rgoue branch Go-only: removed C sources and the
autoconf/VS build system (they remain on master and modern-rogue),
ported the last wizard command (item-probability listing), rewrote
@@ -63,58 +76,45 @@ behavior change; tests unchanged.
# Future Steps
1. Refactor step 2: typed kind enums — PotionKind, ScrollKind,
RingKind, WandKind, WeaponKind, ArmorKind, TrapKind as iota enums
with fmt.Stringer; Object.Which and the ObjInfo tables move to the
typed kinds; the object category glyph bytes ('!', '?', ...) become
an ObjectKind with a Glyph() method so item category and map
character stop sharing one byte namespace.
2. Refactor step 3: un-overload Object fields — split Object.Arm into
ArmorClass/Charges/GoldValue; parse damage dice ("1x4/1x2") once
into a DiceSpec type at table definition instead of per swing;
save-format update with round-trip test migration.
3. Adopt the house Go linting standards: copy .golangci.yml from the
prompts repo and bring game/, term/, and cmd/ lint-clean (the port
is greenfield code, so no exemptions apply).
4. Refactor step 4: method renames, movement/world subsystem
1. Refactor step 4: method renames, movement/world subsystem
(doMove→moveHero, beTrapped→springTrap, rndmove→randomStep,
doRooms/doPassages/doMaze→digRooms/digPassages/digMaze,
chgStr→changeStrength, ...); remove the goto/label flows in doMove,
dispatch, and saveGame in favor of loops and helpers.
5. Refactor step 5: method renames, items/combat/UI subsystems
2. Refactor step 5: method renames, items/combat/UI subsystems
(invName→inventoryName, rollEm→rollAttacks, doPot→applyPotionFuse,
getItem→promptPackItem returning (obj, ok), getDir→promptDirection);
int status codes (attack returning -1) become named results. Two or
three commits, one subsystem each.
6. Refactor step 6: extract types from the god object — MessageLine
3. Refactor step 6: extract types from the god object — MessageLine
owns the msg/addmsg/endmsg machinery; pack/inventory operations move
onto *Player; monster/object list management and map queries
consolidate onto *Level; RogueGame keeps turn orchestration and
cross-system effects only.
7. Refactor step 7: effects dispatch — the giant quaff/readScroll/doZap
4. Refactor step 7: effects dispatch — the giant quaff/readScroll/doZap
switches become per-kind handler tables of small named methods,
keeping effect order and RNG call sequence identical.
8. Refactor step 8: constructor and style pass per the house
5. Refactor step 8: constructor and style pass per the house
styleguide — game.New(game.Params{...}) replacing NewGame(Config);
replace the gameEnd panic unwind with error-based turn results where
feasible; 77-column wrap sweep.
9. Docs refresh: update ARCHITECTURE.md Part 2 and README.md for the
6. Docs refresh: update ARCHITECTURE.md Part 2 and README.md for the
post-refactor names; add the C name → Go name rename table.
10. Playtest hardening pass: play several full games with the tcell
7. Playtest hardening pass: play several full games with the tcell
binary and extend run_test.go to script a deeper multi-level
playthrough (descend past level 5, use potions, scrolls, zapping,
save/restore). Fix any panics, message mismatches, or divergences
from the C behavior that this uncovers, with regression tests.
11. Verify the seed-compatibility claim against the C reference on
8. Verify the seed-compatibility claim against the C reference on
c-master: same seed, same dungeon, same item tables, for several
seeds.
12. Broaden unit test coverage where playtesting finds thin spots
9. Broaden unit test coverage where playtesting finds thin spots
(rings, sticks, wizard commands).
13. Tag a release once a full game (Amulet retrieval and score entry)
10. Tag a release once a full game (Amulet retrieval and score entry)
completes without defects.
14. Full-terminal-size support (deferred by explicit decision
11. Full-terminal-size support (deferred by explicit decision
2026-07-06): per-game dungeon dimensions instead of the 80x24
constants; open design questions are resize policy, gameplay
tuning at larger sizes, and a --classic 80x24 mode.
15. Note: this repo is exempt from the standard policy scaffold. Do not
12. Note: this repo is exempt from the standard policy scaffold. Do not
add Makefile, Dockerfile, or REPO_POLICIES.md.

View File

@@ -13,8 +13,8 @@ import (
"syscall"
"time"
"sneak.berlin/go/rogue/game"
"sneak.berlin/go/rogue/term"
"git.eeqj.de/sneak/rgoue/game"
"git.eeqj.de/sneak/rgoue/term"
)
func main() {

View File

@@ -5,7 +5,7 @@ package game
// wear lets the player put armor on (armor.c wear).
func (g *RogueGame) wear() {
p := &g.Player
obj := g.getItem("wear", int(Armor))
obj := g.getItem("wear", KindArmor)
if obj == nil {
return
}
@@ -18,12 +18,12 @@ func (g *RogueGame) wear() {
g.After = false
return
}
if obj.Type != Armor {
if obj.Kind != KindArmor {
g.msg("you can't wear that")
return
}
g.wasteTime()
obj.Flags.Set(IsKnow)
obj.Flags.Set(Known)
sp := g.invName(obj, true)
p.CurArmor = obj
if !g.Options.Terse {

View File

@@ -9,19 +9,19 @@ const dragonShot = 5
func (g *RogueGame) runners(int) {
list := append([]*Monster(nil), g.Level.Monsters...)
for _, tp := range list {
if !tp.On(IsHeld) && tp.On(IsRun) {
if !tp.On(Held) && tp.On(Awake) {
origPos := tp.Pos
wastarget := tp.On(IsTarget)
wastarget := tp.On(Targeted)
if g.moveMonst(tp) == -1 {
continue
}
if tp.On(IsFly) && distCp(g.Player.Pos, tp.Pos) >= 3 {
if tp.On(Flying) && distCp(g.Player.Pos, tp.Pos) >= 3 {
if g.moveMonst(tp) == -1 {
continue
}
}
if wastarget && origPos != tp.Pos {
tp.Flags.Clear(IsTarget)
tp.Flags.Clear(Targeted)
g.ToDeath = false
}
}
@@ -35,12 +35,12 @@ func (g *RogueGame) runners(int) {
// moveMonst executes a single turn of running for a monster (chase.c
// move_monst). Returns -1 if the monster died or left the level.
func (g *RogueGame) moveMonst(tp *Monster) int {
if !tp.On(IsSlow) || tp.Turn {
if !tp.On(Slowed) || tp.Turn {
if g.doChase(tp) == -1 {
return -1
}
}
if tp.On(IsHaste) {
if tp.On(Hasted) {
if g.doChase(tp) == -1 {
return -1
}
@@ -68,7 +68,7 @@ func (g *RogueGame) relocate(th *Monster, newLoc Coord) {
g.move(newLoc.Y, newLoc.X)
if g.seeMonst(th) {
g.addch(th.Disguise)
} else if g.Player.On(SeeMonst) {
} else if g.Player.On(SenseMonsters) {
g.standout()
g.addch(th.Type)
g.standend()
@@ -83,7 +83,7 @@ func (g *RogueGame) doChase(th *Monster) int {
mindist := 32767
rer := th.Room // find room of chaser
if th.On(IsGreed) && rer.GoldVal == 0 {
if th.On(Greedy) && rer.GoldVal == 0 {
th.Dest = &p.Pos // if gold has been taken, run after hero
}
var ree *Room // find room of chasee
@@ -108,7 +108,7 @@ over:
}
}
if door {
rer = &g.Level.Passages[*g.Level.FlagsAt(th.Pos.Y, th.Pos.X)&FPNum]
rer = &g.Level.Passages[*g.Level.FlagsAt(th.Pos.Y, th.Pos.X)&FPassNum]
door = false
goto over
}
@@ -120,7 +120,7 @@ over:
if th.Type == 'D' && (th.Pos.Y == p.Pos.Y || th.Pos.X == p.Pos.X ||
abs(th.Pos.Y-p.Pos.Y) == abs(th.Pos.X-p.Pos.X)) &&
distCp(th.Pos, p.Pos) <= BoltLength*BoltLength &&
!th.On(IsCanc) && g.rnd(dragonShot) == 0 {
!th.On(Cancelled) && g.rnd(dragonShot) == 0 {
g.Delta.Y = sign(p.Pos.Y - th.Pos.Y)
g.Delta.X = sign(p.Pos.X - th.Pos.X)
if g.HasHit {
@@ -130,7 +130,7 @@ over:
g.Running = false
g.Count = 0
g.Quiet = 0
if g.ToDeath && !th.On(IsTarget) {
if g.ToDeath && !th.On(Targeted) {
g.ToDeath = false
g.Kamikaze = false
}
@@ -147,7 +147,7 @@ over:
if th.Dest == &obj.Pos {
detachObj(&g.Level.Objects, obj)
attachObj(&th.Pack, obj)
if th.Room.Flags.Has(IsGone) {
if th.Room.Flags.Has(Gone) {
g.Level.SetChar(obj.Pos.Y, obj.Pos.X, Passage)
} else {
g.Level.SetChar(obj.Pos.Y, obj.Pos.X, Floor)
@@ -168,7 +168,7 @@ over:
g.relocate(th, g.chRet)
// And stop running if need be
if stoprun && th.Pos == *th.Dest {
th.Flags.Clear(IsRun)
th.Flags.Clear(Awake)
}
return 0
}
@@ -185,14 +185,14 @@ func (g *RogueGame) chase(tp *Monster, ee Coord) bool {
// If the thing is confused, let it move randomly. Invisible Stalkers
// are slightly confused all of the time, and bats are quite confused
// all the time
if (tp.On(IsHuh) && g.rnd(5) != 0) || (tp.Type == 'P' && g.rnd(5) == 0) ||
if (tp.On(Confused) && g.rnd(5) != 0) || (tp.Type == 'P' && g.rnd(5) == 0) ||
(tp.Type == 'B' && g.rnd(2) == 0) {
// get a valid random move
g.chRet = g.rndmove(&tp.Creature)
curdist = distCp(g.chRet, ee)
// Small chance that it will become un-confused
if g.rnd(20) == 0 {
tp.Flags.Clear(IsHuh)
tp.Flags.Clear(Confused)
}
} else {
// Otherwise, find the empty spot next to the chaser that is
@@ -233,7 +233,7 @@ func (g *RogueGame) chase(tp *Monster, ee Coord) bool {
break
}
}
if found != nil && found.Which == SScare {
if found != nil && found.ScrollKind() == ScrollScareMonster {
continue
}
}
@@ -268,8 +268,8 @@ func (g *RogueGame) setOldch(tp *Monster, cp Coord) {
}
sch := tp.OldCh
tp.OldCh = g.mvinch(cp.Y, cp.X)
if !g.Player.On(IsBlind) {
if (sch == Floor || tp.OldCh == Floor) && tp.Room.Flags.Has(IsDark) {
if !g.Player.On(Blind) {
if (sch == Floor || tp.OldCh == Floor) && tp.Room.Flags.Has(Dark) {
tp.OldCh = ' '
} else if distCp(cp, g.Player.Pos) <= LampDist && g.Options.SeeFloor {
tp.OldCh = g.Level.Char(cp.Y, cp.X)
@@ -281,10 +281,10 @@ func (g *RogueGame) setOldch(tp *Monster, cp Coord) {
// see_monst).
func (g *RogueGame) seeMonst(mp *Monster) bool {
p := &g.Player
if p.On(IsBlind) {
if p.On(Blind) {
return false
}
if mp.On(IsInvis) && !p.On(CanSee) {
if mp.On(Invisible) && !p.On(CanSeeInvisible) {
return false
}
y, x := mp.Pos.Y, mp.Pos.X
@@ -298,7 +298,7 @@ func (g *RogueGame) seeMonst(mp *Monster) bool {
if mp.Room != p.Room {
return false
}
return !mp.Room.Flags.Has(IsDark)
return !mp.Room.Flags.Has(Dark)
}
// runto sets a monster running after the hero (chase.c runto).
@@ -308,8 +308,8 @@ func (g *RogueGame) runto(runner Coord) {
return
}
// Start the beastie running
tp.Flags.Set(IsRun)
tp.Flags.Clear(IsHeld)
tp.Flags.Set(Awake)
tp.Flags.Clear(Held)
tp.Dest = g.findDest(tp)
}
@@ -317,8 +317,8 @@ func (g *RogueGame) runto(runner Coord) {
// any room (chase.c roomin).
func (g *RogueGame) roomin(cp Coord) *Room {
fp := *g.Level.FlagsAt(cp.Y, cp.X)
if fp.Has(FPass) {
return &g.Level.Passages[fp&FPNum]
if fp.Has(FPassage) {
return &g.Level.Passages[fp&FPassNum]
}
for i := range g.Level.Rooms {
@@ -348,11 +348,11 @@ func (g *RogueGame) diagOk(sp, ep Coord) bool {
// cansee).
func (g *RogueGame) cansee(y, x int) bool {
p := &g.Player
if p.On(IsBlind) {
if p.On(Blind) {
return false
}
if distance(y, x, p.Pos.Y, p.Pos.X) < LampDist {
if g.Level.FlagsAt(y, x).Has(FPass) {
if g.Level.FlagsAt(y, x).Has(FPassage) {
if y != p.Pos.Y && x != p.Pos.X &&
!stepOk(g.Level.Char(y, p.Pos.X)) &&
!stepOk(g.Level.Char(p.Pos.Y, x)) {
@@ -364,7 +364,7 @@ func (g *RogueGame) cansee(y, x int) bool {
// We can only see if the hero is in the same room as the coordinate
// and the room is lit, or if it is close.
rer := g.roomin(Coord{X: x, Y: y})
return rer == p.Room && !rer.Flags.Has(IsDark)
return rer == p.Room && !rer.Flags.Has(Dark)
}
// findDest finds the proper destination for the monster (chase.c
@@ -375,7 +375,7 @@ func (g *RogueGame) findDest(tp *Monster) *Coord {
return &g.Player.Pos
}
for _, obj := range g.Level.Objects {
if obj.Type == Scroll && obj.Which == SScare {
if obj.Kind == KindScroll && obj.ScrollKind() == ScrollScareMonster {
continue
}
if g.roomin(obj.Pos) == tp.Room && g.rnd(100) < prob {

View File

@@ -7,7 +7,7 @@ package game
func (g *RogueGame) command() {
p := &g.Player
ntimes := 1 // number of player moves
if p.On(IsHaste) {
if p.On(Hasted) {
ntimes++
}
// Let the daemons start up
@@ -21,7 +21,7 @@ func (g *RogueGame) command() {
}
// these are illegal things for the player to be, so if any are
// set, someone's been poking in memory
if p.On(IsSlow | IsGreed | IsInvis | IsRegen | IsTarget) {
if p.On(Slowed | Greedy | Invisible | Regenerates | Targeted) {
panic("player flags corrupted")
}
@@ -59,7 +59,7 @@ func (g *RogueGame) command() {
}
if g.NoCommand != 0 {
if g.NoCommand--; g.NoCommand == 0 {
p.Flags.Set(IsRun)
p.Flags.Set(Awake)
g.msg("you can move again")
}
} else {
@@ -121,14 +121,14 @@ func (g *RogueGame) command() {
}
g.DoDaemons(After)
g.DoFuses(After)
if p.IsRing(Left, RSearch) {
if p.IsRing(Left, RingSearching) {
g.search()
} else if p.IsRing(Left, RTeleport) && g.rnd(50) == 0 {
} else if p.IsRing(Left, RingTeleportation) && g.rnd(50) == 0 {
g.teleport()
}
if p.IsRing(Right, RSearch) {
if p.IsRing(Right, RingSearching) {
g.search()
} else if p.IsRing(Right, RTeleport) && g.rnd(50) == 0 {
} else if p.IsRing(Right, RingTeleportation) && g.rnd(50) == 0 {
g.teleport()
}
}
@@ -149,7 +149,7 @@ over:
}
if found != nil {
if !g.levitCheck() {
g.pickUp(found.Type)
g.pickUp(found.Kind.Glyph())
}
} else {
if !g.Options.Terse {
@@ -197,7 +197,7 @@ over:
g.doRun('n')
case CTRL('H'), CTRL('J'), CTRL('K'), CTRL('L'),
CTRL('Y'), CTRL('U'), CTRL('B'), CTRL('N'):
if !p.On(IsBlind) {
if !p.On(Blind) {
g.DoorStop = true
g.Firstmove = true
}
@@ -219,7 +219,7 @@ over:
g.Delta.Y += p.Pos.Y
g.Delta.X += p.Pos.X
mp := g.Level.MonsterAt(g.Delta.Y, g.Delta.X)
if mp == nil || (!g.seeMonst(mp) && !p.On(SeeMonst)) {
if mp == nil || (!g.seeMonst(mp) && !p.On(SenseMonsters)) {
if !g.Options.Terse {
g.addmsg("I see ")
}
@@ -228,7 +228,7 @@ over:
} else if g.diagOk(p.Pos, g.Delta) {
g.ToDeath = true
g.MaxHit = 0
mp.Flags.Set(IsTarget)
mp.Flags.Set(Targeted)
g.RunCh = g.DirCh
ch = g.DirCh
goto over
@@ -333,10 +333,10 @@ over:
}
if g.Level.Char(g.Delta.Y, g.Delta.X) != Trap {
g.msg("no trap there")
} else if p.On(IsHalu) {
g.msg("%s", trName[g.rnd(NTraps)])
} else if p.On(Hallucinating) {
g.msg("%s", trName[g.rnd(NumTrapTypes)])
} else {
g.msg("%s", trName[*fp&FTMask])
g.msg("%s", trName[*fp&FTrapMask])
fp.Set(FSeen)
}
}
@@ -407,10 +407,10 @@ func (g *RogueGame) wizardCommand(ch byte) {
case CTRL('C'):
g.addPass()
case CTRL('X'):
g.turnSee(p.On(SeeMonst))
g.turnSee(p.On(SenseMonsters))
case CTRL('~'):
if item := g.getItem("charge", int(Stick)); item != nil {
item.SetCharges(10000)
if item := g.getItem("charge", KindWand); item != nil {
item.Charges = 10000
}
case CTRL('I'):
for range 9 {
@@ -418,17 +418,17 @@ func (g *RogueGame) wizardCommand(ch byte) {
}
// Give him a sword (+1,+1)
obj := newObject()
g.initWeapon(obj, TwoSword)
g.initWeapon(obj, WeaponTwoHandedSword)
obj.HPlus = 1
obj.DPlus = 1
g.addPack(obj, true)
p.CurWeapon = obj
// And his suit of armor
obj = newObject()
obj.Type = Armor
obj.Which = PlateMail
obj.Arm = -5
obj.Flags.Set(IsKnow)
obj.Kind = KindArmor
obj.Which = int(ArmorPlateMail)
obj.ArmorClass = -5
obj.Flags.Set(Known)
obj.Count = 1
p.CurArmor = obj
g.addPack(obj, true)
@@ -453,10 +453,10 @@ func (g *RogueGame) search() {
ey := p.Pos.Y + 1
ex := p.Pos.X + 1
probinc := 0
if p.On(IsHalu) {
if p.On(Hallucinating) {
probinc = 3
}
if p.On(IsBlind) {
if p.On(Blind) {
probinc += 2
}
found := false
@@ -486,10 +486,10 @@ func (g *RogueGame) search() {
if !g.Options.Terse {
g.addmsg("you found ")
}
if p.On(IsHalu) {
g.msg("%s", trName[g.rnd(NTraps)])
if p.On(Hallucinating) {
g.msg("%s", trName[g.rnd(NumTrapTypes)])
} else {
g.msg("%s", trName[*fp&FTMask])
g.msg("%s", trName[*fp&FTrapMask])
fp.Set(FSeen)
}
foundone = true
@@ -661,7 +661,7 @@ func (g *RogueGame) uLevel() {
// levitCheck checks whether she's levitating, and if she is, prints an
// appropriate message (command.c levit_check).
func (g *RogueGame) levitCheck() bool {
if !g.Player.On(IsLevit) {
if !g.Player.On(Levitating) {
return false
}
g.msg("You can't. You're floating off the ground!")
@@ -671,7 +671,7 @@ func (g *RogueGame) levitCheck() bool {
// call allows a user to call a potion, scroll, or ring something
// (command.c call).
func (g *RogueGame) call() {
obj := g.getItem("call", Callable)
obj := g.getItem("call", KindCallable)
// Make certain that it is something that we want to name
if obj == nil {
return
@@ -681,20 +681,20 @@ func (g *RogueGame) call() {
var know *bool
var guess *string
it := &g.Items
switch obj.Type {
case Ring:
switch obj.Kind {
case KindRing:
op = &it.Rings[obj.Which]
elsewise = it.RingStones[obj.Which]
case Potion:
case KindPotion:
op = &it.Potions[obj.Which]
elsewise = it.PotColors[obj.Which]
case Scroll:
case KindScroll:
op = &it.Scrolls[obj.Which]
elsewise = it.ScrNames[obj.Which]
case Stick:
case KindWand:
op = &it.Sticks[obj.Which]
elsewise = it.WandMade[obj.Which]
case Food:
case KindFood:
g.msg("you can't call that anything")
return
default:

View File

@@ -40,12 +40,12 @@ type Player struct {
func (c *Creature) On(f CreatureFlags) bool { return c.Flags.Has(f) }
// IsRing is the C ISRING(hand, ring) macro.
func (p *Player) IsRing(hand, ring int) bool {
return p.CurRing[hand] != nil && p.CurRing[hand].Which == ring
func (p *Player) IsRing(hand int, ring RingKind) bool {
return p.CurRing[hand] != nil && p.CurRing[hand].RingKind() == ring
}
// IsWearing is the C ISWEARING(ring) macro: is the ring on either hand.
func (p *Player) IsWearing(ring int) bool {
func (p *Player) IsWearing(ring RingKind) bool {
return p.IsRing(Left, ring) || p.IsRing(Right, ring)
}

View File

@@ -53,10 +53,10 @@ func (g *RogueGame) doctor(int) {
} else if g.Quiet >= 3 {
p.Stats.HP += g.rnd(lv-7) + 1
}
if p.IsRing(Left, RRegen) {
if p.IsRing(Left, RingRegeneration) {
p.Stats.HP++
}
if p.IsRing(Right, RRegen) {
if p.IsRing(Right, RingRegeneration) {
p.Stats.HP++
}
if ohp != p.Stats.HP {
@@ -92,27 +92,27 @@ func wanderTime(g *RogueGame) int { return g.spread(70) }
// unconfuse releases the poor player from his confusion (daemons.c
// unconfuse).
func (g *RogueGame) unconfuse(int) {
g.Player.Flags.Clear(IsHuh)
g.Player.Flags.Clear(Confused)
g.msg("you feel less %s now", g.chooseStr("trippy", "confused"))
}
// unsee turns off the ability to see invisible (daemons.c unsee).
func (g *RogueGame) unsee(int) {
for _, th := range g.Level.Monsters {
if th.On(IsInvis) && g.seeMonst(th) {
if th.On(Invisible) && g.seeMonst(th) {
g.mvaddch(th.Pos.Y, th.Pos.X, th.OldCh)
}
}
g.Player.Flags.Clear(CanSee)
g.Player.Flags.Clear(CanSeeInvisible)
}
// sight gives the hero his sight back (daemons.c sight).
func (g *RogueGame) sight(int) {
p := &g.Player
if p.On(IsBlind) {
if p.On(Blind) {
g.Extinguish(DSight)
p.Flags.Clear(IsBlind)
if !p.Room.Flags.Has(IsGone) {
p.Flags.Clear(Blind)
if !p.Room.Flags.Has(Gone) {
g.enterRoom(p.Pos)
}
g.msg("%s", g.chooseStr("far out! Everything is all cosmic again",
@@ -122,7 +122,7 @@ func (g *RogueGame) sight(int) {
// nohaste ends the hasting (daemons.c nohaste).
func (g *RogueGame) nohaste(int) {
g.Player.Flags.Clear(IsHaste)
g.Player.Flags.Clear(Hasted)
g.msg("you feel yourself slowing down")
}
@@ -170,7 +170,7 @@ func (g *RogueGame) stomach(int) {
}
}
if p.HungryState != origHungry {
p.Flags.Clear(IsRun)
p.Flags.Clear(Awake)
g.Running = false
g.ToDeath = false
g.Count = 0
@@ -180,30 +180,30 @@ func (g *RogueGame) stomach(int) {
// comeDown takes the hero down off her acid trip (daemons.c come_down).
func (g *RogueGame) comeDown(int) {
p := &g.Player
if !p.On(IsHalu) {
if !p.On(Hallucinating) {
return
}
g.KillDaemon(DVisuals)
p.Flags.Clear(IsHalu)
p.Flags.Clear(Hallucinating)
if p.On(IsBlind) {
if p.On(Blind) {
return
}
// undo the things
for _, tp := range g.Level.Objects {
if g.cansee(tp.Pos.Y, tp.Pos.X) {
g.mvaddch(tp.Pos.Y, tp.Pos.X, tp.Type)
g.mvaddch(tp.Pos.Y, tp.Pos.X, tp.Kind.Glyph())
}
}
// undo the monsters
seemonst := p.On(SeeMonst)
seemonst := p.On(SenseMonsters)
for _, tp := range g.Level.Monsters {
g.move(tp.Pos.Y, tp.Pos.X)
if g.cansee(tp.Pos.Y, tp.Pos.X) {
if !tp.On(IsInvis) || p.On(CanSee) {
if !tp.On(Invisible) || p.On(CanSeeInvisible) {
g.addch(tp.Disguise)
} else {
g.addch(g.Level.Char(tp.Pos.Y, tp.Pos.X))
@@ -237,7 +237,7 @@ func (g *RogueGame) visuals(int) {
}
// change the monsters
seemonst := p.On(SeeMonst)
seemonst := p.On(SenseMonsters)
for _, tp := range g.Level.Monsters {
g.move(tp.Pos.Y, tp.Pos.X)
if g.seeMonst(tp) {
@@ -256,7 +256,7 @@ func (g *RogueGame) visuals(int) {
// land lands the hero from a levitation potion (daemons.c land).
func (g *RogueGame) land(int) {
g.Player.Flags.Clear(IsLevit)
g.Player.Flags.Clear(Levitating)
g.msg("%s", g.chooseStr("bummer! You've hit the ground",
"you float gently to the ground"))
}

58
game/dice.go Normal file
View File

@@ -0,0 +1,58 @@
package game
import (
"fmt"
"strings"
)
// Rogue expresses damage as strings like "1x4/3x6": one attack rolling
// 1d4 and a second rolling 3d6. The C code re-parsed these with atoi on
// every swing (fight.c roll_em); the port parses them once, at table
// definition or item creation, into a DiceSpec.
// DiceRoll is one attack's dice: Count rolls of a Sides-sided die.
type DiceRoll struct {
Count int
Sides int
}
// DiceSpec is the sequence of attacks a damage string described.
type DiceSpec []DiceRoll
// ParseDice parses a C damage string with the exact semantics of the
// roll_em loop: leading digits (C atoi) before and after each 'x', attacks
// separated by '/'. Junk like the bestiary's "%%%x0" placeholder parses as
// a single 0x0 attack, as it did in C.
func ParseDice(s string) DiceSpec {
var spec DiceSpec
for s != "" {
count := cAtoi(s)
xi := strings.IndexByte(s, 'x')
if xi < 0 {
break
}
s = s[xi+1:]
spec = append(spec, DiceRoll{Count: count, Sides: cAtoi(s)})
si := strings.IndexByte(s, '/')
if si < 0 {
break
}
s = s[si+1:]
}
return spec
}
// dice is the table-definition shorthand for ParseDice.
func dice(s string) DiceSpec { return ParseDice(s) }
// String renders the spec back in the classic "NxM/NxM" form.
func (d DiceSpec) String() string {
var sb strings.Builder
for i, r := range d {
if i > 0 {
sb.WriteByte('/')
}
fmt.Fprintf(&sb, "%dx%d", r.Count, r.Sides)
}
return sb.String()
}

45
game/dice_test.go Normal file
View File

@@ -0,0 +1,45 @@
package game
import "testing"
// ParseDice must keep the exact semantics of the C roll_em parse loop,
// including its junk-tolerant edges: the bestiary placeholder "%%%x0" and
// the flytrap reset "000x0" both mean a single 0x0 attack.
func TestParseDice(t *testing.T) {
cases := []struct {
in string
want string
len int
}{
{"1x4", "1x4", 1},
{"1x2/1x5/1x5", "1x2/1x5/1x5", 3},
{"2x12/2x4", "2x12/2x4", 2},
{"0x0", "0x0", 1},
{"000x0", "0x0", 1},
{"%%%x0", "0x0", 1},
{"", "", 0},
{"3", "", 0}, // no 'x': C parses nothing
}
for _, c := range cases {
got := ParseDice(c.in)
if len(got) != c.len || got.String() != c.want {
t.Errorf("ParseDice(%q) = %v (len %d), want %q (len %d)",
c.in, got, len(got), c.want, c.len)
}
}
}
// The bestiary and weapon tables must parse to at least one attack each so
// every creature and weapon actually swings.
func TestTablesHaveDice(t *testing.T) {
for i, m := range monsterTable {
if len(m.Stats.Dmg) == 0 {
t.Errorf("monster %c (%s) has no attacks", 'A'+i, m.Name)
}
}
for w, iw := range initWeaps {
if len(iw.dam) == 0 || len(iw.hrl) == 0 {
t.Errorf("weapon %v has empty dice", WeaponKind(w))
}
}
}

View File

@@ -22,8 +22,8 @@ func give(g *RogueGame, obj *Object) byte {
func TestQuaffHealingPotion(t *testing.T) {
g := mkGameInput(t, 5, "")
pot := newObject()
pot.Type = Potion
pot.Which = PHealing
pot.Kind = KindPotion
pot.Which = int(PotionHealing)
ch := give(g, pot)
g.scr.term.(*testTerm).input = []byte{ch}
@@ -32,7 +32,7 @@ func TestQuaffHealingPotion(t *testing.T) {
if g.Player.Stats.HP <= 1 {
t.Error("healing potion did not heal")
}
if !g.Items.Potions[PHealing].Know {
if !g.Items.Potions[PotionHealing].Know {
t.Error("healing potion not identified after drinking")
}
if len(g.Player.Pack) != 5 {
@@ -43,13 +43,13 @@ func TestQuaffHealingPotion(t *testing.T) {
func TestQuaffConfusionSetsFlagAndFuse(t *testing.T) {
g := mkGameInput(t, 5, "")
pot := newObject()
pot.Type = Potion
pot.Which = PConfuse
pot.Kind = KindPotion
pot.Which = int(PotionConfusion)
ch := give(g, pot)
g.scr.term.(*testTerm).input = []byte{ch}
g.quaff()
if !g.Player.On(IsHuh) {
if !g.Player.On(Confused) {
t.Error("confusion potion did not confuse")
}
if g.findSlot(DUnconfuse) == nil {
@@ -59,7 +59,7 @@ func TestQuaffConfusionSetsFlagAndFuse(t *testing.T) {
for range 30 {
g.DoFuses(After)
}
if g.Player.On(IsHuh) {
if g.Player.On(Confused) {
t.Error("confusion never wore off")
}
}
@@ -67,16 +67,16 @@ func TestQuaffConfusionSetsFlagAndFuse(t *testing.T) {
func TestReadEnchantArmor(t *testing.T) {
g := mkGameInput(t, 5, "")
scr := newObject()
scr.Type = Scroll
scr.Which = SArmor
scr.Kind = KindScroll
scr.Which = int(ScrollEnchantArmor)
ch := give(g, scr)
g.scr.term.(*testTerm).input = []byte{ch}
before := g.Player.CurArmor.Arm
before := g.Player.CurArmor.ArmorClass
g.readScroll()
if g.Player.CurArmor.Arm != before-1 {
if g.Player.CurArmor.ArmorClass != before-1 {
t.Errorf("enchant armor: AC %d -> %d, want %d",
before, g.Player.CurArmor.Arm, before-1)
before, g.Player.CurArmor.ArmorClass, before-1)
}
}
@@ -88,16 +88,16 @@ func TestReadHoldMonsterFreezesAdjacent(t *testing.T) {
// TestHoldScrollGreedyMonsterQuirk.
g := mkGameInput(t, 5, "")
tp := spawnAdjacent(g, 'Z')
tp.Flags.Set(IsRun)
tp.Flags.Set(Awake)
scr := newObject()
scr.Type = Scroll
scr.Which = SHold
scr.Kind = KindScroll
scr.Which = int(ScrollHoldMonster)
ch := give(g, scr)
g.scr.term.(*testTerm).input = []byte{ch}
g.readScroll()
t.Logf("after scroll: flags=%o huh=%q", tp.Flags, g.Msgs.Huh)
if tp.On(IsRun) || !tp.On(IsHeld) {
if tp.On(Awake) || !tp.On(Held) {
t.Error("hold monster scroll did not hold the adjacent monster")
}
}
@@ -109,20 +109,20 @@ func TestReadHoldMonsterFreezesAdjacent(t *testing.T) {
func TestHoldScrollGreedyMonsterQuirk(t *testing.T) {
g := mkGameInput(t, 5, "")
tp := spawnAdjacent(g, 'O')
tp.Flags.Set(IsRun)
tp.Flags.Set(Awake)
scr := newObject()
scr.Type = Scroll
scr.Which = SHold
scr.Kind = KindScroll
scr.Which = int(ScrollHoldMonster)
ch := give(g, scr)
g.scr.term.(*testTerm).input = []byte{ch}
g.readScroll()
t.Logf("orc after scroll: flags=%o (IsRun=%v IsHeld=%v)",
tp.Flags, tp.On(IsRun), tp.On(IsHeld))
if !tp.On(IsHeld) {
t.Error("orc lost IsHeld entirely")
t.Logf("orc after scroll: flags=%o (Awake=%v Held=%v)",
tp.Flags, tp.On(Awake), tp.On(Held))
if !tp.On(Held) {
t.Error("orc lost Held entirely")
}
if !tp.On(IsRun) {
if !tp.On(Awake) {
t.Error("quirk changed: greedy monster stayed held; if this is a " +
"deliberate fix, update this test and ARCHITECTURE.md")
}
@@ -132,19 +132,19 @@ func TestZapSlowMonster(t *testing.T) {
g := mkGameInput(t, 5, "")
tp := spawnAdjacent(g, 'Z')
stick := newObject()
stick.Type = Stick
stick.Which = WsSlowM
stick.Kind = KindWand
stick.Which = int(WandSlowMonster)
g.fixStick(stick)
ch := give(g, stick)
g.scr.term.(*testTerm).input = []byte{ch}
g.Delta = Coord{X: 1, Y: 0} // aim at the monster
charges := stick.Charges()
charges := stick.Charges
g.doZap()
if !tp.On(IsSlow) {
if !tp.On(Slowed) {
t.Error("slow monster wand did not slow")
}
if stick.Charges() != charges-1 {
if stick.Charges != charges-1 {
t.Error("zap did not use a charge")
}
}

View File

@@ -1,10 +1,6 @@
package game
import (
"fmt"
"strconv"
"strings"
)
import "strconv"
// fight.c — all the fighting gets done here.
@@ -48,14 +44,14 @@ var addDam = [32]int{
// setMname returns the monster name for the given monster (fight.c
// set_mname).
func (g *RogueGame) setMname(tp *Monster) string {
if !g.seeMonst(tp) && !g.Player.On(SeeMonst) {
if !g.seeMonst(tp) && !g.Player.On(SenseMonsters) {
if g.Options.Terse {
return "it"
}
return "something"
}
var mname string
if g.Player.On(IsHalu) {
if g.Player.On(Hallucinating) {
ch := int(g.mvinch(tp.Pos.Y, tp.Pos.X))
if !isUpper(byte(ch)) {
ch = g.rnd(26)
@@ -83,9 +79,9 @@ func (g *RogueGame) fight(mp Coord, weap *Object, thrown bool) bool {
g.Quiet = 0
g.runto(mp)
// Let him know it was really a xeroc (if it was one).
if tp.Type == 'X' && tp.Disguise != 'X' && !p.On(IsBlind) {
if tp.Type == 'X' && tp.Disguise != 'X' && !p.On(Blind) {
tp.Disguise = 'X'
if p.On(IsHalu) {
if p.On(Hallucinating) {
g.mvaddch(tp.Pos.Y, tp.Pos.X, byte(g.rnd(26)+'A'))
}
g.msg("%s", g.chooseStr("heavy! That's a nasty critter!",
@@ -104,17 +100,17 @@ func (g *RogueGame) fight(mp Coord, weap *Object, thrown bool) bool {
} else {
g.hit("", mname, g.Options.Terse)
}
if p.On(CanHuh) {
if p.On(CanConfuse) {
didHit = true
tp.Flags.Set(IsHuh)
p.Flags.Clear(CanHuh)
tp.Flags.Set(Confused)
p.Flags.Clear(CanConfuse)
g.endmsg()
g.HasHit = false
g.msg("your hands stop glowing %s", g.pickColor("red"))
}
if tp.Stats.HP <= 0 {
g.killed(tp, true)
} else if didHit && !p.On(IsBlind) {
} else if didHit && !p.On(Blind) {
g.msg("%s appears confused", mname)
}
didHit = true
@@ -137,13 +133,13 @@ func (g *RogueGame) attack(mp *Monster) int {
g.Running = false
g.Count = 0
g.Quiet = 0
if g.ToDeath && !mp.On(IsTarget) {
if g.ToDeath && !mp.On(Targeted) {
g.ToDeath = false
g.Kamikaze = false
}
if mp.Type == 'X' && mp.Disguise != 'X' && !p.On(IsBlind) {
if mp.Type == 'X' && mp.Disguise != 'X' && !p.On(Blind) {
mp.Disguise = 'X'
if p.On(IsHalu) {
if p.On(Hallucinating) {
g.mvaddch(mp.Pos.Y, mp.Pos.X, byte(g.rnd(26)+'A'))
}
}
@@ -171,14 +167,14 @@ func (g *RogueGame) attack(mp *Monster) int {
g.ToDeath = false
}
}
if !mp.On(IsCanc) {
if !mp.On(Cancelled) {
switch mp.Type {
case 'A':
// If an aquator hits, you can lose armor class.
g.rustArmor(p.CurArmor)
case 'I':
// The ice monster freezes you
p.Flags.Clear(IsRun)
p.Flags.Clear(Awake)
if g.NoCommand == 0 {
g.addmsg("you are frozen")
if !g.Options.Terse {
@@ -193,7 +189,7 @@ func (g *RogueGame) attack(mp *Monster) int {
case 'R':
// Rattlesnakes have poisonous bites
if !g.save(VsPoison) {
if !p.IsWearing(RSustStr) {
if !p.IsWearing(RingSustainStrength) {
g.chgStr(-1)
if !g.Options.Terse {
g.msg("you feel a bite in your leg and now feel weaker")
@@ -243,9 +239,9 @@ func (g *RogueGame) attack(mp *Monster) int {
}
case 'F':
// Venus Flytrap stops the poor guy from moving
p.Flags.Set(IsHeld)
p.Flags.Set(Held)
p.VfHit++
g.Monsters['F'-'A'].Stats.Dmg = fmt.Sprintf("%dx1", p.VfHit)
g.Monsters['F'-'A'].Stats.Dmg = DiceSpec{{Count: p.VfHit, Sides: 1}}
if p.Stats.HP--; p.Stats.HP <= 0 {
g.death('F')
}
@@ -322,76 +318,64 @@ func (g *RogueGame) rollEm(thatt, thdef *Creature, weap *Object, hurl bool) bool
p := &g.Player
att := &thatt.Stats
def := &thdef.Stats
var cp string
var attacks DiceSpec
var hplus, dplus int
if weap == nil {
cp = att.Dmg
attacks = att.Dmg
} else {
hplus = weap.HPlus
dplus = weap.DPlus
if weap == p.CurWeapon {
if p.IsRing(Left, RAddDam) {
dplus += p.CurRing[Left].Arm
} else if p.IsRing(Left, RAddHit) {
hplus += p.CurRing[Left].Arm
if p.IsRing(Left, RingIncreaseDamage) {
dplus += p.CurRing[Left].Bonus
} else if p.IsRing(Left, RingDexterity) {
hplus += p.CurRing[Left].Bonus
}
if p.IsRing(Right, RAddDam) {
dplus += p.CurRing[Right].Arm
} else if p.IsRing(Right, RAddHit) {
hplus += p.CurRing[Right].Arm
if p.IsRing(Right, RingIncreaseDamage) {
dplus += p.CurRing[Right].Bonus
} else if p.IsRing(Right, RingDexterity) {
hplus += p.CurRing[Right].Bonus
}
}
cp = weap.Damage
attacks = weap.Damage
if hurl {
if weap.Flags.Has(IsMissl) && p.CurWeapon != nil &&
p.CurWeapon.Which == weap.Launch {
cp = weap.HurlDmg
if weap.Flags.Has(Missile) && p.CurWeapon != nil &&
WeaponKind(p.CurWeapon.Which) == weap.Launch {
attacks = weap.HurlDmg
hplus += p.CurWeapon.HPlus
dplus += p.CurWeapon.DPlus
} else if weap.Launch < 0 {
cp = weap.HurlDmg
attacks = weap.HurlDmg
}
}
}
// If the creature being attacked is not running (asleep or held) then
// the attacker gets a plus four bonus to hit.
if !thdef.Flags.Has(IsRun) {
if !thdef.Flags.Has(Awake) {
hplus += 4
}
defArm := def.Arm
defArm := def.ArmorClass
if def == &p.Stats {
if p.CurArmor != nil {
defArm = p.CurArmor.Arm
defArm = p.CurArmor.ArmorClass
}
if p.IsRing(Left, RProtect) {
defArm -= p.CurRing[Left].Arm
if p.IsRing(Left, RingProtection) {
defArm -= p.CurRing[Left].Bonus
}
if p.IsRing(Right, RProtect) {
defArm -= p.CurRing[Right].Arm
if p.IsRing(Right, RingProtection) {
defArm -= p.CurRing[Right].Bonus
}
}
didHit := false
for cp != "" {
ndice := cAtoi(cp)
xi := strings.IndexByte(cp, 'x')
if xi < 0 {
break
}
cp = cp[xi+1:]
nsides := cAtoi(cp)
for _, atk := range attacks {
if g.swing(att.Lvl, defArm, hplus+strPlus[att.Str]) {
proll := g.roll(ndice, nsides)
proll := g.roll(atk.Count, atk.Sides)
damage := dplus + proll + addDam[att.Str]
if damage > 0 {
def.HP -= damage
}
didHit = true
}
si := strings.IndexByte(cp, '/')
if si < 0 {
break
}
cp = cp[si+1:]
}
return didHit
}
@@ -425,7 +409,7 @@ func (g *RogueGame) thunk(weap *Object, mname string, noend bool) {
if g.ToDeath {
return
}
if weap.Type == Weapon {
if weap.Kind == KindWeapon {
g.addmsg("the %s hits ", g.Items.Weapons[weap.Which].Name)
} else {
g.addmsg("you hit ")
@@ -488,7 +472,7 @@ func (g *RogueGame) bounce(weap *Object, mname string, noend bool) {
if g.ToDeath {
return
}
if weap.Type == Weapon {
if weap.Kind == KindWeapon {
g.addmsg("the %s misses ", g.Items.Weapons[weap.Which].Name)
} else {
g.addmsg("you missed ")
@@ -512,7 +496,7 @@ func (g *RogueGame) removeMon(mp Coord, tp *Monster, waskill bool) {
g.Level.SetMonsterAt(mp.Y, mp.X, nil)
g.mvaddch(mp.Y, mp.X, tp.OldCh)
detachMon(&g.Level.Monsters, tp)
if tp.On(IsTarget) {
if tp.On(Targeted) {
g.Kamikaze = false
g.ToDeath = false
if g.Options.FightFlush {
@@ -529,9 +513,9 @@ func (g *RogueGame) killed(tp *Monster, pr bool) {
// If the monster was a venus flytrap, un-hold him
switch tp.Type {
case 'F':
p.Flags.Clear(IsHeld)
p.Flags.Clear(Held)
p.VfHit = 0
g.Monsters['F'-'A'].Stats.Dmg = "000x0"
g.Monsters['F'-'A'].Stats.Dmg = dice("000x0")
case 'L':
pos, ok := g.fallpos(tp.Pos)
if ok {
@@ -539,10 +523,10 @@ func (g *RogueGame) killed(tp *Monster, pr bool) {
}
if ok && g.Depth >= g.MaxDepth {
gold := newObject()
gold.Type = Gold
gold.SetGoldVal(g.goldCalc())
gold.Kind = KindGold
gold.GoldValue = g.goldCalc()
if g.save(VsMagic) {
gold.Arm += g.goldCalc() + g.goldCalc() + g.goldCalc() + g.goldCalc()
gold.GoldValue += g.goldCalc() + g.goldCalc() + g.goldCalc() + g.goldCalc()
}
attachObj(&tp.Pack, gold)
}

View File

@@ -23,9 +23,9 @@ func spawnAdjacent(g *RogueGame, typ byte) *Monster {
func TestRollEmParsesMultiAttackDice(t *testing.T) {
g := mkGame(t, 42)
att := &Creature{Stats: Stats{Str: 16, Lvl: 20, Dmg: "1x4/1x4/1x4"}}
def := &Creature{Stats: Stats{Arm: 10, HP: 1000}}
def.Flags.Set(IsRun)
att := &Creature{Stats: Stats{Str: 16, Lvl: 20, Dmg: dice("1x4/1x4/1x4")}}
def := &Creature{Stats: Stats{ArmorClass: 10, HP: 1000}}
def.Flags.Set(Awake)
// With attacker level 20 vs armor 10, swing always hits
// (rnd(20)+wplus >= (20-20)-10 is always true), so three attacks of
// 1x4 + str bonus 1 each must deal between 6 and 15 damage.
@@ -60,7 +60,7 @@ func TestAttackHurtsPlayer(t *testing.T) {
g := mkGame(t, 9)
tp := spawnAdjacent(g, 'T') // troll: 1x8/1x8/2x6
tp.Stats.Lvl = 20 // always hits
tp.Flags.Clear(IsCanc)
tp.Flags.Clear(Cancelled)
hpBefore := g.Player.Stats.HP
g.Player.Stats.HP = 500
g.Player.Stats.MaxHP = 500
@@ -96,7 +96,7 @@ func TestRunnersChaseHero(t *testing.T) {
}
tp := &Monster{}
g.newMonster(tp, 'H', pos)
tp.Flags.Set(IsRun)
tp.Flags.Set(Awake)
tp.Dest = &p.Pos
d0 := distCp(tp.Pos, p.Pos)
g.runners(0)

View File

@@ -3,19 +3,19 @@ package game
// ItemLore is the per-game item identity state: the randomized appearance
// names and the seven mutable ObjInfo tables (extern.c/init.c).
type ItemLore struct {
PotColors [MaxPotions]string // p_colors: colors of the potions
ScrNames [MaxScrolls]string // s_names: names of the scrolls
RingStones [MaxRings]string // r_stones: stone settings of the rings
WandMade [MaxSticks]string // ws_made: what sticks are made of
WandType [MaxSticks]string // ws_type: "wand" or "staff"
PotColors [NumPotionTypes]string // p_colors: colors of the potions
ScrNames [NumScrollTypes]string // s_names: names of the scrolls
RingStones [NumRingTypes]string // r_stones: stone settings of the rings
WandMade [NumWandTypes]string // ws_made: what sticks are made of
WandType [NumWandTypes]string // ws_type: "wand" or "staff"
Things [NumThings]ObjInfo
Potions [MaxPotions]ObjInfo
Scrolls [MaxScrolls]ObjInfo
Rings [MaxRings]ObjInfo
Sticks [MaxSticks]ObjInfo
Weapons [MaxWeapons + 1]ObjInfo
Armors [MaxArmors]ObjInfo
Potions [NumPotionTypes]ObjInfo
Scrolls [NumScrollTypes]ObjInfo
Rings [NumRingTypes]ObjInfo
Sticks [NumWandTypes]ObjInfo
Weapons [NumWeaponTypes + 1]ObjInfo
Armors [NumArmorTypes]ObjInfo
Group int // group number for the next stack of missiles (weapons.c `group`)
}
@@ -168,7 +168,7 @@ func NewGame(cfg Config) *RogueGame {
g.FileName = cfg.Home + "/rogue.save"
g.rogueOpts = cfg.RogueOpts
if cfg.Wizard {
g.Player.Flags.Set(SeeMonst)
g.Player.Flags.Set(SenseMonsters)
}
if cfg.RogueOpts != "" {
g.ParseOpts(cfg.RogueOpts)
@@ -177,7 +177,7 @@ func NewGame(cfg Config) *RogueGame {
g.Monsters = monsterTable
g.Items.Group = 2 // weapons.c: int group = 2
for i := range g.Level.Passages {
g.Level.Passages[i].Flags = IsGone | IsDark
g.Level.Passages[i].Flags = Gone | Dark
}
g.initProbs() // set up prob tables for objects

View File

@@ -13,37 +13,37 @@ func (g *RogueGame) initPlayer() {
p.FoodLeft = HungerTime
// Give him some food
obj := newObject()
obj.Type = Food
obj.Kind = KindFood
obj.Count = 1
g.addPack(obj, true)
// And his suit of armor
obj = newObject()
obj.Type = Armor
obj.Which = RingMail
obj.Arm = aClass[RingMail] - 1
obj.Flags.Set(IsKnow)
obj.Kind = KindArmor
obj.Which = int(ArmorRingMail)
obj.ArmorClass = aClass[ArmorRingMail] - 1
obj.Flags.Set(Known)
obj.Count = 1
p.CurArmor = obj
g.addPack(obj, true)
// Give him his weaponry. First a mace.
obj = newObject()
g.initWeapon(obj, Mace)
g.initWeapon(obj, WeaponMace)
obj.HPlus = 1
obj.DPlus = 1
obj.Flags.Set(IsKnow)
obj.Flags.Set(Known)
g.addPack(obj, true)
p.CurWeapon = obj
// Now a +1 bow
obj = newObject()
g.initWeapon(obj, Bow)
g.initWeapon(obj, WeaponBow)
obj.HPlus = 1
obj.Flags.Set(IsKnow)
obj.Flags.Set(Known)
g.addPack(obj, true)
// Now some arrows
obj = newObject()
g.initWeapon(obj, Arrow)
g.initWeapon(obj, WeaponArrow)
obj.Count = g.rnd(15) + 25
obj.Flags.Set(IsKnow)
obj.Flags.Set(Known)
g.addPack(obj, true)
}
@@ -51,7 +51,7 @@ func (g *RogueGame) initPlayer() {
// (init.c init_colors).
func (g *RogueGame) initColors() {
used := make([]bool, len(rainbow))
for i := 0; i < MaxPotions; i++ {
for i := PotionKind(0); i < NumPotionTypes; i++ {
var j int
for {
j = g.rnd(len(rainbow))
@@ -66,7 +66,7 @@ func (g *RogueGame) initColors() {
// initNames generates the names of the various scrolls (init.c init_names).
func (g *RogueGame) initNames() {
for i := 0; i < MaxScrolls; i++ {
for i := ScrollKind(0); i < NumScrollTypes; i++ {
var cp strings.Builder
nwords := g.rnd(3) + 2
for ; nwords > 0; nwords-- {
@@ -88,7 +88,7 @@ func (g *RogueGame) initNames() {
// (init.c init_stones).
func (g *RogueGame) initStones() {
used := make([]bool, len(stoneTable))
for i := 0; i < MaxRings; i++ {
for i := RingKind(0); i < NumRingTypes; i++ {
var j int
for {
j = g.rnd(len(stoneTable))
@@ -107,7 +107,7 @@ func (g *RogueGame) initStones() {
func (g *RogueGame) initMaterials() {
used := make([]bool, len(woods))
metused := make([]bool, len(metals))
for i := 0; i < MaxSticks; i++ {
for i := WandKind(0); i < NumWandTypes; i++ {
var str string
for {
if g.rnd(2) == 0 {
@@ -156,14 +156,14 @@ func (g *RogueGame) initProbs() {
sumProbs(g.Items.Scrolls[:])
sumProbs(g.Items.Rings[:])
sumProbs(g.Items.Sticks[:])
sumProbs(g.Items.Weapons[:MaxWeapons]) // C sums MAXWEAPONS, excluding the flame entry
sumProbs(g.Items.Weapons[:NumWeaponTypes]) // C sums MAXWEAPONS, excluding the flame entry
sumProbs(g.Items.Armors[:])
}
// pickColor returns the given color, or a random one if the hero is
// hallucinating (init.c pick_color).
func (g *RogueGame) pickColor(col string) string {
if g.Player.On(IsHalu) {
if g.Player.On(Hallucinating) {
return rainbow[g.rnd(len(rainbow))]
}
return col

View File

@@ -142,9 +142,9 @@ func (g *RogueGame) status() {
p := &g.Player
// If nothing has changed since the last status, don't bother.
temp := p.Stats.Arm
temp := p.Stats.ArmorClass
if p.CurArmor != nil {
temp = p.CurArmor.Arm
temp = p.CurArmor.ArmorClass
}
if s.init && s.hp == p.Stats.HP && s.exp == p.Stats.Exp &&
s.pur == p.Purse && s.arm == temp && s.str == p.Stats.Str &&

View File

@@ -10,13 +10,13 @@ type Place struct {
// Level is the current dungeon level: the map and everything on it. It is
// reset in place by NewLevel, matching the C reuse of the global arrays.
type Level struct {
Places [MaxLines * MaxCols]Place
Rooms [MaxRooms]Room
Passages [MaxPass]Room // one pseudo-room per passage network
Objects []*Object // lvl_obj: objects on this level
Monsters []*Monster // mlist: monsters on the level
Stairs Coord // location of the staircase
NTraps int // number of traps on this level
Places [MaxLines * MaxCols]Place
Rooms [MaxRooms]Room
Passages [MaxPass]Room // one pseudo-room per passage network
Objects []*Object // lvl_obj: objects on this level
Monsters []*Monster // mlist: monsters on the level
Stairs Coord // location of the staircase
TrapCount int // number of traps on this level
}
// At returns the map cell at (y, x); the C INDEX(y,x) macro, including its

View File

@@ -36,7 +36,7 @@ func (g *RogueGame) look(wakeup bool) {
if x < 0 || x >= NumCols {
continue
}
if !p.On(IsBlind) {
if !p.On(Blind) {
if y == hero.Y && x == hero.X {
continue
}
@@ -49,11 +49,11 @@ func (g *RogueGame) look(wakeup bool) {
}
fp := &pp.Flags
if pch != Door && ch != Door {
if (pfl & FPass) != (*fp & FPass) {
if (pfl & FPassage) != (*fp & FPassage) {
continue
}
}
if (fp.Has(FPass) || ch == Door) && (pfl.Has(FPass) || pch == Door) {
if (fp.Has(FPassage) || ch == Door) && (pfl.Has(FPassage) || pch == Door) {
if hero.X != x && hero.Y != y &&
!stepOk(g.Level.Char(y, hero.X)) && !stepOk(g.Level.Char(hero.Y, x)) {
continue
@@ -63,7 +63,7 @@ func (g *RogueGame) look(wakeup bool) {
tp := pp.Monst
if tp == nil {
ch = g.tripCh(y, x, ch)
} else if p.On(SeeMonst) && tp.On(IsInvis) {
} else if p.On(SenseMonsters) && tp.On(Invisible) {
if g.DoorStop && !g.Firstmove {
g.Running = false
}
@@ -73,20 +73,20 @@ func (g *RogueGame) look(wakeup bool) {
g.wakeMonster(y, x)
}
if g.seeMonst(tp) {
if p.On(IsHalu) {
if p.On(Hallucinating) {
ch = byte(g.rnd(26) + 'A')
} else {
ch = tp.Disguise
}
}
}
if p.On(IsBlind) && (y != hero.Y || x != hero.X) {
if p.On(Blind) && (y != hero.Y || x != hero.X) {
continue
}
g.move(y, x)
if p.Room.Flags.Has(IsDark) && !g.Options.SeeFloor && ch == Floor {
if p.Room.Flags.Has(Dark) && !g.Options.SeeFloor && ch == Floor {
ch = ' '
}
@@ -156,7 +156,7 @@ func (g *RogueGame) look(wakeup bool) {
// tripCh returns the character for this space, taking into account whether
// or not the player is tripping (misc.c trip_ch).
func (g *RogueGame) tripCh(y, x int, ch byte) byte {
if g.Player.On(IsHalu) && g.After {
if g.Player.On(Hallucinating) && g.After {
switch ch {
case Floor, ' ', Passage, '-', '|', Door, Trap:
default:
@@ -171,8 +171,8 @@ func (g *RogueGame) tripCh(y, x int, ch byte) byte {
// eraseLamp erases the area shown by a lamp in a dark room
// (misc.c erase_lamp).
func (g *RogueGame) eraseLamp(pos Coord, rp *Room) {
if !(g.Options.SeeFloor && rp.Flags&(IsGone|IsDark) == IsDark &&
!g.Player.On(IsBlind)) {
if !(g.Options.SeeFloor && rp.Flags&(Gone|Dark) == Dark &&
!g.Player.On(Blind)) {
return
}
@@ -195,7 +195,7 @@ func (g *RogueGame) eraseLamp(pos Coord, rp *Room) {
// showFloor reports whether we show the floor in her room at this time
// (misc.c show_floor).
func (g *RogueGame) showFloor() bool {
if g.Player.Room.Flags&(IsGone|IsDark) == IsDark && !g.Player.On(IsBlind) {
if g.Player.Room.Flags&(Gone|Dark) == Dark && !g.Player.On(Blind) {
return g.Options.SeeFloor
}
return true
@@ -213,11 +213,11 @@ func (g *RogueGame) findObj(y, x int) *Object {
// eat lets her try to eat something (misc.c eat).
func (g *RogueGame) eat() {
obj := g.getItem("eat", int(Food))
obj := g.getItem("eat", KindFood)
if obj == nil {
return
}
if obj.Type != Food {
if obj.Kind != KindFood {
if !g.Options.Terse {
g.msg("ugh, you would get ill if you ate that")
} else {
@@ -278,11 +278,11 @@ func (g *RogueGame) chgStr(amt int) {
p := &g.Player
addStr(&p.Stats.Str, amt)
comp := p.Stats.Str
if p.IsRing(Left, RAddStr) {
addStr(&comp, -p.CurRing[Left].Arm)
if p.IsRing(Left, RingAddStrength) {
addStr(&comp, -p.CurRing[Left].Bonus)
}
if p.IsRing(Right, RAddStr) {
addStr(&comp, -p.CurRing[Right].Arm)
if p.IsRing(Right, RingAddStrength) {
addStr(&comp, -p.CurRing[Right].Bonus)
}
if comp > p.MaxStats.Str {
p.MaxStats.Str = comp
@@ -301,14 +301,14 @@ func addStr(sp *int, amt int) {
// addHaste adds a haste to the player (misc.c add_haste).
func (g *RogueGame) addHaste(potion bool) bool {
p := &g.Player
if p.On(IsHaste) {
if p.On(Hasted) {
g.NoCommand += g.rnd(8)
p.Flags.Clear(IsRun | IsHaste)
p.Flags.Clear(Awake | Hasted)
g.Extinguish(DNohaste)
g.msg("you faint from exhaustion")
return false
}
p.Flags.Set(IsHaste)
p.Flags.Set(Hasted)
if potion {
g.Fuse(DNohaste, 0, g.rnd(4)+4, After)
}
@@ -403,7 +403,7 @@ func (g *RogueGame) getDir() bool {
g.LastDir = g.DirCh
g.lastDelt = g.Delta
}
if g.Player.On(IsHuh) && g.rnd(5) == 0 {
if g.Player.On(Confused) && g.rnd(5) == 0 {
for {
g.Delta.Y = g.rnd(3) - 1
g.Delta.X = g.rnd(3) - 1
@@ -451,7 +451,7 @@ func (g *RogueGame) rndThing() byte {
// chooseStr picks the first or second string depending on whether the
// player is tripping (misc.c choose_str).
func (g *RogueGame) chooseStr(ts, ns string) string {
if g.Player.On(IsHalu) {
if g.Player.On(Hallucinating) {
return ts
}
return ns

View File

@@ -54,17 +54,17 @@ func (g *RogueGame) newMonster(tp *Monster, typ byte, cp Coord) {
tp.Stats.Lvl = mp.Stats.Lvl + levAdd
tp.Stats.MaxHP = g.roll(tp.Stats.Lvl, 8)
tp.Stats.HP = tp.Stats.MaxHP
tp.Stats.Arm = mp.Stats.Arm - levAdd
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(IsHaste)
tp.Flags.Set(Hasted)
}
tp.Turn = true
tp.Pack = nil
if g.Player.IsWearing(RAggr) {
if g.Player.IsWearing(RingAggravateMonsters) {
g.runto(cp)
}
if typ == 'X' {
@@ -101,9 +101,9 @@ func (g *RogueGame) wanderer() {
}
}
g.newMonster(tp, g.randMonster(true), cp)
if g.Player.On(SeeMonst) {
if g.Player.On(SenseMonsters) {
g.standout()
if !g.Player.On(IsHalu) {
if !g.Player.On(Hallucinating) {
g.addch(tp.Type)
} else {
g.addch(byte(g.rnd(26) + 'A'))
@@ -123,24 +123,24 @@ func (g *RogueGame) wakeMonster(y, x int) *Monster {
}
ch := tp.Type
// Every time he sees a mean monster, it might start chasing him
if !tp.On(IsRun) && g.rnd(3) != 0 && tp.On(IsMean) && !tp.On(IsHeld) &&
!p.IsWearing(RStealth) && !p.On(IsLevit) {
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(IsRun)
tp.Flags.Set(Awake)
}
if ch == 'M' && !p.On(IsBlind) && !p.On(IsHalu) &&
!tp.On(IsFound) && !tp.On(IsCanc) && tp.On(IsRun) {
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(IsDark)) ||
if (rp != nil && !rp.Flags.Has(Dark)) ||
distance(y, x, p.Pos.Y, p.Pos.X) < LampDist {
tp.Flags.Set(IsFound)
tp.Flags.Set(Found)
if !g.save(VsMagic) {
if p.On(IsHuh) {
if p.On(Confused) {
g.Lengthen(DUnconfuse, g.spread(HuhDuration))
} else {
g.Fuse(DUnconfuse, 0, g.spread(HuhDuration), After)
}
p.Flags.Set(IsHuh)
p.Flags.Set(Confused)
mname := g.setMname(tp)
g.addmsg("%s", mname)
if mname != "it" {
@@ -151,8 +151,8 @@ func (g *RogueGame) wakeMonster(y, x int) *Monster {
}
}
// Let greedy ones guard gold
if tp.On(IsGreed) && !tp.On(IsRun) {
tp.Flags.Set(IsRun)
if tp.On(Greedy) && !tp.On(Awake) {
tp.Flags.Set(Awake)
if p.Room.GoldVal != 0 {
tp.Dest = &p.Room.Gold
} else {
@@ -182,11 +182,11 @@ func (g *RogueGame) saveThrow(which int, st *Stats) bool {
func (g *RogueGame) save(which int) bool {
p := &g.Player
if which == VsMagic {
if p.IsRing(Left, RProtect) {
which -= p.CurRing[Left].Arm
if p.IsRing(Left, RingProtection) {
which -= p.CurRing[Left].Bonus
}
if p.IsRing(Right, RProtect) {
which -= p.CurRing[Right].Arm
if p.IsRing(Right, RingProtection) {
which -= p.CurRing[Right].Bonus
}
}
return g.saveThrow(which, &p.Stats)

View File

@@ -21,7 +21,7 @@ func (g *RogueGame) doMove(dy, dx int) {
}
// Do a confused move (maybe)
var nh Coord
if p.On(IsHuh) && g.rnd(5) != 0 {
if p.On(Confused) && g.rnd(5) != 0 {
nh = g.rndmove(&p.Creature)
if nh == p.Pos {
g.After = false
@@ -52,12 +52,12 @@ over:
fl = *g.Level.FlagsAt(nh.Y, nh.X)
ch = g.Level.VisibleChar(nh.Y, nh.X)
if !fl.Has(FReal) && ch == Floor {
if !p.On(IsLevit) {
if !p.On(Levitating) {
ch = Trap
g.Level.SetChar(nh.Y, nh.X, Trap)
g.Level.FlagsAt(nh.Y, nh.X).Set(FReal)
}
} else if p.On(IsHeld) && ch != 'F' {
} else if p.On(Held) && ch != 'F' {
g.msg("you are being held")
return
}
@@ -67,8 +67,8 @@ over:
}
switch ch {
case ' ', '|', '-':
if g.Options.PassGo && g.Running && p.Room.Flags.Has(IsGone) &&
!p.On(IsBlind) {
if g.Options.PassGo && g.Running && p.Room.Flags.Has(Gone) &&
!p.On(Blind) {
var b1, b2 bool
switch g.RunCh {
case 'h', 'l':
@@ -109,13 +109,13 @@ over:
g.After = false
case Door:
g.Running = false
if g.Level.FlagsAt(p.Pos.Y, p.Pos.X).Has(FPass) {
if g.Level.FlagsAt(p.Pos.Y, p.Pos.X).Has(FPassage) {
g.enterRoom(nh)
}
g.moveStuff(nh, fl)
case Trap:
tr := g.beTrapped(nh)
if tr == TDoor || tr == TTelep {
if tr == TrapDoor || tr == TrapTeleport {
return
}
g.moveStuff(nh, fl)
@@ -151,7 +151,7 @@ over:
func (g *RogueGame) moveStuff(nh Coord, fl PlaceFlags) {
p := &g.Player
g.mvaddch(p.Pos.Y, p.Pos.X, g.floorAt())
if fl.Has(FPass) && g.Level.Char(g.Oldpos.Y, g.Oldpos.X) == Door {
if fl.Has(FPassage) && g.Level.Char(g.Oldpos.Y, g.Oldpos.X) == Door {
g.leaveRoom(nh)
}
p.Pos = nh
@@ -161,7 +161,7 @@ func (g *RogueGame) moveStuff(nh Coord, fl PlaceFlags) {
// (move.c turn_ok).
func (g *RogueGame) turnOk(y, x int) bool {
pp := g.Level.At(y, x)
return pp.Ch == Door || pp.Flags&(FReal|FPass) == (FReal|FPass)
return pp.Ch == Door || pp.Flags&(FReal|FPassage) == (FReal|FPassage)
}
// turnref decides whether to refresh at a passage turning (move.c turnref).
@@ -179,7 +179,7 @@ func (g *RogueGame) turnref() {
// doorOpen is called to wake up things in a room that might move when the
// hero enters (move.c door_open).
func (g *RogueGame) doorOpen(rp *Room) {
if rp.Flags.Has(IsGone) {
if rp.Flags.Has(Gone) {
return
}
for y := rp.Pos.Y; y < rp.Pos.Y+rp.Max.Y; y++ {
@@ -192,26 +192,26 @@ func (g *RogueGame) doorOpen(rp *Room) {
}
// beTrapped makes him pay for stepping on a trap (move.c be_trapped).
func (g *RogueGame) beTrapped(tc Coord) int {
func (g *RogueGame) beTrapped(tc Coord) TrapKind {
p := &g.Player
if p.On(IsLevit) {
return TRust // anything that's not a door or teleport
if p.On(Levitating) {
return TrapRust // anything that's not a door or teleport
}
g.Running = false
g.Count = 0
pp := g.Level.At(tc.Y, tc.X)
pp.Ch = Trap
tr := int(pp.Flags & FTMask)
tr := TrapKind(pp.Flags & FTrapMask)
pp.Flags.Set(FSeen)
switch tr {
case TDoor:
case TrapDoor:
g.Depth++
g.NewLevel()
g.msg("you fell into a trap!")
case TBear:
case TrapBear:
g.NoMove += g.spread(3) // BEARTIME
g.msg("you are caught in a bear trap")
case TMyst:
case TrapMystery:
switch g.rnd(11) {
case 0:
g.msg("you are suddenly in a parallel dimension")
@@ -236,12 +236,12 @@ func (g *RogueGame) beTrapped(tc Coord) int {
case 10:
g.msg("you pack turns %s!", rainbow[g.rnd(len(rainbow))])
}
case TSleep:
case TrapSleep:
g.NoCommand += g.spread(5) // SLEEPTIME
p.Flags.Clear(IsRun)
p.Flags.Clear(Awake)
g.msg("a strange white mist envelops you and you fall asleep")
case TArrow:
if g.swing(p.Stats.Lvl-1, p.Stats.Arm, 1) {
case TrapArrow:
if g.swing(p.Stats.Lvl-1, p.Stats.ArmorClass, 1) {
p.Stats.HP -= g.roll(1, 6)
if p.Stats.HP <= 0 {
g.msg("an arrow killed you")
@@ -251,19 +251,19 @@ func (g *RogueGame) beTrapped(tc Coord) int {
}
} else {
arrow := newObject()
g.initWeapon(arrow, Arrow)
g.initWeapon(arrow, WeaponArrow)
arrow.Count = 1
arrow.Pos = p.Pos
g.fall(arrow, false)
g.msg("an arrow shoots past you")
}
case TTelep:
case TrapTeleport:
// since the hero's leaving, look() won't put a TRAP down for us,
// so we have to do it ourself
g.teleport()
g.mvaddch(tc.Y, tc.X, Trap)
case TDart:
if !g.swing(p.Stats.Lvl+1, p.Stats.Arm, 1) {
case TrapDart:
if !g.swing(p.Stats.Lvl+1, p.Stats.ArmorClass, 1) {
g.msg("a small dart whizzes by your ear and vanishes")
} else {
p.Stats.HP -= g.roll(1, 4)
@@ -271,12 +271,12 @@ func (g *RogueGame) beTrapped(tc Coord) int {
g.msg("a poisoned dart killed you")
g.death('d')
}
if !p.IsWearing(RSustStr) && !g.save(VsPoison) {
if !p.IsWearing(RingSustainStrength) && !g.save(VsPoison) {
g.chgStr(-1)
}
g.msg("a small dart just hit you in the shoulder")
}
case TRust:
case TrapRust:
g.msg("a gush of water hits you on the head")
g.rustArmor(p.CurArmor)
}
@@ -311,7 +311,7 @@ func (g *RogueGame) rndmove(who *Creature) Coord {
break
}
}
if found != nil && found.Which == SScare {
if found != nil && found.ScrollKind() == ScrollScareMonster {
return who.Pos
}
}
@@ -321,17 +321,17 @@ func (g *RogueGame) rndmove(who *Creature) Coord {
// rustArmor rusts the given armor, if it is a legal kind to rust, and we
// aren't wearing a magic ring (move.c rust_armor).
func (g *RogueGame) rustArmor(arm *Object) {
if arm == nil || arm.Type != Armor || arm.Which == Leather ||
arm.Arm >= 9 {
if arm == nil || arm.Kind != KindArmor || arm.ArmorKind() == ArmorLeather ||
arm.ArmorClass >= 9 {
return
}
if arm.Flags.Has(IsProt) || g.Player.IsWearing(RSustArm) {
if arm.Flags.Has(Protected) || g.Player.IsWearing(RingMaintainArmor) {
if !g.ToDeath {
g.msg("the rust vanishes instantly")
}
} else {
arm.Arm++
arm.ArmorClass++
if !g.Options.Terse {
g.msg("your armor appears to be weaker now. Oh my!")
} else {

View File

@@ -12,7 +12,7 @@ const (
// NewLevel digs and draws a new level (new_level.c new_level).
func (g *RogueGame) NewLevel() {
p := &g.Player
p.Flags.Clear(IsHeld) // unhold when you go down just in case
p.Flags.Clear(Held) // unhold when you go down just in case
if g.Depth > g.MaxDepth {
g.MaxDepth = g.Depth
}
@@ -31,11 +31,11 @@ func (g *RogueGame) NewLevel() {
g.putThings() // Place objects (if any)
// Place the traps
if g.rnd(10) < g.Depth {
g.Level.NTraps = g.rnd(g.Depth/4) + 1
if g.Level.NTraps > MaxTraps {
g.Level.NTraps = MaxTraps
g.Level.TrapCount = g.rnd(g.Depth/4) + 1
if g.Level.TrapCount > MaxTraps {
g.Level.TrapCount = MaxTraps
}
for i := g.Level.NTraps; i > 0; i-- {
for i := g.Level.TrapCount; i > 0; i-- {
// not only wouldn't it be NICE to have traps in mazes (not
// that we care about being nice), since the trap number is
// stored where the passage number is, we can't actually do it.
@@ -48,7 +48,7 @@ func (g *RogueGame) NewLevel() {
}
sp := g.Level.FlagsAt(stairs.Y, stairs.X)
sp.Clear(FReal)
*sp |= PlaceFlags(g.rnd(NTraps))
*sp |= PlaceFlags(g.rnd(NumTrapTypes))
}
}
// Place the staircase down.
@@ -65,10 +65,10 @@ func (g *RogueGame) NewLevel() {
p.Pos = hero
g.enterRoom(hero)
g.mvaddch(hero.Y, hero.X, PlayerCh)
if p.On(SeeMonst) {
if p.On(SenseMonsters) {
g.turnSee(false)
}
if p.On(IsHalu) {
if p.On(Hallucinating) {
g.visuals(0)
}
}
@@ -77,7 +77,7 @@ func (g *RogueGame) NewLevel() {
func (g *RogueGame) rndRoom() int {
for {
rm := g.rnd(MaxRooms)
if !g.Level.Rooms[rm].Flags.Has(IsGone) {
if !g.Level.Rooms[rm].Flags.Has(Gone) {
return rm
}
}
@@ -103,7 +103,7 @@ func (g *RogueGame) putThings() {
attachObj(&g.Level.Objects, obj)
// Put it somewhere
obj.Pos, _ = g.findFloor(nil, 0, false)
g.Level.SetChar(obj.Pos.Y, obj.Pos.X, obj.Type)
g.Level.SetChar(obj.Pos.Y, obj.Pos.X, obj.Kind.Glyph())
}
}
// If he is really deep in the dungeon and he hasn't found the amulet
@@ -111,10 +111,10 @@ func (g *RogueGame) putThings() {
if g.Depth >= AmuletLevel && !g.HasAmulet {
obj := newObject()
attachObj(&g.Level.Objects, obj)
obj.Damage = "0x0"
obj.HurlDmg = "0x0"
obj.Arm = 11
obj.Type = Amulet
obj.Damage = dice("0x0")
obj.HurlDmg = dice("0x0")
obj.ArmorClass = 11
obj.Kind = KindAmulet
// Put it somewhere
obj.Pos, _ = g.findFloor(nil, 0, false)
g.Level.SetChar(obj.Pos.Y, obj.Pos.X, Amulet)
@@ -134,7 +134,7 @@ func (g *RogueGame) treasRoom() {
tp := g.newThing()
tp.Pos = mp
attachObj(&g.Level.Objects, tp)
g.Level.SetChar(mp.Y, mp.X, tp.Type)
g.Level.SetChar(mp.Y, mp.X, tp.Kind.Glyph())
}
// fill up room with monsters from the next level down
@@ -151,7 +151,7 @@ func (g *RogueGame) treasRoom() {
if mp, ok := g.findFloorIn(rp, maxTries, true); ok {
tp := &Monster{}
g.newMonster(tp, g.randMonster(false), mp)
tp.Flags.Set(IsMean) // no sloughers in THIS room
tp.Flags.Set(Mean) // no sloughers in THIS room
g.givePack(tp)
}
}

View File

@@ -75,9 +75,10 @@ func TestNewLevelInvariants(t *testing.T) {
// share cells only with monsters standing on them).
for _, obj := range g.Level.Objects {
ch := g.Level.Char(obj.Pos.Y, obj.Pos.X)
if ch != obj.Type && g.Level.MonsterAt(obj.Pos.Y, obj.Pos.X) == nil {
t.Errorf("seed %d: object %q at (%d,%d) but map shows %q",
seed, obj.Type, obj.Pos.Y, obj.Pos.X, ch)
if ch != obj.Kind.Glyph() &&
g.Level.MonsterAt(obj.Pos.Y, obj.Pos.X) == nil {
t.Errorf("seed %d: object %v at (%d,%d) but map shows %q",
seed, obj.Kind, obj.Pos.Y, obj.Pos.X, ch)
}
}
@@ -86,10 +87,12 @@ func TestNewLevelInvariants(t *testing.T) {
t.Errorf("seed %d: starting pack has %d items, want 5",
seed, len(g.Player.Pack))
}
if g.Player.CurWeapon == nil || g.Player.CurWeapon.Which != Mace {
if g.Player.CurWeapon == nil ||
g.Player.CurWeapon.WeaponKind() != WeaponMace {
t.Errorf("seed %d: not wielding the starting mace", seed)
}
if g.Player.CurArmor == nil || g.Player.CurArmor.Which != RingMail {
if g.Player.CurArmor == nil ||
g.Player.CurArmor.ArmorKind() != ArmorRingMail {
t.Errorf("seed %d: not wearing the starting ring mail", seed)
}
}

View File

@@ -1,42 +1,142 @@
package game
// ObjectKind is the category of an item. In C this was the o_type byte,
// which doubled as the character drawn on the map; the port separates the
// category (ObjectKind) from its display character (Glyph).
type ObjectKind int
const (
KindNone ObjectKind = iota
KindPotion
KindScroll
KindFood
KindWeapon
KindArmor
KindRing
KindWand // a "stick": wand or staff
KindAmulet
KindGold
)
// Prompt-filter pseudo-kinds for item selection (rogue.h CALLABLE and
// R_OR_S): they never appear on an Object, only as getItem filters.
const (
KindCallable ObjectKind = -1
KindRingOrStick ObjectKind = -2
)
// kindGlyphs maps each kind to the character Rogue draws for it.
var kindGlyphs = [...]byte{
KindNone: ' ',
KindPotion: Potion,
KindScroll: Scroll,
KindFood: Food,
KindWeapon: Weapon,
KindArmor: Armor,
KindRing: Ring,
KindWand: Stick,
KindAmulet: Amulet,
KindGold: Gold,
}
// Glyph returns the map/display character for this kind of object.
func (k ObjectKind) Glyph() byte {
if k < 0 || int(k) >= len(kindGlyphs) {
return ' '
}
return kindGlyphs[k]
}
// String names the category the way the C type_name() did.
func (k ObjectKind) String() string {
switch k {
case KindPotion:
return "potion"
case KindScroll:
return "scroll"
case KindFood:
return "food"
case KindWeapon:
return "weapon"
case KindArmor:
return "suit of armor"
case KindRing:
return "ring"
case KindWand:
return "wand or staff"
case KindAmulet:
return "amulet"
case KindGold:
return "gold"
case KindRingOrStick:
return "ring, wand or staff"
}
return "bizarre thing"
}
// objectKindForGlyph is the reverse of Glyph: what category of item does a
// map character denote. Returns KindNone for non-item characters.
func objectKindForGlyph(ch byte) ObjectKind {
for k, g := range kindGlyphs {
if g == ch && ObjectKind(k) != KindNone {
return ObjectKind(k)
}
}
return KindNone
}
// MergesInPack reports whether picking up another of this kind merges into
// an existing pack entry's count (rogue.h ISMULT).
func (k ObjectKind) MergesInPack() bool {
return k == KindPotion || k == KindScroll || k == KindFood
}
// Object is the _o arm of the C THING union: anything that can lie on the
// floor or ride in a pack.
type Object struct {
Type byte // what kind of object it is (Potion, Scroll, Weapon, ...)
Pos Coord // where it lives on the screen
Text string // what it says if you read it
Launch int // what you need to launch it (weapon index, -1 none)
PackCh byte // what character it is in the pack
Damage string // damage if used like sword
HurlDmg string // damage if thrown
Count int // count for plural objects
Which int // which object of a type it is
HPlus int // plusses to hit
DPlus int // plusses to damage
Arm int // armor protection — overloaded as in C (see Charges/GoldVal)
Flags ObjFlags
Group int // group number for this object
Label string // label for object
Kind ObjectKind // what kind of object it is (o_type)
Pos Coord // where it lives on the screen
Text string // what it says if you read it
Launch WeaponKind // what you need to launch it (noWeapon if none)
PackCh byte // what character it is in the pack
Damage DiceSpec // damage if used like sword
HurlDmg DiceSpec // damage if thrown
Count int // count for plural objects
Which int // which object of a type it is (index for the Kind's table)
HPlus int // plusses to hit
DPlus int // plusses to damage
ArmorClass int // armor protection (armor only)
Charges int // charges remaining (wands and staffs only)
GoldValue int // worth (gold piles only)
Bonus int // magic bonus (rings only: protection, add strength, ...)
Flags ObjFlags
Group int // group number for this object
Label string // label for object
}
// newObject is list.c new_item() for objects: a zeroed Object with the
// same non-zero defaults the C code relies on.
func newObject() *Object {
return &Object{Launch: -1}
return &Object{Launch: noWeapon}
}
// Charges is the o_charges alias for Arm on wands and staffs.
func (o *Object) Charges() int { return o.Arm }
// PotionKind returns Which as a potion kind; valid only for KindPotion.
func (o *Object) PotionKind() PotionKind { return PotionKind(o.Which) }
// SetCharges stores a charge count in the overloaded Arm field.
func (o *Object) SetCharges(n int) { o.Arm = n }
// ScrollKind returns Which as a scroll kind; valid only for KindScroll.
func (o *Object) ScrollKind() ScrollKind { return ScrollKind(o.Which) }
// GoldVal is the o_goldval alias for Arm on gold piles.
func (o *Object) GoldVal() int { return o.Arm }
// RingKind returns Which as a ring kind; valid only for KindRing.
func (o *Object) RingKind() RingKind { return RingKind(o.Which) }
// SetGoldVal stores a gold value in the overloaded Arm field.
func (o *Object) SetGoldVal(n int) { o.Arm = n }
// WandKind returns Which as a wand kind; valid only for KindWand.
func (o *Object) WandKind() WandKind { return WandKind(o.Which) }
// WeaponKind returns Which as a weapon kind; valid only for KindWeapon.
func (o *Object) WeaponKind() WeaponKind { return WeaponKind(o.Which) }
// ArmorKind returns Which as an armor kind; valid only for KindArmor.
func (o *Object) ArmorKind() ArmorKind { return ArmorKind(o.Which) }
// attachObj is list.c attach(): push item onto the front of a list.
func attachObj(list *[]*Object, item *Object) {

View File

@@ -15,10 +15,10 @@ func (g *RogueGame) addPack(obj *Object, silent bool) {
}
// Check for and deal with scare monster scrolls
if obj.Type == Scroll && obj.Which == SScare && obj.Flags.Has(ObjIsFound) {
if obj.Kind == KindScroll && obj.ScrollKind() == ScrollScareMonster && obj.Flags.Has(WasFound) {
detachObj(&g.Level.Objects, obj)
g.mvaddch(p.Pos.Y, p.Pos.X, g.floorCh())
if p.Room.Flags.Has(IsGone) {
if p.Room.Flags.Has(Gone) {
g.Level.SetChar(p.Pos.Y, p.Pos.X, Passage)
} else {
g.Level.SetChar(p.Pos.Y, p.Pos.X, Floor)
@@ -39,12 +39,12 @@ func (g *RogueGame) addPack(obj *Object, silent bool) {
lp := -1
merged := false
for i := 0; i < len(p.Pack); i++ {
if p.Pack[i].Type != obj.Type {
if p.Pack[i].Kind != obj.Kind {
lp = i
continue
}
// found the group of our type: scan for matching subtype
for p.Pack[i].Type == obj.Type && p.Pack[i].Which != obj.Which {
for p.Pack[i].Kind == obj.Kind && p.Pack[i].Which != obj.Which {
lp = i
if i+1 >= len(p.Pack) {
break
@@ -52,8 +52,8 @@ func (g *RogueGame) addPack(obj *Object, silent bool) {
i++
}
op := p.Pack[i]
if op.Type == obj.Type && op.Which == obj.Which {
if IsMult(op.Type) {
if op.Kind == obj.Kind && op.Which == obj.Which {
if op.Kind.MergesInPack() {
if !g.packRoom(fromFloor, obj) {
return
}
@@ -63,7 +63,7 @@ func (g *RogueGame) addPack(obj *Object, silent bool) {
merged = true
} else if obj.Group != 0 {
lp = i
for p.Pack[i].Type == obj.Type &&
for p.Pack[i].Kind == obj.Kind &&
p.Pack[i].Which == obj.Which &&
p.Pack[i].Group != obj.Group {
lp = i
@@ -73,7 +73,7 @@ func (g *RogueGame) addPack(obj *Object, silent bool) {
i++
}
op = p.Pack[i]
if op.Type == obj.Type && op.Which == obj.Which &&
if op.Kind == obj.Kind && op.Which == obj.Which &&
op.Group == obj.Group {
op.Count += obj.Count
p.Inpack--
@@ -101,7 +101,7 @@ func (g *RogueGame) addPack(obj *Object, silent bool) {
}
}
obj.Flags.Set(ObjIsFound)
obj.Flags.Set(WasFound)
// If this was the object of something's desire, that monster will get
// mad and run at the hero.
@@ -111,7 +111,7 @@ func (g *RogueGame) addPack(obj *Object, silent bool) {
}
}
if obj.Type == Amulet {
if obj.Kind == KindAmulet {
g.HasAmulet = true
}
// Notify the user
@@ -146,7 +146,7 @@ func (g *RogueGame) packRoom(fromFloor bool, obj *Object) bool {
if fromFloor {
detachObj(&g.Level.Objects, obj)
g.mvaddch(p.Pos.Y, p.Pos.X, g.floorCh())
if p.Room.Flags.Has(IsGone) {
if p.Room.Flags.Has(Gone) {
g.Level.SetChar(p.Pos.Y, p.Pos.X, Passage)
} else {
g.Level.SetChar(p.Pos.Y, p.Pos.X, Floor)
@@ -193,12 +193,14 @@ func (g *RogueGame) packChar() byte {
// inventory lists what is in the pack; returns true if there is something
// of the given type (pack.c inventory).
func (g *RogueGame) inventory(list []*Object, typ int) bool {
func (g *RogueGame) inventory(list []*Object, kind ObjectKind) bool {
g.NObjs = 0
for _, item := range list {
if typ != 0 && typ != int(item.Type) &&
!(typ == Callable && item.Type != Food && item.Type != Amulet) &&
!(typ == RorS && (item.Type == Ring || item.Type == Stick)) {
if kind != KindNone && kind != item.Kind &&
!(kind == KindCallable && item.Kind != KindFood &&
item.Kind != KindAmulet) &&
!(kind == KindRingOrStick &&
(item.Kind == KindRing || item.Kind == KindWand)) {
continue
}
g.NObjs++
@@ -213,13 +215,13 @@ func (g *RogueGame) inventory(list []*Object, typ int) bool {
}
if g.NObjs == 0 {
if g.Options.Terse {
if typ == 0 {
if kind == KindNone {
g.msg("empty handed")
} else {
g.msg("nothing appropriate")
}
} else {
if typ == 0 {
if kind == KindNone {
g.msg("you are empty handed")
} else {
g.msg("you don't have anything appropriate")
@@ -234,7 +236,7 @@ func (g *RogueGame) inventory(list []*Object, typ int) bool {
// pickUp adds something to the character's pack (pack.c pick_up).
func (g *RogueGame) pickUp(ch byte) {
p := &g.Player
if p.On(IsLevit) {
if p.On(Levitating) {
return
}
@@ -248,7 +250,7 @@ func (g *RogueGame) pickUp(ch byte) {
if obj == nil {
return
}
g.money(obj.GoldVal())
g.money(obj.GoldValue)
detachObj(&g.Level.Objects, obj)
p.Room.GoldVal = 0
default:
@@ -294,7 +296,7 @@ func (g *RogueGame) pickyInven() {
}
// getItem picks something out of a pack for a purpose (pack.c get_item).
func (g *RogueGame) getItem(purpose string, typ int) *Object {
func (g *RogueGame) getItem(purpose string, kind ObjectKind) *Object {
p := &g.Player
if len(p.Pack) == 0 {
g.msg("you aren't carrying anything")
@@ -328,7 +330,7 @@ func (g *RogueGame) getItem(purpose string, typ int) *Object {
g.NObjs = 1 // normal case: person types one char
if ch == '*' {
g.Msgs.Mpos = 0
if !g.inventory(p.Pack, typ) {
if !g.inventory(p.Pack, kind) {
g.After = false
return nil
}
@@ -348,7 +350,7 @@ func (g *RogueGame) money(value int) {
p := &g.Player
p.Purse += value
g.mvaddch(p.Pos.Y, p.Pos.X, g.floorCh())
if p.Room.Flags.Has(IsGone) {
if p.Room.Flags.Has(Gone) {
g.Level.SetChar(p.Pos.Y, p.Pos.X, Passage)
} else {
g.Level.SetChar(p.Pos.Y, p.Pos.X, Floor)
@@ -364,7 +366,7 @@ func (g *RogueGame) money(value int) {
// floorCh returns the appropriate floor character for her room
// (pack.c floor_ch).
func (g *RogueGame) floorCh() byte {
if g.Player.Room.Flags.Has(IsGone) {
if g.Player.Room.Flags.Has(Gone) {
return Passage
}
if g.showFloor() {

View File

@@ -110,24 +110,24 @@ func (g *RogueGame) conn(r1, r2 int) {
var del, turnDelta, spos, epos Coord
var distance, turnDistance int
if direc == 'd' {
rmt := rm + 3 // room # of dest
rpt = &g.Level.Rooms[rmt] // room pointer of dest
del = Coord{X: 0, Y: 1} // direction of move
spos = rpf.Pos // start of move
epos = rpt.Pos // end of move
if !rpf.Flags.Has(IsGone) { // if not gone pick door pos
rmt := rm + 3 // room # of dest
rpt = &g.Level.Rooms[rmt] // room pointer of dest
del = Coord{X: 0, Y: 1} // direction of move
spos = rpf.Pos // start of move
epos = rpt.Pos // end of move
if !rpf.Flags.Has(Gone) { // if not gone pick door pos
for {
spos.X = rpf.Pos.X + g.rnd(rpf.Max.X-2) + 1
spos.Y = rpf.Pos.Y + rpf.Max.Y - 1
if !(rpf.Flags.Has(IsMaze) && !g.Level.FlagsAt(spos.Y, spos.X).Has(FPass)) {
if !(rpf.Flags.Has(Maze) && !g.Level.FlagsAt(spos.Y, spos.X).Has(FPassage)) {
break
}
}
}
if !rpt.Flags.Has(IsGone) {
if !rpt.Flags.Has(Gone) {
for {
epos.X = rpt.Pos.X + g.rnd(rpt.Max.X-2) + 1
if !(rpt.Flags.Has(IsMaze) && !g.Level.FlagsAt(epos.Y, epos.X).Has(FPass)) {
if !(rpt.Flags.Has(Maze) && !g.Level.FlagsAt(epos.Y, epos.X).Has(FPassage)) {
break
}
}
@@ -146,19 +146,19 @@ func (g *RogueGame) conn(r1, r2 int) {
del = Coord{X: 1, Y: 0}
spos = rpf.Pos
epos = rpt.Pos
if !rpf.Flags.Has(IsGone) {
if !rpf.Flags.Has(Gone) {
for {
spos.X = rpf.Pos.X + rpf.Max.X - 1
spos.Y = rpf.Pos.Y + g.rnd(rpf.Max.Y-2) + 1
if !(rpf.Flags.Has(IsMaze) && !g.Level.FlagsAt(spos.Y, spos.X).Has(FPass)) {
if !(rpf.Flags.Has(Maze) && !g.Level.FlagsAt(spos.Y, spos.X).Has(FPassage)) {
break
}
}
}
if !rpt.Flags.Has(IsGone) {
if !rpt.Flags.Has(Gone) {
for {
epos.Y = rpt.Pos.Y + g.rnd(rpt.Max.Y-2) + 1
if !(rpt.Flags.Has(IsMaze) && !g.Level.FlagsAt(epos.Y, epos.X).Has(FPass)) {
if !(rpt.Flags.Has(Maze) && !g.Level.FlagsAt(epos.Y, epos.X).Has(FPassage)) {
break
}
}
@@ -177,12 +177,12 @@ func (g *RogueGame) conn(r1, r2 int) {
// Draw in the doors on either side of the passage or just put #'s if
// the rooms are gone.
if !rpf.Flags.Has(IsGone) {
if !rpf.Flags.Has(Gone) {
g.door(rpf, spos)
} else {
g.putpass(spos)
}
if !rpt.Flags.Has(IsGone) {
if !rpt.Flags.Has(Gone) {
g.door(rpt, epos)
} else {
g.putpass(epos)
@@ -216,7 +216,7 @@ func (g *RogueGame) conn(r1, r2 int) {
// putpass).
func (g *RogueGame) putpass(cp Coord) {
pp := g.Level.At(cp.Y, cp.X)
pp.Flags.Set(FPass)
pp.Flags.Set(FPassage)
if g.rnd(10)+1 < g.Depth && g.rnd(40) == 0 {
pp.Flags.Clear(FReal)
} else {
@@ -229,7 +229,7 @@ func (g *RogueGame) putpass(cp Coord) {
func (g *RogueGame) door(rm *Room, cp Coord) {
rm.Exits = append(rm.Exits, cp)
if rm.Flags.Has(IsMaze) {
if rm.Flags.Has(Maze) {
return
}
@@ -252,10 +252,10 @@ func (g *RogueGame) addPass() {
for y := 1; y < NumLines-1; y++ {
for x := 0; x < NumCols; x++ {
pp := g.Level.At(y, x)
if pp.Flags.Has(FPass) || pp.Ch == Door ||
if pp.Flags.Has(FPassage) || pp.Ch == Door ||
(!pp.Flags.Has(FReal) && (pp.Ch == '|' || pp.Ch == '-')) {
ch := pp.Ch
if pp.Flags.Has(FPass) {
if pp.Flags.Has(FPassage) {
ch = Passage
}
pp.Flags.Set(FSeen)
@@ -266,7 +266,7 @@ func (g *RogueGame) addPass() {
g.addch(ch)
} else {
g.standout()
if pp.Flags.Has(FPass) {
if pp.Flags.Has(FPassage) {
g.addch(Passage)
} else {
g.addch(Door)
@@ -301,7 +301,7 @@ func (g *RogueGame) numpass(y, x int) {
return
}
fp := g.Level.FlagsAt(y, x)
if fp.Has(FPNum) {
if fp.Has(FPassNum) {
return
}
if g.newpnum {
@@ -314,7 +314,7 @@ func (g *RogueGame) numpass(y, x int) {
(!fp.Has(FReal) && (ch == '|' || ch == '-')) {
rp := &g.Level.Passages[g.pnum]
rp.Exits = append(rp.Exits, Coord{Y: y, X: x})
} else if !fp.Has(FPass) {
} else if !fp.Has(FPassage) {
return
}
*fp |= PlaceFlags(g.pnum)

View File

@@ -15,18 +15,18 @@ type pact struct {
// 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,
var pActions = [NumPotionTypes]pact{
PotionConfusion: {Confused, DUnconfuse, HuhDuration,
"what a tripy feeling!",
"wait, what's going on here. Huh? What? Who?"},
PLSD: {IsHalu, DComeDown, SeeDuration,
PotionLSD: {Hallucinating, DComeDown, SeeDuration,
"Oh, wow! Everything seems so cosmic!",
"Oh, wow! Everything seems so cosmic!"},
PSeeInvis: {CanSee, DUnsee, SeeDuration, "", ""},
PBlind: {IsBlind, DSight, SeeDuration,
PotionSeeInvisible: {CanSeeInvisible, DUnsee, SeeDuration, "", ""},
PotionBlindness: {Blind, DSight, SeeDuration,
"oh, bummer! Everything is dark! Help!",
"a cloak of darkness falls around you"},
PLevit: {IsLevit, DLand, HealTime,
PotionLevitation: {Levitating, DLand, HealTime,
"oh, wow! You're floating in the air!",
"you start to float in the air"},
}
@@ -34,12 +34,12 @@ var pActions = [MaxPotions]pact{
// quaff drinks a potion from the pack (potions.c quaff).
func (g *RogueGame) quaff() {
p := &g.Player
obj := g.getItem("quaff", int(Potion))
obj := g.getItem("quaff", KindPotion)
// Make certain that it is something that we want to drink
if obj == nil {
return
}
if obj.Type != Potion {
if obj.Kind != KindPotion {
if !g.Options.Terse {
g.msg("yuk! Why would you want to drink that?")
} else {
@@ -52,40 +52,40 @@ func (g *RogueGame) quaff() {
}
// Calculate the effect it has on the poor guy.
trip := p.On(IsHalu)
trip := p.On(Hallucinating)
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) {
switch obj.PotionKind() {
case PotionConfusion:
g.doPot(PotionConfusion, !trip)
case PotionPoison:
g.Items.Potions[PotionPoison].Know = true
if p.IsWearing(RingSustainStrength) {
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
case PotionHealing:
g.Items.Potions[PotionHealing].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
case PotionGainStrength:
g.Items.Potions[PotionGainStrength].Know = true
g.chgStr(1)
g.msg("you feel stronger, now. What bulging muscles!")
case PMFind:
p.Flags.Set(SeeMonst)
case PotionDetectMonsters:
p.Flags.Set(SenseMonsters)
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:
case PotionDetectMagic:
// Potion of magic detection. Show the potions and scrolls
show := false
if len(g.Level.Objects) > 0 {
@@ -94,7 +94,7 @@ func (g *RogueGame) quaff() {
if tp.isMagic() {
show = true
g.scr.Hw.MvAddCh(tp.Pos.Y, tp.Pos.X, Magic)
g.Items.Potions[PTFind].Know = true
g.Items.Potions[PotionDetectMagic].Know = true
}
}
for _, mp := range g.Level.Monsters {
@@ -107,34 +107,34 @@ func (g *RogueGame) quaff() {
}
}
if show {
g.Items.Potions[PTFind].Know = true
g.Items.Potions[PotionDetectMagic].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:
case PotionLSD:
if !trip {
if p.On(SeeMonst) {
if p.On(SenseMonsters) {
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)
g.doPot(PotionLSD, true)
case PotionSeeInvisible:
show := p.On(CanSeeInvisible)
g.doPot(PotionSeeInvisible, false)
if !show {
g.invisOn()
}
g.sight(0)
case PRaise:
g.Items.Potions[PRaise].Know = true
case PotionRaiseLevel:
g.Items.Potions[PotionRaiseLevel].Know = true
g.msg("you suddenly feel much more skillful")
g.raiseLevel()
case PXHeal:
g.Items.Potions[PXHeal].Know = true
case PotionExtraHealing:
g.Items.Potions[PotionExtraHealing].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++
@@ -145,33 +145,33 @@ func (g *RogueGame) quaff() {
g.sight(0)
g.comeDown(0)
g.msg("you begin to feel much better")
case PHaste:
g.Items.Potions[PHaste].Know = true
case PotionHaste:
g.Items.Potions[PotionHaste].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)
case PotionRestoreStrength:
if p.IsRing(Left, RingAddStrength) {
addStr(&p.Stats.Str, -p.CurRing[Left].Bonus)
}
if p.IsRing(Right, RAddStr) {
addStr(&p.Stats.Str, -p.CurRing[Right].Arm)
if p.IsRing(Right, RingAddStrength) {
addStr(&p.Stats.Str, -p.CurRing[Right].Bonus)
}
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(Left, RingAddStrength) {
addStr(&p.Stats.Str, p.CurRing[Left].Bonus)
}
if p.IsRing(Right, RAddStr) {
addStr(&p.Stats.Str, p.CurRing[Right].Arm)
if p.IsRing(Right, RingAddStrength) {
addStr(&p.Stats.Str, p.CurRing[Right].Bonus)
}
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)
case PotionBlindness:
g.doPot(PotionBlindness, true)
case PotionLevitation:
g.doPot(PotionLevitation, true)
}
g.status()
// Throw the item away
@@ -187,10 +187,10 @@ func (g *RogueGame) raiseLevel() {
// 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
func (g *RogueGame) doPot(kind PotionKind, knowit bool) {
pp := &pActions[kind]
if !g.Items.Potions[kind].Know {
g.Items.Potions[kind].Know = knowit
}
t := g.spread(pp.time)
if !g.Player.On(pp.flags) {
@@ -201,7 +201,7 @@ func (g *RogueGame) doPot(typ int, knowit bool) {
g.Lengthen(pp.daemon, t)
}
high, straight := pp.high, pp.straight
if typ == PSeeInvis {
if kind == PotionSeeInvisible {
s := fmt.Sprintf("this potion tastes like %s juice", g.Fruit)
high, straight = s, s
}
@@ -210,12 +210,12 @@ func (g *RogueGame) doPot(typ int, knowit bool) {
// 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:
switch o.Kind {
case KindArmor:
return o.Flags.Has(Protected) || o.ArmorClass != aClass[o.Which]
case KindWeapon:
return o.HPlus != 0 || o.DPlus != 0
case Potion, Scroll, Stick, Ring, Amulet:
case KindPotion, KindScroll, KindWand, KindRing, KindAmulet:
return true
}
return false
@@ -224,9 +224,9 @@ func (o *Object) isMagic() bool {
// invisOn turns on the ability to see invisible monsters (potions.c
// invis_on).
func (g *RogueGame) invisOn() {
g.Player.Flags.Set(CanSee)
g.Player.Flags.Set(CanSeeInvisible)
for _, mp := range g.Level.Monsters {
if mp.On(IsInvis) && g.seeMonst(mp) && !g.Player.On(IsHalu) {
if mp.On(Invisible) && g.seeMonst(mp) && !g.Player.On(Hallucinating) {
g.mvaddch(mp.Pos.Y, mp.Pos.X, mp.Disguise)
}
}
@@ -247,7 +247,7 @@ func (g *RogueGame) turnSee(turnOff bool) bool {
if !canSee {
g.standout()
}
if !g.Player.On(IsHalu) {
if !g.Player.On(Hallucinating) {
g.addch(mp.Type)
} else {
g.addch(byte(g.rnd(26) + 'A'))
@@ -259,9 +259,9 @@ func (g *RogueGame) turnSee(turnOff bool) bool {
}
}
if turnOff {
g.Player.Flags.Clear(SeeMonst)
g.Player.Flags.Clear(SenseMonsters)
} else {
g.Player.Flags.Set(SeeMonst)
g.Player.Flags.Set(SenseMonsters)
}
return addNew
}
@@ -279,10 +279,10 @@ func (g *RogueGame) seenStairs() bool {
}
// 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:
if g.seeMonst(tp) && tp.On(Awake) { // visible and awake:
return true // it must have moved there
}
if g.Player.On(SeeMonst) && tp.OldCh == Stairs {
if g.Player.On(SenseMonsters) && tp.OldCh == Stairs {
return true
}
}

View File

@@ -7,12 +7,12 @@ import "fmt"
// ringOn puts a ring on a hand (rings.c ring_on).
func (g *RogueGame) ringOn() {
p := &g.Player
obj := g.getItem("put on", int(Ring))
obj := g.getItem("put on", KindRing)
// Make certain that it is something that we want to wear
if obj == nil {
return
}
if obj.Type != Ring {
if obj.Kind != KindRing {
if !g.Options.Terse {
g.msg("it would be difficult to wrap that around a finger")
} else {
@@ -47,12 +47,12 @@ func (g *RogueGame) ringOn() {
p.CurRing[ring] = obj
// Calculate the effect it has on the poor guy.
switch obj.Which {
case RAddStr:
g.chgStr(obj.Arm)
case RSeeInvis:
switch obj.RingKind() {
case RingAddStrength:
g.chgStr(obj.Bonus)
case RingSeeInvisible:
g.invisOn()
case RAggr:
case RingAggravateMonsters:
g.aggravate()
}
@@ -123,7 +123,7 @@ func (g *RogueGame) gethand() int {
// ringUses is the rings.c ring_eat static uses[] table: how much food each
// ring type uses up per turn (negative = a 1-in-n chance of 1).
var ringUses = [MaxRings]int{
var ringUses = [NumRingTypes]int{
1, // R_PROTECT
1, // R_ADDSTR
1, // R_SUSTSTR
@@ -147,7 +147,7 @@ func (g *RogueGame) ringEat(hand int) int {
if ring == nil {
return 0
}
eat := ringUses[ring.Which]
eat := ringUses[ring.RingKind()]
if eat < 0 {
if g.rnd(-eat) == 0 {
eat = 1
@@ -155,7 +155,7 @@ func (g *RogueGame) ringEat(hand int) int {
eat = 0
}
}
if ring.Which == RDigest {
if ring.RingKind() == RingSlowDigestion {
eat = -eat
}
return eat
@@ -163,12 +163,12 @@ func (g *RogueGame) ringEat(hand int) int {
// ringNum prints ring bonuses (rings.c ring_num).
func ringNum(g *RogueGame, obj *Object) string {
if !obj.Flags.Has(IsKnow) {
if !obj.Flags.Has(Known) {
return ""
}
switch obj.Which {
case RProtect, RAddStr, RAddDam, RAddHit:
return fmt.Sprintf(" [%s]", num(obj.Arm, 0, Ring))
switch obj.RingKind() {
case RingProtection, RingAddStrength, RingIncreaseDamage, RingDexterity:
return fmt.Sprintf(" [%s]", num(obj.Bonus, 0, Ring))
}
return ""
}

View File

@@ -115,58 +115,58 @@ func (g *RogueGame) totalWinner() {
for _, obj := range p.Pack {
worth := 0
it := &g.Items
switch obj.Type {
case Food:
switch obj.Kind {
case KindFood:
worth = 2 * obj.Count
case Weapon:
case KindWeapon:
worth = it.Weapons[obj.Which].Worth
worth *= 3*(obj.HPlus+obj.DPlus) + obj.Count
obj.Flags.Set(IsKnow)
case Armor:
obj.Flags.Set(Known)
case KindArmor:
worth = it.Armors[obj.Which].Worth
worth += (9 - obj.Arm) * 100
worth += 10 * (aClass[obj.Which] - obj.Arm)
obj.Flags.Set(IsKnow)
case Scroll:
worth += (9 - obj.ArmorClass) * 100
worth += 10 * (aClass[obj.Which] - obj.ArmorClass)
obj.Flags.Set(Known)
case KindScroll:
op := &it.Scrolls[obj.Which]
worth = op.Worth * obj.Count
if !op.Know {
worth /= 2
}
op.Know = true
case Potion:
case KindPotion:
op := &it.Potions[obj.Which]
worth = op.Worth * obj.Count
if !op.Know {
worth /= 2
}
op.Know = true
case Ring:
case KindRing:
op := &it.Rings[obj.Which]
worth = op.Worth
if obj.Which == RAddStr || obj.Which == RAddDam ||
obj.Which == RProtect || obj.Which == RAddHit {
if obj.Arm > 0 {
worth += obj.Arm * 100
if obj.RingKind() == RingAddStrength || obj.RingKind() == RingIncreaseDamage ||
obj.RingKind() == RingProtection || obj.RingKind() == RingDexterity {
if obj.Bonus > 0 {
worth += obj.Bonus * 100
} else {
worth = 10
}
}
if !obj.Flags.Has(IsKnow) {
if !obj.Flags.Has(Known) {
worth /= 2
}
obj.Flags.Set(IsKnow)
obj.Flags.Set(Known)
op.Know = true
case Stick:
case KindWand:
op := &it.Sticks[obj.Which]
worth = op.Worth
worth += 20 * obj.Charges()
if !obj.Flags.Has(IsKnow) {
worth += 20 * obj.Charges
if !obj.Flags.Has(Known) {
worth /= 2
}
obj.Flags.Set(IsKnow)
obj.Flags.Set(Known)
op.Know = true
case Amulet:
case KindAmulet:
worth = 1000
}
if worth < 0 {

View File

@@ -33,14 +33,14 @@ func (g *RogueGame) doRooms() {
// Put the gone rooms, if any, on the level
leftOut := g.rnd(4)
for i := 0; i < leftOut; i++ {
g.Level.Rooms[g.rndRoom()].Flags.Set(IsGone)
g.Level.Rooms[g.rndRoom()].Flags.Set(Gone)
}
// dig and populate all the rooms on the level
for i := range g.Level.Rooms {
rp := &g.Level.Rooms[i]
// Find upper left corner of box that this room goes in
top := Coord{X: (i%3)*bsze.X + 1, Y: (i / 3) * bsze.Y}
if rp.Flags.Has(IsGone) {
if rp.Flags.Has(Gone) {
// Place a gone room. Make certain that there is a blank line
// for passage drawing.
for {
@@ -56,13 +56,13 @@ func (g *RogueGame) doRooms() {
}
// set room type
if g.rnd(10) < g.Depth-1 {
rp.Flags.Set(IsDark) // dark room
rp.Flags.Set(Dark) // dark room
if g.rnd(15) == 0 {
rp.Flags = IsMaze // maze room
rp.Flags = Maze // maze room
}
}
// Find a place and size for a random room
if rp.Flags.Has(IsMaze) {
if rp.Flags.Has(Maze) {
rp.Max.X = bsze.X - 1
rp.Max.Y = bsze.Y - 1
if rp.Pos.X = top.X; rp.Pos.X == 1 {
@@ -88,13 +88,13 @@ func (g *RogueGame) doRooms() {
if g.rnd(2) == 0 && (!g.HasAmulet || g.Depth >= g.MaxDepth) {
gold := newObject()
rp.GoldVal = g.goldCalc()
gold.SetGoldVal(rp.GoldVal)
gold.GoldValue = rp.GoldVal
rp.Gold, _ = g.findFloorIn(rp, 0, false)
gold.Pos = rp.Gold
g.Level.SetChar(rp.Gold.Y, rp.Gold.X, Gold)
gold.Flags = IsMany
gold.Flags = Stackable
gold.Group = goldGrp
gold.Type = Gold
gold.Kind = KindGold
attachObj(&g.Level.Objects, gold)
}
// Put the monster in
@@ -114,7 +114,7 @@ func (g *RogueGame) doRooms() {
// drawRoom draws a box around a room and lays down the floor for normal
// rooms; for maze rooms, draws the maze (rooms.c draw_room).
func (g *RogueGame) drawRoom(rp *Room) {
if rp.Flags.Has(IsMaze) {
if rp.Flags.Has(Maze) {
g.doMaze(rp)
return
}
@@ -180,7 +180,7 @@ func (g *RogueGame) dig(y, x int) {
if newy < 0 || newy > m.maxy || newx < 0 || newx > m.maxx {
continue
}
if g.Level.FlagsAt(newy+m.starty, newx+m.startx).Has(FPass) {
if g.Level.FlagsAt(newy+m.starty, newx+m.startx).Has(FPassage) {
continue
}
if cnt++; g.rnd(cnt) == 0 {
@@ -257,7 +257,7 @@ func (g *RogueGame) findFloorImpl(rp *Room, limit int, monst, pickroom bool) (Co
var compchar byte
if !pickroom {
compchar = Floor
if rp.Flags.Has(IsMaze) {
if rp.Flags.Has(Maze) {
compchar = Passage
}
}
@@ -271,7 +271,7 @@ func (g *RogueGame) findFloorImpl(rp *Room, limit int, monst, pickroom bool) (Co
if pickroom {
rp = &g.Level.Rooms[g.rndRoom()]
compchar = Floor
if rp.Flags.Has(IsMaze) {
if rp.Flags.Has(Maze) {
compchar = Passage
}
}
@@ -294,7 +294,7 @@ func (g *RogueGame) enterRoom(cp Coord) {
rp := g.roomin(cp)
p.Room = rp
g.doorOpen(rp)
if !rp.Flags.Has(IsDark) && !p.On(IsBlind) {
if !rp.Flags.Has(Dark) && !p.On(Blind) {
for y := rp.Pos.Y; y < rp.Max.Y+rp.Pos.Y; y++ {
g.move(y, rp.Pos.X)
for x := rp.Pos.X; x < rp.Max.X+rp.Pos.X; x++ {
@@ -309,7 +309,7 @@ func (g *RogueGame) enterRoom(cp Coord) {
} else {
tp.OldCh = ch
if !g.seeMonst(tp) {
if p.On(SeeMonst) {
if p.On(SenseMonsters) {
g.standout()
g.addch(tp.Disguise)
g.standend()
@@ -330,21 +330,21 @@ func (g *RogueGame) leaveRoom(cp Coord) {
p := &g.Player
rp := p.Room
if rp.Flags.Has(IsMaze) {
if rp.Flags.Has(Maze) {
return
}
var floor byte
switch {
case rp.Flags.Has(IsGone):
case rp.Flags.Has(Gone):
floor = Passage
case !rp.Flags.Has(IsDark) || p.On(IsBlind):
case !rp.Flags.Has(Dark) || p.On(Blind):
floor = Floor
default:
floor = ' '
}
p.Room = &g.Level.Passages[*g.Level.FlagsAt(cp.Y, cp.X)&FPNum]
p.Room = &g.Level.Passages[*g.Level.FlagsAt(cp.Y, cp.X)&FPassNum]
for y := rp.Pos.Y; y < rp.Max.Y+rp.Pos.Y; y++ {
for x := rp.Pos.X; x < rp.Max.X+rp.Pos.X; x++ {
g.move(y, x)
@@ -357,7 +357,7 @@ func (g *RogueGame) leaveRoom(cp Coord) {
// to check for monster, we have to strip out the standout
// bit (our Window returns the bare character already)
if isUpper(ch) {
if p.On(SeeMonst) {
if p.On(SenseMonsters) {
g.standout()
g.addch(ch)
g.standend()

View File

@@ -11,6 +11,13 @@ import (
// what state.c's rs_fix_thing did: pointers that alias other structures
// (equipment, chase targets, room membership) are encoded as indices.
// saveFormatVersion identifies the snapshot layout; it changes whenever a
// field rename or retype would make gob silently mis-decode an older
// save ("go2": Object.Type became Kind ObjectKind; "go3": Object.Arm split
// into ArmorClass/Charges/GoldValue/Bonus, damage strings became
// DiceSpec).
const saveFormatVersion = Release + "-go3"
// destRef encodes a monster's chase target (state.c rs_write_thing's
// (kind, index) pairs).
type destRef struct {
@@ -74,14 +81,14 @@ type SaveState struct {
HasAmulet bool
SeenStairs bool
Places []savedPlace
Rooms [MaxRooms]Room
Passages [MaxPass]Room
Objects []Object
Monsters []savedCreature
Dests []destRef // parallel to Monsters
Stairs Coord
NTraps int
Places []savedPlace
Rooms [MaxRooms]Room
Passages [MaxPass]Room
Objects []Object
Monsters []savedCreature
Dests []destRef // parallel to Monsters
Stairs Coord
TrapCount int
After bool
Again bool
@@ -165,7 +172,7 @@ func (g *RogueGame) packIdx(obj *Object) int {
func (g *RogueGame) snapshot() *SaveState {
p := &g.Player
st := &SaveState{
Version: Release,
Version: saveFormatVersion,
Seed: g.Rng.Seed,
Dnum: g.Dnum,
Whoami: g.Whoami,
@@ -179,7 +186,7 @@ func (g *RogueGame) snapshot() *SaveState {
Rooms: g.Level.Rooms,
Passages: g.Level.Passages,
Stairs: g.Level.Stairs,
NTraps: g.Level.NTraps,
TrapCount: g.Level.TrapCount,
After: g.After,
Again: g.Again,
NoScoreF: g.NoScore,
@@ -308,7 +315,7 @@ func (g *RogueGame) applySnapshot(st *SaveState) {
g.Level.Rooms = st.Rooms
g.Level.Passages = st.Passages
g.Level.Stairs = st.Stairs
g.Level.NTraps = st.NTraps
g.Level.TrapCount = st.TrapCount
g.After = st.After
g.Again = st.Again
g.NoScore = st.NoScoreF
@@ -530,7 +537,7 @@ func Restore(path string, cfg Config) (*RogueGame, error) {
if err := gob.NewDecoder(f).Decode(&st); err != nil {
return nil, fmt.Errorf("%s: corrupt or incompatible save file: %w", path, err)
}
if st.Version != Release {
if st.Version != saveFormatVersion {
return nil, fmt.Errorf("sorry, saved game is out of date")
}

View File

@@ -13,11 +13,11 @@ func TestSaveRestoreRoundTrip(t *testing.T) {
g.Player.Purse = 123
g.Player.FoodLeft = 777
g.HasAmulet = true
g.Items.Potions[PHealing].Know = true
g.Items.Scrolls[SMap].Guess = "map???"
g.Monsters['F'-'A'].Stats.Dmg = "3x1" // mutated bestiary must survive
g.Items.Potions[PotionHealing].Know = true
g.Items.Scrolls[ScrollMagicMapping].Guess = "map???"
g.Monsters['F'-'A'].Stats.Dmg = dice("3x1") // mutated bestiary must survive
if len(g.Level.Monsters) > 0 {
g.Level.Monsters[0].Flags.Set(IsRun)
g.Level.Monsters[0].Flags.Set(Awake)
g.Level.Monsters[0].Dest = &g.Player.Pos
}
@@ -42,13 +42,13 @@ func TestSaveRestoreRoundTrip(t *testing.T) {
if !h.HasAmulet {
t.Error("amulet flag lost")
}
if !h.Items.Potions[PHealing].Know {
if !h.Items.Potions[PotionHealing].Know {
t.Error("potion identification lost")
}
if h.Items.Scrolls[SMap].Guess != "map???" {
if h.Items.Scrolls[ScrollMagicMapping].Guess != "map???" {
t.Error("scroll guess lost")
}
if h.Monsters['F'-'A'].Stats.Dmg != "3x1" {
if h.Monsters['F'-'A'].Stats.Dmg.String() != "3x1" {
t.Error("mutated bestiary lost")
}
if h.Rng.Seed != g.Rng.Seed {
@@ -83,7 +83,7 @@ func TestSaveRestoreRoundTrip(t *testing.T) {
len(h.Player.Pack))
found := false
for i, o := range h.Player.Pack {
t.Logf(" pack[%d]=%p type=%c which=%d", i, o, o.Type, o.Which)
t.Logf(" pack[%d]=%p type=%v which=%d", i, o, o.Kind, o.Which)
if o == h.Player.CurWeapon {
found = true
}

View File

@@ -2,25 +2,25 @@ package game
// scrolls.c — read a scroll and let it happen.
// idType maps identify scrolls to the type they identify (scrolls.c
// static id_type).
var idType = [SIDRorS + 1]int{
SIDPotion: int(Potion),
SIDScroll: int(Scroll),
SIDWeapon: int(Weapon),
SIDArmor: int(Armor),
SIDRorS: RorS,
// idType maps identify scrolls to the kind of item they identify
// (scrolls.c static id_type).
var idType = [ScrollIdentifyRingOrStick + 1]ObjectKind{
ScrollIdentifyPotion: KindPotion,
ScrollIdentifyScroll: KindScroll,
ScrollIdentifyWeapon: KindWeapon,
ScrollIdentifyArmor: KindArmor,
ScrollIdentifyRingOrStick: KindRingOrStick,
}
// readScroll reads a scroll from the pack and does the appropriate thing
// (scrolls.c read_scroll).
func (g *RogueGame) readScroll() {
p := &g.Player
obj := g.getItem("read", int(Scroll))
obj := g.getItem("read", KindScroll)
if obj == nil {
return
}
if obj.Type != Scroll {
if obj.Kind != KindScroll {
if !g.Options.Terse {
g.msg("there is nothing on it to read")
} else {
@@ -35,18 +35,18 @@ func (g *RogueGame) readScroll() {
// Get rid of the thing
g.leavePack(obj, false, false)
switch obj.Which {
case SConfuse:
switch obj.ScrollKind() {
case ScrollMonsterConfusion:
// Scroll of monster confusion. Give him that power.
p.Flags.Set(CanHuh)
p.Flags.Set(CanConfuse)
g.msg("your hands begin to glow %s", g.pickColor("red"))
case SArmor:
case ScrollEnchantArmor:
if p.CurArmor != nil {
p.CurArmor.Arm--
p.CurArmor.Flags.Clear(IsCursed)
p.CurArmor.ArmorClass--
p.CurArmor.Flags.Clear(Cursed)
g.msg("your armor glows %s for a moment", g.pickColor("silver"))
}
case SHold:
case ScrollHoldMonster:
// Hold monster scroll. Stop all monsters within two spaces from
// chasing after the hero.
held := 0
@@ -58,9 +58,9 @@ func (g *RogueGame) readScroll() {
if y < 0 || y > NumLines-1 {
continue
}
if mp := g.Level.MonsterAt(y, x); mp != nil && mp.On(IsRun) {
mp.Flags.Clear(IsRun)
mp.Flags.Set(IsHeld)
if mp := g.Level.MonsterAt(y, x); mp != nil && mp.On(Awake) {
mp.Flags.Clear(Awake)
mp.Flags.Set(Held)
held++
}
}
@@ -75,17 +75,17 @@ func (g *RogueGame) readScroll() {
g.addmsg("s")
}
g.endmsg()
g.Items.Scrolls[SHold].Know = true
g.Items.Scrolls[ScrollHoldMonster].Know = true
} else {
g.msg("you feel a strange sense of loss")
}
case SSleep:
case ScrollSleep:
// Scroll which makes you fall asleep
g.Items.Scrolls[SSleep].Know = true
g.Items.Scrolls[ScrollSleep].Know = true
g.NoCommand += g.rnd(g.spread(5)) + 4 // SLEEPTIME
p.Flags.Clear(IsRun)
p.Flags.Clear(Awake)
g.msg("you fall asleep")
case SCreate:
case ScrollCreateMonster:
// Create a monster: first look in a circle around him, next try
// his room, otherwise give up
i := 0
@@ -99,7 +99,7 @@ func (g *RogueGame) readScroll() {
// Or anything else nasty
if ch := g.Level.VisibleChar(y, x); stepOk(ch) {
if ch == Scroll {
if fo := g.findObj(y, x); fo != nil && fo.Which == SScare {
if fo := g.findObj(y, x); fo != nil && fo.ScrollKind() == ScrollScareMonster {
continue
}
}
@@ -115,14 +115,14 @@ func (g *RogueGame) readScroll() {
tp := &Monster{}
g.newMonster(tp, g.randMonster(false), mp)
}
case SIDPotion, SIDScroll, SIDWeapon, SIDArmor, SIDRorS:
case ScrollIdentifyPotion, ScrollIdentifyScroll, ScrollIdentifyWeapon, ScrollIdentifyArmor, ScrollIdentifyRingOrStick:
// Identify, let him figure something out
g.Items.Scrolls[obj.Which].Know = true
g.msg("this scroll is an %s scroll", g.Items.Scrolls[obj.Which].Name)
g.whatis(true, idType[obj.Which])
case SMap:
g.whatis(true, idType[obj.ScrollKind()])
case ScrollMagicMapping:
// Scroll of magic mapping.
g.Items.Scrolls[SMap].Know = true
g.Items.Scrolls[ScrollMagicMapping].Know = true
g.msg("oh, now this scroll has a map on it")
// take all the things we want to keep hidden out of the window
for y := 1; y < NumLines-1; y++ {
@@ -141,7 +141,7 @@ func (g *RogueGame) readScroll() {
case ' ':
if pp.Flags.Has(FReal) {
// def: hidden things in walls stay hidden
if pp.Flags.Has(FPass) {
if pp.Flags.Has(FPassage) {
pass = true
} else {
ch = ' '
@@ -162,7 +162,7 @@ func (g *RogueGame) readScroll() {
pp.Flags.Set(FSeen | FReal)
}
default:
if pp.Flags.Has(FPass) {
if pp.Flags.Has(FPassage) {
pass = true
} else {
ch = ' '
@@ -178,7 +178,7 @@ func (g *RogueGame) readScroll() {
if ch != ' ' {
if tp := pp.Monst; tp != nil {
tp.OldCh = ch
if !p.On(SeeMonst) {
if !p.On(SenseMonsters) {
g.mvaddch(y, x, ch)
}
} else {
@@ -187,34 +187,34 @@ func (g *RogueGame) readScroll() {
}
}
}
case SFDet:
case ScrollFoodDetection:
// Food detection
found := false
g.scr.Hw.Clear()
for _, fo := range g.Level.Objects {
if fo.Type == Food {
if fo.Kind == KindFood {
found = true
g.scr.Hw.MvAddCh(fo.Pos.Y, fo.Pos.X, Food)
}
}
if found {
g.Items.Scrolls[SFDet].Know = true
g.Items.Scrolls[ScrollFoodDetection].Know = true
g.showWin("Your nose tingles and you smell food.--More--")
} else {
g.msg("your nose tingles")
}
case STelep:
case ScrollTeleportation:
// Scroll of teleportation: make him disappear and reappear
curRoom := p.Room
g.teleport()
if curRoom != p.Room {
g.Items.Scrolls[STelep].Know = true
g.Items.Scrolls[ScrollTeleportation].Know = true
}
case SEnch:
if p.CurWeapon == nil || p.CurWeapon.Type != Weapon {
case ScrollEnchantWeapon:
if p.CurWeapon == nil || p.CurWeapon.Kind != KindWeapon {
g.msg("you feel a strange sense of loss")
} else {
p.CurWeapon.Flags.Clear(IsCursed)
p.CurWeapon.Flags.Clear(Cursed)
if g.rnd(2) == 0 {
p.CurWeapon.HPlus++
} else {
@@ -223,25 +223,25 @@ func (g *RogueGame) readScroll() {
g.msg("your %s glows %s for a moment",
g.Items.Weapons[p.CurWeapon.Which].Name, g.pickColor("blue"))
}
case SScare:
case ScrollScareMonster:
// Reading it is a mistake and produces laughter at her poor boo
// boo.
g.msg("you hear maniacal laughter in the distance")
case SRemove:
case ScrollRemoveCurse:
uncurse(p.CurArmor)
uncurse(p.CurWeapon)
uncurse(p.CurRing[Left])
uncurse(p.CurRing[Right])
g.msg("%s", g.chooseStr("you feel in touch with the Universal Onenes",
"you feel as if somebody is watching over you"))
case SAggr:
case ScrollAggravateMonsters:
// This scroll aggravates all the monsters on the current level
// and sets them running towards the hero
g.aggravate()
g.msg("you hear a high pitched humming noise")
case SProtect:
case ScrollProtectArmor:
if p.CurArmor != nil {
p.CurArmor.Flags.Set(IsProt)
p.CurArmor.Flags.Set(Protected)
g.msg("your armor is covered by a shimmering %s shield",
g.pickColor("gold"))
} else {
@@ -257,6 +257,6 @@ func (g *RogueGame) readScroll() {
// uncurse uncurses an item (scrolls.c uncurse).
func uncurse(obj *Object) {
if obj != nil {
obj.Flags.Clear(IsCursed)
obj.Flags.Clear(Cursed)
}
}

View File

@@ -7,27 +7,27 @@ import "fmt"
// doZap performs a zap with a wand (sticks.c do_zap).
func (g *RogueGame) doZap() {
p := &g.Player
obj := g.getItem("zap with", int(Stick))
obj := g.getItem("zap with", KindWand)
if obj == nil {
return
}
if obj.Type != Stick {
if obj.Kind != KindWand {
g.After = false
g.msg("you can't zap with that!")
return
}
if obj.Charges() == 0 {
if obj.Charges == 0 {
g.msg("nothing happens")
return
}
switch obj.Which {
case WsLight:
switch obj.WandKind() {
case WandLight:
// Reddy Kilowatt wand. Light up the room
g.Items.Sticks[WsLight].Know = true
if p.Room.Flags.Has(IsGone) {
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(IsDark)
p.Room.Flags.Clear(Dark)
// Light the room and put the player back up
g.enterRoom(p.Pos)
g.addmsg("the room is lit")
@@ -36,7 +36,7 @@ func (g *RogueGame) doZap() {
}
g.endmsg()
}
case WsDrain:
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)
@@ -45,7 +45,7 @@ func (g *RogueGame) doZap() {
return
}
g.drain()
case WsInvis, WsPolymorph, WsTelAway, WsTelTo, WsCancel:
case WandInvisibility, WandPolymorph, WandTeleportAway, WandTeleportTo, WandCancellation:
y := p.Pos.Y
x := p.Pos.X
for stepOk(g.Level.VisibleChar(y, x)) {
@@ -55,15 +55,15 @@ func (g *RogueGame) doZap() {
if tp := g.Level.MonsterAt(y, x); tp != nil {
monster := tp.Type
if monster == 'F' {
p.Flags.Clear(IsHeld)
p.Flags.Clear(Held)
}
switch obj.Which {
case WsInvis:
tp.Flags.Set(IsInvis)
switch obj.WandKind() {
case WandInvisibility:
tp.Flags.Set(Invisible)
if g.cansee(y, x) {
g.mvaddch(y, x, tp.OldCh)
}
case WsPolymorph:
case WandPolymorph:
pp := tp.Pack
detachMon(&g.Level.Monsters, tp)
if g.seeMonst(tp) {
@@ -80,18 +80,18 @@ func (g *RogueGame) doZap() {
tp.OldCh = oldch
tp.Pack = pp
if g.seeMonst(tp) {
g.Items.Sticks[WsPolymorph].Know = true
g.Items.Sticks[WandPolymorph].Know = true
}
case WsCancel:
tp.Flags.Set(IsCanc)
tp.Flags.Clear(IsInvis | CanHuh)
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 WsTelAway, WsTelTo:
case WandTeleportAway, WandTeleportTo:
var newPos Coord
if obj.Which == WsTelAway {
if obj.WandKind() == WandTeleportAway {
for {
newPos, _ = g.findFloor(nil, 0, true)
if newPos != p.Pos {
@@ -103,20 +103,20 @@ func (g *RogueGame) doZap() {
newPos.X = p.Pos.X + g.Delta.X
}
tp.Dest = &p.Pos
tp.Flags.Set(IsRun)
tp.Flags.Set(Awake)
g.relocate(tp, newPos)
}
}
case WsMissile:
g.Items.Sticks[WsMissile].Know = true
case WandMagicMissile:
g.Items.Sticks[WandMagicMissile].Know = true
bolt := newObject()
bolt.Type = '*'
bolt.HurlDmg = "1x4"
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 = IsMissl
bolt.Flags = Missile
if p.CurWeapon != nil {
bolt.Launch = p.CurWeapon.Which
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 &&
@@ -127,7 +127,7 @@ func (g *RogueGame) doZap() {
} else {
g.msg("the missle vanishes with a puff of smoke")
}
case WsHasteM, WsSlowM:
case WandHasteMonster, WandSlowMonster:
y := p.Pos.Y
x := p.Pos.X
for stepOk(g.Level.VisibleChar(y, x)) {
@@ -135,17 +135,17 @@ func (g *RogueGame) doZap() {
x += g.Delta.X
}
if tp := g.Level.MonsterAt(y, x); tp != nil {
if obj.Which == WsHasteM {
if tp.On(IsSlow) {
tp.Flags.Clear(IsSlow)
if obj.WandKind() == WandHasteMonster {
if tp.On(Slowed) {
tp.Flags.Clear(Slowed)
} else {
tp.Flags.Set(IsHaste)
tp.Flags.Set(Hasted)
}
} else {
if tp.On(IsHaste) {
tp.Flags.Clear(IsHaste)
if tp.On(Hasted) {
tp.Flags.Clear(Hasted)
} else {
tp.Flags.Set(IsSlow)
tp.Flags.Set(Slowed)
}
tp.Turn = true
}
@@ -153,21 +153,21 @@ func (g *RogueGame) doZap() {
g.Delta.X = x
g.runto(g.Delta)
}
case WsElect, WsFire, WsCold:
case WandLightning, WandFire, WandCold:
var name string
switch obj.Which {
case WsElect:
switch obj.WandKind() {
case WandLightning:
name = "bolt"
case WsFire:
case WandFire:
name = "flame"
default:
name = "ice"
}
g.fireBolt(p.Pos, &g.Delta, name)
g.Items.Sticks[obj.Which].Know = true
case WsNop:
case WandNothing:
}
obj.SetCharges(obj.Charges() - 1)
obj.Charges--
}
// drain does the drain-hit-points-from-player schtick (sticks.c drain).
@@ -176,14 +176,14 @@ func (g *RogueGame) drain() {
// First count how many things we need to spread the hit points among
var corp *Room
if g.Level.Char(p.Pos.Y, p.Pos.X) == Door {
corp = &g.Level.Passages[*g.Level.FlagsAt(p.Pos.Y, p.Pos.X)&FPNum]
corp = &g.Level.Passages[*g.Level.FlagsAt(p.Pos.Y, p.Pos.X)&FPassNum]
}
inpass := p.Room.Flags.Has(IsGone)
inpass := p.Room.Flags.Has(Gone)
var drainee []*Monster
for _, mp := range g.Level.Monsters {
if mp.Room == p.Room || mp.Room == corp ||
(inpass && g.Level.Char(mp.Pos.Y, mp.Pos.X) == Door &&
&g.Level.Passages[*g.Level.FlagsAt(mp.Pos.Y, mp.Pos.X)&FPNum] == p.Room) {
&g.Level.Passages[*g.Level.FlagsAt(mp.Pos.Y, mp.Pos.X)&FPassNum] == p.Room) {
drainee = append(drainee, mp)
}
}
@@ -211,12 +211,12 @@ func (g *RogueGame) fireBolt(start Coord, dir *Coord, name string) {
fromHero := start == p.Pos
bolt := newObject()
bolt.Type = Weapon
bolt.Which = Flame
bolt.HurlDmg = "6x6"
bolt.Kind = KindWeapon
bolt.Which = int(WeaponFlame)
bolt.HurlDmg = dice("6x6")
bolt.HPlus = 100
bolt.DPlus = 0
g.Items.Weapons[Flame].Name = name
g.Items.Weapons[WeaponFlame].Name = name
var dirch byte
switch dir.Y + dir.X {
case 0:
@@ -322,28 +322,28 @@ func (g *RogueGame) fireBolt(start Coord, dir *Coord, name string) {
// fixStick sets up a new wand or staff (sticks.c fix_stick).
func (g *RogueGame) fixStick(cur *Object) {
if g.Items.WandType[cur.Which] == "staff" {
cur.Damage = "2x3"
cur.Damage = dice("2x3")
} else {
cur.Damage = "1x1"
cur.Damage = dice("1x1")
}
cur.HurlDmg = "1x1"
cur.HurlDmg = dice("1x1")
switch cur.Which {
case WsLight:
cur.SetCharges(g.rnd(10) + 10)
switch cur.WandKind() {
case WandLight:
cur.Charges = g.rnd(10) + 10
default:
cur.SetCharges(g.rnd(5) + 3)
cur.Charges = g.rnd(5) + 3
}
}
// chargeStr returns the charge-count suffix for an identified stick
// (sticks.c charge_str).
func chargeStr(g *RogueGame, obj *Object) string {
if !obj.Flags.Has(IsKnow) {
if !obj.Flags.Has(Known) {
return ""
}
if g.Options.Terse {
return fmt.Sprintf(" [%d]", obj.Charges())
return fmt.Sprintf(" [%d]", obj.Charges)
}
return fmt.Sprintf(" [%d charges]", obj.Charges())
return fmt.Sprintf(" [%d charges]", obj.Charges)
}

View File

@@ -6,10 +6,10 @@ package game
// change during play) are cloned into RogueGame.Items by NewGame.
// initStats is the C INIT_STATS: the player's starting statistics.
var initStats = Stats{Str: 16, Exp: 0, Lvl: 1, Arm: 10, HP: 12, Dmg: "1x4", MaxHP: 12}
var initStats = Stats{Str: 16, Exp: 0, Lvl: 1, ArmorClass: 10, HP: 12, Dmg: dice("1x4"), MaxHP: 12}
// aClass is extern.c a_class[]: armor class for each armor type.
var aClass = [MaxArmors]int{
var aClass = [NumArmorTypes]int{
8, // LEATHER
7, // RING_MAIL
7, // STUDDED_LEATHER
@@ -28,7 +28,7 @@ var eLevels = []int{
}
// trName is extern.c tr_name[]: names of the traps.
var trName = [NTraps]string{
var trName = [NumTrapTypes]string{
"a trapdoor",
"an arrow trap",
"a sleeping gas trap",
@@ -47,32 +47,32 @@ var invTName = []string{"Overwrite", "Slow", "Clear"}
// rolled from the level at creation time.
var monsterTable = [26]MonsterKind{
/* Name CARRY FLAGS str exp lvl arm hp dmg */
{"aquator", 0, IsMean, Stats{10, 20, 5, 2, 1, "0x0/0x0", 0}},
{"bat", 0, IsFly, Stats{10, 1, 1, 3, 1, "1x2", 0}},
{"centaur", 15, 0, Stats{10, 17, 4, 4, 1, "1x2/1x5/1x5", 0}},
{"dragon", 100, IsMean, Stats{10, 5000, 10, -1, 1, "1x8/1x8/3x10", 0}},
{"emu", 0, IsMean, Stats{10, 2, 1, 7, 1, "1x2", 0}},
{"venus flytrap", 0, IsMean, Stats{10, 80, 8, 3, 1, "%%%x0", 0}},
{"griffin", 20, IsMean | IsFly | IsRegen, Stats{10, 2000, 13, 2, 1, "4x3/3x5", 0}},
{"hobgoblin", 0, IsMean, Stats{10, 3, 1, 5, 1, "1x8", 0}},
{"ice monster", 0, 0, Stats{10, 5, 1, 9, 1, "0x0", 0}},
{"jabberwock", 70, 0, Stats{10, 3000, 15, 6, 1, "2x12/2x4", 0}},
{"kestrel", 0, IsMean | IsFly, Stats{10, 1, 1, 7, 1, "1x4", 0}},
{"leprechaun", 0, 0, Stats{10, 10, 3, 8, 1, "1x1", 0}},
{"medusa", 40, IsMean, Stats{10, 200, 8, 2, 1, "3x4/3x4/2x5", 0}},
{"nymph", 100, 0, Stats{10, 37, 3, 9, 1, "0x0", 0}},
{"orc", 15, IsGreed, Stats{10, 5, 1, 6, 1, "1x8", 0}},
{"phantom", 0, IsInvis, Stats{10, 120, 8, 3, 1, "4x4", 0}},
{"quagga", 0, IsMean, Stats{10, 15, 3, 3, 1, "1x5/1x5", 0}},
{"rattlesnake", 0, IsMean, Stats{10, 9, 2, 3, 1, "1x6", 0}},
{"snake", 0, IsMean, Stats{10, 2, 1, 5, 1, "1x3", 0}},
{"troll", 50, IsRegen | IsMean, Stats{10, 120, 6, 4, 1, "1x8/1x8/2x6", 0}},
{"black unicorn", 0, IsMean, Stats{10, 190, 7, -2, 1, "1x9/1x9/2x9", 0}},
{"vampire", 20, IsRegen | IsMean, Stats{10, 350, 8, 1, 1, "1x10", 0}},
{"wraith", 0, 0, Stats{10, 55, 5, 4, 1, "1x6", 0}},
{"xeroc", 30, 0, Stats{10, 100, 7, 7, 1, "4x4", 0}},
{"yeti", 30, 0, Stats{10, 50, 4, 6, 1, "1x6/1x6", 0}},
{"zombie", 0, IsMean, Stats{10, 6, 2, 8, 1, "1x8", 0}},
{"aquator", 0, Mean, Stats{10, 20, 5, 2, 1, dice("0x0/0x0"), 0}},
{"bat", 0, Flying, Stats{10, 1, 1, 3, 1, dice("1x2"), 0}},
{"centaur", 15, 0, Stats{10, 17, 4, 4, 1, dice("1x2/1x5/1x5"), 0}},
{"dragon", 100, Mean, Stats{10, 5000, 10, -1, 1, dice("1x8/1x8/3x10"), 0}},
{"emu", 0, Mean, Stats{10, 2, 1, 7, 1, dice("1x2"), 0}},
{"venus flytrap", 0, Mean, Stats{10, 80, 8, 3, 1, dice("%%%x0"), 0}},
{"griffin", 20, Mean | Flying | Regenerates, Stats{10, 2000, 13, 2, 1, dice("4x3/3x5"), 0}},
{"hobgoblin", 0, Mean, Stats{10, 3, 1, 5, 1, dice("1x8"), 0}},
{"ice monster", 0, 0, Stats{10, 5, 1, 9, 1, dice("0x0"), 0}},
{"jabberwock", 70, 0, Stats{10, 3000, 15, 6, 1, dice("2x12/2x4"), 0}},
{"kestrel", 0, Mean | Flying, Stats{10, 1, 1, 7, 1, dice("1x4"), 0}},
{"leprechaun", 0, 0, Stats{10, 10, 3, 8, 1, dice("1x1"), 0}},
{"medusa", 40, Mean, Stats{10, 200, 8, 2, 1, dice("3x4/3x4/2x5"), 0}},
{"nymph", 100, 0, Stats{10, 37, 3, 9, 1, dice("0x0"), 0}},
{"orc", 15, Greedy, Stats{10, 5, 1, 6, 1, dice("1x8"), 0}},
{"phantom", 0, Invisible, Stats{10, 120, 8, 3, 1, dice("4x4"), 0}},
{"quagga", 0, Mean, Stats{10, 15, 3, 3, 1, dice("1x5/1x5"), 0}},
{"rattlesnake", 0, Mean, Stats{10, 9, 2, 3, 1, dice("1x6"), 0}},
{"snake", 0, Mean, Stats{10, 2, 1, 5, 1, dice("1x3"), 0}},
{"troll", 50, Regenerates | Mean, Stats{10, 120, 6, 4, 1, dice("1x8/1x8/2x6"), 0}},
{"black unicorn", 0, Mean, Stats{10, 190, 7, -2, 1, dice("1x9/1x9/2x9"), 0}},
{"vampire", 20, Regenerates | Mean, Stats{10, 350, 8, 1, 1, dice("1x10"), 0}},
{"wraith", 0, 0, Stats{10, 55, 5, 4, 1, dice("1x6"), 0}},
{"xeroc", 30, 0, Stats{10, 100, 7, 7, 1, dice("4x4"), 0}},
{"yeti", 30, 0, Stats{10, 50, 4, 6, 1, dice("1x6/1x6"), 0}},
{"zombie", 0, Mean, Stats{10, 6, 2, 8, 1, dice("1x8"), 0}},
}
// Base ObjInfo tables (extern.c). These are templates: NewGame copies them
@@ -88,7 +88,7 @@ var baseThings = [NumThings]ObjInfo{
{Prob: 4}, // stick
}
var baseArmInfo = [MaxArmors]ObjInfo{
var baseArmInfo = [NumArmorTypes]ObjInfo{
{Name: "leather armor", Prob: 20, Worth: 20},
{Name: "ring mail", Prob: 15, Worth: 25},
{Name: "studded leather armor", Prob: 15, Worth: 20},
@@ -99,7 +99,7 @@ var baseArmInfo = [MaxArmors]ObjInfo{
{Name: "plate mail", Prob: 5, Worth: 150},
}
var basePotInfo = [MaxPotions]ObjInfo{
var basePotInfo = [NumPotionTypes]ObjInfo{
{Name: "confusion", Prob: 7, Worth: 5},
{Name: "hallucination", Prob: 8, Worth: 5},
{Name: "poison", Prob: 8, Worth: 5},
@@ -116,7 +116,7 @@ var basePotInfo = [MaxPotions]ObjInfo{
{Name: "levitation", Prob: 6, Worth: 75},
}
var baseRingInfo = [MaxRings]ObjInfo{
var baseRingInfo = [NumRingTypes]ObjInfo{
{Name: "protection", Prob: 9, Worth: 400},
{Name: "add strength", Prob: 9, Worth: 400},
{Name: "sustain strength", Prob: 5, Worth: 280},
@@ -133,7 +133,7 @@ var baseRingInfo = [MaxRings]ObjInfo{
{Name: "maintain armor", Prob: 5, Worth: 380},
}
var baseScrInfo = [MaxScrolls]ObjInfo{
var baseScrInfo = [NumScrollTypes]ObjInfo{
{Name: "monster confusion", Prob: 7, Worth: 140},
{Name: "magic mapping", Prob: 4, Worth: 150},
{Name: "hold monster", Prob: 2, Worth: 180},
@@ -154,7 +154,7 @@ var baseScrInfo = [MaxScrolls]ObjInfo{
{Name: "protect armor", Prob: 2, Worth: 250},
}
var baseWeapInfo = [MaxWeapons + 1]ObjInfo{
var baseWeapInfo = [NumWeaponTypes + 1]ObjInfo{
{Name: "mace", Prob: 11, Worth: 8},
{Name: "long sword", Prob: 11, Worth: 15},
{Name: "short bow", Prob: 12, Worth: 15},
@@ -167,7 +167,7 @@ var baseWeapInfo = [MaxWeapons + 1]ObjInfo{
{}, // DO NOT REMOVE: fake entry for dragon's breath
}
var baseWsInfo = [MaxSticks]ObjInfo{
var baseWsInfo = [NumWandTypes]ObjInfo{
{Name: "light", Prob: 12, Worth: 250},
{Name: "invisibility", Prob: 6, Worth: 5},
{Name: "lightning", Prob: 3, Worth: 330},
@@ -323,5 +323,5 @@ var helpStr = []helpEntry{
// the Go save format does not use them.
const (
Release = "5.4.4"
Version = "rogue (sneak.berlin/go/rogue port of rogueforge 5.4.4)"
Version = "rogue (git.eeqj.de/sneak/rgoue port of rogueforge 5.4.4)"
)

View File

@@ -17,7 +17,7 @@ func TestProbabilitiesSumTo100(t *testing.T) {
"scrolls": baseScrInfo[:],
"rings": baseRingInfo[:],
"sticks": baseWsInfo[:],
"weapons": baseWeapInfo[:MaxWeapons], // excludes the flame entry
"weapons": baseWeapInfo[:NumWeaponTypes], // excludes the flame entry
"armor": baseArmInfo[:],
}
for name, tab := range tables {
@@ -29,11 +29,11 @@ func TestProbabilitiesSumTo100(t *testing.T) {
func TestInitProbsCumulative(t *testing.T) {
g := NewGame(Config{Seed: 1})
last := g.Items.Potions[MaxPotions-1].Prob
last := g.Items.Potions[NumPotionTypes-1].Prob
if last != 100 {
t.Errorf("cumulative potion probability ends at %d, want 100", last)
}
for i := 1; i < MaxPotions; i++ {
for i := PotionKind(1); i < NumPotionTypes; i++ {
if g.Items.Potions[i].Prob < g.Items.Potions[i-1].Prob {
t.Errorf("potion probs not nondecreasing at %d", i)
}

View File

@@ -13,14 +13,14 @@ func (g *RogueGame) invName(obj *Object, drop bool) string {
var pb strings.Builder
which := obj.Which
it := &g.Items
switch obj.Type {
case Potion:
switch obj.Kind {
case KindPotion:
g.nameit(&pb, obj, "potion", it.PotColors[which], &it.Potions[which], nullstr)
case Ring:
case KindRing:
g.nameit(&pb, obj, "ring", it.RingStones[which], &it.Rings[which], ringNum)
case Stick:
case KindWand:
g.nameit(&pb, obj, it.WandType[which], it.WandMade[which], &it.Sticks[which], chargeStr)
case Scroll:
case KindScroll:
if obj.Count == 1 {
pb.WriteString("A scroll ")
} else {
@@ -34,7 +34,7 @@ func (g *RogueGame) invName(obj *Object, drop bool) string {
} else {
fmt.Fprintf(&pb, "titled '%s'", it.ScrNames[which])
}
case Food:
case KindFood:
if which == 1 {
if obj.Count == 1 {
fmt.Fprintf(&pb, "A%s %s", vowelstr(g.Fruit), g.Fruit)
@@ -48,14 +48,14 @@ func (g *RogueGame) invName(obj *Object, drop bool) string {
fmt.Fprintf(&pb, "%d rations of food", obj.Count)
}
}
case Weapon:
case KindWeapon:
sp := it.Weapons[which].Name
if obj.Count > 1 {
fmt.Fprintf(&pb, "%d ", obj.Count)
} else {
fmt.Fprintf(&pb, "A%s ", vowelstr(sp))
}
if obj.Flags.Has(IsKnow) {
if obj.Flags.Has(Known) {
fmt.Fprintf(&pb, "%s %s", num(obj.HPlus, obj.DPlus, Weapon), sp)
} else {
pb.WriteString(sp)
@@ -66,24 +66,24 @@ func (g *RogueGame) invName(obj *Object, drop bool) string {
if obj.Label != "" {
fmt.Fprintf(&pb, " called %s", obj.Label)
}
case Armor:
case KindArmor:
sp := it.Armors[which].Name
if obj.Flags.Has(IsKnow) {
fmt.Fprintf(&pb, "%s %s [", num(aClass[which]-obj.Arm, 0, Armor), sp)
if obj.Flags.Has(Known) {
fmt.Fprintf(&pb, "%s %s [", num(aClass[which]-obj.ArmorClass, 0, Armor), sp)
if !g.Options.Terse {
pb.WriteString("protection ")
}
fmt.Fprintf(&pb, "%d]", 10-obj.Arm)
fmt.Fprintf(&pb, "%d]", 10-obj.ArmorClass)
} else {
pb.WriteString(sp)
}
if obj.Label != "" {
fmt.Fprintf(&pb, " called %s", obj.Label)
}
case Amulet:
case KindAmulet:
pb.WriteString("The Amulet of Yendor")
case Gold:
fmt.Fprintf(&pb, "%d Gold pieces", obj.GoldVal())
case KindGold:
fmt.Fprintf(&pb, "%d Gold pieces", obj.GoldValue)
}
out := pb.String()
@@ -121,20 +121,20 @@ func (g *RogueGame) dropIt() {
g.msg("there is something there already")
return
}
obj := g.getItem("drop", 0)
obj := g.getItem("drop", KindNone)
if obj == nil {
return
}
if !g.dropCheck(obj) {
return
}
obj = g.leavePack(obj, true, !IsMult(obj.Type))
obj = g.leavePack(obj, true, !obj.Kind.MergesInPack())
// Link it into the level object list
attachObj(&g.Level.Objects, obj)
g.Level.SetChar(p.Pos.Y, p.Pos.X, obj.Type)
g.Level.SetChar(p.Pos.Y, p.Pos.X, obj.Kind.Glyph())
g.Level.FlagsAt(p.Pos.Y, p.Pos.X).Set(FDropped)
obj.Pos = p.Pos
if obj.Type == Amulet {
if obj.Kind == KindAmulet {
g.HasAmulet = false
}
g.msg("dropped %s", g.invName(obj, true))
@@ -151,7 +151,7 @@ func (g *RogueGame) dropCheck(obj *Object) bool {
obj != p.CurRing[Left] && obj != p.CurRing[Right] {
return true
}
if obj.Flags.Has(IsCursed) {
if obj.Flags.Has(Cursed) {
g.msg("you can't. It appears to be cursed")
return false
}
@@ -166,10 +166,10 @@ func (g *RogueGame) dropCheck(obj *Object) bool {
hand = Left
}
p.CurRing[hand] = nil
switch obj.Which {
case RAddStr:
g.chgStr(-obj.Arm)
case RSeeInvis:
switch obj.RingKind() {
case RingAddStrength:
g.chgStr(-obj.Bonus)
case RingSeeInvisible:
g.unsee(0)
g.Extinguish(DUnsee)
}
@@ -180,9 +180,9 @@ func (g *RogueGame) dropCheck(obj *Object) bool {
// newThing returns a new random thing for the dungeon (things.c new_thing).
func (g *RogueGame) newThing() *Object {
cur := newObject()
cur.Damage = "0x0"
cur.HurlDmg = "0x0"
cur.Arm = 11
cur.Damage = dice("0x0")
cur.HurlDmg = dice("0x0")
cur.ArmorClass = 11
cur.Count = 1
// Decide what kind of object it will be; if we haven't had food for a
@@ -195,13 +195,13 @@ func (g *RogueGame) newThing() *Object {
}
switch kind {
case 0:
cur.Type = Potion
cur.Kind = KindPotion
cur.Which = pickOne(g, g.Items.Potions[:])
case 1:
cur.Type = Scroll
cur.Kind = KindScroll
cur.Which = pickOne(g, g.Items.Scrolls[:])
case 2:
cur.Type = Food
cur.Kind = KindFood
g.Player.NoFood = 0
if g.rnd(10) != 0 {
cur.Which = 0
@@ -209,37 +209,37 @@ func (g *RogueGame) newThing() *Object {
cur.Which = 1
}
case 3:
g.initWeapon(cur, pickOne(g, g.Items.Weapons[:MaxWeapons]))
g.initWeapon(cur, WeaponKind(pickOne(g, g.Items.Weapons[:NumWeaponTypes])))
if r := g.rnd(100); r < 10 {
cur.Flags.Set(IsCursed)
cur.Flags.Set(Cursed)
cur.HPlus -= g.rnd(3) + 1
} else if r < 15 {
cur.HPlus += g.rnd(3) + 1
}
case 4:
cur.Type = Armor
cur.Kind = KindArmor
cur.Which = pickOne(g, g.Items.Armors[:])
cur.Arm = aClass[cur.Which]
cur.ArmorClass = aClass[cur.Which]
if r := g.rnd(100); r < 20 {
cur.Flags.Set(IsCursed)
cur.Arm += g.rnd(3) + 1
cur.Flags.Set(Cursed)
cur.ArmorClass += g.rnd(3) + 1
} else if r < 28 {
cur.Arm -= g.rnd(3) + 1
cur.ArmorClass -= g.rnd(3) + 1
}
case 5:
cur.Type = Ring
cur.Kind = KindRing
cur.Which = pickOne(g, g.Items.Rings[:])
switch cur.Which {
case RAddStr, RProtect, RAddHit, RAddDam:
if cur.Arm = g.rnd(3); cur.Arm == 0 {
cur.Arm = -1
cur.Flags.Set(IsCursed)
switch cur.RingKind() {
case RingAddStrength, RingProtection, RingDexterity, RingIncreaseDamage:
if cur.Bonus = g.rnd(3); cur.Bonus == 0 {
cur.Bonus = -1
cur.Flags.Set(Cursed)
}
case RAggr, RTeleport:
cur.Flags.Set(IsCursed)
case RingAggravateMonsters, RingTeleportation:
cur.Flags.Set(Cursed)
}
case 6:
cur.Type = Stick
cur.Kind = KindWand
cur.Which = pickOne(g, g.Items.Sticks[:])
g.fixStick(cur)
}
@@ -336,7 +336,7 @@ func (g *RogueGame) printDisc(typ byte) {
numFound := 0
for i := range info {
if info[order[i]].Know || info[order[i]].Guess != "" {
obj.Type = typ
obj.Kind = objectKindForGlyph(typ)
obj.Which = order[i]
g.addLine("%s", g.invName(&obj, false))
numFound++
@@ -525,7 +525,7 @@ func (g *RogueGame) prList() {
case Armor:
g.prSpec(g.Items.Armors[:])
case Weapon:
g.prSpec(g.Items.Weapons[:MaxWeapons])
g.prSpec(g.Items.Weapons[:NumWeaponTypes])
}
}

View File

@@ -62,12 +62,6 @@ const (
Stick byte = '/'
)
// Pseudo-types for item-selection prompts (rogue.h)
const (
Callable = -1
RorS = -2
)
// Various constants (rogue.h)
const (
HealTime = 30
@@ -100,9 +94,9 @@ const (
type RoomFlags int16
const (
IsDark RoomFlags = 1 << iota // room is dark
IsGone // room is gone (a corridor)
IsMaze // room is a maze
Dark RoomFlags = 1 << iota // room is dark
Gone // room is gone (a corridor)
Maze // room is a maze
)
func (f RoomFlags) Has(b RoomFlags) bool { return f&b != 0 }
@@ -113,12 +107,12 @@ func (f *RoomFlags) Clear(b RoomFlags) { *f &^= b }
type ObjFlags int32
const (
IsCursed ObjFlags = 1 << iota // object is cursed
IsKnow // player knows details about the object
IsMissl // object is a missile type
IsMany // object comes in groups
ObjIsFound // object has been seen (ISFOUND shares the bit with creatures)
IsProt // armor is permanently protected
Cursed ObjFlags = 1 << iota // ISCURSED: object is cursed
Known // ISKNOW: player knows details about the object
Missile // ISMISL: object is a missile type
Stackable // ISMANY: object comes in groups
WasFound // ISFOUND (objects): object has been seen (ISFOUND shares the bit with creatures)
Protected // ISPROT: armor is permanently protected
)
func (f ObjFlags) Has(b ObjFlags) bool { return f&b != 0 }
@@ -131,25 +125,25 @@ func (f *ObjFlags) Clear(b ObjFlags) { *f &^= b }
type CreatureFlags int32
const (
CanHuh CreatureFlags = 0o000001 // creature can confuse
CanSee CreatureFlags = 0o000002 // creature can see invisible creatures
IsBlind CreatureFlags = 0o000004 // creature is blind
IsCanc CreatureFlags = 0o000010 // creature has special qualities cancelled
IsLevit CreatureFlags = 0o000010 // hero is levitating
IsFound CreatureFlags = 0o000020 // creature has been seen
IsGreed CreatureFlags = 0o000040 // creature runs to protect gold
IsHaste CreatureFlags = 0o000100 // creature has been hastened
IsTarget CreatureFlags = 0o000200 // creature is the target of an 'f' command
IsHeld CreatureFlags = 0o000400 // creature has been held
IsHuh CreatureFlags = 0o001000 // creature is confused
IsInvis CreatureFlags = 0o002000 // creature is invisible
IsMean CreatureFlags = 0o004000 // creature can wake when player enters room
IsHalu CreatureFlags = 0o004000 // hero is on acid trip
IsRegen CreatureFlags = 0o010000 // creature can regenerate
IsRun CreatureFlags = 0o020000 // creature is running at the player
SeeMonst CreatureFlags = 0o040000 // hero can detect unseen monsters
IsFly CreatureFlags = 0o040000 // creature can fly
IsSlow CreatureFlags = 0o100000 // creature has been slowed
CanConfuse CreatureFlags = 0o000001 // CANHUH: creature can confuse
CanSeeInvisible CreatureFlags = 0o000002 // CANSEE: creature can see invisible creatures
Blind CreatureFlags = 0o000004 // ISBLIND: creature is blind
Cancelled CreatureFlags = 0o000010 // ISCANC: creature has special qualities cancelled
Levitating CreatureFlags = 0o000010 // ISLEVIT: hero is levitating
Found CreatureFlags = 0o000020 // ISFOUND: creature has been seen
Greedy CreatureFlags = 0o000040 // ISGREED: creature runs to protect gold
Hasted CreatureFlags = 0o000100 // ISHASTE: creature has been hastened
Targeted CreatureFlags = 0o000200 // ISTARGET: creature is the target of an 'f' command
Held CreatureFlags = 0o000400 // ISHELD: creature has been held
Confused CreatureFlags = 0o001000 // ISHUH: creature is confused
Invisible CreatureFlags = 0o002000 // ISINVIS: creature is invisible
Mean CreatureFlags = 0o004000 // ISMEAN: creature can wake when player enters room
Hallucinating CreatureFlags = 0o004000 // ISHALU: hero is on acid trip
Regenerates CreatureFlags = 0o010000 // ISREGEN: creature can regenerate
Awake CreatureFlags = 0o020000 // ISRUN: creature is running at the player
SenseMonsters CreatureFlags = 0o040000 // SEEMONST: hero can detect unseen monsters
Flying CreatureFlags = 0o040000 // ISFLY: creature can fly
Slowed CreatureFlags = 0o100000 // ISSLOW: creature has been slowed
)
func (f CreatureFlags) Has(b CreatureFlags) bool { return f&b != 0 }
@@ -160,140 +154,218 @@ func (f *CreatureFlags) Clear(b CreatureFlags) { *f &^= b }
type PlaceFlags uint8
const (
FPass PlaceFlags = 0x80 // is a passageway
FSeen PlaceFlags = 0x40 // have seen this spot before
FDropped PlaceFlags = 0x20 // object was dropped here
FLocked PlaceFlags = 0x20 // door is locked
FReal PlaceFlags = 0x10 // what you see is what you get
FPNum PlaceFlags = 0x0f // passage number mask
FTMask PlaceFlags = 0x07 // trap number mask
FPassage PlaceFlags = 0x80 // F_PASS: is a passageway
FSeen PlaceFlags = 0x40 // have seen this spot before
FDropped PlaceFlags = 0x20 // object was dropped here
FLocked PlaceFlags = 0x20 // door is locked
FReal PlaceFlags = 0x10 // what you see is what you get
FPassNum PlaceFlags = 0x0f // F_PNUM: passage number mask
FTrapMask PlaceFlags = 0x07 // F_TMASK: trap number mask
)
func (f PlaceFlags) Has(b PlaceFlags) bool { return f&b != 0 }
func (f *PlaceFlags) Set(b PlaceFlags) { *f |= b }
func (f *PlaceFlags) Clear(b PlaceFlags) { *f &^= b }
// Trap types (rogue.h)
// TrapKind identifies a trap (rogue.h trap types). The kind is stored in
// the low bits of a map cell's PlaceFlags (FTrapMask).
type TrapKind int
const (
TDoor = 0
TArrow = 1
TSleep = 2
TBear = 3
TTelep = 4
TDart = 5
TRust = 6
TMyst = 7
NTraps = 8
TrapDoor TrapKind = 0
TrapArrow TrapKind = 1
TrapSleep TrapKind = 2
TrapBear TrapKind = 3
TrapTeleport TrapKind = 4
TrapDart TrapKind = 5
TrapRust TrapKind = 6
TrapMystery TrapKind = 7
NumTrapTypes = 8
)
// Potion types (rogue.h)
// String returns the trap's display name, article included, as the C
// tr_name table had it.
func (t TrapKind) String() string {
if t < 0 || t >= NumTrapTypes {
return "a bizarre trap"
}
return trName[t]
}
// PotionKind identifies a potion (rogue.h potion types).
type PotionKind int
// Potion kinds (rogue.h)
const (
PConfuse = iota
PLSD
PPoison
PStrength
PSeeInvis
PHealing
PMFind
PTFind
PRaise
PXHeal
PHaste
PRestore
PBlind
PLevit
MaxPotions
PotionConfusion PotionKind = iota
PotionLSD
PotionPoison
PotionGainStrength
PotionSeeInvisible
PotionHealing
PotionDetectMonsters
PotionDetectMagic
PotionRaiseLevel
PotionExtraHealing
PotionHaste
PotionRestoreStrength
PotionBlindness
PotionLevitation
NumPotionTypes
)
// Scroll types (rogue.h)
// String returns the potion's true name ("healing", "haste self", ...).
func (p PotionKind) String() string {
if p < 0 || p >= NumPotionTypes {
return "strange potion"
}
return basePotInfo[p].Name
}
// ScrollKind identifies a scroll (rogue.h scroll types).
type ScrollKind int
// Scroll kinds (rogue.h)
const (
SConfuse = iota
SMap
SHold
SSleep
SArmor
SIDPotion
SIDScroll
SIDWeapon
SIDArmor
SIDRorS
SScare
SFDet
STelep
SEnch
SCreate
SRemove
SAggr
SProtect
MaxScrolls
ScrollMonsterConfusion ScrollKind = iota
ScrollMagicMapping
ScrollHoldMonster
ScrollSleep
ScrollEnchantArmor
ScrollIdentifyPotion
ScrollIdentifyScroll
ScrollIdentifyWeapon
ScrollIdentifyArmor
ScrollIdentifyRingOrStick
ScrollScareMonster
ScrollFoodDetection
ScrollTeleportation
ScrollEnchantWeapon
ScrollCreateMonster
ScrollRemoveCurse
ScrollAggravateMonsters
ScrollProtectArmor
NumScrollTypes
)
// Weapon types (rogue.h)
// String returns the scroll's true name ("magic mapping", ...).
func (s ScrollKind) String() string {
if s < 0 || s >= NumScrollTypes {
return "strange scroll"
}
return baseScrInfo[s].Name
}
// WeaponKind identifies a weapon (rogue.h weapon types).
type WeaponKind int
// Weapon kinds (rogue.h)
const (
Mace = iota
Sword
Bow
Arrow
Dagger
TwoSword
Dart
Shiraken
Spear
Flame // fake entry for dragon breath (ick)
MaxWeapons = Flame
WeaponMace WeaponKind = iota
WeaponLongSword
WeaponBow
WeaponArrow
WeaponDagger
WeaponTwoHandedSword
WeaponDart
WeaponShuriken
WeaponSpear
WeaponFlame // fake entry for dragon breath (ick)
NumWeaponTypes = WeaponFlame
)
// Armor types (rogue.h)
// String returns the weapon's name ("mace", "two handed sword", ...).
func (w WeaponKind) String() string {
if w < 0 || w > WeaponFlame {
return "strange weapon"
}
return baseWeapInfo[w].Name
}
// ArmorKind identifies a suit of armor (rogue.h armor types).
type ArmorKind int
// Armor kinds (rogue.h)
const (
Leather = iota
RingMail
StuddedLeather
ScaleMail
ChainMail
SplintMail
BandedMail
PlateMail
MaxArmors
ArmorLeather ArmorKind = iota
ArmorRingMail
ArmorStuddedLeather
ArmorScaleMail
ArmorChainMail
ArmorSplintMail
ArmorBandedMail
ArmorPlateMail
NumArmorTypes
)
// Ring types (rogue.h)
// String returns the armor's name ("ring mail", "plate mail", ...).
func (a ArmorKind) String() string {
if a < 0 || a >= NumArmorTypes {
return "strange armor"
}
return baseArmInfo[a].Name
}
// RingKind identifies a ring (rogue.h ring types).
type RingKind int
// Ring kinds (rogue.h)
const (
RProtect = iota
RAddStr
RSustStr
RSearch
RSeeInvis
RNop
RAggr
RAddHit
RAddDam
RRegen
RDigest
RTeleport
RStealth
RSustArm
MaxRings
RingProtection RingKind = iota
RingAddStrength
RingSustainStrength
RingSearching
RingSeeInvisible
RingAdornment
RingAggravateMonsters
RingDexterity
RingIncreaseDamage
RingRegeneration
RingSlowDigestion
RingTeleportation
RingStealth
RingMaintainArmor
NumRingTypes
)
// Rod/Wand/Staff types (rogue.h)
// String returns the ring's true name ("add strength", "stealth", ...).
func (r RingKind) String() string {
if r < 0 || r >= NumRingTypes {
return "strange ring"
}
return baseRingInfo[r].Name
}
// WandKind identifies a wand or staff (rogue.h rod/wand/staff types).
type WandKind int
// Wand kinds (rogue.h)
const (
WsLight = iota
WsInvis
WsElect
WsFire
WsCold
WsPolymorph
WsMissile
WsHasteM
WsSlowM
WsDrain
WsNop
WsTelAway
WsTelTo
WsCancel
MaxSticks
WandLight WandKind = iota
WandInvisibility
WandLightning
WandFire
WandCold
WandPolymorph
WandMagicMissile
WandHasteMonster
WandSlowMonster
WandDrainLife
WandNothing
WandTeleportAway
WandTeleportTo
WandCancellation
NumWandTypes
)
// String returns the wand/staff's true name ("lightning", ...).
func (w WandKind) String() string {
if w < 0 || w >= NumWandTypes {
return "strange stick"
}
return baseWsInfo[w].Name
}
// Coord is a position on the level (rogue.h coord). A value type: the C
// ce(a,b) macro is plain == here.
type Coord struct {
@@ -302,13 +374,13 @@ type Coord struct {
// Stats describes a fighting being (rogue.h struct stats).
type Stats struct {
Str int // strength (s_str; 3..31)
Exp int // experience
Lvl int // level of mastery
Arm int // armor class
HP int // hit points (s_hpt)
Dmg string // damage dice, e.g. "1x4/1x2"
MaxHP int
Str int // strength (s_str; 3..31)
Exp int // experience
Lvl int // level of mastery
ArmorClass int // armor class (s_arm)
HP int // hit points (s_hpt)
Dmg DiceSpec // damage dice, e.g. "1x4/1x2"
MaxHP int
}
// Room describes a room or passage network (rogue.h struct room).
@@ -349,9 +421,6 @@ type Stone struct {
// CTRL maps a letter to its control character, as the C CTRL() macro.
func CTRL(c byte) byte { return c & 0o37 }
// IsMult reports whether an object type stacks in the pack (rogue.h ISMULT).
func IsMult(typ byte) bool { return typ == Potion || typ == Scroll || typ == Food }
// distance returns the squared distance between two points (chase.c dist).
func distance(y1, x1, y2, x2 int) int {
dx := x2 - x1

View File

@@ -4,12 +4,12 @@ import "fmt"
// weapons.c — functions for dealing with problems brought about by weapons.
const noWeapon = -1
const noWeapon WeaponKind = -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))
obj := g.getItem("throw", KindWeapon)
if obj == nil {
return
}
@@ -48,7 +48,7 @@ func (g *RogueGame) doMotion(obj *Object, ydelta, xdelta int) {
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.mvaddch(obj.Pos.Y, obj.Pos.X, obj.Kind.Glyph())
g.refresh()
}
continue
@@ -61,13 +61,13 @@ func (g *RogueGame) doMotion(obj *Object, ydelta, xdelta int) {
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
pp.Ch = obj.Kind.Glyph()
obj.Pos = fpos
if g.cansee(fpos.Y, fpos.X) {
if pp.Monst != nil {
pp.Monst.OldCh = obj.Type
pp.Monst.OldCh = obj.Kind.Glyph()
} else {
g.mvaddch(fpos.Y, fpos.X, obj.Type)
g.mvaddch(fpos.Y, fpos.X, obj.Kind.Glyph())
}
}
attachObj(&g.Level.Objects, obj)
@@ -98,12 +98,12 @@ func (g *RogueGame) wield() {
return
}
p.CurWeapon = oweapon
obj := g.getItem("wield", int(Weapon))
obj := g.getItem("wield", KindWeapon)
if obj == nil {
g.After = false
return
}
if obj.Type == Armor {
if obj.Kind == KindArmor {
g.msg("you can't wield armor")
g.After = false
return
@@ -122,39 +122,39 @@ func (g *RogueGame) wield() {
}
// initWeaps is the weapons.c init_dam[] table.
var initWeaps = [MaxWeapons]struct {
dam string // damage when wielded
hrl string // damage when thrown
launch int // launching weapon
var initWeaps = [NumWeaponTypes]struct {
dam DiceSpec // damage when wielded
hrl DiceSpec // damage when thrown
launch WeaponKind // launching weapon
flags ObjFlags
}{
{"2x4", "1x3", noWeapon, 0}, // Mace
{"3x4", "1x2", noWeapon, 0}, // Long sword
{"1x1", "1x1", noWeapon, 0}, // Bow
{"1x1", "2x3", Bow, IsMany | IsMissl}, // Arrow
{"1x6", "1x4", noWeapon, IsMissl}, // Dagger
{"4x4", "1x2", noWeapon, 0}, // 2h sword
{"1x1", "1x3", noWeapon, IsMany | IsMissl}, // Dart
{"1x2", "2x4", noWeapon, IsMany | IsMissl}, // Shuriken
{"2x3", "1x6", noWeapon, IsMissl}, // Spear
{dice("2x4"), dice("1x3"), noWeapon, 0}, // WeaponMace
{dice("3x4"), dice("1x2"), noWeapon, 0}, // Long sword
{dice("1x1"), dice("1x1"), noWeapon, 0}, // WeaponBow
{dice("1x1"), dice("2x3"), WeaponBow, Stackable | Missile}, // WeaponArrow
{dice("1x6"), dice("1x4"), noWeapon, Missile}, // WeaponDagger
{dice("4x4"), dice("1x2"), noWeapon, 0}, // 2h sword
{dice("1x1"), dice("1x3"), noWeapon, Stackable | Missile}, // WeaponDart
{dice("1x2"), dice("2x4"), noWeapon, Stackable | Missile}, // Shuriken
{dice("2x3"), dice("1x6"), noWeapon, Missile}, // WeaponSpear
}
// initWeapon sets up a new weapon (weapons.c init_weapon).
func (g *RogueGame) initWeapon(weap *Object, which int) {
func (g *RogueGame) initWeapon(weap *Object, which WeaponKind) {
iwp := &initWeaps[which]
weap.Type = Weapon
weap.Which = which
weap.Kind = KindWeapon
weap.Which = int(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 == Dagger {
if which == WeaponDagger {
weap.Count = g.rnd(4) + 2
weap.Group = g.Items.Group
g.Items.Group++
} else if weap.Flags.Has(IsMany) {
} else if weap.Flags.Has(Stackable) {
weap.Count = g.rnd(8) + 8
weap.Group = g.Items.Group
g.Items.Group++

View File

@@ -9,9 +9,9 @@ package game
func (g *RogueGame) createObj() {
obj := newObject()
g.msg("type of item: ")
obj.Type = g.readchar()
obj.Kind = objectKindForGlyph(g.readchar())
g.Msgs.Mpos = 0
g.msg("which %c do you want? (0-f)", obj.Type)
g.msg("which %c do you want? (0-f)", obj.Kind.Glyph())
ch := g.readchar()
if isDigit(ch) {
obj.Which = int(ch - '0')
@@ -22,15 +22,15 @@ func (g *RogueGame) createObj() {
obj.Count = 1
g.Msgs.Mpos = 0
switch {
case obj.Type == Weapon || obj.Type == Armor:
case obj.Kind == KindWeapon || obj.Kind == KindArmor:
g.msg("blessing? (+,-,n)")
bless := g.readchar()
g.Msgs.Mpos = 0
if bless == '-' {
obj.Flags.Set(IsCursed)
obj.Flags.Set(Cursed)
}
if obj.Type == Weapon {
g.initWeapon(obj, obj.Which)
if obj.Kind == KindWeapon {
g.initWeapon(obj, WeaponKind(obj.Which))
if bless == '-' {
obj.HPlus -= g.rnd(3) + 1
}
@@ -38,36 +38,36 @@ func (g *RogueGame) createObj() {
obj.HPlus += g.rnd(3) + 1
}
} else {
obj.Arm = aClass[obj.Which]
obj.ArmorClass = aClass[obj.Which]
if bless == '-' {
obj.Arm += g.rnd(3) + 1
obj.ArmorClass += g.rnd(3) + 1
}
if bless == '+' {
obj.Arm -= g.rnd(3) + 1
obj.ArmorClass -= g.rnd(3) + 1
}
}
case obj.Type == Ring:
switch obj.Which {
case RProtect, RAddStr, RAddHit, RAddDam:
case obj.Kind == KindRing:
switch obj.RingKind() {
case RingProtection, RingAddStrength, RingDexterity, RingIncreaseDamage:
g.msg("blessing? (+,-,n)")
bless := g.readchar()
g.Msgs.Mpos = 0
if bless == '-' {
obj.Flags.Set(IsCursed)
obj.Arm = -1
obj.Flags.Set(Cursed)
obj.Bonus = -1
} else {
obj.Arm = g.rnd(2) + 1
obj.Bonus = g.rnd(2) + 1
}
case RAggr, RTeleport:
obj.Flags.Set(IsCursed)
case RingAggravateMonsters, RingTeleportation:
obj.Flags.Set(Cursed)
}
case obj.Type == Stick:
case obj.Kind == KindWand:
g.fixStick(obj)
case obj.Type == Gold:
case obj.Kind == KindGold:
g.msg("how much?")
buf := ""
if g.getStr(&buf, g.scr.Std) == Norm {
obj.SetGoldVal(cAtoi(buf))
obj.GoldValue = cAtoi(buf)
}
}
g.addPack(obj, false)
@@ -93,7 +93,7 @@ func (g *RogueGame) showMap() {
}
// whatis identifies what a certain object is (wizard.c whatis).
func (g *RogueGame) whatis(insist bool, typ int) {
func (g *RogueGame) whatis(insist bool, kind ObjectKind) {
p := &g.Player
if len(p.Pack) == 0 {
g.msg("you don't have anything in your pack to identify")
@@ -102,7 +102,7 @@ func (g *RogueGame) whatis(insist bool, typ int) {
var obj *Object
for {
obj = g.getItem("identify", typ)
obj = g.getItem("identify", kind)
if !insist {
break
}
@@ -111,9 +111,10 @@ func (g *RogueGame) whatis(insist bool, typ int) {
}
if obj == nil {
g.msg("you must identify something")
} else if typ != 0 && int(obj.Type) != typ &&
!(typ == RorS && (obj.Type == Ring || obj.Type == Stick)) {
g.msg("you must identify a %s", typeName(typ))
} else if kind != KindNone && obj.Kind != kind &&
!(kind == KindRingOrStick &&
(obj.Kind == KindRing || obj.Kind == KindWand)) {
g.msg("you must identify a %s", kind)
} else {
break
}
@@ -123,16 +124,16 @@ func (g *RogueGame) whatis(insist bool, typ int) {
return
}
switch obj.Type {
case Scroll:
switch obj.Kind {
case KindScroll:
setKnow(obj, g.Items.Scrolls[:])
case Potion:
case KindPotion:
setKnow(obj, g.Items.Potions[:])
case Stick:
case KindWand:
setKnow(obj, g.Items.Sticks[:])
case Weapon, Armor:
obj.Flags.Set(IsKnow)
case Ring:
case KindWeapon, KindArmor:
obj.Flags.Set(Known)
case KindRing:
setKnow(obj, g.Items.Rings[:])
}
g.msg("%s", g.invName(obj, false))
@@ -142,34 +143,12 @@ func (g *RogueGame) whatis(insist bool, typ int) {
// set_know).
func setKnow(obj *Object, info []ObjInfo) {
info[obj.Which].Know = true
obj.Flags.Set(IsKnow)
obj.Flags.Set(Known)
info[obj.Which].Guess = ""
}
// typeNameTable is the wizard.c type_name static tlist.
var typeNameTable = []struct {
ch int
desc string
}{
{int(Potion), "potion"},
{int(Scroll), "scroll"},
{int(Food), "food"},
{RorS, "ring, wand or staff"},
{int(Ring), "ring"},
{int(Stick), "wand or staff"},
{int(Weapon), "weapon"},
{int(Armor), "suit of armor"},
}
// typeName returns the name of an object type (wizard.c type_name).
func typeName(typ int) string {
for _, hp := range typeNameTable {
if typ == hp.ch {
return hp.desc
}
}
return ""
}
// The C type_name()/tlist table is gone: ObjectKind.String() carries the
// same vocabulary.
// teleport bamfs the hero someplace else (wizard.c teleport).
func (g *RogueGame) teleport() {
@@ -187,10 +166,10 @@ func (g *RogueGame) teleport() {
g.mvaddch(p.Pos.Y, p.Pos.X, PlayerCh)
// turn off ISHELD in case teleportation was done while fighting a
// Flytrap
if p.On(IsHeld) {
p.Flags.Clear(IsHeld)
if p.On(Held) {
p.Flags.Clear(Held)
p.VfHit = 0
g.Monsters['F'-'A'].Stats.Dmg = "000x0"
g.Monsters['F'-'A'].Stats.Dmg = dice("000x0")
}
g.NoMove = 0
g.Count = 0

5
go.mod
View File

@@ -1,10 +1,11 @@
module sneak.berlin/go/rogue
module git.eeqj.de/sneak/rgoue
go 1.25.7
require github.com/gdamore/tcell/v2 v2.13.10
require (
github.com/gdamore/encoding v1.0.1 // indirect
github.com/gdamore/tcell/v2 v2.13.10 // indirect
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
golang.org/x/sys v0.38.0 // indirect

View File

@@ -11,7 +11,7 @@ import (
"github.com/gdamore/tcell/v2"
"sneak.berlin/go/rogue/game"
"git.eeqj.de/sneak/rgoue/game"
)
// Tcell renders game Windows on a tcell screen and turns key events into