Three behaviors from 5.4.4 that the port dropped silently. Each is a few
lines; grouped because they are all "restore something C did".
1. sticks.c 237: the "otherwise" arm closing do_zap's switch printed
"what a bizarre schtick!", and doZap had turned it into doing nothing.
The arm is under #ifdef MASTER, not under a runtime wizard test, so in
the MASTER build this port is it printed for every player and must not
be gated on g.Wizard. WS_NOP is a case of that switch in its own right
("when WS_NOP: break;"), so "no handler ran" cannot be the trigger:
the wand of nothing does nothing quietly. C's switch covers all 14 WS_
values, so its otherwise is reachable only for an o_which outside the
table, which is what Object.hasValidWhich already screens for. All
three arms fall through to obj.Charges--, as C's do.
2. command.c 288-291: CTRL('R') is "after = FALSE; clearok(curscr, TRUE);
wrefresh(curscr);" — a forced full repaint. The port called
g.refresh(), the ordinary diffing blit, which cannot fix the only
situation the command exists for: a screen corrupted by another
program's output leaves the game's record of it still correct, so the
diff sends nothing. New Terminal.Repaint (tcell Screen.Sync, which
discards tcell's record of the terminal rather than diffing against
it), Screen.Repaint and g.repaint(), implemented in term.Tcell and in
both headless test terminals. Named for the curses operation: the
interface is the game's abstraction, not tcell's. It repaints what was
last rendered — C repainted curscr, not stdscr — so it takes no
window.
3. main.c 107-113: the startup greeting existed nowhere in the tree. New
game.Greeting, printed on stdout by cmd/rogue/main.go before
term.New(), the port's initscr(). Only the wizard wording is #ifdef
MASTER; the other is unconditional. The %d is dnum, which main.c has
just assigned to seed, so it is Params.Seed. Neither wording ends in a
newline. Two placement details the tests pin: the printf sits after
parse_opts, so a ROGUEOPTS name= is what the player is greeted by; and
it sits after the -s/-d handling and after restore(), which never
returns, so a resumed game does not announce that a dungeon is being
dug (digsNewDungeon).
Greeting parses ROGUEOPTS into a throwaway game built the way New builds
the real one, tables and home directory included: ParseOpts handles every
option, not just the one the greeting reads, and inven= is matched
against inv_t_name[], which lives on the game.
All three message strings verified byte-for-byte against origin/c-master
sticks.c and main.c. No RNG call is added on any path and nothing under
game/testdata/ changed; TestSeedCompatItemTables is green against the
untouched golden.
Mutation-proved, each behavior removed in turn with only its own test
failing: dropping the message arm fails
TestZapUnhandledWandSaysBizarreSchtick; extending the message to WS_NOP
fails TestZapWandOfNothingIsSilent; putting g.refresh() back fails
TestRedrawCommandForcesFullRepaint; swapping the two wordings, and
ignoring the ROGUEOPTS name, both fail TestGreeting; greeting on the
restore path fails TestDigsNewDungeon.
ARCHITECTURE.md 5.3 gains Repaint and why a blit cannot substitute for
it; nothing here is deliberately dropped, so section 9 is unchanged.
TODO.md gets a Completed Steps entry; Next Step deliberately not rotated,
this being out-of-band issue work.
935 lines
33 KiB
Go
935 lines
33 KiB
Go
//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07)
|
|
package game
|
|
|
|
// tables.go — the static data tables the C game kept as file-scope globals
|
|
// (extern.c, init.c, and assorted per-file statics). The port gathers them
|
|
// into gameData: every RogueGame carries its own copy, so the package holds
|
|
// no package-level state. Per-game mutable copies (the ObjInfo tables,
|
|
// whose probabilities are re-summed and whose Know/Guess fields change
|
|
// during play) are cloned into RogueGame.Items by NewGame.
|
|
|
|
// helpEntry is rogue.h struct h_list.
|
|
type helpEntry struct {
|
|
Ch byte
|
|
Desc string
|
|
Print bool
|
|
}
|
|
|
|
// gameData is the bundle of static tables, built by newGameData and hung on
|
|
// RogueGame as g.data. Nothing in it mutates during play: the one table the
|
|
// C code writes to (the venus flytrap damage hack) operates on the game's
|
|
// Monsters copy, not on this template.
|
|
type gameData struct {
|
|
// initStats is the C INIT_STATS: the player's starting statistics.
|
|
initStats Stats
|
|
|
|
// aClass is extern.c a_class[]: armor class for each armor type.
|
|
aClass [NumArmorTypes]int
|
|
|
|
// eLevels is extern.c e_levels[]: experience thresholds per level; the
|
|
// zero terminates the table as in C.
|
|
eLevels []int
|
|
|
|
// trName is extern.c tr_name[]: names of the traps.
|
|
trName [NumTrapTypes]string
|
|
|
|
// invTName is extern.c inv_t_name[]: the inventory style names.
|
|
invTName []string
|
|
|
|
// monsterTable is extern.c monsters[26]: all monster kinds, indexed by
|
|
// letter - 'A'. Monster strength (XX in C) is always 10; HP (___ in C)
|
|
// is rolled from the level at creation time.
|
|
monsterTable [26]MonsterKind
|
|
|
|
// Base ObjInfo tables (extern.c). These are templates: NewGame copies
|
|
// them into ItemLore before initProbs converts Prob to cumulative form.
|
|
baseThings [NumThings]ObjInfo
|
|
baseArmInfo [NumArmorTypes]ObjInfo
|
|
basePotInfo [NumPotionTypes]ObjInfo
|
|
baseRingInfo [NumRingTypes]ObjInfo
|
|
baseScrInfo [NumScrollTypes]ObjInfo
|
|
baseWeapInfo [NumWeaponTypes + 1]ObjInfo
|
|
baseWsInfo [NumWandTypes]ObjInfo
|
|
|
|
// rainbow is init.c rainbow[]: the possible potion colors.
|
|
rainbow []string
|
|
|
|
// sylls is init.c sylls[]: syllables for generated scroll names.
|
|
sylls []string
|
|
|
|
// stoneTable is init.c stones[]: ring stones and their worth.
|
|
stoneTable []Stone
|
|
|
|
// woods is init.c wood[]: what staffs are made of.
|
|
woods []string
|
|
|
|
// metals is init.c metal[]: what wands are made of.
|
|
metals []string
|
|
|
|
// helpStr is extern.c helpstr[]: the '?' command help text.
|
|
helpStr []helpEntry
|
|
|
|
// hNames are the strings for hitting; the first four are used when the
|
|
// player strikes, the second four for monsters (fight.c h_names).
|
|
hNames [8]string
|
|
|
|
// mNames are the strings for missing (fight.c m_names).
|
|
mNames [8]string
|
|
|
|
// strPlus adjusts hit probabilities due to strength (fight.c str_plus).
|
|
strPlus [32]int
|
|
|
|
// addDam adjusts damage done due to strength (fight.c add_dam).
|
|
addDam [32]int
|
|
|
|
// lvlMons and wandMons list monsters in rough order of vorpalness;
|
|
// zero entries in wandMons never wander (monsters.c).
|
|
lvlMons [26]byte
|
|
wandMons [26]byte
|
|
|
|
// 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).
|
|
ringUses [NumRingTypes]int
|
|
|
|
// initWeaps is the weapons.c init_dam[] table.
|
|
initWeaps [NumWeaponTypes]weaponSetup
|
|
|
|
// pActions is potions.c p_actions[]. The P_SEEINVIS message is dynamic
|
|
// (it names the fruit) and is computed in applyPotionFuse.
|
|
pActions [NumPotionTypes]pact
|
|
|
|
// idType maps identify scrolls to the kind of item they identify
|
|
// (scrolls.c static id_type).
|
|
idType [ScrollIdentifyRingOrStick + 1]ObjectKind
|
|
|
|
// rdesConn is the hardcoded 3x3 room adjacency matrix from
|
|
// passages.c do_passages.
|
|
rdesConn [MaxRooms][MaxRooms]bool
|
|
|
|
// thingList is misc.c rnd_thing()'s static table.
|
|
thingList []byte
|
|
|
|
// identList is command.c's static ident_list.
|
|
identList []helpEntry
|
|
|
|
// hungerStateName is io.c state_name[].
|
|
hungerStateName [4]string
|
|
|
|
// ripArt is the rip.c rip[] tombstone art.
|
|
ripArt []string
|
|
|
|
// killnameTable is the rip.c nlist[]: special death causes.
|
|
killnameTable []helpEntry
|
|
|
|
// scoreReasons is the rip.c reason[] scoreboard strings.
|
|
scoreReasons [4]string
|
|
|
|
// quaffHandlers dispatches each potion kind to its effect method:
|
|
// the cases of the potions.c quaff switch. The bool is trip — was
|
|
// the hero hallucinating when the potion went down.
|
|
quaffHandlers [NumPotionTypes]func(g *RogueGame, trip bool)
|
|
|
|
// readHandlers dispatches each scroll kind to its effect method:
|
|
// the cases of the scrolls.c read_scroll switch. The handler gets
|
|
// the scroll being read (the identify family needs its subtype).
|
|
readHandlers [NumScrollTypes]func(g *RogueGame, obj *Object)
|
|
|
|
// zapHandlers dispatches each wand kind to its effect method: the
|
|
// cases of the sticks.c do_zap switch. A false return aborts the
|
|
// zap without using a charge (drain life on a too-weak hero).
|
|
zapHandlers [NumWandTypes]func(g *RogueGame, obj *Object) bool
|
|
|
|
// hitHandlers dispatches a monster's special power when its hit
|
|
// lands, indexed by monster letter - 'A': the cases of the fight.c
|
|
// attack switch. A true return means the monster removed itself.
|
|
hitHandlers [26]func(g *RogueGame, mp *Monster, mname string) bool
|
|
|
|
// commandHandlers dispatches the ordinary command keys: the simple
|
|
// cases of the big command.c switch. Keys that re-dispatch (runs,
|
|
// fight, repeat, move-on) and wizard keys stay in dispatchKey.
|
|
commandHandlers map[byte]func(g *RogueGame)
|
|
|
|
// trapHandlers dispatches each sprung trap to its effect method:
|
|
// the cases of the move.c be_trapped switch.
|
|
trapHandlers [NumTrapTypes]func(g *RogueGame, tc Coord)
|
|
|
|
// daemonHandlers dispatches DaemonIDs to their callbacks: the C
|
|
// d_func function pointers (daemons.c / daemon.c).
|
|
daemonHandlers [DTurnSee + 1]func(g *RogueGame, arg int)
|
|
}
|
|
|
|
// ripWall is the repeated blank wall line of the tombstone art.
|
|
const ripWall = " | |"
|
|
|
|
// newGameData builds the static tables. Each game gets a fresh copy, which
|
|
// keeps the package free of globals.
|
|
//
|
|
//nolint:funlen,maintidx // a single composite literal holding every C data table
|
|
func newGameData() *gameData {
|
|
return &gameData{
|
|
initStats: Stats{
|
|
Str: 16, Exp: 0, Lvl: 1, ArmorClass: 10, HP: 12,
|
|
Dmg: dice("1x4"), MaxHP: 12,
|
|
},
|
|
|
|
aClass: [NumArmorTypes]int{
|
|
8, // LEATHER
|
|
7, // RING_MAIL
|
|
7, // STUDDED_LEATHER
|
|
6, // SCALE_MAIL
|
|
5, // CHAIN_MAIL
|
|
4, // SPLINT_MAIL
|
|
4, // BANDED_MAIL
|
|
3, // PLATE_MAIL
|
|
},
|
|
|
|
eLevels: []int{
|
|
10, 20, 40, 80, 160, 320, 640, 1300, 2600, 5200, 13000, 26000,
|
|
50000, 100000, 200000, 400000, 800000, 2000000, 4000000, 8000000, 0,
|
|
},
|
|
|
|
trName: [NumTrapTypes]string{
|
|
"a trapdoor",
|
|
"an arrow trap",
|
|
"a sleeping gas trap",
|
|
"a beartrap",
|
|
"a teleport trap",
|
|
"a poison dart trap",
|
|
"a rust trap",
|
|
"a mysterious trap",
|
|
},
|
|
|
|
invTName: []string{"Overwrite", "Slow", "Clear"},
|
|
|
|
monsterTable: [26]MonsterKind{
|
|
/* Name CARRY FLAGS str exp lvl arm hp dmg */
|
|
{"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}},
|
|
},
|
|
|
|
baseThings: [NumThings]ObjInfo{
|
|
{Prob: 26}, // potion
|
|
{Prob: 36}, // scroll
|
|
{Prob: 16}, // food
|
|
{Prob: 7}, // weapon
|
|
{Prob: 7}, // armor
|
|
{Prob: 4}, // ring
|
|
{Prob: 4}, // stick
|
|
},
|
|
|
|
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},
|
|
{Name: "scale mail", Prob: 13, Worth: 30},
|
|
{Name: "chain mail", Prob: 12, Worth: 75},
|
|
{Name: "splint mail", Prob: 10, Worth: 80},
|
|
{Name: "banded mail", Prob: 10, Worth: 90},
|
|
{Name: "plate mail", Prob: 5, Worth: 150},
|
|
},
|
|
|
|
//nolint:dupl // distinct C data tables that merely share their shape
|
|
basePotInfo: [NumPotionTypes]ObjInfo{
|
|
{Name: "confusion", Prob: 7, Worth: 5},
|
|
{Name: "hallucination", Prob: 8, Worth: 5},
|
|
{Name: "poison", Prob: 8, Worth: 5},
|
|
{Name: "gain strength", Prob: 13, Worth: 150},
|
|
{Name: "see invisible", Prob: 3, Worth: 100},
|
|
{Name: "healing", Prob: 13, Worth: 130},
|
|
{Name: "monster detection", Prob: 6, Worth: 130},
|
|
{Name: "magic detection", Prob: 6, Worth: 105},
|
|
{Name: "raise level", Prob: 2, Worth: 250},
|
|
{Name: "extra healing", Prob: 5, Worth: 200},
|
|
{Name: "haste self", Prob: 5, Worth: 190},
|
|
{Name: "restore strength", Prob: 13, Worth: 130},
|
|
{Name: "blindness", Prob: 5, Worth: 5},
|
|
{Name: "levitation", Prob: 6, Worth: 75},
|
|
},
|
|
|
|
//nolint:dupl // distinct C data tables that merely share their shape
|
|
baseRingInfo: [NumRingTypes]ObjInfo{
|
|
{Name: "protection", Prob: 9, Worth: 400},
|
|
{Name: "add strength", Prob: 9, Worth: 400},
|
|
{Name: "sustain strength", Prob: 5, Worth: 280},
|
|
{Name: "searching", Prob: 10, Worth: 420},
|
|
{Name: "see invisible", Prob: 10, Worth: 310},
|
|
{Name: "adornment", Prob: 1, Worth: 10},
|
|
{Name: "aggravate monster", Prob: 10, Worth: 10},
|
|
{Name: "dexterity", Prob: 8, Worth: 440},
|
|
{Name: "increase damage", Prob: 8, Worth: 400},
|
|
{Name: "regeneration", Prob: 4, Worth: 460},
|
|
{Name: "slow digestion", Prob: 9, Worth: 240},
|
|
{Name: "teleportation", Prob: 5, Worth: 30},
|
|
{Name: "stealth", Prob: 7, Worth: 470},
|
|
{Name: "maintain armor", Prob: 5, Worth: 380},
|
|
},
|
|
|
|
baseScrInfo: [NumScrollTypes]ObjInfo{
|
|
{Name: "monster confusion", Prob: 7, Worth: 140},
|
|
{Name: "magic mapping", Prob: 4, Worth: 150},
|
|
{Name: "hold monster", Prob: 2, Worth: 180},
|
|
{Name: "sleep", Prob: 3, Worth: 5},
|
|
{Name: "enchant armor", Prob: 7, Worth: 160},
|
|
{Name: "identify potion", Prob: 10, Worth: 80},
|
|
{Name: "identify scroll", Prob: 10, Worth: 80},
|
|
{Name: "identify weapon", Prob: 6, Worth: 80},
|
|
{Name: "identify armor", Prob: 7, Worth: 100},
|
|
{Name: "identify ring, wand or staff", Prob: 10, Worth: 115},
|
|
{Name: "scare monster", Prob: 3, Worth: 200},
|
|
{Name: "food detection", Prob: 2, Worth: 60},
|
|
{Name: "teleportation", Prob: 5, Worth: 165},
|
|
{Name: "enchant weapon", Prob: 8, Worth: 150},
|
|
{Name: "create monster", Prob: 4, Worth: 75},
|
|
{Name: "remove curse", Prob: 7, Worth: 105},
|
|
{Name: "aggravate monsters", Prob: 3, Worth: 20},
|
|
{Name: "protect armor", Prob: 2, Worth: 250},
|
|
},
|
|
|
|
baseWeapInfo: [NumWeaponTypes + 1]ObjInfo{
|
|
{Name: "mace", Prob: 11, Worth: 8},
|
|
{Name: "long sword", Prob: 11, Worth: 15},
|
|
{Name: "short bow", Prob: 12, Worth: 15},
|
|
{Name: "arrow", Prob: 12, Worth: 1},
|
|
{Name: "dagger", Prob: 8, Worth: 3},
|
|
{Name: "two handed sword", Prob: 10, Worth: 75},
|
|
{Name: "dart", Prob: 12, Worth: 2},
|
|
{Name: "shuriken", Prob: 12, Worth: 5},
|
|
{Name: "spear", Prob: 12, Worth: 5},
|
|
{}, // DO NOT REMOVE: fake entry for dragon's breath
|
|
},
|
|
|
|
//nolint:dupl // distinct C data tables that merely share their shape
|
|
baseWsInfo: [NumWandTypes]ObjInfo{
|
|
{Name: "light", Prob: 12, Worth: 250},
|
|
{Name: "invisibility", Prob: 6, Worth: 5},
|
|
{Name: "lightning", Prob: 3, Worth: 330},
|
|
{Name: "fire", Prob: 3, Worth: 330},
|
|
{Name: "cold", Prob: 3, Worth: 330},
|
|
{Name: "polymorph", Prob: 15, Worth: 310},
|
|
{Name: "magic missile", Prob: 10, Worth: 170},
|
|
{Name: "haste monster", Prob: 10, Worth: 5},
|
|
{Name: "slow monster", Prob: 11, Worth: 350},
|
|
{Name: "drain life", Prob: 9, Worth: 300},
|
|
{Name: "nothing", Prob: 1, Worth: 5},
|
|
{Name: "teleport away", Prob: 6, Worth: 340},
|
|
{Name: "teleport to", Prob: 6, Worth: 50},
|
|
{Name: "cancellation", Prob: 5, Worth: 280},
|
|
},
|
|
|
|
rainbow: []string{
|
|
"amber", "aquamarine", "black", "blue", "brown", "clear", "crimson",
|
|
"cyan", "ecru", "gold", "green", "grey", "magenta", "orange", "pink",
|
|
"plaid", "purple", "red", "silver", "tan", "tangerine", "topaz",
|
|
"turquoise", "vermilion", "violet", "white", "yellow",
|
|
},
|
|
|
|
//nolint:misspell // "ther" is a C scroll syllable, kept faithfully
|
|
sylls: []string{
|
|
"a", "ab", "ag", "aks", "ala", "an", "app", "arg", "arze", "ash",
|
|
"bek", "bie", "bit", "bjor", "blu", "bot", "bu", "byt", "comp",
|
|
"con", "cos", "cre", "dalf", "dan", "den", "do", "e", "eep", "el",
|
|
"eng", "er", "ere", "erk", "esh", "evs", "fa", "fid", "fri", "fu",
|
|
"gan", "gar", "glen", "gop", "gre", "ha", "hyd", "i", "ing", "ip",
|
|
"ish", "it", "ite", "iv", "jo", "kho", "kli", "klis", "la", "lech",
|
|
"mar", "me", "mi", "mic", "mik", "mon", "mung", "mur", "nej",
|
|
"nelg", "nep", "ner", "nes", "nes", "nih", "nin", "o", "od", "ood",
|
|
"org", "orn", "ox", "oxy", "pay", "ple", "plu", "po", "pot",
|
|
"prok", "re", "rea", "rhov", "ri", "ro", "rog", "rok", "rol", "sa",
|
|
"san", "sat", "sef", "seh", "shu", "ski", "sna", "sne", "snik",
|
|
"sno", "so", "sol", "sri", "sta", "sun", "ta", "tab", "tem",
|
|
"ther", "ti", "tox", "trol", "tue", "turs", "u", "ulk", "um", "un",
|
|
"uni", "ur", "val", "viv", "vly", "vom", "wah", "wed", "werg",
|
|
"wex", "whon", "wun", "xo", "y", "yot", "yu", "zant", "zeb", "zim",
|
|
"zok", "zon", "zum",
|
|
},
|
|
|
|
stoneTable: []Stone{
|
|
{"agate", 25}, {"alexandrite", 40}, {"amethyst", 50},
|
|
{"carnelian", 40}, {"diamond", 300}, {"emerald", 300},
|
|
{"germanium", 225}, {"granite", 5}, {"garnet", 50},
|
|
{"jade", 150}, {"kryptonite", 300}, {"lapis lazuli", 50},
|
|
{"moonstone", 50}, {"obsidian", 15}, {"onyx", 60},
|
|
{"opal", 200}, {"pearl", 220}, {"peridot", 63},
|
|
{"ruby", 350}, {"sapphire", 285}, {"stibotantalite", 200},
|
|
{"tiger eye", 50}, {"topaz", 60}, {"turquoise", 70},
|
|
{"taaffeite", 300}, {"zircon", 80},
|
|
},
|
|
|
|
woods: []string{
|
|
"avocado wood", "balsa", "bamboo", "banyan", "birch", "cedar",
|
|
"cherry", "cinnibar", "cypress", "dogwood", "driftwood", "ebony",
|
|
"elm", "eucalyptus", "fall", "hemlock", "holly", "ironwood",
|
|
"kukui wood", "mahogany", "manzanita", "maple", "oaken",
|
|
"persimmon wood", "pecan", "pine", "poplar", "redwood", "rosewood",
|
|
"spruce", "teak", "walnut", "zebrawood",
|
|
},
|
|
|
|
metals: []string{
|
|
"aluminum", "beryllium", "bone", "brass", "bronze", "copper",
|
|
"electrum", "gold", "iron", "lead", "magnesium", "mercury",
|
|
"nickel", "pewter", "platinum", "steel", "silver", "silicon",
|
|
"tin", "titanium", "tungsten", "zinc",
|
|
},
|
|
|
|
helpStr: []helpEntry{
|
|
{'?', " prints help", true},
|
|
{'/', " identify object", true},
|
|
{'h', " left", true},
|
|
{'j', " down", true},
|
|
{'k', " up", true},
|
|
{'l', " right", true},
|
|
{'y', " up & left", true},
|
|
{'u', " up & right", true},
|
|
{'b', " down & left", true},
|
|
{'n', " down & right", true},
|
|
{'H', " run left", false},
|
|
{'J', " run down", false},
|
|
{'K', " run up", false},
|
|
{'L', " run right", false},
|
|
{'Y', " run up & left", false},
|
|
{'U', " run up & right", false},
|
|
{'B', " run down & left", false},
|
|
{'N', " run down & right", false},
|
|
{CTRL('H'), " run left until adjacent", false},
|
|
{CTRL('J'), " run down until adjacent", false},
|
|
{CTRL('K'), " run up until adjacent", false},
|
|
{CTRL('L'), " run right until adjacent", false},
|
|
{CTRL('Y'), " run up & left until adjacent", false},
|
|
{CTRL('U'), " run up & right until adjacent", false},
|
|
{CTRL('B'), " run down & left until adjacent", false},
|
|
{CTRL('N'), " run down & right until adjacent", false},
|
|
{0, " <SHIFT><dir>: run that way", true},
|
|
{0, " <CTRL><dir>: run till adjacent", true},
|
|
{'f', "<dir> fight till death or near death", true},
|
|
{'t', "<dir> throw something", true},
|
|
{'m', "<dir> move onto without picking up", true},
|
|
{'z', "<dir> zap a wand in a direction", true},
|
|
{'^', "<dir> identify trap type", true},
|
|
{'s', " search for trap/secret door", true},
|
|
{'>', " go down a staircase", true},
|
|
{'<', " go up a staircase", true},
|
|
{'.', " rest for a turn", true},
|
|
{',', " pick something up", true},
|
|
{'i', " inventory", true},
|
|
{'I', " inventory single item", true},
|
|
{'q', " quaff potion", true},
|
|
{'r', " read scroll", true},
|
|
{'e', " eat food", true},
|
|
{'w', " wield a weapon", true},
|
|
{'W', " wear armor", true},
|
|
{'T', " take armor off", true},
|
|
{'P', " put on ring", true},
|
|
{'R', " remove ring", true},
|
|
{'d', " drop object", true},
|
|
{'c', " call object", true},
|
|
{'a', " repeat last command", true},
|
|
{')', " print current weapon", true},
|
|
{']', " print current armor", true},
|
|
{'=', " print current rings", true},
|
|
{'@', " print current stats", true},
|
|
{'D', " recall what's been discovered", true},
|
|
{'o', " examine/set options", true},
|
|
{CTRL('R'), " redraw screen", true},
|
|
{CTRL('P'), " repeat last message", true},
|
|
{Escape, " cancel command", true},
|
|
{'S', " save game", true},
|
|
{'Q', " quit", true},
|
|
{'!', " shell escape", true},
|
|
{'F', "<dir> fight till either of you dies", true},
|
|
{'v', " print version number", true},
|
|
},
|
|
|
|
hNames: [8]string{
|
|
" scored an excellent hit on ",
|
|
" hit ",
|
|
" have injured ",
|
|
" swing and hit ",
|
|
" scored an excellent hit on ",
|
|
" hit ",
|
|
" has injured ",
|
|
" swings and hits ",
|
|
},
|
|
|
|
mNames: [8]string{
|
|
" miss",
|
|
" swing and miss",
|
|
" barely miss",
|
|
" don't hit",
|
|
" misses",
|
|
" swings and misses",
|
|
" barely misses",
|
|
" doesn't hit",
|
|
},
|
|
|
|
strPlus: [32]int{
|
|
-7, -6, -5, -4, -3, -2, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
|
|
1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3,
|
|
},
|
|
|
|
addDam: [32]int{
|
|
-7, -6, -5, -4, -3, -2, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3,
|
|
3, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6,
|
|
},
|
|
|
|
lvlMons: [26]byte{
|
|
'K', 'E', 'B', 'S', 'H', 'I', 'R', 'O', 'Z', 'L', 'C', 'Q', 'A',
|
|
'N', 'Y', 'F', 'T', 'W', 'P', 'X', 'U', 'M', 'V', 'G', 'J', 'D',
|
|
},
|
|
|
|
wandMons: [26]byte{
|
|
'K', 'E', 'B', 'S', 'H', 0, 'R', 'O', 'Z', 0, 'C', 'Q', 'A',
|
|
0, 'Y', 0, 'T', 'W', 'P', 0, 'U', 'M', 'V', 'G', 'J', 0,
|
|
},
|
|
|
|
ringUses: [NumRingTypes]int{
|
|
1, // R_PROTECT
|
|
1, // R_ADDSTR
|
|
1, // R_SUSTSTR
|
|
-3, // R_SEARCH
|
|
-5, // R_SEEINVIS
|
|
0, // R_NOP
|
|
0, // R_AGGR
|
|
-3, // R_ADDHIT
|
|
-3, // R_ADDDAM
|
|
2, // R_REGEN
|
|
-2, // R_DIGEST
|
|
0, // R_TELEPORT
|
|
1, // R_STEALTH
|
|
1, // R_SUSTARM
|
|
},
|
|
|
|
initWeaps: [NumWeaponTypes]weaponSetup{
|
|
{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
|
|
},
|
|
|
|
pActions: [NumPotionTypes]pact{
|
|
PotionConfusion: {Confused, DUnconfuse, HuhDuration,
|
|
"what a tripy feeling!",
|
|
"wait, what's going on here. Huh? What? Who?"},
|
|
PotionLSD: {Hallucinating, DComeDown, SeeDuration,
|
|
"Oh, wow! Everything seems so cosmic!",
|
|
"Oh, wow! Everything seems so cosmic!"},
|
|
PotionSeeInvisible: {CanSeeInvisible, DUnsee, SeeDuration, "", ""},
|
|
PotionBlindness: {Blind, DSight, SeeDuration,
|
|
"oh, bummer! Everything is dark! Help!",
|
|
"a cloak of darkness falls around you"},
|
|
PotionLevitation: {Levitating, DLand, HealTime,
|
|
"oh, wow! You're floating in the air!",
|
|
"you start to float in the air"},
|
|
},
|
|
|
|
idType: [ScrollIdentifyRingOrStick + 1]ObjectKind{
|
|
ScrollIdentifyPotion: KindPotion,
|
|
ScrollIdentifyScroll: KindScroll,
|
|
ScrollIdentifyWeapon: KindWeapon,
|
|
ScrollIdentifyArmor: KindArmor,
|
|
ScrollIdentifyRingOrStick: KindRingOrStick,
|
|
},
|
|
|
|
rdesConn: [MaxRooms][MaxRooms]bool{
|
|
{false, true, false, true, false, false, false, false, false},
|
|
{true, false, true, false, true, false, false, false, false},
|
|
{false, true, false, false, false, true, false, false, false},
|
|
{true, false, false, false, true, false, true, false, false},
|
|
{false, true, false, true, false, true, false, true, false},
|
|
{false, false, true, false, true, false, false, false, true},
|
|
{false, false, false, true, false, false, false, true, false},
|
|
{false, false, false, false, true, false, true, false, true},
|
|
{false, false, false, false, false, true, false, true, false},
|
|
},
|
|
|
|
thingList: []byte{
|
|
Potion, Scroll, Ring, Stick, Food, Weapon, Armor, Stairs, Gold, Amulet,
|
|
},
|
|
|
|
identList: []helpEntry{
|
|
{'|', "wall of a room", false},
|
|
{'-', "wall of a room", false},
|
|
{Gold, goldName, false},
|
|
{Stairs, "a staircase", false},
|
|
{Door, "door", false},
|
|
{Floor, "room floor", false},
|
|
{PlayerCh, "you", false},
|
|
{Passage, "passage", false},
|
|
{Trap, "trap", false},
|
|
{Potion, potionName, false},
|
|
{Scroll, scrollName, false},
|
|
{Food, "food", false},
|
|
{Weapon, "weapon", false},
|
|
{' ', "solid rock", false},
|
|
{Armor, "armor", false},
|
|
{Amulet, "the Amulet of Yendor", false},
|
|
{Ring, ringName, false},
|
|
{Stick, "wand or staff", false},
|
|
},
|
|
|
|
hungerStateName: [4]string{"", "Hungry", "Weak", "Faint"},
|
|
|
|
ripArt: []string{
|
|
" __________",
|
|
" / \\",
|
|
" / REST \\",
|
|
" / IN \\",
|
|
" / PEACE \\",
|
|
" / \\",
|
|
ripWall,
|
|
ripWall,
|
|
" | killed by a |",
|
|
ripWall,
|
|
" | 1980 |",
|
|
" *| * * * | *",
|
|
" ________)/\\\\_//(\\/(/\\)/\\//\\/|_)_______",
|
|
},
|
|
|
|
killnameTable: []helpEntry{
|
|
{'a', "arrow", true},
|
|
{'b', "bolt", true},
|
|
{'d', "dart", true},
|
|
{'h', "hypothermia", false},
|
|
{'s', "starvation", false},
|
|
},
|
|
|
|
scoreReasons: [4]string{
|
|
"killed",
|
|
"quit",
|
|
"A total winner",
|
|
"killed with Amulet",
|
|
},
|
|
|
|
quaffHandlers: [NumPotionTypes]func(*RogueGame, bool){
|
|
PotionConfusion: (*RogueGame).quaffConfusion,
|
|
PotionLSD: (*RogueGame).quaffLSD,
|
|
PotionPoison: (*RogueGame).quaffPoison,
|
|
PotionGainStrength: (*RogueGame).quaffGainStrength,
|
|
PotionSeeInvisible: (*RogueGame).quaffSeeInvisible,
|
|
PotionHealing: (*RogueGame).quaffHealing,
|
|
PotionDetectMonsters: (*RogueGame).quaffDetectMonsters,
|
|
PotionDetectMagic: (*RogueGame).quaffDetectMagic,
|
|
PotionRaiseLevel: (*RogueGame).quaffRaiseLevel,
|
|
PotionExtraHealing: (*RogueGame).quaffExtraHealing,
|
|
PotionHaste: (*RogueGame).quaffHaste,
|
|
PotionRestoreStrength: (*RogueGame).quaffRestoreStrength,
|
|
PotionBlindness: (*RogueGame).quaffBlindness,
|
|
PotionLevitation: (*RogueGame).quaffLevitation,
|
|
},
|
|
|
|
readHandlers: [NumScrollTypes]func(*RogueGame, *Object){
|
|
ScrollMonsterConfusion: (*RogueGame).readMonsterConfusion,
|
|
ScrollMagicMapping: (*RogueGame).readMagicMapping,
|
|
ScrollHoldMonster: (*RogueGame).readHoldMonster,
|
|
ScrollSleep: (*RogueGame).readSleep,
|
|
ScrollEnchantArmor: (*RogueGame).readEnchantArmor,
|
|
ScrollIdentifyPotion: (*RogueGame).readIdentify,
|
|
ScrollIdentifyScroll: (*RogueGame).readIdentify,
|
|
ScrollIdentifyWeapon: (*RogueGame).readIdentify,
|
|
ScrollIdentifyArmor: (*RogueGame).readIdentify,
|
|
ScrollIdentifyRingOrStick: (*RogueGame).readIdentify,
|
|
ScrollScareMonster: (*RogueGame).readScareMonster,
|
|
ScrollFoodDetection: (*RogueGame).readFoodDetection,
|
|
ScrollTeleportation: (*RogueGame).readTeleportation,
|
|
ScrollEnchantWeapon: (*RogueGame).readEnchantWeapon,
|
|
ScrollCreateMonster: (*RogueGame).readCreateMonster,
|
|
ScrollRemoveCurse: (*RogueGame).readRemoveCurse,
|
|
ScrollAggravateMonsters: (*RogueGame).readAggravateMonsters,
|
|
ScrollProtectArmor: (*RogueGame).readProtectArmor,
|
|
},
|
|
|
|
zapHandlers: [NumWandTypes]func(*RogueGame, *Object) bool{
|
|
WandLight: (*RogueGame).zapLight,
|
|
WandInvisibility: (*RogueGame).zapInvisibility,
|
|
WandLightning: (*RogueGame).zapBolt,
|
|
WandFire: (*RogueGame).zapBolt,
|
|
WandCold: (*RogueGame).zapBolt,
|
|
WandPolymorph: (*RogueGame).zapPolymorph,
|
|
WandMagicMissile: (*RogueGame).zapMagicMissile,
|
|
WandHasteMonster: (*RogueGame).zapSpeed,
|
|
WandSlowMonster: (*RogueGame).zapSpeed,
|
|
WandDrainLife: (*RogueGame).zapDrainLife,
|
|
WandTeleportAway: (*RogueGame).zapTeleport,
|
|
WandTeleportTo: (*RogueGame).zapTeleport,
|
|
WandCancellation: (*RogueGame).zapCancellation,
|
|
},
|
|
|
|
hitHandlers: [26]func(*RogueGame, *Monster, string) bool{
|
|
0: (*RogueGame).hitAquator, // 'A' - 'A'
|
|
'F' - 'A': (*RogueGame).hitFlytrap,
|
|
'I' - 'A': (*RogueGame).hitIceMonster,
|
|
'L' - 'A': (*RogueGame).hitLeprechaun,
|
|
'N' - 'A': (*RogueGame).hitNymph,
|
|
'R' - 'A': (*RogueGame).hitRattlesnake,
|
|
'V' - 'A': (*RogueGame).hitLifeDrainer,
|
|
'W' - 'A': (*RogueGame).hitLifeDrainer,
|
|
},
|
|
|
|
commandHandlers: map[byte]func(*RogueGame){
|
|
',': (*RogueGame).pickupCommand,
|
|
'!': (*RogueGame).shell,
|
|
'h': func(g *RogueGame) { g.moveHero(0, -1) },
|
|
'j': func(g *RogueGame) { g.moveHero(1, 0) },
|
|
'k': func(g *RogueGame) { g.moveHero(-1, 0) },
|
|
'l': func(g *RogueGame) { g.moveHero(0, 1) },
|
|
'y': func(g *RogueGame) { g.moveHero(-1, -1) },
|
|
'u': func(g *RogueGame) { g.moveHero(-1, 1) },
|
|
'b': func(g *RogueGame) { g.moveHero(1, -1) },
|
|
'n': func(g *RogueGame) { g.moveHero(1, 1) },
|
|
'H': func(g *RogueGame) { g.startRun('h') },
|
|
'J': func(g *RogueGame) { g.startRun('j') },
|
|
'K': func(g *RogueGame) { g.startRun('k') },
|
|
'L': func(g *RogueGame) { g.startRun('l') },
|
|
'Y': func(g *RogueGame) { g.startRun('y') },
|
|
'U': func(g *RogueGame) { g.startRun('u') },
|
|
'B': func(g *RogueGame) { g.startRun('b') },
|
|
'N': func(g *RogueGame) { g.startRun('n') },
|
|
't': func(g *RogueGame) {
|
|
if !g.promptDirection() {
|
|
g.After = false
|
|
} else {
|
|
g.missile(g.Delta.Y, g.Delta.X)
|
|
}
|
|
},
|
|
'q': (*RogueGame).quaff,
|
|
'Q': func(g *RogueGame) {
|
|
g.After = false
|
|
g.QComm = true
|
|
g.quit(0)
|
|
g.QComm = false
|
|
},
|
|
'i': func(g *RogueGame) {
|
|
g.After = false
|
|
g.inventory(g.Player.Pack, 0)
|
|
},
|
|
'I': func(g *RogueGame) {
|
|
g.After = false
|
|
g.pickyInven()
|
|
},
|
|
'd': (*RogueGame).dropIt,
|
|
'r': (*RogueGame).readScroll,
|
|
'e': (*RogueGame).eat,
|
|
'w': (*RogueGame).wield,
|
|
'W': (*RogueGame).wear,
|
|
'T': (*RogueGame).takeOff,
|
|
'P': (*RogueGame).ringOn,
|
|
'R': (*RogueGame).ringOff,
|
|
'o': func(g *RogueGame) {
|
|
g.option()
|
|
g.After = false
|
|
},
|
|
'c': func(g *RogueGame) {
|
|
g.call()
|
|
g.After = false
|
|
},
|
|
'>': func(g *RogueGame) {
|
|
g.After = false
|
|
g.dLevel()
|
|
},
|
|
'<': func(g *RogueGame) {
|
|
g.After = false
|
|
g.uLevel()
|
|
},
|
|
'?': func(g *RogueGame) {
|
|
g.After = false
|
|
g.help()
|
|
},
|
|
'/': func(g *RogueGame) {
|
|
g.After = false
|
|
g.identify()
|
|
},
|
|
's': (*RogueGame).search,
|
|
'z': func(g *RogueGame) {
|
|
if g.promptDirection() {
|
|
g.doZap()
|
|
} else {
|
|
g.After = false
|
|
}
|
|
},
|
|
'D': func(g *RogueGame) {
|
|
g.After = false
|
|
g.discovered()
|
|
},
|
|
CTRL('P'): func(g *RogueGame) {
|
|
g.After = false
|
|
g.msg("%s", g.Msgs.Huh)
|
|
},
|
|
CTRL('R'): func(g *RogueGame) {
|
|
g.After = false
|
|
// C forces a full repaint here (clearok + wrefresh on
|
|
// curscr), not the diffing refresh: the command exists
|
|
// for screens the game's own record no longer matches.
|
|
g.repaint()
|
|
},
|
|
'v': func(g *RogueGame) {
|
|
g.After = false
|
|
g.msg("version %s. (mctesq was here)", Release)
|
|
},
|
|
'S': func(g *RogueGame) {
|
|
g.After = false
|
|
g.saveGame()
|
|
},
|
|
'.': func(*RogueGame) {
|
|
// rest command
|
|
},
|
|
' ': func(g *RogueGame) {
|
|
g.After = false // "legal" illegal command
|
|
},
|
|
'^': (*RogueGame).identifyTrapCommand,
|
|
'+': (*RogueGame).wizardToggleCommand,
|
|
Escape: func(g *RogueGame) {
|
|
g.DoorStop = false
|
|
g.Count = 0
|
|
g.After = false
|
|
g.Again = false
|
|
},
|
|
')': func(g *RogueGame) {
|
|
g.current(g.Player.CurWeapon, "wielding", "")
|
|
},
|
|
']': func(g *RogueGame) {
|
|
g.current(g.Player.CurArmor, "wearing", "")
|
|
},
|
|
'=': func(g *RogueGame) {
|
|
g.current(g.Player.CurRing[Left], "wearing",
|
|
g.chooseTerse("(L)", "on left hand"))
|
|
g.current(g.Player.CurRing[Right], "wearing",
|
|
g.chooseTerse("(R)", "on right hand"))
|
|
},
|
|
'@': func(g *RogueGame) {
|
|
g.StatMsg = true
|
|
g.status()
|
|
g.StatMsg = false
|
|
g.After = false
|
|
},
|
|
},
|
|
|
|
trapHandlers: [NumTrapTypes]func(*RogueGame, Coord){
|
|
TrapDoor: (*RogueGame).trapFall,
|
|
TrapArrow: (*RogueGame).trapArrow,
|
|
TrapSleep: (*RogueGame).trapSleep,
|
|
TrapBear: (*RogueGame).trapBear,
|
|
TrapTeleport: (*RogueGame).trapTeleport,
|
|
TrapDart: (*RogueGame).trapDart,
|
|
TrapRust: (*RogueGame).trapRust,
|
|
TrapMystery: (*RogueGame).trapMystery,
|
|
},
|
|
|
|
daemonHandlers: [DTurnSee + 1]func(*RogueGame, int){
|
|
DRollwand: (*RogueGame).rollwand,
|
|
DDoctor: (*RogueGame).doctor,
|
|
DStomach: (*RogueGame).stomach,
|
|
DRunners: (*RogueGame).runners,
|
|
DSwander: (*RogueGame).swander,
|
|
DNohaste: (*RogueGame).nohaste,
|
|
DUnconfuse: (*RogueGame).unconfuse,
|
|
DUnsee: (*RogueGame).unsee,
|
|
DSight: (*RogueGame).sight,
|
|
DVisuals: (*RogueGame).visuals,
|
|
DComeDown: (*RogueGame).comeDown,
|
|
DLand: (*RogueGame).land,
|
|
DTurnSee: func(g *RogueGame, arg int) {
|
|
g.turnSee(arg != 0)
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// The three effect-table lookups below are the guarded form of a raw
|
|
// index into quaffHandlers/readHandlers/zapHandlers. An object whose
|
|
// Which is out of range for its kind reports no handler rather than
|
|
// panicking, which lands on the same do-nothing behavior C's switches
|
|
// had when no case matched.
|
|
|
|
// quaffHandler returns the potion effect for obj, or nil when obj is not
|
|
// a potion the table knows about.
|
|
func (d *gameData) quaffHandler(obj *Object) func(g *RogueGame, trip bool) {
|
|
if !obj.hasValidWhich() {
|
|
return nil
|
|
}
|
|
|
|
return d.quaffHandlers[obj.PotionKind()]
|
|
}
|
|
|
|
// readHandler returns the scroll effect for obj, or nil when obj is not
|
|
// a scroll the table knows about.
|
|
func (d *gameData) readHandler(obj *Object) func(g *RogueGame, obj *Object) {
|
|
if !obj.hasValidWhich() {
|
|
return nil
|
|
}
|
|
|
|
return d.readHandlers[obj.ScrollKind()]
|
|
}
|
|
|
|
// zapHandler returns the wand effect for obj, or nil when obj is not a
|
|
// wand the table knows about.
|
|
func (d *gameData) zapHandler(obj *Object) func(g *RogueGame, obj *Object) bool {
|
|
if !obj.hasValidWhich() {
|
|
return nil
|
|
}
|
|
|
|
return d.zapHandlers[obj.WandKind()]
|
|
}
|
|
|
|
// identifyType reads extern.c's identify-scroll to item-kind map,
|
|
// returning KindNone for any scroll past the last identify scroll (the
|
|
// table is shorter than the scroll table it is keyed by). No scroll that
|
|
// can reach readIdentify is past that end, so the guard is defensive
|
|
// rather than a live bound; it exists so a future table resize cannot
|
|
// turn the lookup into a panic.
|
|
func (d *gameData) identifyType(kind ScrollKind) ObjectKind {
|
|
if kind < 0 || int(kind) >= len(d.idType) {
|
|
return KindNone
|
|
}
|
|
|
|
return d.idType[kind]
|
|
}
|
|
|
|
// armorClass reads extern.c a_class[] for an armor object's Which,
|
|
// returning 0 when Which is out of range. Only a malformed object can
|
|
// hit that arm — createObj and Restore both reject one — but a bounds
|
|
// check here is what keeps a bad index from panicking the process
|
|
// instead of merely naming a suit of armor oddly.
|
|
func (d *gameData) armorClass(which int) int {
|
|
if which < 0 || which >= int(NumArmorTypes) {
|
|
return 0
|
|
}
|
|
|
|
return d.aClass[which]
|
|
}
|
|
|
|
// Version strings (vers.c). The encstr/statlist XOR keys are not ported:
|
|
// the Go save format does not use them.
|
|
const (
|
|
Release = "5.4.4"
|
|
Version = "rogue (git.eeqj.de/sneak/rgoue port of rogueforge 5.4.4)"
|
|
)
|