C's command.c 317-338 has a `when '+'` arm in the main command switch, under #ifdef MASTER, that toggles wizard mode. The port had no '+' at all, so the key fell through dispatchKey's default to illcom and answered "illegal command '+'". The password half of that arm was dropped deliberately (wizard mode is ROGUE_WIZARD configuration) and is recorded in ARCHITECTURE.md section 9. The leave half was lost silently, and it is a different decision: it does not touch the password machinery. The substantive part of it is turn_see(TRUE) rather than the flag -- wizard sight draws every monster the hero cannot see, so without the re-hide there is no way back to normal visibility, and clearing the flag alone would leave the screen lying. New wizardToggleCommand, registered in commandHandlers between '^' and Escape, which is C's own switch order. Because C's arm sits in the main switch rather than the `if (wizard) switch (ch)` sub-switch that wizardCommand ports, it is reachable whether or not wizard is set, so the non-wizard case was a divergence too. It resolves the way the dropped passwd() forces: a password check that no longer exists can never succeed, so the else arm is what C did on a wrong answer -- the message "sorry", with no prompt, since nothing typed into one could change the outcome, and none of the noscore/turn_see(FALSE) bookkeeping of C's unreachable success branch. The choice is stated in the doc comment and in section 9. Two tests drive '+' through g.dispatch. The wizard one spawns a phantom (ISINVIS straight from the monster table, so seeMonst is false and it is on screen only because wizard sight put it there), asserts the precondition, then asserts the flag cleared, SenseMonsters cleared, the cell restored to the map char under the monster with standout off, the exact message text, and After false. Deleting the turnSee(true) call fails it on all three visibility assertions. The other pins "sorry". No RNG call is added: the turn_off arm of turn_see never reaches rnd. TestSeedCompatItemTables is green against the untouched golden.
932 lines
33 KiB
Go
932 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
|
|
g.refresh()
|
|
},
|
|
'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)"
|
|
)
|