Keep track of how a role|race|gender|alignment option got its value so that role:!Tourist in .nethackrc and role:!Priest in NETHACKOPTIONS yield 'role:!Priest' rather than merging into 'role:!Priest !Tourist'. It also doesn't write the value into new config file for #saveoptions if that value comes from environment or command line (not applicable since the command line arguments for role,&c don't go through options handling). Also, the old config file value takes precedence over the current game's value file so that 'role:random' doesn't become 'role:Healer' or such in a new config after the random value gets picked for play. This only tracks the role, race, gender, and alignment options but the concept could be extended to all options. The data would need to be saved and restored if values set interactively need to be retained in restore sessions (doesn't apply to role,&c since those don't change during play).
3020 lines
101 KiB
C
3020 lines
101 KiB
C
/* NetHack 3.7 role.c $NHDT-Date: 1596498206 2020/08/03 23:43:26 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.71 $ */
|
|
/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985-1999. */
|
|
/*-Copyright (c) Robert Patrick Rankin, 2012. */
|
|
/* NetHack may be freely redistributed. See license for details. */
|
|
|
|
#include "hack.h"
|
|
|
|
/*** Table of all roles ***/
|
|
/* According to AD&D, HD for some classes (ex. Wizard) should be smaller
|
|
* (4-sided for wizards). But this is not AD&D, and using the AD&D
|
|
* rule here produces an unplayable character. Thus I have used a minimum
|
|
* of an 10-sided hit die for everything. Another AD&D change: wizards get
|
|
* a minimum strength of 4 since without one you can't teleport or cast
|
|
* spells. --KAA
|
|
*
|
|
* As the wizard has been updated (wizard patch 5 jun '96) their HD can be
|
|
* brought closer into line with AD&D. This forces wizards to use magic more
|
|
* and distance themselves from their attackers. --LSZ
|
|
*
|
|
* With the introduction of races, some hit points and energy
|
|
* has been reallocated for each race. The values assigned
|
|
* to the roles has been reduced by the amount allocated to
|
|
* humans. --KMH
|
|
*
|
|
* God names use a leading underscore to flag goddesses.
|
|
*/
|
|
const struct Role roles[NUM_ROLES+1] = {
|
|
{ { "Archeologist", 0 },
|
|
{ { "Digger", 0 },
|
|
{ "Field Worker", 0 },
|
|
{ "Investigator", 0 },
|
|
{ "Exhumer", 0 },
|
|
{ "Excavator", 0 },
|
|
{ "Spelunker", 0 },
|
|
{ "Speleologist", 0 },
|
|
{ "Collector", 0 },
|
|
{ "Curator", 0 } },
|
|
"Quetzalcoatl", "Camaxtli", "Huhetotl", /* Central American */
|
|
"Arc",
|
|
"the College of Archeology",
|
|
"the Tomb of the Toltec Kings",
|
|
PM_ARCHEOLOGIST,
|
|
NON_PM,
|
|
PM_LORD_CARNARVON,
|
|
PM_STUDENT,
|
|
PM_MINION_OF_HUHETOTL,
|
|
NON_PM,
|
|
PM_HUMAN_MUMMY,
|
|
S_SNAKE,
|
|
S_MUMMY,
|
|
ART_ORB_OF_DETECTION,
|
|
MH_HUMAN | MH_DWARF | MH_GNOME | ROLE_MALE | ROLE_FEMALE | ROLE_LAWFUL
|
|
| ROLE_NEUTRAL,
|
|
/* Str Int Wis Dex Con Cha */
|
|
{ 7, 10, 10, 7, 7, 7 },
|
|
{ 20, 20, 20, 10, 20, 10 },
|
|
/* Init Lower Higher */
|
|
{ 11, 0, 0, 8, 1, 0 }, /* Hit points */
|
|
{ 1, 0, 0, 1, 0, 1 },
|
|
14, /* Energy */
|
|
10,
|
|
5,
|
|
0,
|
|
2,
|
|
10,
|
|
A_INT,
|
|
SPE_MAGIC_MAPPING,
|
|
-4 },
|
|
{ { "Barbarian", 0 },
|
|
{ { "Plunderer", "Plunderess" },
|
|
{ "Pillager", 0 },
|
|
{ "Bandit", 0 },
|
|
{ "Brigand", 0 },
|
|
{ "Raider", 0 },
|
|
{ "Reaver", 0 },
|
|
{ "Slayer", 0 },
|
|
{ "Chieftain", "Chieftainess" },
|
|
{ "Conqueror", "Conqueress" } },
|
|
"Mitra", "Crom", "Set", /* Hyborian */
|
|
"Bar",
|
|
"the Camp of the Duali Tribe",
|
|
"the Duali Oasis",
|
|
PM_BARBARIAN,
|
|
NON_PM,
|
|
PM_PELIAS,
|
|
PM_CHIEFTAIN,
|
|
PM_THOTH_AMON,
|
|
PM_OGRE,
|
|
PM_TROLL,
|
|
S_OGRE,
|
|
S_TROLL,
|
|
ART_HEART_OF_AHRIMAN,
|
|
MH_HUMAN | MH_ORC | ROLE_MALE | ROLE_FEMALE | ROLE_NEUTRAL
|
|
| ROLE_CHAOTIC,
|
|
/* Str Int Wis Dex Con Cha */
|
|
{ 16, 7, 7, 15, 16, 6 },
|
|
{ 30, 6, 7, 20, 30, 7 },
|
|
/* Init Lower Higher */
|
|
{ 14, 0, 0, 10, 2, 0 }, /* Hit points */
|
|
{ 1, 0, 0, 1, 0, 1 },
|
|
10, /* Energy */
|
|
10,
|
|
14,
|
|
0,
|
|
0,
|
|
8,
|
|
A_INT,
|
|
SPE_HASTE_SELF,
|
|
-4 },
|
|
{ { "Caveman", "Cavewoman" },
|
|
{ { "Troglodyte", 0 },
|
|
{ "Aborigine", 0 },
|
|
{ "Wanderer", 0 },
|
|
{ "Vagrant", 0 },
|
|
{ "Wayfarer", 0 },
|
|
{ "Roamer", 0 },
|
|
{ "Nomad", 0 },
|
|
{ "Rover", 0 },
|
|
{ "Pioneer", 0 } },
|
|
"Anu", "_Ishtar", "Anshar", /* Babylonian */
|
|
"Cav",
|
|
"the Caves of the Ancestors",
|
|
"the Dragon's Lair",
|
|
PM_CAVE_DWELLER,
|
|
PM_LITTLE_DOG,
|
|
PM_SHAMAN_KARNOV,
|
|
PM_NEANDERTHAL,
|
|
PM_CHROMATIC_DRAGON,
|
|
PM_BUGBEAR,
|
|
PM_HILL_GIANT,
|
|
S_HUMANOID,
|
|
S_GIANT,
|
|
ART_SCEPTRE_OF_MIGHT,
|
|
MH_HUMAN | MH_DWARF | MH_GNOME | ROLE_MALE | ROLE_FEMALE | ROLE_LAWFUL
|
|
| ROLE_NEUTRAL,
|
|
/* Str Int Wis Dex Con Cha */
|
|
{ 10, 7, 7, 7, 8, 6 },
|
|
{ 30, 6, 7, 20, 30, 7 },
|
|
/* Init Lower Higher */
|
|
{ 14, 0, 0, 8, 2, 0 }, /* Hit points */
|
|
{ 1, 0, 0, 1, 0, 1 },
|
|
10, /* Energy */
|
|
0,
|
|
12,
|
|
0,
|
|
1,
|
|
8,
|
|
A_INT,
|
|
SPE_DIG,
|
|
-4 },
|
|
{ { "Healer", 0 },
|
|
{ { "Rhizotomist", 0 },
|
|
{ "Empiric", 0 },
|
|
{ "Embalmer", 0 },
|
|
{ "Dresser", 0 },
|
|
{ "Medicus ossium", "Medica ossium" },
|
|
{ "Herbalist", 0 },
|
|
{ "Magister", "Magistra" },
|
|
{ "Physician", 0 },
|
|
{ "Chirurgeon", 0 } },
|
|
"_Athena", "Hermes", "Poseidon", /* Greek */
|
|
"Hea",
|
|
"the Temple of Epidaurus",
|
|
"the Temple of Coeus",
|
|
PM_HEALER,
|
|
NON_PM,
|
|
PM_HIPPOCRATES,
|
|
PM_ATTENDANT,
|
|
PM_CYCLOPS,
|
|
PM_GIANT_RAT,
|
|
PM_SNAKE,
|
|
S_RODENT,
|
|
S_YETI,
|
|
ART_STAFF_OF_AESCULAPIUS,
|
|
MH_HUMAN | MH_GNOME | ROLE_MALE | ROLE_FEMALE | ROLE_NEUTRAL,
|
|
/* Str Int Wis Dex Con Cha */
|
|
{ 7, 7, 13, 7, 11, 16 },
|
|
{ 15, 20, 20, 15, 25, 5 },
|
|
/* Init Lower Higher */
|
|
{ 11, 0, 0, 8, 1, 0 }, /* Hit points */
|
|
{ 1, 4, 0, 1, 0, 2 },
|
|
20, /* Energy */
|
|
10,
|
|
3,
|
|
-3,
|
|
2,
|
|
10,
|
|
A_WIS,
|
|
SPE_CURE_SICKNESS,
|
|
-4 },
|
|
{ { "Knight", 0 },
|
|
{ { "Gallant", 0 },
|
|
{ "Esquire", 0 },
|
|
{ "Bachelor", 0 },
|
|
{ "Sergeant", 0 },
|
|
{ "Knight", 0 },
|
|
{ "Banneret", 0 },
|
|
{ "Chevalier", "Chevaliere" },
|
|
{ "Seignieur", "Dame" },
|
|
{ "Paladin", 0 } },
|
|
"Lugh", "_Brigit", "Manannan Mac Lir", /* Celtic */
|
|
"Kni",
|
|
"Camelot Castle",
|
|
"the Isle of Glass",
|
|
PM_KNIGHT,
|
|
PM_PONY,
|
|
PM_KING_ARTHUR,
|
|
PM_PAGE,
|
|
PM_IXOTH,
|
|
PM_QUASIT,
|
|
PM_OCHRE_JELLY,
|
|
S_IMP,
|
|
S_JELLY,
|
|
ART_MAGIC_MIRROR_OF_MERLIN,
|
|
MH_HUMAN | ROLE_MALE | ROLE_FEMALE | ROLE_LAWFUL,
|
|
/* Str Int Wis Dex Con Cha */
|
|
{ 13, 7, 14, 8, 10, 17 },
|
|
{ 30, 15, 15, 10, 20, 10 },
|
|
/* Init Lower Higher */
|
|
{ 14, 0, 0, 8, 2, 0 }, /* Hit points */
|
|
{ 1, 4, 0, 1, 0, 2 },
|
|
10, /* Energy */
|
|
10,
|
|
8,
|
|
-2,
|
|
0,
|
|
9,
|
|
A_WIS,
|
|
SPE_TURN_UNDEAD,
|
|
-4 },
|
|
{ { "Monk", 0 },
|
|
{ { "Candidate", 0 },
|
|
{ "Novice", 0 },
|
|
{ "Initiate", 0 },
|
|
{ "Student of Stones", 0 },
|
|
{ "Student of Waters", 0 },
|
|
{ "Student of Metals", 0 },
|
|
{ "Student of Winds", 0 },
|
|
{ "Student of Fire", 0 },
|
|
{ "Master", 0 } },
|
|
"Shan Lai Ching", "Chih Sung-tzu", "Huan Ti", /* Chinese */
|
|
"Mon",
|
|
"the Monastery of Chan-Sune",
|
|
"the Monastery of the Earth-Lord",
|
|
PM_MONK,
|
|
NON_PM,
|
|
PM_GRAND_MASTER,
|
|
PM_ABBOT,
|
|
PM_MASTER_KAEN,
|
|
PM_EARTH_ELEMENTAL,
|
|
PM_XORN,
|
|
S_ELEMENTAL,
|
|
S_XORN,
|
|
ART_EYES_OF_THE_OVERWORLD,
|
|
MH_HUMAN | ROLE_MALE | ROLE_FEMALE | ROLE_LAWFUL | ROLE_NEUTRAL
|
|
| ROLE_CHAOTIC,
|
|
/* Str Int Wis Dex Con Cha */
|
|
{ 10, 7, 8, 8, 7, 7 },
|
|
{ 25, 10, 20, 20, 15, 10 },
|
|
/* Init Lower Higher */
|
|
{ 12, 0, 0, 8, 1, 0 }, /* Hit points */
|
|
{ 2, 2, 0, 2, 0, 2 },
|
|
10, /* Energy */
|
|
10,
|
|
8,
|
|
-2,
|
|
2,
|
|
20,
|
|
A_WIS,
|
|
SPE_RESTORE_ABILITY,
|
|
-4 },
|
|
{ { "Priest", "Priestess" },
|
|
{ { "Aspirant", 0 },
|
|
{ "Acolyte", 0 },
|
|
{ "Adept", 0 },
|
|
{ "Priest", "Priestess" },
|
|
{ "Curate", 0 },
|
|
{ "Canon", "Canoness" },
|
|
{ "Lama", 0 },
|
|
{ "Patriarch", "Matriarch" },
|
|
{ "High Priest", "High Priestess" } },
|
|
0, 0, 0, /* deities from a randomly chosen other role will be used */
|
|
"Pri",
|
|
"the Great Temple",
|
|
"the Temple of Nalzok",
|
|
PM_CLERIC,
|
|
NON_PM,
|
|
PM_ARCH_PRIEST,
|
|
PM_ACOLYTE,
|
|
PM_NALZOK,
|
|
PM_HUMAN_ZOMBIE,
|
|
PM_WRAITH,
|
|
S_ZOMBIE,
|
|
S_WRAITH,
|
|
ART_MITRE_OF_HOLINESS,
|
|
MH_HUMAN | MH_ELF | ROLE_MALE | ROLE_FEMALE | ROLE_LAWFUL | ROLE_NEUTRAL
|
|
| ROLE_CHAOTIC,
|
|
/* Str Int Wis Dex Con Cha */
|
|
{ 7, 7, 10, 7, 7, 7 },
|
|
{ 15, 10, 30, 15, 20, 10 },
|
|
/* Init Lower Higher */
|
|
{ 12, 0, 0, 8, 1, 0 }, /* Hit points */
|
|
{ 4, 3, 0, 2, 0, 2 },
|
|
10, /* Energy */
|
|
0,
|
|
3,
|
|
-2,
|
|
2,
|
|
10,
|
|
A_WIS,
|
|
SPE_REMOVE_CURSE,
|
|
-4 },
|
|
/* Note: Rogue precedes Ranger so that use of `-R' on the command line
|
|
retains its traditional meaning. */
|
|
{ { "Rogue", 0 },
|
|
{ { "Footpad", 0 },
|
|
{ "Cutpurse", 0 },
|
|
{ "Rogue", 0 },
|
|
{ "Pilferer", 0 },
|
|
{ "Robber", 0 },
|
|
{ "Burglar", 0 },
|
|
{ "Filcher", 0 },
|
|
{ "Magsman", "Magswoman" },
|
|
{ "Thief", 0 } },
|
|
"Issek", "Mog", "Kos", /* Nehwon */
|
|
"Rog",
|
|
"the Thieves' Guild Hall",
|
|
"the Assassins' Guild Hall",
|
|
PM_ROGUE,
|
|
NON_PM,
|
|
PM_MASTER_OF_THIEVES,
|
|
PM_THUG,
|
|
PM_MASTER_ASSASSIN,
|
|
PM_LEPRECHAUN,
|
|
PM_GUARDIAN_NAGA,
|
|
S_NYMPH,
|
|
S_NAGA,
|
|
ART_MASTER_KEY_OF_THIEVERY,
|
|
MH_HUMAN | MH_ORC | ROLE_MALE | ROLE_FEMALE | ROLE_CHAOTIC,
|
|
/* Str Int Wis Dex Con Cha */
|
|
{ 7, 7, 7, 10, 7, 6 },
|
|
{ 20, 10, 10, 30, 20, 10 },
|
|
/* Init Lower Higher */
|
|
{ 10, 0, 0, 8, 1, 0 }, /* Hit points */
|
|
{ 1, 0, 0, 1, 0, 1 },
|
|
11, /* Energy */
|
|
10,
|
|
8,
|
|
0,
|
|
1,
|
|
9,
|
|
A_INT,
|
|
SPE_DETECT_TREASURE,
|
|
-4 },
|
|
{ { "Ranger", 0 },
|
|
{
|
|
#if 0 /* OBSOLETE */
|
|
{"Edhel", "Elleth"},
|
|
{"Edhel", "Elleth"}, /* elf-maid */
|
|
{"Ohtar", "Ohtie"}, /* warrior */
|
|
{"Kano", "Kanie"}, /* commander (Q.) ['a] educated guess,
|
|
until further research- SAC */
|
|
{"Arandur"," Aranduriel"}, /* king's servant, minister (Q.) - guess */
|
|
{"Hir", "Hiril"}, /* lord, lady (S.) ['ir] */
|
|
{"Aredhel", "Arwen"}, /* noble elf, maiden (S.) */
|
|
{"Ernil", "Elentariel"}, /* prince (S.), elf-maiden (Q.) */
|
|
{"Elentar", "Elentari"}, /* Star-king, -queen (Q.) */
|
|
"Solonor Thelandira", "Aerdrie Faenya", "Lolth", /* Elven */
|
|
#endif
|
|
{ "Tenderfoot", 0 },
|
|
{ "Lookout", 0 },
|
|
{ "Trailblazer", 0 },
|
|
{ "Reconnoiterer", "Reconnoiteress" },
|
|
{ "Scout", 0 },
|
|
{ "Arbalester", 0 }, /* One skilled at crossbows */
|
|
{ "Archer", 0 },
|
|
{ "Sharpshooter", 0 },
|
|
{ "Marksman", "Markswoman" } },
|
|
"Mercury", "_Venus", "Mars", /* Roman/planets */
|
|
"Ran",
|
|
"Orion's camp",
|
|
"the cave of the wumpus",
|
|
PM_RANGER,
|
|
PM_LITTLE_DOG /* Orion & canis major */,
|
|
PM_ORION,
|
|
PM_HUNTER,
|
|
PM_SCORPIUS,
|
|
PM_FOREST_CENTAUR,
|
|
PM_SCORPION,
|
|
S_CENTAUR,
|
|
S_SPIDER,
|
|
ART_LONGBOW_OF_DIANA,
|
|
MH_HUMAN | MH_ELF | MH_GNOME | MH_ORC | ROLE_MALE | ROLE_FEMALE
|
|
| ROLE_NEUTRAL | ROLE_CHAOTIC,
|
|
/* Str Int Wis Dex Con Cha */
|
|
{ 13, 13, 13, 9, 13, 7 },
|
|
{ 30, 10, 10, 20, 20, 10 },
|
|
/* Init Lower Higher */
|
|
{ 13, 0, 0, 6, 1, 0 }, /* Hit points */
|
|
{ 1, 0, 0, 1, 0, 1 },
|
|
12, /* Energy */
|
|
10,
|
|
9,
|
|
2,
|
|
1,
|
|
10,
|
|
A_INT,
|
|
SPE_INVISIBILITY,
|
|
-4 },
|
|
{ { "Samurai", 0 },
|
|
{ { "Hatamoto", 0 }, /* Banner Knight */
|
|
{ "Ronin", 0 }, /* no allegiance */
|
|
{ "Ninja", "Kunoichi" }, /* secret society */
|
|
{ "Joshu", 0 }, /* heads a castle */
|
|
{ "Ryoshu", 0 }, /* has a territory */
|
|
{ "Kokushu", 0 }, /* heads a province */
|
|
{ "Daimyo", 0 }, /* a samurai lord */
|
|
{ "Kuge", 0 }, /* Noble of the Court */
|
|
{ "Shogun", 0 } }, /* supreme commander, warlord */
|
|
"_Amaterasu Omikami", "Raijin", "Susanowo", /* Japanese */
|
|
"Sam",
|
|
"the Castle of the Taro Clan",
|
|
"the Shogun's Castle",
|
|
PM_SAMURAI,
|
|
PM_LITTLE_DOG,
|
|
PM_LORD_SATO,
|
|
PM_ROSHI,
|
|
PM_ASHIKAGA_TAKAUJI,
|
|
PM_WOLF,
|
|
PM_STALKER,
|
|
S_DOG,
|
|
S_ELEMENTAL,
|
|
ART_TSURUGI_OF_MURAMASA,
|
|
MH_HUMAN | ROLE_MALE | ROLE_FEMALE | ROLE_LAWFUL,
|
|
/* Str Int Wis Dex Con Cha */
|
|
{ 10, 8, 7, 10, 17, 6 },
|
|
{ 30, 10, 8, 30, 14, 8 },
|
|
/* Init Lower Higher */
|
|
{ 13, 0, 0, 8, 1, 0 }, /* Hit points */
|
|
{ 1, 0, 0, 1, 0, 1 },
|
|
11, /* Energy */
|
|
10,
|
|
10,
|
|
0,
|
|
0,
|
|
8,
|
|
A_INT,
|
|
SPE_CLAIRVOYANCE,
|
|
-4 },
|
|
{ { "Tourist", 0 },
|
|
{ { "Rambler", 0 },
|
|
{ "Sightseer", 0 },
|
|
{ "Excursionist", 0 },
|
|
{ "Peregrinator", "Peregrinatrix" },
|
|
{ "Traveler", 0 },
|
|
{ "Journeyer", 0 },
|
|
{ "Voyager", 0 },
|
|
{ "Explorer", 0 },
|
|
{ "Adventurer", 0 } },
|
|
"Blind Io", "_The Lady", "Offler", /* Discworld */
|
|
"Tou",
|
|
"Ankh-Morpork",
|
|
"the Thieves' Guild Hall",
|
|
PM_TOURIST,
|
|
NON_PM,
|
|
PM_TWOFLOWER,
|
|
PM_GUIDE,
|
|
PM_MASTER_OF_THIEVES,
|
|
PM_GIANT_SPIDER,
|
|
PM_FOREST_CENTAUR,
|
|
S_SPIDER,
|
|
S_CENTAUR,
|
|
ART_YENDORIAN_EXPRESS_CARD,
|
|
MH_HUMAN | ROLE_MALE | ROLE_FEMALE | ROLE_NEUTRAL,
|
|
/* Str Int Wis Dex Con Cha */
|
|
{ 7, 10, 6, 7, 7, 10 },
|
|
{ 15, 10, 10, 15, 30, 20 },
|
|
/* Init Lower Higher */
|
|
{ 8, 0, 0, 8, 0, 0 }, /* Hit points */
|
|
{ 1, 0, 0, 1, 0, 1 },
|
|
14, /* Energy */
|
|
0,
|
|
5,
|
|
1,
|
|
2,
|
|
10,
|
|
A_INT,
|
|
SPE_CHARM_MONSTER,
|
|
-4 },
|
|
{ { "Valkyrie", 0 },
|
|
{ { "Stripling", 0 },
|
|
{ "Skirmisher", 0 },
|
|
{ "Fighter", 0 },
|
|
{ "Man-at-arms", "Woman-at-arms" },
|
|
{ "Warrior", 0 },
|
|
{ "Swashbuckler", 0 },
|
|
{ "Hero", "Heroine" },
|
|
{ "Champion", 0 },
|
|
{ "Lord", "Lady" } },
|
|
"Tyr", "Odin", "Loki", /* Norse */
|
|
"Val",
|
|
"the Shrine of Destiny",
|
|
"the cave of Surtur",
|
|
PM_VALKYRIE,
|
|
NON_PM /*PM_WINTER_WOLF_CUB*/,
|
|
PM_NORN,
|
|
PM_WARRIOR,
|
|
PM_LORD_SURTUR,
|
|
PM_FIRE_ANT,
|
|
PM_FIRE_GIANT,
|
|
S_ANT,
|
|
S_GIANT,
|
|
ART_ORB_OF_FATE,
|
|
MH_HUMAN | MH_DWARF | ROLE_FEMALE | ROLE_LAWFUL | ROLE_NEUTRAL,
|
|
/* Str Int Wis Dex Con Cha */
|
|
{ 10, 7, 7, 7, 10, 7 },
|
|
{ 30, 6, 7, 20, 30, 7 },
|
|
/* Init Lower Higher */
|
|
{ 14, 0, 0, 8, 2, 0 }, /* Hit points */
|
|
{ 1, 0, 0, 1, 0, 1 },
|
|
10, /* Energy */
|
|
0,
|
|
10,
|
|
-2,
|
|
0,
|
|
9,
|
|
A_WIS,
|
|
SPE_CONE_OF_COLD,
|
|
-4 },
|
|
{ { "Wizard", 0 },
|
|
{ { "Evoker", 0 },
|
|
{ "Conjurer", 0 },
|
|
{ "Thaumaturge", 0 },
|
|
{ "Magician", 0 },
|
|
{ "Enchanter", "Enchantress" },
|
|
{ "Sorcerer", "Sorceress" },
|
|
{ "Necromancer", 0 },
|
|
{ "Wizard", 0 },
|
|
{ "Mage", 0 } },
|
|
"Ptah", "Thoth", "Anhur", /* Egyptian */
|
|
"Wiz",
|
|
"the Lonely Tower",
|
|
"the Tower of Darkness",
|
|
PM_WIZARD,
|
|
PM_KITTEN,
|
|
PM_NEFERET_THE_GREEN,
|
|
PM_APPRENTICE,
|
|
PM_DARK_ONE,
|
|
PM_VAMPIRE_BAT,
|
|
PM_XORN,
|
|
S_BAT,
|
|
S_WRAITH,
|
|
ART_EYE_OF_THE_AETHIOPICA,
|
|
MH_HUMAN | MH_ELF | MH_GNOME | MH_ORC | ROLE_MALE | ROLE_FEMALE
|
|
| ROLE_NEUTRAL | ROLE_CHAOTIC,
|
|
/* Str Int Wis Dex Con Cha */
|
|
{ 7, 10, 7, 7, 7, 7 },
|
|
{ 10, 30, 10, 20, 20, 10 },
|
|
/* Init Lower Higher */
|
|
{ 10, 0, 0, 8, 1, 0 }, /* Hit points */
|
|
{ 4, 3, 0, 2, 0, 3 },
|
|
12, /* Energy */
|
|
0,
|
|
1,
|
|
0,
|
|
3,
|
|
10,
|
|
A_INT,
|
|
SPE_MAGIC_MISSILE,
|
|
-4 },
|
|
/* Array terminator */
|
|
{ { 0, 0 } }
|
|
};
|
|
|
|
/* Table of all races */
|
|
const struct Race races[] = {
|
|
{
|
|
"human",
|
|
"human",
|
|
"humanity",
|
|
"Hum",
|
|
{ "man", "woman" },
|
|
PM_HUMAN,
|
|
PM_HUMAN_MUMMY,
|
|
PM_HUMAN_ZOMBIE,
|
|
MH_HUMAN | ROLE_MALE | ROLE_FEMALE | ROLE_LAWFUL | ROLE_NEUTRAL
|
|
| ROLE_CHAOTIC,
|
|
MH_HUMAN,
|
|
0,
|
|
MH_GNOME | MH_ORC,
|
|
/* Str Int Wis Dex Con Cha */
|
|
{ 3, 3, 3, 3, 3, 3 },
|
|
{ STR18(100), 18, 18, 18, 18, 18 },
|
|
/* Init Lower Higher */
|
|
{ 2, 0, 0, 2, 1, 0 }, /* Hit points */
|
|
{ 1, 0, 2, 0, 2, 0 } /* Energy */
|
|
},
|
|
{
|
|
"elf",
|
|
"elven",
|
|
"elvenkind",
|
|
"Elf",
|
|
{ 0, 0 },
|
|
PM_ELF,
|
|
PM_ELF_MUMMY,
|
|
PM_ELF_ZOMBIE,
|
|
MH_ELF | ROLE_MALE | ROLE_FEMALE | ROLE_CHAOTIC,
|
|
MH_ELF,
|
|
MH_ELF,
|
|
MH_ORC,
|
|
/* Str Int Wis Dex Con Cha */
|
|
{ 3, 3, 3, 3, 3, 3 },
|
|
{ 18, 20, 20, 18, 16, 18 },
|
|
/* Init Lower Higher */
|
|
{ 1, 0, 0, 1, 1, 0 }, /* Hit points */
|
|
{ 2, 0, 3, 0, 3, 0 } /* Energy */
|
|
},
|
|
{
|
|
"dwarf",
|
|
"dwarven",
|
|
"dwarvenkind",
|
|
"Dwa",
|
|
{ 0, 0 },
|
|
PM_DWARF,
|
|
PM_DWARF_MUMMY,
|
|
PM_DWARF_ZOMBIE,
|
|
MH_DWARF | ROLE_MALE | ROLE_FEMALE | ROLE_LAWFUL,
|
|
MH_DWARF,
|
|
MH_DWARF | MH_GNOME,
|
|
MH_ORC,
|
|
/* Str Int Wis Dex Con Cha */
|
|
{ 3, 3, 3, 3, 3, 3 },
|
|
{ STR18(100), 16, 16, 20, 20, 16 },
|
|
/* Init Lower Higher */
|
|
{ 4, 0, 0, 3, 2, 0 }, /* Hit points */
|
|
{ 0, 0, 0, 0, 0, 0 } /* Energy */
|
|
},
|
|
{
|
|
"gnome",
|
|
"gnomish",
|
|
"gnomehood",
|
|
"Gno",
|
|
{ 0, 0 },
|
|
PM_GNOME,
|
|
PM_GNOME_MUMMY,
|
|
PM_GNOME_ZOMBIE,
|
|
MH_GNOME | ROLE_MALE | ROLE_FEMALE | ROLE_NEUTRAL,
|
|
MH_GNOME,
|
|
MH_DWARF | MH_GNOME,
|
|
MH_HUMAN,
|
|
/* Str Int Wis Dex Con Cha */
|
|
{ 3, 3, 3, 3, 3, 3 },
|
|
{ STR18(50), 19, 18, 18, 18, 18 },
|
|
/* Init Lower Higher */
|
|
{ 1, 0, 0, 1, 0, 0 }, /* Hit points */
|
|
{ 2, 0, 2, 0, 2, 0 } /* Energy */
|
|
},
|
|
{
|
|
"orc",
|
|
"orcish",
|
|
"orcdom",
|
|
"Orc",
|
|
{ 0, 0 },
|
|
PM_ORC,
|
|
PM_ORC_MUMMY,
|
|
PM_ORC_ZOMBIE,
|
|
MH_ORC | ROLE_MALE | ROLE_FEMALE | ROLE_CHAOTIC,
|
|
MH_ORC,
|
|
0,
|
|
MH_HUMAN | MH_ELF | MH_DWARF,
|
|
/* Str Int Wis Dex Con Cha */
|
|
{ 3, 3, 3, 3, 3, 3 },
|
|
{ STR18(50), 16, 16, 18, 18, 16 },
|
|
/* Init Lower Higher */
|
|
{ 1, 0, 0, 1, 0, 0 }, /* Hit points */
|
|
{ 1, 0, 1, 0, 1, 0 } /* Energy */
|
|
},
|
|
/* Array terminator */
|
|
{ 0, 0, 0, 0, { 0, 0 }, NON_PM }
|
|
};
|
|
|
|
/* Table of all genders */
|
|
const struct Gender genders[] = {
|
|
{ "male", "he", "him", "his", "Mal", ROLE_MALE },
|
|
{ "female", "she", "her", "her", "Fem", ROLE_FEMALE },
|
|
{ "neuter", "it", "it", "its", "Ntr", ROLE_NEUTER },
|
|
/* used by pronoun_gender() when hallucinating */
|
|
{ "group", "they", "them", "their", "Grp", 0 },
|
|
};
|
|
|
|
/* Table of all alignments */
|
|
const struct Align aligns[] = {
|
|
{ "law", "lawful", "Law", ROLE_LAWFUL, A_LAWFUL },
|
|
{ "balance", "neutral", "Neu", ROLE_NEUTRAL, A_NEUTRAL },
|
|
{ "chaos", "chaotic", "Cha", ROLE_CHAOTIC, A_CHAOTIC },
|
|
{ "evil", "unaligned", "Una", 0, A_NONE }
|
|
};
|
|
|
|
static int randrole_filtered(void);
|
|
static char *promptsep(char *, int);
|
|
static int role_gendercount(int);
|
|
static int race_alignmentcount(int);
|
|
|
|
/* used by str2XXX() */
|
|
static char NEARDATA randomstr[] = "random";
|
|
|
|
boolean
|
|
validrole(int rolenum)
|
|
{
|
|
return (boolean) (rolenum >= 0 && rolenum < SIZE(roles) - 1);
|
|
}
|
|
|
|
int
|
|
randrole(boolean for_display)
|
|
{
|
|
int res = SIZE(roles) - 1;
|
|
|
|
if (for_display)
|
|
res = rn2_on_display_rng(res);
|
|
else
|
|
res = rn2(res);
|
|
return res;
|
|
}
|
|
|
|
static int
|
|
randrole_filtered(void)
|
|
{
|
|
int i, n = 0, set[SIZE(roles)];
|
|
|
|
/* this doesn't rule out impossible combinations but attempts to
|
|
honor all the filter masks */
|
|
for (i = 0; i < SIZE(roles) - 1; ++i) /* -1: avoid terminating element */
|
|
if (ok_role(i, ROLE_NONE, ROLE_NONE, ROLE_NONE)
|
|
&& ok_race(i, ROLE_RANDOM, ROLE_NONE, ROLE_NONE)
|
|
&& ok_gend(i, ROLE_NONE, ROLE_RANDOM, ROLE_NONE)
|
|
&& ok_align(i, ROLE_NONE, ROLE_NONE, ROLE_RANDOM))
|
|
set[n++] = i;
|
|
return n ? set[rn2(n)] : randrole(FALSE);
|
|
}
|
|
|
|
int
|
|
str2role(const char *str)
|
|
{
|
|
int i, len;
|
|
|
|
/* Is str valid? */
|
|
if (!str || !str[0])
|
|
return ROLE_NONE;
|
|
|
|
/* Match as much of str as is provided */
|
|
len = Strlen(str);
|
|
for (i = 0; roles[i].name.m; i++) {
|
|
/* Does it match the male name? */
|
|
if (!strncmpi(str, roles[i].name.m, len))
|
|
return i;
|
|
/* Or the female name? */
|
|
if (roles[i].name.f && !strncmpi(str, roles[i].name.f, len))
|
|
return i;
|
|
/* Or the filecode? */
|
|
if (!strcmpi(str, roles[i].filecode))
|
|
return i;
|
|
}
|
|
|
|
if ((len == 1 && (*str == '*' || *str == '@'))
|
|
|| !strncmpi(str, randomstr, len))
|
|
return ROLE_RANDOM;
|
|
|
|
/* Couldn't find anything appropriate */
|
|
return ROLE_NONE;
|
|
}
|
|
|
|
boolean
|
|
validrace(int rolenum, int racenum)
|
|
{
|
|
/* Assumes validrole */
|
|
return (boolean) (racenum >= 0 && racenum < SIZE(races) - 1
|
|
&& (roles[rolenum].allow & races[racenum].allow
|
|
& ROLE_RACEMASK));
|
|
}
|
|
|
|
int
|
|
randrace(int rolenum)
|
|
{
|
|
int i, n = 0;
|
|
|
|
/* Count the number of valid races */
|
|
for (i = 0; races[i].noun; i++)
|
|
if (roles[rolenum].allow & races[i].allow & ROLE_RACEMASK)
|
|
n++;
|
|
|
|
/* Pick a random race */
|
|
/* Use a factor of 100 in case of bad random number generators */
|
|
if (n)
|
|
n = rn2(n * 100) / 100;
|
|
for (i = 0; races[i].noun; i++)
|
|
if (roles[rolenum].allow & races[i].allow & ROLE_RACEMASK) {
|
|
if (n)
|
|
n--;
|
|
else
|
|
return i;
|
|
}
|
|
|
|
/* This role has no permitted races? */
|
|
return rn2(SIZE(races) - 1);
|
|
}
|
|
|
|
int
|
|
str2race(const char *str)
|
|
{
|
|
int i, len;
|
|
|
|
/* Is str valid? */
|
|
if (!str || !str[0])
|
|
return ROLE_NONE;
|
|
|
|
/* Match as much of str as is provided */
|
|
len = Strlen(str);
|
|
for (i = 0; races[i].noun; i++) {
|
|
/* Does it match the noun? */
|
|
if (!strncmpi(str, races[i].noun, len))
|
|
return i;
|
|
/* check adjective too */
|
|
if (races[i].adj && !strncmpi(str, races[i].adj, len))
|
|
return i;
|
|
/* Or the filecode? */
|
|
if (!strcmpi(str, races[i].filecode))
|
|
return i;
|
|
}
|
|
|
|
if ((len == 1 && (*str == '*' || *str == '@'))
|
|
|| !strncmpi(str, randomstr, len))
|
|
return ROLE_RANDOM;
|
|
|
|
/* Couldn't find anything appropriate */
|
|
return ROLE_NONE;
|
|
}
|
|
|
|
boolean
|
|
validgend(int rolenum, int racenum, int gendnum)
|
|
{
|
|
/* Assumes validrole and validrace */
|
|
return (boolean) (gendnum >= 0 && gendnum < ROLE_GENDERS
|
|
&& (roles[rolenum].allow & races[racenum].allow
|
|
& genders[gendnum].allow & ROLE_GENDMASK));
|
|
}
|
|
|
|
int
|
|
randgend(int rolenum, int racenum)
|
|
{
|
|
int i, n = 0;
|
|
|
|
/* Count the number of valid genders */
|
|
for (i = 0; i < ROLE_GENDERS; i++)
|
|
if (roles[rolenum].allow & races[racenum].allow & genders[i].allow
|
|
& ROLE_GENDMASK)
|
|
n++;
|
|
|
|
/* Pick a random gender */
|
|
if (n)
|
|
n = rn2(n);
|
|
for (i = 0; i < ROLE_GENDERS; i++)
|
|
if (roles[rolenum].allow & races[racenum].allow & genders[i].allow
|
|
& ROLE_GENDMASK) {
|
|
if (n)
|
|
n--;
|
|
else
|
|
return i;
|
|
}
|
|
|
|
/* This role/race has no permitted genders? */
|
|
return rn2(ROLE_GENDERS);
|
|
}
|
|
|
|
int
|
|
str2gend(const char *str)
|
|
{
|
|
int i, len;
|
|
|
|
/* Is str valid? */
|
|
if (!str || !str[0])
|
|
return ROLE_NONE;
|
|
|
|
/* Match as much of str as is provided */
|
|
len = Strlen(str);
|
|
for (i = 0; i < ROLE_GENDERS; i++) {
|
|
/* Does it match the adjective? */
|
|
if (!strncmpi(str, genders[i].adj, len))
|
|
return i;
|
|
/* Or the filecode? */
|
|
if (!strcmpi(str, genders[i].filecode))
|
|
return i;
|
|
}
|
|
if ((len == 1 && (*str == '*' || *str == '@'))
|
|
|| !strncmpi(str, randomstr, len))
|
|
return ROLE_RANDOM;
|
|
|
|
/* Couldn't find anything appropriate */
|
|
return ROLE_NONE;
|
|
}
|
|
|
|
boolean
|
|
validalign(int rolenum, int racenum, int alignnum)
|
|
{
|
|
/* Assumes validrole and validrace */
|
|
return (boolean) (alignnum >= 0 && alignnum < ROLE_ALIGNS
|
|
&& (roles[rolenum].allow & races[racenum].allow
|
|
& aligns[alignnum].allow & ROLE_ALIGNMASK));
|
|
}
|
|
|
|
int
|
|
randalign(int rolenum, int racenum)
|
|
{
|
|
int i, n = 0;
|
|
|
|
/* Count the number of valid alignments */
|
|
for (i = 0; i < ROLE_ALIGNS; i++)
|
|
if (roles[rolenum].allow & races[racenum].allow & aligns[i].allow
|
|
& ROLE_ALIGNMASK)
|
|
n++;
|
|
|
|
/* Pick a random alignment */
|
|
if (n)
|
|
n = rn2(n);
|
|
for (i = 0; i < ROLE_ALIGNS; i++)
|
|
if (roles[rolenum].allow & races[racenum].allow & aligns[i].allow
|
|
& ROLE_ALIGNMASK) {
|
|
if (n)
|
|
n--;
|
|
else
|
|
return i;
|
|
}
|
|
|
|
/* This role/race has no permitted alignments? */
|
|
return rn2(ROLE_ALIGNS);
|
|
}
|
|
|
|
int
|
|
str2align(const char *str)
|
|
{
|
|
int i, len;
|
|
|
|
/* Is str valid? */
|
|
if (!str || !str[0])
|
|
return ROLE_NONE;
|
|
|
|
/* Match as much of str as is provided */
|
|
len = Strlen(str);
|
|
for (i = 0; i < ROLE_ALIGNS; i++) {
|
|
/* Does it match the adjective? */
|
|
if (!strncmpi(str, aligns[i].adj, len))
|
|
return i;
|
|
/* Or the filecode? */
|
|
if (!strcmpi(str, aligns[i].filecode))
|
|
return i;
|
|
}
|
|
if ((len == 1 && (*str == '*' || *str == '@'))
|
|
|| !strncmpi(str, randomstr, len))
|
|
return ROLE_RANDOM;
|
|
|
|
/* Couldn't find anything appropriate */
|
|
return ROLE_NONE;
|
|
}
|
|
|
|
/* is rolenum compatible with any racenum/gendnum/alignnum constraints? */
|
|
boolean
|
|
ok_role(int rolenum, int racenum, int gendnum, int alignnum)
|
|
{
|
|
int i;
|
|
short allow;
|
|
|
|
if (rolenum >= 0 && rolenum < SIZE(roles) - 1) {
|
|
if (gr.rfilter.roles[rolenum])
|
|
return FALSE;
|
|
allow = roles[rolenum].allow;
|
|
if (racenum >= 0 && racenum < SIZE(races) - 1
|
|
&& !(allow & races[racenum].allow & ROLE_RACEMASK))
|
|
return FALSE;
|
|
if (gendnum >= 0 && gendnum < ROLE_GENDERS
|
|
&& !(allow & genders[gendnum].allow & ROLE_GENDMASK))
|
|
return FALSE;
|
|
if (alignnum >= 0 && alignnum < ROLE_ALIGNS
|
|
&& !(allow & aligns[alignnum].allow & ROLE_ALIGNMASK))
|
|
return FALSE;
|
|
return TRUE;
|
|
} else {
|
|
/* random; check whether any selection is possible */
|
|
for (i = 0; i < SIZE(roles) - 1; i++) {
|
|
if (gr.rfilter.roles[i])
|
|
continue;
|
|
allow = roles[i].allow;
|
|
if (racenum >= 0 && racenum < SIZE(races) - 1
|
|
&& !(allow & races[racenum].allow & ROLE_RACEMASK))
|
|
continue;
|
|
if (gendnum >= 0 && gendnum < ROLE_GENDERS
|
|
&& !(allow & genders[gendnum].allow & ROLE_GENDMASK))
|
|
continue;
|
|
if (alignnum >= 0 && alignnum < ROLE_ALIGNS
|
|
&& !(allow & aligns[alignnum].allow & ROLE_ALIGNMASK))
|
|
continue;
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* pick a random role subject to any racenum/gendnum/alignnum constraints */
|
|
/* If pickhow == PICK_RIGID a role is returned only if there is */
|
|
/* a single possibility */
|
|
int
|
|
pick_role(int racenum, int gendnum, int alignnum, int pickhow)
|
|
{
|
|
int i;
|
|
int roles_ok = 0, set[SIZE(roles)];
|
|
|
|
for (i = 0; i < SIZE(roles) - 1; i++) {
|
|
if (ok_role(i, racenum, gendnum, alignnum)
|
|
&& ok_race(i, (racenum >= 0) ? racenum : ROLE_RANDOM,
|
|
gendnum, alignnum)
|
|
&& ok_gend(i, racenum,
|
|
(gendnum >= 0) ? gendnum : ROLE_RANDOM, alignnum)
|
|
&& ok_align(i, racenum,
|
|
gendnum, (alignnum >= 0) ? alignnum : ROLE_RANDOM))
|
|
set[roles_ok++] = i;
|
|
}
|
|
if (roles_ok == 0 || (roles_ok > 1 && pickhow == PICK_RIGID))
|
|
return ROLE_NONE;
|
|
return set[rn2(roles_ok)];
|
|
}
|
|
|
|
/* is racenum compatible with any rolenum/gendnum/alignnum constraints? */
|
|
boolean
|
|
ok_race(int rolenum, int racenum, int gendnum, int alignnum)
|
|
{
|
|
int i;
|
|
short allow;
|
|
|
|
if (racenum >= 0 && racenum < SIZE(races) - 1) {
|
|
if (gr.rfilter.mask & races[racenum].selfmask)
|
|
return FALSE;
|
|
allow = races[racenum].allow;
|
|
if (rolenum >= 0 && rolenum < SIZE(roles) - 1
|
|
&& !(allow & roles[rolenum].allow & ROLE_RACEMASK))
|
|
return FALSE;
|
|
if (gendnum >= 0 && gendnum < ROLE_GENDERS
|
|
&& !(allow & genders[gendnum].allow & ROLE_GENDMASK))
|
|
return FALSE;
|
|
if (alignnum >= 0 && alignnum < ROLE_ALIGNS
|
|
&& !(allow & aligns[alignnum].allow & ROLE_ALIGNMASK))
|
|
return FALSE;
|
|
return TRUE;
|
|
} else {
|
|
/* random; check whether any selection is possible */
|
|
for (i = 0; i < SIZE(races) - 1; i++) {
|
|
if (gr.rfilter.mask & races[i].selfmask)
|
|
continue;
|
|
allow = races[i].allow;
|
|
if (rolenum >= 0 && rolenum < SIZE(roles) - 1
|
|
&& !(allow & roles[rolenum].allow & ROLE_RACEMASK))
|
|
continue;
|
|
if (gendnum >= 0 && gendnum < ROLE_GENDERS
|
|
&& !(allow & genders[gendnum].allow & ROLE_GENDMASK))
|
|
continue;
|
|
if (alignnum >= 0 && alignnum < ROLE_ALIGNS
|
|
&& !(allow & aligns[alignnum].allow & ROLE_ALIGNMASK))
|
|
continue;
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* Pick a random race subject to any rolenum/gendnum/alignnum constraints.
|
|
If pickhow == PICK_RIGID a race is returned only if there is
|
|
a single possibility. */
|
|
int
|
|
pick_race(int rolenum, int gendnum, int alignnum, int pickhow)
|
|
{
|
|
int i;
|
|
int races_ok = 0;
|
|
|
|
for (i = 0; i < SIZE(races) - 1; i++) {
|
|
if (ok_race(rolenum, i, gendnum, alignnum))
|
|
races_ok++;
|
|
}
|
|
if (races_ok == 0 || (races_ok > 1 && pickhow == PICK_RIGID))
|
|
return ROLE_NONE;
|
|
races_ok = rn2(races_ok);
|
|
for (i = 0; i < SIZE(races) - 1; i++) {
|
|
if (ok_race(rolenum, i, gendnum, alignnum)) {
|
|
if (races_ok == 0)
|
|
return i;
|
|
else
|
|
races_ok--;
|
|
}
|
|
}
|
|
return ROLE_NONE;
|
|
}
|
|
|
|
/* is gendnum compatible with any rolenum/racenum/alignnum constraints? */
|
|
/* gender and alignment are not comparable (and also not constrainable) */
|
|
boolean
|
|
ok_gend(int rolenum, int racenum, int gendnum, int alignnum UNUSED)
|
|
{
|
|
int i;
|
|
short allow;
|
|
|
|
if (gendnum >= 0 && gendnum < ROLE_GENDERS) {
|
|
if (gr.rfilter.mask & genders[gendnum].allow)
|
|
return FALSE;
|
|
allow = genders[gendnum].allow;
|
|
if (rolenum >= 0 && rolenum < SIZE(roles) - 1
|
|
&& !(allow & roles[rolenum].allow & ROLE_GENDMASK))
|
|
return FALSE;
|
|
if (racenum >= 0 && racenum < SIZE(races) - 1
|
|
&& !(allow & races[racenum].allow & ROLE_GENDMASK))
|
|
return FALSE;
|
|
return TRUE;
|
|
} else {
|
|
/* random; check whether any selection is possible */
|
|
for (i = 0; i < ROLE_GENDERS; i++) {
|
|
if (gr.rfilter.mask & genders[i].allow)
|
|
continue;
|
|
allow = genders[i].allow;
|
|
if (rolenum >= 0 && rolenum < SIZE(roles) - 1
|
|
&& !(allow & roles[rolenum].allow & ROLE_GENDMASK))
|
|
continue;
|
|
if (racenum >= 0 && racenum < SIZE(races) - 1
|
|
&& !(allow & races[racenum].allow & ROLE_GENDMASK))
|
|
continue;
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* pick a random gender subject to any rolenum/racenum/alignnum constraints */
|
|
/* gender and alignment are not comparable (and also not constrainable) */
|
|
/* If pickhow == PICK_RIGID a gender is returned only if there is */
|
|
/* a single possibility */
|
|
int
|
|
pick_gend(int rolenum, int racenum, int alignnum, int pickhow)
|
|
{
|
|
int i;
|
|
int gends_ok = 0;
|
|
|
|
for (i = 0; i < ROLE_GENDERS; i++) {
|
|
if (ok_gend(rolenum, racenum, i, alignnum))
|
|
gends_ok++;
|
|
}
|
|
if (gends_ok == 0 || (gends_ok > 1 && pickhow == PICK_RIGID))
|
|
return ROLE_NONE;
|
|
gends_ok = rn2(gends_ok);
|
|
for (i = 0; i < ROLE_GENDERS; i++) {
|
|
if (ok_gend(rolenum, racenum, i, alignnum)) {
|
|
if (gends_ok == 0)
|
|
return i;
|
|
else
|
|
gends_ok--;
|
|
}
|
|
}
|
|
return ROLE_NONE;
|
|
}
|
|
|
|
/* is alignnum compatible with any rolenum/racenum/gendnum constraints? */
|
|
/* alignment and gender are not comparable (and also not constrainable) */
|
|
boolean
|
|
ok_align(int rolenum, int racenum, int gendnum UNUSED, int alignnum)
|
|
{
|
|
int i;
|
|
short allow;
|
|
|
|
if (alignnum >= 0 && alignnum < ROLE_ALIGNS) {
|
|
if (gr.rfilter.mask & aligns[alignnum].allow)
|
|
return FALSE;
|
|
allow = aligns[alignnum].allow;
|
|
if (rolenum >= 0 && rolenum < SIZE(roles) - 1
|
|
&& !(allow & roles[rolenum].allow & ROLE_ALIGNMASK))
|
|
return FALSE;
|
|
if (racenum >= 0 && racenum < SIZE(races) - 1
|
|
&& !(allow & races[racenum].allow & ROLE_ALIGNMASK))
|
|
return FALSE;
|
|
return TRUE;
|
|
} else {
|
|
/* random; check whether any selection is possible */
|
|
for (i = 0; i < ROLE_ALIGNS; i++) {
|
|
if (gr.rfilter.mask & aligns[i].allow)
|
|
continue;
|
|
allow = aligns[i].allow;
|
|
if (rolenum >= 0 && rolenum < SIZE(roles) - 1
|
|
&& !(allow & roles[rolenum].allow & ROLE_ALIGNMASK))
|
|
continue;
|
|
if (racenum >= 0 && racenum < SIZE(races) - 1
|
|
&& !(allow & races[racenum].allow & ROLE_ALIGNMASK))
|
|
continue;
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* Pick a random alignment subject to any rolenum/racenum/gendnum constraints;
|
|
alignment and gender are not comparable (and also not constrainable).
|
|
If pickhow == PICK_RIGID an alignment is returned only if there is
|
|
a single possibility. */
|
|
int
|
|
pick_align(int rolenum, int racenum, int gendnum, int pickhow)
|
|
{
|
|
int i;
|
|
int aligns_ok = 0;
|
|
|
|
for (i = 0; i < ROLE_ALIGNS; i++) {
|
|
if (ok_align(rolenum, racenum, gendnum, i))
|
|
aligns_ok++;
|
|
}
|
|
if (aligns_ok == 0 || (aligns_ok > 1 && pickhow == PICK_RIGID))
|
|
return ROLE_NONE;
|
|
aligns_ok = rn2(aligns_ok);
|
|
for (i = 0; i < ROLE_ALIGNS; i++) {
|
|
if (ok_align(rolenum, racenum, gendnum, i)) {
|
|
if (aligns_ok == 0)
|
|
return i;
|
|
else
|
|
aligns_ok--;
|
|
}
|
|
}
|
|
return ROLE_NONE;
|
|
}
|
|
|
|
void
|
|
rigid_role_checks(void)
|
|
{
|
|
int tmp;
|
|
|
|
/* Some roles are limited to a single race, alignment, or gender and
|
|
* calling this routine prior to XXX_player_selection() will help
|
|
* prevent an extraneous prompt that actually doesn't allow
|
|
* you to choose anything further. Note the use of PICK_RIGID which
|
|
* causes the pick_XX() routine to return a value only if there is one
|
|
* single possible selection, otherwise it returns ROLE_NONE.
|
|
*
|
|
*/
|
|
if (flags.initrole == ROLE_RANDOM) {
|
|
/* If the role was explicitly specified as ROLE_RANDOM
|
|
* via -uXXXX-@ or OPTIONS=role:random then choose the role
|
|
* in here to narrow down later choices.
|
|
*/
|
|
flags.initrole = pick_role(flags.initrace, flags.initgend,
|
|
flags.initalign, PICK_RANDOM);
|
|
if (flags.initrole < 0)
|
|
flags.initrole = randrole_filtered();
|
|
}
|
|
if (flags.initrace == ROLE_RANDOM
|
|
&& (tmp = pick_race(flags.initrole, flags.initgend,
|
|
flags.initalign, PICK_RANDOM)) != ROLE_NONE)
|
|
flags.initrace = tmp;
|
|
if (flags.initalign == ROLE_RANDOM
|
|
&& (tmp = pick_align(flags.initrole, flags.initrace,
|
|
flags.initgend, PICK_RANDOM)) != ROLE_NONE)
|
|
flags.initalign = tmp;
|
|
if (flags.initgend == ROLE_RANDOM
|
|
&& (tmp = pick_gend(flags.initrole, flags.initrace,
|
|
flags.initalign, PICK_RANDOM)) != ROLE_NONE)
|
|
flags.initgend = tmp;
|
|
|
|
if (flags.initrole != ROLE_NONE) {
|
|
if (flags.initrace == ROLE_NONE)
|
|
flags.initrace = pick_race(flags.initrole, flags.initgend,
|
|
flags.initalign, PICK_RIGID);
|
|
if (flags.initalign == ROLE_NONE)
|
|
flags.initalign = pick_align(flags.initrole, flags.initrace,
|
|
flags.initgend, PICK_RIGID);
|
|
if (flags.initgend == ROLE_NONE)
|
|
flags.initgend = pick_gend(flags.initrole, flags.initrace,
|
|
flags.initalign, PICK_RIGID);
|
|
}
|
|
}
|
|
|
|
boolean
|
|
setrolefilter(const char *bufp)
|
|
{
|
|
int i;
|
|
boolean reslt = TRUE;
|
|
|
|
if ((i = str2role(bufp)) != ROLE_NONE && i != ROLE_RANDOM)
|
|
gr.rfilter.roles[i] = TRUE;
|
|
else if ((i = str2race(bufp)) != ROLE_NONE && i != ROLE_RANDOM)
|
|
gr.rfilter.mask |= races[i].selfmask;
|
|
else if ((i = str2gend(bufp)) != ROLE_NONE && i != ROLE_RANDOM)
|
|
gr.rfilter.mask |= genders[i].allow;
|
|
else if ((i = str2align(bufp)) != ROLE_NONE && i != ROLE_RANDOM)
|
|
gr.rfilter.mask |= aligns[i].allow;
|
|
else
|
|
reslt = FALSE;
|
|
return reslt;
|
|
}
|
|
|
|
boolean
|
|
gotrolefilter(void)
|
|
{
|
|
int i;
|
|
|
|
if (gr.rfilter.mask)
|
|
return TRUE;
|
|
for (i = 0; i < SIZE(roles); ++i)
|
|
if (gr.rfilter.roles[i])
|
|
return TRUE;
|
|
return FALSE;
|
|
}
|
|
|
|
/* create a string like " !Bar !Kni" or " !chaotic" that can be
|
|
put back into an RC file by #saveoptions */
|
|
char *
|
|
rolefilterstring(char *outbuf, int which)
|
|
{
|
|
int i;
|
|
|
|
outbuf[0] = outbuf[1] = '\0';
|
|
switch (which) {
|
|
case RS_ROLE:
|
|
for (i = 0; i < SIZE(roles); ++i) {
|
|
if (gr.rfilter.roles[i])
|
|
Sprintf(eos(outbuf), " !%.3s", roles[i].name.m);
|
|
}
|
|
break;
|
|
case RS_RACE:
|
|
for (i = 0; i < SIZE(races); ++i) {
|
|
if ((gr.rfilter.mask & races[i].selfmask) != 0)
|
|
Sprintf(eos(outbuf), " !%s", races[i].noun);
|
|
}
|
|
break;
|
|
case RS_GENDER:
|
|
for (i = 0; i < SIZE(genders); ++i) {
|
|
if ((gr.rfilter.mask & genders[i].allow) != 0)
|
|
Sprintf(eos(outbuf), " !%s", genders[i].adj);
|
|
}
|
|
break;
|
|
case RS_ALGNMNT:
|
|
for (i = 0; i < SIZE(aligns); ++i) {
|
|
if ((gr.rfilter.mask & aligns[i].allow) != 0)
|
|
Sprintf(eos(outbuf), " !%s", aligns[i].adj);
|
|
}
|
|
break;
|
|
default:
|
|
impossible("rolefilterstring: bad role aspect (%d)", which);
|
|
Strcpy(outbuf, " ?");
|
|
break;
|
|
}
|
|
/* constructed with a leading space; drop it */
|
|
return &outbuf[1];
|
|
}
|
|
|
|
void
|
|
clearrolefilter(int which)
|
|
{
|
|
int i;
|
|
|
|
switch (which) {
|
|
case RS_filter:
|
|
gr.rfilter.mask = 0; /* clear race, gender, and alignment filters */
|
|
/*FALLTHRU*/
|
|
case RS_ROLE:
|
|
for (i = 0; i < SIZE(roles); ++i)
|
|
gr.rfilter.roles[i] = FALSE;
|
|
break;
|
|
case RS_RACE:
|
|
gr.rfilter.mask &= ~ROLE_RACEMASK;
|
|
break;
|
|
case RS_GENDER:
|
|
gr.rfilter.mask &= ~ROLE_GENDMASK;
|
|
break;
|
|
case RS_ALGNMNT:
|
|
gr.rfilter.mask &= ~ROLE_ALIGNMASK;
|
|
break;
|
|
}
|
|
}
|
|
|
|
static char *
|
|
promptsep(char *buf, int num_post_attribs)
|
|
{
|
|
const char *conjuct = "and ";
|
|
|
|
if (num_post_attribs > 1 && gr.role_post_attribs < num_post_attribs
|
|
&& gr.role_post_attribs > 1)
|
|
Strcat(buf, ",");
|
|
Strcat(buf, " ");
|
|
--gr.role_post_attribs;
|
|
if (!gr.role_post_attribs && num_post_attribs > 1)
|
|
Strcat(buf, conjuct);
|
|
return buf;
|
|
}
|
|
|
|
static int
|
|
role_gendercount(int rolenum)
|
|
{
|
|
int gendcount = 0;
|
|
|
|
if (validrole(rolenum)) {
|
|
if (roles[rolenum].allow & ROLE_MALE)
|
|
++gendcount;
|
|
if (roles[rolenum].allow & ROLE_FEMALE)
|
|
++gendcount;
|
|
if (roles[rolenum].allow & ROLE_NEUTER)
|
|
++gendcount;
|
|
}
|
|
return gendcount;
|
|
}
|
|
|
|
static int
|
|
race_alignmentcount(int racenum)
|
|
{
|
|
int aligncount = 0;
|
|
|
|
if (racenum != ROLE_NONE && racenum != ROLE_RANDOM) {
|
|
if (races[racenum].allow & ROLE_CHAOTIC)
|
|
++aligncount;
|
|
if (races[racenum].allow & ROLE_LAWFUL)
|
|
++aligncount;
|
|
if (races[racenum].allow & ROLE_NEUTRAL)
|
|
++aligncount;
|
|
}
|
|
return aligncount;
|
|
}
|
|
|
|
char *
|
|
root_plselection_prompt(
|
|
char *suppliedbuf, int buflen,
|
|
int rolenum, int racenum, int gendnum, int alignnum)
|
|
{
|
|
int k, gendercount = 0, aligncount = 0;
|
|
char buf[BUFSZ];
|
|
static char err_ret[] = " character's";
|
|
boolean donefirst = FALSE;
|
|
|
|
if (!suppliedbuf || buflen < 1)
|
|
return err_ret;
|
|
|
|
/* initialize these static variables each time this is called */
|
|
gr.role_post_attribs = 0;
|
|
for (k = 0; k < NUM_BP; ++k)
|
|
gr.role_pa[k] = 0;
|
|
buf[0] = '\0';
|
|
*suppliedbuf = '\0';
|
|
|
|
/* How many alignments are allowed for the desired race? */
|
|
if (racenum != ROLE_NONE && racenum != ROLE_RANDOM)
|
|
aligncount = race_alignmentcount(racenum);
|
|
|
|
if (alignnum != ROLE_NONE && alignnum != ROLE_RANDOM
|
|
&& ok_align(rolenum, racenum, gendnum, alignnum)) {
|
|
#if 0 /* 'if' and 'else' had duplicate code here; probably a copy+parse
|
|
* oversight; if a problem with filtering of random role selection
|
|
* crops up, this is probably the place to start looking */
|
|
|
|
/* if race specified, and multiple choice of alignments for it */
|
|
if ((racenum >= 0) && (aligncount > 1)) {
|
|
} else {
|
|
}
|
|
#endif /* the four lines of code below were in both 'if' and 'else' above */
|
|
if (donefirst)
|
|
Strcat(buf, " ");
|
|
Strcat(buf, aligns[alignnum].adj);
|
|
donefirst = TRUE;
|
|
} else {
|
|
/* in case we got here by failing the ok_align() test */
|
|
if (alignnum != ROLE_RANDOM)
|
|
alignnum = ROLE_NONE;
|
|
/* if alignment not specified, but race is specified
|
|
and only one choice of alignment for that race then
|
|
don't include it in the later list */
|
|
if ((((racenum != ROLE_NONE && racenum != ROLE_RANDOM)
|
|
&& ok_race(rolenum, racenum, gendnum, alignnum))
|
|
&& (aligncount > 1))
|
|
|| (racenum == ROLE_NONE || racenum == ROLE_RANDOM)) {
|
|
gr.role_pa[BP_ALIGN] = 1;
|
|
gr.role_post_attribs++;
|
|
}
|
|
}
|
|
/* <your lawful> */
|
|
|
|
/* How many genders are allowed for the desired role? */
|
|
if (validrole(rolenum))
|
|
gendercount = role_gendercount(rolenum);
|
|
|
|
if (gendnum != ROLE_NONE && gendnum != ROLE_RANDOM) {
|
|
if (validrole(rolenum)) {
|
|
/* if role specified, and multiple choice of genders for it,
|
|
and name of role itself does not distinguish gender */
|
|
if ((rolenum != ROLE_NONE) && (gendercount > 1)
|
|
&& !roles[rolenum].name.f) {
|
|
if (donefirst)
|
|
Strcat(buf, " ");
|
|
Strcat(buf, genders[gendnum].adj);
|
|
donefirst = TRUE;
|
|
}
|
|
} else {
|
|
if (donefirst)
|
|
Strcat(buf, " ");
|
|
Strcat(buf, genders[gendnum].adj);
|
|
donefirst = TRUE;
|
|
}
|
|
} else {
|
|
/* if gender not specified, but role is specified
|
|
and only one choice of gender then
|
|
don't include it in the later list */
|
|
if ((validrole(rolenum) && (gendercount > 1))
|
|
|| !validrole(rolenum)) {
|
|
gr.role_pa[BP_GEND] = 1;
|
|
gr.role_post_attribs++;
|
|
}
|
|
}
|
|
/* <your lawful female> */
|
|
|
|
if (racenum != ROLE_NONE && racenum != ROLE_RANDOM) {
|
|
if (validrole(rolenum)
|
|
&& ok_race(rolenum, racenum, gendnum, alignnum)) {
|
|
if (donefirst)
|
|
Strcat(buf, " ");
|
|
Strcat(buf, (rolenum == ROLE_NONE) ? races[racenum].noun
|
|
: races[racenum].adj);
|
|
donefirst = TRUE;
|
|
} else if (!validrole(rolenum)) {
|
|
if (donefirst)
|
|
Strcat(buf, " ");
|
|
Strcat(buf, races[racenum].noun);
|
|
donefirst = TRUE;
|
|
} else {
|
|
gr.role_pa[BP_RACE] = 1;
|
|
gr.role_post_attribs++;
|
|
}
|
|
} else {
|
|
gr.role_pa[BP_RACE] = 1;
|
|
gr.role_post_attribs++;
|
|
}
|
|
/* <your lawful female gnomish> || <your lawful female gnome> */
|
|
|
|
if (validrole(rolenum)) {
|
|
if (donefirst)
|
|
Strcat(buf, " ");
|
|
if (gendnum != ROLE_NONE) {
|
|
if (gendnum == 1 && roles[rolenum].name.f)
|
|
Strcat(buf, roles[rolenum].name.f);
|
|
else
|
|
Strcat(buf, roles[rolenum].name.m);
|
|
} else {
|
|
if (roles[rolenum].name.f) {
|
|
Strcat(buf, roles[rolenum].name.m);
|
|
Strcat(buf, "/");
|
|
Strcat(buf, roles[rolenum].name.f);
|
|
} else
|
|
Strcat(buf, roles[rolenum].name.m);
|
|
}
|
|
donefirst = TRUE;
|
|
} else if (rolenum == ROLE_NONE) {
|
|
gr.role_pa[BP_ROLE] = 1;
|
|
gr.role_post_attribs++;
|
|
}
|
|
|
|
if ((racenum == ROLE_NONE || racenum == ROLE_RANDOM)
|
|
&& !validrole(rolenum)) {
|
|
if (donefirst)
|
|
Strcat(buf, " ");
|
|
Strcat(buf, "character");
|
|
donefirst = TRUE;
|
|
}
|
|
/* <your lawful female gnomish cavewoman> || <your lawful female gnome>
|
|
* || <your lawful female character>
|
|
*/
|
|
if (buflen > (int) (strlen(buf) + 1)) {
|
|
Strcpy(suppliedbuf, buf);
|
|
return suppliedbuf;
|
|
} else
|
|
return err_ret;
|
|
}
|
|
|
|
char *
|
|
build_plselection_prompt(
|
|
char *buf, int buflen,
|
|
int rolenum, int racenum, int gendnum, int alignnum)
|
|
{
|
|
const char *defprompt = "Shall I pick a character for you? [ynaq] ";
|
|
int num_post_attribs = 0;
|
|
char tmpbuf[BUFSZ], *p;
|
|
|
|
if (buflen < QBUFSZ)
|
|
return (char *) defprompt;
|
|
|
|
Strcpy(tmpbuf, "Shall I pick ");
|
|
if (racenum != ROLE_NONE || validrole(rolenum))
|
|
Strcat(tmpbuf, "your ");
|
|
else
|
|
Strcat(tmpbuf, "a ");
|
|
/* <your> */
|
|
|
|
(void) root_plselection_prompt(eos(tmpbuf), buflen - Strlen(tmpbuf),
|
|
rolenum, racenum, gendnum, alignnum);
|
|
/* "Shall I pick a character's role, race, gender, and alignment for you?"
|
|
plus " [ynaq] (y)" is a little too long for a conventional 80 columns;
|
|
also, "pick a character's <anything>" sounds a bit stilted */
|
|
strsubst(tmpbuf, "pick a character", "pick character");
|
|
Sprintf(buf, "%s", s_suffix(tmpbuf));
|
|
/* don't bother splitting caveman/cavewoman or priest/priestess
|
|
in order to apply possessive suffix to both halves, but do
|
|
change "priest/priestess'" to "priest/priestess's" */
|
|
if ((p = strstri(buf, "priest/priestess'")) != 0
|
|
&& p[sizeof "priest/priestess'" - sizeof ""] == '\0')
|
|
strkitten(buf, 's');
|
|
|
|
/* buf should now be:
|
|
* <your lawful female gnomish cavewoman's>
|
|
* || <your lawful female gnome's>
|
|
* || <your lawful female character's>
|
|
*
|
|
* Now append the post attributes to it
|
|
*/
|
|
num_post_attribs = gr.role_post_attribs;
|
|
if (!num_post_attribs) {
|
|
/* some constraints might have been mutually exclusive, in which case
|
|
some prompting that would have been omitted is needed after all */
|
|
if (flags.initrole == ROLE_NONE && !gr.role_pa[BP_ROLE])
|
|
gr.role_pa[BP_ROLE] = ++gr.role_post_attribs;
|
|
if (flags.initrace == ROLE_NONE && !gr.role_pa[BP_RACE])
|
|
gr.role_pa[BP_RACE] = ++gr.role_post_attribs;
|
|
if (flags.initalign == ROLE_NONE && !gr.role_pa[BP_ALIGN])
|
|
gr.role_pa[BP_ALIGN] = ++gr.role_post_attribs;
|
|
if (flags.initgend == ROLE_NONE && !gr.role_pa[BP_GEND])
|
|
gr.role_pa[BP_GEND] = ++gr.role_post_attribs;
|
|
num_post_attribs = gr.role_post_attribs;
|
|
}
|
|
if (num_post_attribs) {
|
|
if (gr.role_pa[BP_RACE]) {
|
|
(void) promptsep(eos(buf), num_post_attribs);
|
|
Strcat(buf, "race");
|
|
}
|
|
if (gr.role_pa[BP_ROLE]) {
|
|
(void) promptsep(eos(buf), num_post_attribs);
|
|
Strcat(buf, "role");
|
|
}
|
|
if (gr.role_pa[BP_GEND]) {
|
|
(void) promptsep(eos(buf), num_post_attribs);
|
|
Strcat(buf, "gender");
|
|
}
|
|
if (gr.role_pa[BP_ALIGN]) {
|
|
(void) promptsep(eos(buf), num_post_attribs);
|
|
Strcat(buf, "alignment");
|
|
}
|
|
}
|
|
Strcat(buf, " for you? [ynaq] ");
|
|
return buf;
|
|
}
|
|
|
|
#undef BP_ALIGN
|
|
#undef BP_GEND
|
|
#undef BP_RACE
|
|
#undef BP_ROLE
|
|
#undef NUM_BP
|
|
|
|
void
|
|
plnamesuffix(void)
|
|
{
|
|
char *sptr, *eptr;
|
|
int i;
|
|
|
|
/* some generic user names will be ignored in favor of prompting */
|
|
if (sysopt.genericusers) {
|
|
if (*sysopt.genericusers == '*') {
|
|
gp.plname[0] = '\0';
|
|
} else {
|
|
/* need to ignore appended '-role-race-gender-alignment';
|
|
'plnamelen' is non-zero when dealing with plname[] value that
|
|
contains a username with dash(es) in it and is usually 0 */
|
|
i = ((eptr = strchr(gp.plname + gp.plnamelen, '-')) != 0)
|
|
? (int) (eptr - gp.plname)
|
|
: (int) Strlen(gp.plname);
|
|
/* look for plname[] in the 'genericusers' space-separated list */
|
|
if (findword(sysopt.genericusers, gp.plname, i, FALSE))
|
|
/* it's generic; remove it so that askname() will be called */
|
|
gp.plname[0] = '\0';
|
|
}
|
|
if (!gp.plname[0])
|
|
gp.plnamelen = 0;
|
|
}
|
|
|
|
do {
|
|
if (!gp.plname[0]) {
|
|
askname(); /* fill gp.plname[] if necessary, or set defer_plname */
|
|
gp.plnamelen = 0; /* plname[] might have -role-race-&c attached */
|
|
}
|
|
|
|
/* Look for tokens delimited by '-' */
|
|
sptr = gp.plname + gp.plnamelen;
|
|
if ((eptr = strchr(sptr, '-')) != (char *) 0)
|
|
*eptr++ = '\0';
|
|
while (eptr) {
|
|
/* Isolate the next token */
|
|
sptr = eptr;
|
|
if ((eptr = strchr(sptr, '-')) != (char *) 0)
|
|
*eptr++ = '\0';
|
|
|
|
/* Try to match it to something */
|
|
if ((i = str2role(sptr)) != ROLE_NONE)
|
|
flags.initrole = i;
|
|
else if ((i = str2race(sptr)) != ROLE_NONE)
|
|
flags.initrace = i;
|
|
else if ((i = str2gend(sptr)) != ROLE_NONE)
|
|
flags.initgend = i;
|
|
else if ((i = str2align(sptr)) != ROLE_NONE)
|
|
flags.initalign = i;
|
|
}
|
|
} while (!gp.plname[0] && !iflags.defer_plname);
|
|
|
|
/* commas in the gp.plname confuse the record file, convert to spaces */
|
|
(void) strNsubst(gp.plname, ",", " ", 0);
|
|
}
|
|
|
|
/* show current settings for name, role, race, gender, and alignment
|
|
in the specified window */
|
|
void
|
|
role_selection_prolog(int which, winid where)
|
|
{
|
|
static const char NEARDATA choosing[] = " choosing now",
|
|
not_yet[] = " not yet specified",
|
|
rand_choice[] = " random";
|
|
char buf[BUFSZ];
|
|
int r, c, gend, a, allowmask;
|
|
|
|
r = flags.initrole;
|
|
c = flags.initrace;
|
|
gend = flags.initgend;
|
|
a = flags.initalign;
|
|
if (r >= 0) {
|
|
allowmask = roles[r].allow;
|
|
if ((allowmask & ROLE_RACEMASK) == MH_HUMAN)
|
|
c = 0; /* races[human] */
|
|
else if (c >= 0 && !(allowmask & ROLE_RACEMASK & races[c].allow))
|
|
c = ROLE_RANDOM;
|
|
if ((allowmask & ROLE_GENDMASK) == ROLE_MALE)
|
|
gend = 0; /* role forces male (hypothetical) */
|
|
else if ((allowmask & ROLE_GENDMASK) == ROLE_FEMALE)
|
|
gend = 1; /* role forces female (valkyrie) */
|
|
if ((allowmask & ROLE_ALIGNMASK) == AM_LAWFUL)
|
|
a = 0; /* aligns[lawful] */
|
|
else if ((allowmask & ROLE_ALIGNMASK) == AM_NEUTRAL)
|
|
a = 1; /* aligns[neutral] */
|
|
else if ((allowmask & ROLE_ALIGNMASK) == AM_CHAOTIC)
|
|
a = 2; /* alings[chaotic] */
|
|
}
|
|
if (c >= 0) {
|
|
allowmask = races[c].allow;
|
|
if ((allowmask & ROLE_ALIGNMASK) == AM_LAWFUL)
|
|
a = 0; /* aligns[lawful] */
|
|
else if ((allowmask & ROLE_ALIGNMASK) == AM_NEUTRAL)
|
|
a = 1; /* aligns[neutral] */
|
|
else if ((allowmask & ROLE_ALIGNMASK) == AM_CHAOTIC)
|
|
a = 2; /* alings[chaotic] */
|
|
/* [c never forces gender] */
|
|
}
|
|
/* [g and a don't constrain anything sufficiently
|
|
to narrow something done to a single choice] */
|
|
|
|
Sprintf(buf, "%12s ", "name:");
|
|
Strcat(buf, (which == RS_NAME) ? choosing
|
|
: !*gp.plname ? not_yet : gp.plname);
|
|
putstr(where, 0, buf);
|
|
Sprintf(buf, "%12s ", "role:");
|
|
Strcat(buf, (which == RS_ROLE) ? choosing
|
|
: (r == ROLE_NONE) ? not_yet
|
|
: (r == ROLE_RANDOM) ? rand_choice
|
|
: roles[r].name.m);
|
|
if (r >= 0 && roles[r].name.f) {
|
|
/* distinct female name [caveman/cavewoman, priest/priestess] */
|
|
if (gend == 1)
|
|
/* female specified; replace male role name with female one */
|
|
Sprintf(strchr(buf, ':'), ": %s", roles[r].name.f);
|
|
else if (gend < 0)
|
|
/* gender unspecified; append slash and female role name */
|
|
Sprintf(eos(buf), "/%s", roles[r].name.f);
|
|
}
|
|
putstr(where, 0, buf);
|
|
Sprintf(buf, "%12s ", "race:");
|
|
Strcat(buf, (which == RS_RACE) ? choosing
|
|
: (c == ROLE_NONE) ? not_yet
|
|
: (c == ROLE_RANDOM) ? rand_choice
|
|
: races[c].noun);
|
|
putstr(where, 0, buf);
|
|
Sprintf(buf, "%12s ", "gender:");
|
|
Strcat(buf, (which == RS_GENDER) ? choosing
|
|
: (gend == ROLE_NONE) ? not_yet
|
|
: (gend == ROLE_RANDOM) ? rand_choice
|
|
: genders[gend].adj);
|
|
putstr(where, 0, buf);
|
|
Sprintf(buf, "%12s ", "alignment:");
|
|
Strcat(buf, (which == RS_ALGNMNT) ? choosing
|
|
: (a == ROLE_NONE) ? not_yet
|
|
: (a == ROLE_RANDOM) ? rand_choice
|
|
: aligns[a].adj);
|
|
putstr(where, 0, buf);
|
|
}
|
|
|
|
/* add a "pick alignment first"-type entry to the specified menu */
|
|
void
|
|
role_menu_extra(int which, winid where, boolean preselect)
|
|
{
|
|
static NEARDATA const char RS_menu_let[] = {
|
|
'=', /* name */
|
|
'?', /* role */
|
|
'/', /* race */
|
|
'\"', /* gender */
|
|
'[', /* alignment */
|
|
};
|
|
anything any;
|
|
char buf[BUFSZ];
|
|
const char *what = 0, *constrainer = 0, *forcedvalue = 0;
|
|
int f = 0, r, c, gend, a, i, allowmask;
|
|
int clr = 0;
|
|
|
|
r = flags.initrole;
|
|
c = flags.initrace;
|
|
switch (which) {
|
|
case RS_NAME:
|
|
what = "name";
|
|
break;
|
|
case RS_ROLE:
|
|
what = "role";
|
|
f = r;
|
|
for (i = 0; i < SIZE(roles); ++i)
|
|
if (i != f && !gr.rfilter.roles[i])
|
|
break;
|
|
if (i == SIZE(roles)) {
|
|
constrainer = "filter";
|
|
forcedvalue = "role";
|
|
}
|
|
break;
|
|
case RS_RACE:
|
|
what = "race";
|
|
f = flags.initrace;
|
|
c = ROLE_NONE; /* override player's setting */
|
|
if (r >= 0) {
|
|
allowmask = roles[r].allow & ROLE_RACEMASK;
|
|
if (allowmask == MH_HUMAN)
|
|
c = 0; /* races[human] */
|
|
if (c >= 0) {
|
|
constrainer = "role";
|
|
forcedvalue = races[c].noun;
|
|
} else if (f >= 0
|
|
&& (allowmask & ~gr.rfilter.mask) == races[f].selfmask) {
|
|
/* if there is only one race choice available due to user
|
|
options disallowing others, race menu entry is disabled */
|
|
constrainer = "filter";
|
|
forcedvalue = "race";
|
|
}
|
|
}
|
|
break;
|
|
case RS_GENDER:
|
|
what = "gender";
|
|
f = flags.initgend;
|
|
gend = ROLE_NONE;
|
|
if (r >= 0) {
|
|
allowmask = roles[r].allow & ROLE_GENDMASK;
|
|
if (allowmask == ROLE_MALE)
|
|
gend = 0; /* genders[male] */
|
|
else if (allowmask == ROLE_FEMALE)
|
|
gend = 1; /* genders[female] */
|
|
if (gend >= 0) {
|
|
constrainer = "role";
|
|
forcedvalue = genders[gend].adj;
|
|
} else if (f >= 0
|
|
&& (allowmask & ~gr.rfilter.mask) == genders[f].allow) {
|
|
/* if there is only one gender choice available due to user
|
|
options disallowing other, gender menu entry is disabled */
|
|
constrainer = "filter";
|
|
forcedvalue = "gender";
|
|
}
|
|
}
|
|
break;
|
|
case RS_ALGNMNT:
|
|
what = "alignment";
|
|
f = flags.initalign;
|
|
a = ROLE_NONE;
|
|
if (r >= 0) {
|
|
allowmask = roles[r].allow & ROLE_ALIGNMASK;
|
|
if (allowmask == AM_LAWFUL)
|
|
a = 0; /* aligns[lawful] */
|
|
else if (allowmask == AM_NEUTRAL)
|
|
a = 1; /* aligns[neutral] */
|
|
else if (allowmask == AM_CHAOTIC)
|
|
a = 2; /* aligns[chaotic] */
|
|
if (a >= 0)
|
|
constrainer = "role";
|
|
}
|
|
if (c >= 0 && !constrainer) {
|
|
allowmask = races[c].allow & ROLE_ALIGNMASK;
|
|
if (allowmask == AM_LAWFUL)
|
|
a = 0; /* aligns[lawful] */
|
|
else if (allowmask == AM_NEUTRAL)
|
|
a = 1; /* aligns[neutral] */
|
|
else if (allowmask == AM_CHAOTIC)
|
|
a = 2; /* aligns[chaotic] */
|
|
if (a >= 0)
|
|
constrainer = "race";
|
|
}
|
|
if (f >= 0 && !constrainer
|
|
&& (ROLE_ALIGNMASK & ~gr.rfilter.mask) == aligns[f].allow) {
|
|
/* if there is only one alignment choice available due to user
|
|
options disallowing others, algn menu entry is disabled */
|
|
constrainer = "filter";
|
|
forcedvalue = "alignment";
|
|
}
|
|
if (a >= 0)
|
|
forcedvalue = aligns[a].adj;
|
|
break;
|
|
}
|
|
|
|
any = cg.zeroany; /* zero out all bits */
|
|
if (constrainer) {
|
|
any.a_int = 0;
|
|
/* use four spaces of padding to fake a grayed out menu choice */
|
|
Sprintf(buf, "%4s%s forces %s", "", constrainer, forcedvalue);
|
|
add_menu(where, &nul_glyphinfo, &any, 0, 0, ATR_NONE, clr, buf,
|
|
MENU_ITEMFLAGS_NONE);
|
|
} else if (what) {
|
|
any.a_int = RS_menu_arg(which);
|
|
Sprintf(buf, "Pick%s %s first", (f >= 0) ? " another" : "", what);
|
|
add_menu(where, &nul_glyphinfo, &any, RS_menu_let[which], 0,
|
|
ATR_NONE, clr, buf, MENU_ITEMFLAGS_NONE);
|
|
} else if (which == RS_filter) {
|
|
char setfiltering[40];
|
|
|
|
any.a_int = RS_menu_arg(RS_filter);
|
|
Sprintf(setfiltering, "%s role/race/&c filtering",
|
|
gotrolefilter() ? "Reset" : "Set");
|
|
add_menu(where, &nul_glyphinfo, &any, '~', 0, ATR_NONE,
|
|
clr, setfiltering, MENU_ITEMFLAGS_NONE);
|
|
} else if (which == ROLE_RANDOM) {
|
|
any.a_int = ROLE_RANDOM;
|
|
add_menu(where, &nul_glyphinfo, &any, '*', 0,
|
|
ATR_NONE, clr, "Random",
|
|
preselect ? MENU_ITEMFLAGS_SELECTED : MENU_ITEMFLAGS_NONE);
|
|
} else if (which == ROLE_NONE) {
|
|
any.a_int = ROLE_NONE;
|
|
add_menu(where, &nul_glyphinfo, &any, 'q', 0,
|
|
ATR_NONE, clr, "Quit",
|
|
preselect ? MENU_ITEMFLAGS_SELECTED : MENU_ITEMFLAGS_NONE);
|
|
} else {
|
|
impossible("role_menu_extra: bad arg (%d)", which);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Special setup modifications here:
|
|
*
|
|
* Unfortunately, this is going to have to be done
|
|
* on each newgame or restore, because you lose the permonst mods
|
|
* across a save/restore. :-)
|
|
*
|
|
* 1 - The Rogue Leader is the Tourist Nemesis.
|
|
* 2 - Priests start with a random alignment - convert the leader and
|
|
* guardians here.
|
|
* 3 - Priests also get their set of deities from a randomly chosen role.
|
|
* 4 - [obsolete] Elves can have one of two different leaders,
|
|
* but can't work it out here because it requires hacking the
|
|
* level file data (see sp_lev.c).
|
|
*
|
|
* This code also replaces quest_init().
|
|
*/
|
|
void
|
|
role_init(void)
|
|
{
|
|
int alignmnt;
|
|
struct permonst *pm;
|
|
|
|
/* Strip the role letter out of the player name.
|
|
* This is included for backwards compatibility.
|
|
*/
|
|
plnamesuffix();
|
|
|
|
/* Check for a valid role. Try flags.initrole first. */
|
|
if (!validrole(flags.initrole)) {
|
|
/* Try the player letter second */
|
|
if ((flags.initrole = str2role(gp.pl_character)) < 0)
|
|
/* None specified; pick a random role */
|
|
flags.initrole = randrole_filtered();
|
|
}
|
|
|
|
/* We now have a valid role index. Copy the role name back. */
|
|
/* This should become OBSOLETE */
|
|
Strcpy(gp.pl_character, roles[flags.initrole].name.m);
|
|
gp.pl_character[PL_CSIZ - 1] = '\0';
|
|
|
|
/* Check for a valid race */
|
|
if (!validrace(flags.initrole, flags.initrace))
|
|
flags.initrace = randrace(flags.initrole);
|
|
|
|
/* Check for a valid gender. If new game, check both initgend
|
|
* and female. On restore, assume flags.female is correct. */
|
|
if (flags.pantheon == -1) { /* new game */
|
|
if (!validgend(flags.initrole, flags.initrace, flags.female))
|
|
flags.female = !flags.female;
|
|
}
|
|
if (!validgend(flags.initrole, flags.initrace, flags.initgend))
|
|
/* Note that there is no way to check for an unspecified gender. */
|
|
flags.initgend = flags.female;
|
|
|
|
/* Check for a valid alignment */
|
|
if (!validalign(flags.initrole, flags.initrace, flags.initalign))
|
|
/* Pick a random alignment */
|
|
flags.initalign = randalign(flags.initrole, flags.initrace);
|
|
alignmnt = aligns[flags.initalign].value;
|
|
|
|
/* Initialize gu.urole and gu.urace */
|
|
gu.urole = roles[flags.initrole];
|
|
gu.urace = races[flags.initrace];
|
|
|
|
/* Fix up the quest leader */
|
|
if (gu.urole.ldrnum != NON_PM) {
|
|
pm = &mons[gu.urole.ldrnum];
|
|
pm->msound = MS_LEADER;
|
|
pm->mflags2 |= (M2_PEACEFUL);
|
|
pm->mflags3 |= M3_CLOSE;
|
|
pm->maligntyp = alignmnt * 3;
|
|
/* if gender is random, we choose it now instead of waiting
|
|
until the leader monster is created */
|
|
gq.quest_status.ldrgend =
|
|
is_neuter(pm) ? 2 : is_female(pm) ? 1 : is_male(pm)
|
|
? 0
|
|
: (rn2(100) < 50);
|
|
}
|
|
|
|
/* Fix up the quest guardians */
|
|
if (gu.urole.guardnum != NON_PM) {
|
|
pm = &mons[gu.urole.guardnum];
|
|
pm->mflags2 |= (M2_PEACEFUL);
|
|
pm->maligntyp = alignmnt * 3;
|
|
}
|
|
|
|
/* Fix up the quest nemesis */
|
|
if (gu.urole.neminum != NON_PM) {
|
|
pm = &mons[gu.urole.neminum];
|
|
pm->msound = MS_NEMESIS;
|
|
pm->mflags2 &= ~(M2_PEACEFUL);
|
|
pm->mflags2 |= (M2_NASTY | M2_STALK | M2_HOSTILE);
|
|
pm->mflags3 &= ~(M3_CLOSE);
|
|
pm->mflags3 |= M3_WANTSARTI | M3_WAITFORU;
|
|
/* if gender is random, we choose it now instead of waiting
|
|
until the nemesis monster is created */
|
|
gq.quest_status.nemgend = is_neuter(pm) ? 2 : is_female(pm) ? 1
|
|
: is_male(pm) ? 0 : (rn2(100) < 50);
|
|
}
|
|
|
|
/* Fix up the god names */
|
|
if (flags.pantheon == -1) { /* new game */
|
|
int trycnt = 0;
|
|
flags.pantheon = flags.initrole; /* use own gods */
|
|
/* unless they're missing */
|
|
while (!roles[flags.pantheon].lgod && ++trycnt < 100)
|
|
flags.pantheon = randrole(FALSE);
|
|
if (!roles[flags.pantheon].lgod) {
|
|
int i;
|
|
for (i = 0; i < SIZE(roles) - 1; i++)
|
|
if (roles[i].lgod) {
|
|
flags.pantheon = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!gu.urole.lgod) {
|
|
gu.urole.lgod = roles[flags.pantheon].lgod;
|
|
gu.urole.ngod = roles[flags.pantheon].ngod;
|
|
gu.urole.cgod = roles[flags.pantheon].cgod;
|
|
}
|
|
/* 0 or 1; no gods are neuter, nor is gender randomized */
|
|
gq.quest_status.godgend = !strcmpi(align_gtitle(alignmnt), "goddess");
|
|
|
|
#if 0
|
|
/*
|
|
* Disable this fixup so that mons[] can be const. The only
|
|
* place where it actually matters for the hero is in set_uasmon()
|
|
* and that can use mons[race] rather than mons[role] for this
|
|
* particular property. Despite the comment, it is checked--where
|
|
* needed--via instrinsic 'Infravision' which set_uasmon() manages.
|
|
*/
|
|
/* Fix up infravision */
|
|
if (mons[gu.urace.mnum].mflags3 & M3_INFRAVISION) {
|
|
/* although an infravision intrinsic is possible, infravision
|
|
* is purely a property of the physical race. This means that we
|
|
* must put the infravision flag in the player's current race
|
|
* (either that or have separate permonst entries for
|
|
* elven/non-elven members of each class). The side effect is that
|
|
* all NPCs of that class will have (probably bogus) infravision,
|
|
* but since infravision has no effect for NPCs anyway we can
|
|
* ignore this.
|
|
*/
|
|
mons[gu.urole.mnum].mflags3 |= M3_INFRAVISION;
|
|
}
|
|
#endif /*0*/
|
|
|
|
/* Artifacts are fixed in hack_artifacts() */
|
|
|
|
/* Success! */
|
|
return;
|
|
}
|
|
|
|
const char *
|
|
Hello(struct monst* mtmp)
|
|
{
|
|
switch (Role_switch) {
|
|
case PM_KNIGHT:
|
|
return "Salutations"; /* Olde English */
|
|
case PM_SAMURAI:
|
|
return (mtmp && mtmp->data == &mons[PM_SHOPKEEPER])
|
|
? "Irasshaimase"
|
|
: "Konnichi wa"; /* Japanese */
|
|
case PM_TOURIST:
|
|
return "Aloha"; /* Hawaiian */
|
|
case PM_VALKYRIE:
|
|
return
|
|
#ifdef MAIL_STRUCTURES
|
|
(mtmp && mtmp->data == &mons[PM_MAIL_DAEMON]) ? "Hallo" :
|
|
#endif
|
|
"Velkommen"; /* Norse */
|
|
default:
|
|
return "Hello";
|
|
}
|
|
}
|
|
|
|
const char *
|
|
Goodbye(void)
|
|
{
|
|
switch (Role_switch) {
|
|
case PM_KNIGHT:
|
|
return "Fare thee well"; /* Olde English */
|
|
case PM_SAMURAI:
|
|
return "Sayonara"; /* Japanese */
|
|
case PM_TOURIST:
|
|
return "Aloha"; /* Hawaiian */
|
|
case PM_VALKYRIE:
|
|
return "Farvel"; /* Norse */
|
|
default:
|
|
return "Goodbye";
|
|
}
|
|
}
|
|
|
|
/* if pmindex is any player race (not necessarily the hero's),
|
|
return a pointer to the races[] entry for it */
|
|
const struct Race *
|
|
character_race(short pmindex)
|
|
{
|
|
const struct Race *r;
|
|
|
|
for (r = races; r->mnum >= LOW_PM; ++r)
|
|
if (r->mnum == pmindex)
|
|
return r;
|
|
return (const struct Race *) NULL;
|
|
}
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
/* potential interface routine */
|
|
void
|
|
genl_player_selection(void)
|
|
{
|
|
if (genl_player_setup(0))
|
|
return;
|
|
|
|
/* player cancelled role/race/&c selection, so quit */
|
|
nh_terminate(EXIT_SUCCESS);
|
|
/*NOTREACHED*/
|
|
}
|
|
|
|
#if defined(TTY_GRAPHICS) || defined(CURSES_GRAPHICS)
|
|
/* ['#else' far below] */
|
|
|
|
static boolean reset_role_filtering(void);
|
|
static winid plsel_startmenu(int, int);
|
|
static int maybe_skip_seps(int, int);
|
|
static void setup_rolemenu(winid, boolean, int, int, int);
|
|
static void setup_racemenu(winid, boolean, int, int, int);
|
|
static void setup_gendmenu(winid, boolean, int, int, int);
|
|
static void setup_algnmenu(winid, boolean, int, int, int);
|
|
|
|
/* try to reduce clutter in the code below... */
|
|
#define ROLE flags.initrole
|
|
#define RACE flags.initrace
|
|
#define GEND flags.initgend
|
|
#define ALGN flags.initalign
|
|
|
|
/* guts of tty's player_selection() */
|
|
int
|
|
genl_player_setup(int screenheight)
|
|
{
|
|
char pbuf[QBUFSZ];
|
|
anything any;
|
|
int i, k, n, choice, nextpick;
|
|
boolean getconfirmation, picksomething;
|
|
winid win = WIN_ERR;
|
|
menu_item *selected = 0;
|
|
int clr = 0;
|
|
char pick4u = 'n';
|
|
int result = 0; /* assume failure (player chooses to 'quit') */
|
|
|
|
gp.program_state.in_role_selection++; /* affects tty menu cleanup */
|
|
/* Used to avoid "Is this ok?" if player has already specified all
|
|
* four facets of role.
|
|
* Note that rigid_role_checks might force any unspecified facets to
|
|
* have a specific value, but that will still require confirmation;
|
|
* player can specify the forced ones if avoiding that is demanded.
|
|
*/
|
|
picksomething = (ROLE == ROLE_NONE || RACE == ROLE_NONE
|
|
|| GEND == ROLE_NONE || ALGN == ROLE_NONE);
|
|
/* Used for '-@';
|
|
* choose randomly without asking for all unspecified facets.
|
|
*/
|
|
if (flags.randomall && picksomething) {
|
|
if (ROLE == ROLE_NONE)
|
|
ROLE = ROLE_RANDOM;
|
|
if (RACE == ROLE_NONE)
|
|
RACE = ROLE_RANDOM;
|
|
if (GEND == ROLE_NONE)
|
|
GEND = ROLE_RANDOM;
|
|
if (ALGN == ROLE_NONE)
|
|
ALGN = ROLE_RANDOM;
|
|
}
|
|
|
|
/* prevent unnecessary prompting if role forces race (samurai) or gender
|
|
(valkyrie) or alignment (rogue), or race forces alignment (orc), &c */
|
|
rigid_role_checks();
|
|
|
|
if (ROLE == ROLE_NONE || RACE == ROLE_NONE
|
|
|| GEND == ROLE_NONE || ALGN == ROLE_NONE) {
|
|
char *prompt = build_plselection_prompt(pbuf, QBUFSZ,
|
|
ROLE, RACE, GEND, ALGN);
|
|
/* prompt[] contains "Shall I pick ... for you? [ynaq] "
|
|
y - game picks role,&c then asks player to confirm;
|
|
n - player manually chooses via menu selections;
|
|
a - like 'y', but skips confirmation and starts game;
|
|
q - quit
|
|
*/
|
|
#if 1
|
|
trimspaces(prompt); /* 'prompt' is constructed with trailing space */
|
|
/* accept any character and do validation ourselves so that we can
|
|
shorten prompt; it will be "Shall I pick ... for you? [ynaq] "
|
|
with final space appended by yn_function() [for tty at least] */
|
|
do {
|
|
pick4u = yn_function(prompt, (char *) 0, '\0', FALSE);
|
|
pick4u = lowc(pick4u);
|
|
if (pick4u == '\033' || pick4u == 'q') /* handle [q] */
|
|
goto setup_done;
|
|
if (pick4u == ' ' || pick4u == '\n' || pick4u == '\r')
|
|
pick4u = 'y'; /* default */
|
|
else if (pick4u == '@' || pick4u == '*')
|
|
pick4u = 'a'; /* similar to '-@' on command line */
|
|
/* TODO? handle response of '?' */
|
|
} while (pick4u != 'y' && pick4u != 'n' && pick4u != 'a'); /* [yna] */
|
|
|
|
#else /* slightly simpler but more likely to end up being wrapped */
|
|
|
|
char *p;
|
|
/* strip choices off prompt string; yn_function() will show them */
|
|
if ((p = strchr(prompt, '[')) != 0)
|
|
*p = '\0';
|
|
trimspaces(prompt); /* remove trailing space */
|
|
/* prompt becomes "Shall I pick ... for you? [ynaq] (y) "
|
|
with " [ynaq] (y) " appended by yn_function() which also changes
|
|
user's <space> and <return> to 'y', <escape> to 'q' */
|
|
pick4u = yn_function(prompt, ynaqchars, 'y', FALSE);
|
|
if (pick4u != 'y' && pick4u != 'a' && pick4u != 'n')
|
|
goto setup_done; /* bail */
|
|
#endif
|
|
}
|
|
|
|
makepicks:
|
|
nextpick = RS_ROLE;
|
|
do {
|
|
if (nextpick == RS_ROLE) {
|
|
nextpick = RS_RACE;
|
|
/* Select a role, if necessary;
|
|
we'll try to be compatible with pre-selected
|
|
race/gender/alignment, but may not succeed. */
|
|
if (ROLE < 0) {
|
|
/* process the choice */
|
|
if (pick4u == 'y' || pick4u == 'a' || ROLE == ROLE_RANDOM) {
|
|
/* pick a random role */
|
|
k = pick_role(RACE, GEND, ALGN, PICK_RANDOM);
|
|
if (k < 0) {
|
|
pline("Incompatible role!");
|
|
k = randrole(FALSE);
|
|
}
|
|
} else {
|
|
/* 'excess' is used to try to avoid tty pagination */
|
|
int excess = maybe_skip_seps(screenheight, RS_ROLE);
|
|
|
|
/* prompt for a role */
|
|
win = plsel_startmenu(screenheight, RS_ROLE);
|
|
/* populate the menu with role choices */
|
|
setup_rolemenu(win, TRUE, RACE, GEND, ALGN);
|
|
/* add miscellaneous menu entries */
|
|
role_menu_extra(ROLE_RANDOM, win, TRUE);
|
|
any = cg.zeroany; /* separator, not a choice */
|
|
if (excess < 1 || excess > 2)
|
|
add_menu(win, &nul_glyphinfo, &any, 0, 0,
|
|
ATR_NONE, clr, "", MENU_ITEMFLAGS_NONE);
|
|
role_menu_extra(RS_RACE, win, FALSE);
|
|
role_menu_extra(RS_GENDER, win, FALSE);
|
|
role_menu_extra(RS_ALGNMNT, win, FALSE);
|
|
role_menu_extra(RS_filter, win, FALSE);
|
|
role_menu_extra(ROLE_NONE, win, FALSE); /* quit */
|
|
Strcpy(pbuf, "Pick a role or profession");
|
|
end_menu(win, pbuf);
|
|
n = select_menu(win, PICK_ONE, &selected);
|
|
/*
|
|
* PICK_ONE with preselected choice behaves strangely:
|
|
* n == -1 -- <escape>, so use quit choice;
|
|
* n == 0 -- explicitly chose preselected entry,
|
|
* toggling it off, so use it;
|
|
* n == 1 -- implicitly chose preselected entry
|
|
* with <space> or <return>;
|
|
* n == 2 -- explicitly chose a different entry, so
|
|
* both it and preselected one are in list.
|
|
*/
|
|
if (n > 0) {
|
|
choice = selected[0].item.a_int;
|
|
if (n > 1 && choice == ROLE_RANDOM)
|
|
choice = selected[1].item.a_int;
|
|
} else
|
|
choice = (n == 0) ? ROLE_RANDOM : ROLE_NONE;
|
|
if (selected)
|
|
free((genericptr_t) selected), selected = 0;
|
|
destroy_nhwindow(win), win = WIN_ERR;
|
|
|
|
if (choice == ROLE_NONE) {
|
|
goto setup_done; /* selected quit */
|
|
} else if (choice == RS_menu_arg(RS_ALGNMNT)) {
|
|
ALGN = k = ROLE_NONE;
|
|
nextpick = RS_ALGNMNT;
|
|
} else if (choice == RS_menu_arg(RS_GENDER)) {
|
|
GEND = k = ROLE_NONE;
|
|
nextpick = RS_GENDER;
|
|
} else if (choice == RS_menu_arg(RS_RACE)) {
|
|
RACE = k = ROLE_NONE;
|
|
nextpick = RS_RACE;
|
|
} else if (choice == RS_menu_arg(RS_filter)) {
|
|
ROLE = k = ROLE_NONE;
|
|
(void) reset_role_filtering();
|
|
nextpick = RS_ROLE;
|
|
} else if (choice == ROLE_RANDOM) {
|
|
k = pick_role(RACE, GEND, ALGN, PICK_RANDOM);
|
|
if (k < 0)
|
|
k = randrole(FALSE);
|
|
} else {
|
|
k = choice - 1;
|
|
}
|
|
}
|
|
ROLE = k;
|
|
} /* needed role */
|
|
} /* picking role */
|
|
|
|
if (nextpick == RS_RACE) {
|
|
nextpick = (ROLE < 0) ? RS_ROLE : RS_GENDER;
|
|
/* Select a race, if necessary;
|
|
force compatibility with role, try for compatibility
|
|
with pre-selected gender/alignment. */
|
|
if (RACE < 0 || !validrace(ROLE, RACE)) {
|
|
/* no race yet, or pre-selected race not valid */
|
|
if (pick4u == 'y' || pick4u == 'a' || RACE == ROLE_RANDOM) {
|
|
k = pick_race(ROLE, GEND, ALGN, PICK_RANDOM);
|
|
if (k < 0) {
|
|
pline("Incompatible race!");
|
|
k = randrace(ROLE);
|
|
}
|
|
} else { /* pick4u == 'n' */
|
|
/* Count the number of valid races */
|
|
n = 0; /* number valid */
|
|
k = 0; /* valid race */
|
|
for (i = 0; races[i].noun; i++)
|
|
if (ok_race(ROLE, i, GEND, ALGN)) {
|
|
n++;
|
|
k = i;
|
|
}
|
|
if (n == 0) {
|
|
for (i = 0; races[i].noun; i++)
|
|
if (validrace(ROLE, i)) {
|
|
n++;
|
|
k = i;
|
|
}
|
|
}
|
|
/* Permit the user to pick, if there is more than one */
|
|
if (n > 1) {
|
|
win = plsel_startmenu(screenheight, RS_RACE);
|
|
any = cg.zeroany; /* zero out all bits */
|
|
/* populate the menu with role choices */
|
|
setup_racemenu(win, TRUE, ROLE, GEND, ALGN);
|
|
/* add miscellaneous menu entries */
|
|
role_menu_extra(ROLE_RANDOM, win, TRUE);
|
|
any.a_int = 0; /* separator, not a choice */
|
|
add_menu(win, &nul_glyphinfo, &any, 0, 0,
|
|
ATR_NONE, clr, "", MENU_ITEMFLAGS_NONE);
|
|
role_menu_extra(RS_ROLE, win, FALSE);
|
|
role_menu_extra(RS_GENDER, win, FALSE);
|
|
role_menu_extra(RS_ALGNMNT, win, FALSE);
|
|
role_menu_extra(RS_filter, win, FALSE);
|
|
role_menu_extra(ROLE_NONE, win, FALSE); /* quit */
|
|
Strcpy(pbuf, "Pick a race or species");
|
|
end_menu(win, pbuf);
|
|
n = select_menu(win, PICK_ONE, &selected);
|
|
if (n > 0) {
|
|
choice = selected[0].item.a_int;
|
|
if (n > 1 && choice == ROLE_RANDOM)
|
|
choice = selected[1].item.a_int;
|
|
} else
|
|
choice = (n == 0) ? ROLE_RANDOM : ROLE_NONE;
|
|
if (selected)
|
|
free((genericptr_t) selected), selected = 0;
|
|
destroy_nhwindow(win), win = WIN_ERR;
|
|
|
|
if (choice == ROLE_NONE) {
|
|
goto setup_done; /* selected quit */
|
|
} else if (choice == RS_menu_arg(RS_ALGNMNT)) {
|
|
ALGN = k = ROLE_NONE;
|
|
nextpick = RS_ALGNMNT;
|
|
} else if (choice == RS_menu_arg(RS_GENDER)) {
|
|
GEND = k = ROLE_NONE;
|
|
nextpick = RS_GENDER;
|
|
} else if (choice == RS_menu_arg(RS_ROLE)) {
|
|
ROLE = k = ROLE_NONE;
|
|
nextpick = RS_ROLE;
|
|
} else if (choice == RS_menu_arg(RS_filter)) {
|
|
RACE = k = ROLE_NONE;
|
|
if (reset_role_filtering())
|
|
nextpick = RS_ROLE;
|
|
else
|
|
nextpick = RS_RACE;
|
|
} else if (choice == ROLE_RANDOM) {
|
|
k = pick_race(ROLE, GEND, ALGN, PICK_RANDOM);
|
|
if (k < 0)
|
|
k = randrace(ROLE);
|
|
} else {
|
|
k = choice - 1;
|
|
}
|
|
}
|
|
}
|
|
RACE = k;
|
|
} /* needed race */
|
|
} /* picking race */
|
|
|
|
if (nextpick == RS_GENDER) {
|
|
nextpick = (ROLE < 0) ? RS_ROLE : (RACE < 0) ? RS_RACE
|
|
: RS_ALGNMNT;
|
|
/* Select a gender, if necessary;
|
|
force compatibility with role/race, try for compatibility
|
|
with pre-selected alignment. */
|
|
if (GEND < 0 || !validgend(ROLE, RACE, GEND)) {
|
|
/* no gender yet, or pre-selected gender not valid */
|
|
if (pick4u == 'y' || pick4u == 'a' || GEND == ROLE_RANDOM) {
|
|
k = pick_gend(ROLE, RACE, ALGN, PICK_RANDOM);
|
|
if (k < 0) {
|
|
pline("Incompatible gender!");
|
|
k = randgend(ROLE, RACE);
|
|
}
|
|
} else { /* pick4u == 'n' */
|
|
/* Count the number of valid genders */
|
|
n = 0; /* number valid */
|
|
k = 0; /* valid gender */
|
|
for (i = 0; i < ROLE_GENDERS; i++)
|
|
if (ok_gend(ROLE, RACE, i, ALGN)) {
|
|
n++;
|
|
k = i;
|
|
}
|
|
if (n == 0) {
|
|
for (i = 0; i < ROLE_GENDERS; i++)
|
|
if (validgend(ROLE, RACE, i)) {
|
|
n++;
|
|
k = i;
|
|
}
|
|
}
|
|
/* Permit the user to pick, if there is more than one */
|
|
if (n > 1) {
|
|
win = plsel_startmenu(screenheight, RS_GENDER);
|
|
any = cg.zeroany; /* zero out all bits */
|
|
/* populate the menu with gender choices */
|
|
setup_gendmenu(win, TRUE, ROLE, RACE, ALGN);
|
|
/* add miscellaneous menu entries */
|
|
role_menu_extra(ROLE_RANDOM, win, TRUE);
|
|
any.a_int = 0; /* separator, not a choice */
|
|
add_menu(win, &nul_glyphinfo, &any, 0, 0,
|
|
ATR_NONE, clr, "", MENU_ITEMFLAGS_NONE);
|
|
role_menu_extra(RS_ROLE, win, FALSE);
|
|
role_menu_extra(RS_RACE, win, FALSE);
|
|
role_menu_extra(RS_ALGNMNT, win, FALSE);
|
|
role_menu_extra(RS_filter, win, FALSE);
|
|
role_menu_extra(ROLE_NONE, win, FALSE); /* quit */
|
|
Strcpy(pbuf, "Pick a gender or sex");
|
|
end_menu(win, pbuf);
|
|
n = select_menu(win, PICK_ONE, &selected);
|
|
if (n > 0) {
|
|
choice = selected[0].item.a_int;
|
|
if (n > 1 && choice == ROLE_RANDOM)
|
|
choice = selected[1].item.a_int;
|
|
} else
|
|
choice = (n == 0) ? ROLE_RANDOM : ROLE_NONE;
|
|
if (selected)
|
|
free((genericptr_t) selected), selected = 0;
|
|
destroy_nhwindow(win), win = WIN_ERR;
|
|
|
|
if (choice == ROLE_NONE) {
|
|
goto setup_done; /* selected quit */
|
|
} else if (choice == RS_menu_arg(RS_ALGNMNT)) {
|
|
ALGN = k = ROLE_NONE;
|
|
nextpick = RS_ALGNMNT;
|
|
} else if (choice == RS_menu_arg(RS_RACE)) {
|
|
RACE = k = ROLE_NONE;
|
|
nextpick = RS_RACE;
|
|
} else if (choice == RS_menu_arg(RS_ROLE)) {
|
|
ROLE = k = ROLE_NONE;
|
|
nextpick = RS_ROLE;
|
|
} else if (choice == RS_menu_arg(RS_filter)) {
|
|
GEND = k = ROLE_NONE;
|
|
if (reset_role_filtering())
|
|
nextpick = RS_ROLE;
|
|
else
|
|
nextpick = RS_GENDER;
|
|
} else if (choice == ROLE_RANDOM) {
|
|
k = pick_gend(ROLE, RACE, ALGN, PICK_RANDOM);
|
|
if (k < 0)
|
|
k = randgend(ROLE, RACE);
|
|
} else {
|
|
k = choice - 1;
|
|
}
|
|
}
|
|
}
|
|
GEND = k;
|
|
} /* needed gender */
|
|
} /* picking gender */
|
|
|
|
if (nextpick == RS_ALGNMNT) {
|
|
nextpick = (ROLE < 0) ? RS_ROLE : (RACE < 0) ? RS_RACE : RS_GENDER;
|
|
/* Select an alignment, if necessary;
|
|
force compatibility with role/race/gender. */
|
|
if (ALGN < 0 || !validalign(ROLE, RACE, ALGN)) {
|
|
/* no alignment yet, or pre-selected alignment not valid */
|
|
if (pick4u == 'y' || pick4u == 'a' || ALGN == ROLE_RANDOM) {
|
|
k = pick_align(ROLE, RACE, GEND, PICK_RANDOM);
|
|
if (k < 0) {
|
|
pline("Incompatible alignment!");
|
|
k = randalign(ROLE, RACE);
|
|
}
|
|
} else { /* pick4u == 'n' */
|
|
/* Count the number of valid alignments */
|
|
n = 0; /* number valid */
|
|
k = 0; /* valid alignment */
|
|
for (i = 0; i < ROLE_ALIGNS; i++)
|
|
if (ok_align(ROLE, RACE, GEND, i)) {
|
|
n++;
|
|
k = i;
|
|
}
|
|
if (n == 0) {
|
|
for (i = 0; i < ROLE_ALIGNS; i++)
|
|
if (validalign(ROLE, RACE, i)) {
|
|
n++;
|
|
k = i;
|
|
}
|
|
}
|
|
/* Permit the user to pick, if there is more than one */
|
|
if (n > 1) {
|
|
win = plsel_startmenu(screenheight, RS_ALGNMNT);
|
|
any = cg.zeroany; /* zero out all bits */
|
|
setup_algnmenu(win, TRUE, ROLE, RACE, GEND);
|
|
role_menu_extra(ROLE_RANDOM, win, TRUE);
|
|
any.a_int = 0; /* separator, not a choice */
|
|
add_menu(win, &nul_glyphinfo, &any, 0, 0,
|
|
ATR_NONE, clr, "", MENU_ITEMFLAGS_NONE);
|
|
role_menu_extra(RS_ROLE, win, FALSE);
|
|
role_menu_extra(RS_RACE, win, FALSE);
|
|
role_menu_extra(RS_GENDER, win, FALSE);
|
|
role_menu_extra(RS_filter, win, FALSE);
|
|
role_menu_extra(ROLE_NONE, win, FALSE); /* quit */
|
|
Strcpy(pbuf, "Pick an alignment or creed");
|
|
end_menu(win, pbuf);
|
|
n = select_menu(win, PICK_ONE, &selected);
|
|
if (n > 0) {
|
|
choice = selected[0].item.a_int;
|
|
if (n > 1 && choice == ROLE_RANDOM)
|
|
choice = selected[1].item.a_int;
|
|
} else
|
|
choice = (n == 0) ? ROLE_RANDOM : ROLE_NONE;
|
|
if (selected)
|
|
free((genericptr_t) selected), selected = 0;
|
|
destroy_nhwindow(win), win = WIN_ERR;
|
|
|
|
if (choice == ROLE_NONE) {
|
|
goto setup_done; /* selected quit */
|
|
} else if (choice == RS_menu_arg(RS_GENDER)) {
|
|
GEND = k = ROLE_NONE;
|
|
nextpick = RS_GENDER;
|
|
} else if (choice == RS_menu_arg(RS_RACE)) {
|
|
RACE = k = ROLE_NONE;
|
|
nextpick = RS_RACE;
|
|
} else if (choice == RS_menu_arg(RS_ROLE)) {
|
|
ROLE = k = ROLE_NONE;
|
|
nextpick = RS_ROLE;
|
|
} else if (choice == RS_menu_arg(RS_filter)) {
|
|
ALGN = k = ROLE_NONE;
|
|
if (reset_role_filtering())
|
|
nextpick = RS_ROLE;
|
|
else
|
|
nextpick = RS_ALGNMNT;
|
|
} else if (choice == ROLE_RANDOM) {
|
|
k = pick_align(ROLE, RACE, GEND, PICK_RANDOM);
|
|
if (k < 0)
|
|
k = randalign(ROLE, RACE);
|
|
} else {
|
|
k = choice - 1;
|
|
}
|
|
}
|
|
}
|
|
ALGN = k;
|
|
} /* needed alignment */
|
|
} /* picking alignment */
|
|
|
|
} while (ROLE < 0 || RACE < 0 || GEND < 0 || ALGN < 0);
|
|
|
|
/*
|
|
* Role, race, &c have now been determined;
|
|
* ask for confirmation and maybe go back to choose all over again.
|
|
*
|
|
* Uses ynaq for familiarity, although 'a' is usually a
|
|
* superset of 'y' but here is an alternate form of 'n'.
|
|
* Menu layout:
|
|
* title: Is this ok? [ynaq]
|
|
* blank:
|
|
* text: $name, $alignment $gender $race $role
|
|
* blank:
|
|
* menu: y + yes; play
|
|
* n - no; pick again
|
|
* maybe: a - no; rename hero
|
|
* q - quit
|
|
* (end)
|
|
*/
|
|
getconfirmation = (picksomething && pick4u != 'a' && !flags.randomall);
|
|
while (getconfirmation) {
|
|
win = plsel_startmenu(screenheight, RS_filter); /* filter: not ROLE */
|
|
any = cg.zeroany; /* zero out all bits */
|
|
/* [ynaq] menu choices */
|
|
any.a_int = 1;
|
|
add_menu(win, &nul_glyphinfo, &any, 'y', 0,
|
|
ATR_NONE, clr, "Yes; start game", MENU_ITEMFLAGS_SELECTED);
|
|
any.a_int = 2;
|
|
add_menu(win, &nul_glyphinfo, &any, 'n', 0,
|
|
ATR_NONE, clr, "No; choose role again", MENU_ITEMFLAGS_NONE);
|
|
if (iflags.renameallowed) {
|
|
any.a_int = 3;
|
|
add_menu(win, &nul_glyphinfo, &any, 'a', 0, ATR_NONE,
|
|
clr, "Not yet; choose another name", MENU_ITEMFLAGS_NONE);
|
|
}
|
|
any.a_int = -1;
|
|
add_menu(win, &nul_glyphinfo, &any, 'q', 0,
|
|
ATR_NONE, clr, "Quit", MENU_ITEMFLAGS_NONE);
|
|
Sprintf(pbuf, "Is this ok? [yn%sq]", iflags.renameallowed ? "a" : "");
|
|
end_menu(win, pbuf);
|
|
n = select_menu(win, PICK_ONE, &selected);
|
|
/* [pick-one menus with a preselected entry behave oddly...] */
|
|
choice = (n > 0) ? selected[n - 1].item.a_int : (n == 0) ? 1 : -1;
|
|
if (selected)
|
|
free((genericptr_t) selected), selected = 0;
|
|
destroy_nhwindow(win);
|
|
|
|
switch (choice) {
|
|
default: /* 'q' or ESC */
|
|
goto setup_done; /* quit */
|
|
break;
|
|
case 3: { /* 'a' */
|
|
/*
|
|
* TODO: what, if anything, should be done if the name is
|
|
* changed to or from "wizard" after port-specific startup
|
|
* code has set flags.debug based on the original name?
|
|
*/
|
|
int saveROLE, saveRACE, saveGEND, saveALGN;
|
|
|
|
iflags.renameinprogress = TRUE; /* affects main() in unixmain.c */
|
|
/* plnamesuffix() can change any or all of ROLE, RACE,
|
|
GEND, ALGN; we'll override that and honor only the name */
|
|
saveROLE = ROLE, saveRACE = RACE, saveGEND = GEND, saveALGN = ALGN;
|
|
gp.plname[0] = '\0';
|
|
plnamesuffix(); /* calls askname() when gp.plname[] is empty */
|
|
ROLE = saveROLE, RACE = saveRACE, GEND = saveGEND, ALGN = saveALGN;
|
|
break; /* getconfirmation is still True */
|
|
}
|
|
case 2: /* 'n' */
|
|
/* start fresh, but bypass "shall I pick everything for you?"
|
|
step; any partial role selection via config file, command
|
|
line, or name suffix is discarded this time */
|
|
pick4u = 'n';
|
|
ROLE = RACE = GEND = ALGN = ROLE_NONE;
|
|
goto makepicks;
|
|
break;
|
|
case 1: /* 'y' or Space or Return/Enter */
|
|
/* success; drop out through end of function */
|
|
getconfirmation = FALSE;
|
|
break;
|
|
}
|
|
} /* while 'getconfirmation' */
|
|
/* Success! */
|
|
result = 1;
|
|
|
|
setup_done:
|
|
gp.program_state.in_role_selection--;
|
|
return result;
|
|
}
|
|
|
|
static boolean
|
|
reset_role_filtering(void)
|
|
{
|
|
winid win;
|
|
anything any;
|
|
int i, n, clr = 0;
|
|
char filterprompt[QBUFSZ];
|
|
menu_item *selected = 0;
|
|
|
|
win = create_nhwindow(NHW_MENU);
|
|
start_menu(win, MENU_BEHAVE_STANDARD);
|
|
any = cg.zeroany;
|
|
|
|
/* no extra blank line preceding this entry; end_menu supplies one */
|
|
add_menu(win, &nul_glyphinfo, &any, 0, 0, ATR_NONE, clr,
|
|
"Unacceptable roles", MENU_ITEMFLAGS_NONE);
|
|
setup_rolemenu(win, FALSE, ROLE_NONE, ROLE_NONE, ROLE_NONE);
|
|
|
|
add_menu(win, &nul_glyphinfo, &any, 0, 0, ATR_NONE,
|
|
clr, "", MENU_ITEMFLAGS_NONE);
|
|
add_menu(win, &nul_glyphinfo, &any, 0, 0, ATR_NONE,
|
|
clr, "Unacceptable races", MENU_ITEMFLAGS_NONE);
|
|
setup_racemenu(win, FALSE, ROLE_NONE, ROLE_NONE, ROLE_NONE);
|
|
|
|
add_menu(win, &nul_glyphinfo, &any, 0, 0, ATR_NONE,
|
|
clr, "", MENU_ITEMFLAGS_NONE);
|
|
add_menu(win, &nul_glyphinfo, &any, 0, 0, ATR_NONE,
|
|
clr, "Unacceptable genders", MENU_ITEMFLAGS_NONE);
|
|
setup_gendmenu(win, FALSE, ROLE_NONE, ROLE_NONE, ROLE_NONE);
|
|
|
|
add_menu(win, &nul_glyphinfo, &any, 0, 0, ATR_NONE,
|
|
clr, "", MENU_ITEMFLAGS_NONE);
|
|
add_menu(win, &nul_glyphinfo, &any, 0, 0, ATR_NONE,
|
|
clr, "Unacceptable alignments", MENU_ITEMFLAGS_NONE);
|
|
setup_algnmenu(win, FALSE, ROLE_NONE, ROLE_NONE, ROLE_NONE);
|
|
|
|
Sprintf(filterprompt, "Pick all that apply%s",
|
|
gotrolefilter() ? " and/or unpick any that no longer apply" : "");
|
|
end_menu(win, filterprompt);
|
|
n = select_menu(win, PICK_ANY, &selected);
|
|
|
|
if (n >= 0) { /* n==0: clear current filters and don't set new ones */
|
|
clearrolefilter(RS_filter);
|
|
for (i = 0; i < n; i++)
|
|
setrolefilter(selected[i].item.a_string);
|
|
|
|
ROLE = RACE = GEND = ALGN = ROLE_NONE;
|
|
}
|
|
if (selected)
|
|
free((genericptr_t) selected), selected = 0;
|
|
destroy_nhwindow(win);
|
|
return (n > 0) ? TRUE : FALSE;
|
|
}
|
|
|
|
/* the change in format when this extended role selection was converted from
|
|
tty-only to tty+curses+? made the role selection menu require two pages
|
|
on a traditional 24-line tty; that wasn't fair to tty, so squeeze out
|
|
some blank separator lines from the menu if that will make it fit on one */
|
|
static int
|
|
maybe_skip_seps(int rows, int aspect)
|
|
{
|
|
int i, n = 0;
|
|
|
|
/* not much point to generalizing this to other aspects */
|
|
if (aspect != RS_ROLE)
|
|
return 0;
|
|
/*
|
|
* If there are one or two excess lines, setup_rolemenu() will omit
|
|
* the separator between 'random' and 'pick race first'. If there are
|
|
* two, plsel_startmenu() will omit the one between role info so far
|
|
* ("<role> <race> ...") and the set of role entries.
|
|
*/
|
|
|
|
n += 4; /* title and ensuing separator, role info so far and separator */
|
|
for (i = 0; roles[i].name.m; ++i)
|
|
if (ok_role(i, RACE, GEND, ALGN) && ok_race(i, RACE, GEND, ALGN)
|
|
&& ok_gend(i, RACE, GEND, ALGN) && ok_align(i, RACE, GEND, ALGN))
|
|
++n;
|
|
n += 2; /* 'random' and separator */
|
|
n += 5; /* race 1st, gender 1st, alignment 1st, reset filter, quit */
|
|
n += 1; /* footer/prompt */
|
|
if (rows > 0 && n > rows)
|
|
return n - rows;
|
|
return 0;
|
|
}
|
|
|
|
/* start a menu; show role aspects specified so far as a header line */
|
|
static winid
|
|
plsel_startmenu(int ttyrows, int aspect)
|
|
{
|
|
char qbuf[QBUFSZ];
|
|
winid win;
|
|
anything any;
|
|
const char *rolename;
|
|
int clr = 0;
|
|
|
|
/* whatever aspect was just chosen might force others (Orc => chaotic,
|
|
Samurai => Human+lawful, Valkyrie => female) */
|
|
rigid_role_checks();
|
|
|
|
rolename = (ROLE < 0) ? "<role>"
|
|
: (GEND == 1 && roles[ROLE].name.f) ? roles[ROLE].name.f
|
|
: roles[ROLE].name.m;
|
|
if (!gp.plname[0] || ROLE < 0 || RACE < 0 || GEND < 0 || ALGN < 0) {
|
|
/* "<role> <race.noun> <gender> <alignment>" */
|
|
Sprintf(qbuf, "%.20s %.20s %.20s %.20s",
|
|
rolename,
|
|
(RACE < 0) ? "<race>" : races[RACE].noun,
|
|
(GEND < 0) ? "<gender>" : genders[GEND].adj,
|
|
(ALGN < 0) ? "<alignment>" : aligns[ALGN].adj);
|
|
} else {
|
|
/* "<name> the <alignment> <gender> <race.adjective> <role>" */
|
|
Sprintf(qbuf, "%.20s the %.20s %.20s %.20s %.20s",
|
|
gp.plname,
|
|
aligns[ALGN].adj,
|
|
genders[GEND].adj,
|
|
races[RACE].adj,
|
|
rolename);
|
|
}
|
|
|
|
win = create_nhwindow(NHW_MENU);
|
|
if (win == WIN_ERR)
|
|
panic("could not create role selection window");
|
|
start_menu(win, MENU_BEHAVE_STANDARD);
|
|
|
|
any = cg.zeroany;
|
|
add_menu(win, &nul_glyphinfo, &any, 0, 0, ATR_NONE, clr,
|
|
qbuf, MENU_ITEMFLAGS_NONE);
|
|
if (maybe_skip_seps(ttyrows, aspect) != 2)
|
|
add_menu(win, &nul_glyphinfo, &any, 0, 0, ATR_NONE, clr,
|
|
"", MENU_ITEMFLAGS_NONE);
|
|
return win;
|
|
}
|
|
|
|
#undef ROLE
|
|
#undef RACE
|
|
#undef GEND
|
|
#undef ALGN
|
|
|
|
/* add entries a-Archeologist, b-Barbarian, &c to menu being built in 'win' */
|
|
static void
|
|
setup_rolemenu(
|
|
winid win,
|
|
boolean filtering, /* True => exclude filtered roles;
|
|
* False => filter reset */
|
|
int race, int gend, int algn) /* all ROLE_NONE for !filtering case */
|
|
{
|
|
anything any;
|
|
int i;
|
|
boolean role_ok;
|
|
char thisch, lastch = '\0', rolenamebuf[50];
|
|
int clr = 0;
|
|
|
|
any = cg.zeroany; /* zero out all bits */
|
|
for (i = 0; roles[i].name.m; i++) {
|
|
/* role can be constrained by any of race, gender, or alignment */
|
|
role_ok = (ok_role(i, race, gend, algn)
|
|
&& ok_race(i, race, gend, algn)
|
|
&& ok_gend(i, race, gend, algn)
|
|
&& ok_align(i, race, gend, algn));
|
|
if (filtering && !role_ok)
|
|
continue;
|
|
if (filtering)
|
|
any.a_int = i + 1;
|
|
else
|
|
any.a_string = roles[i].name.m;
|
|
thisch = lowc(*roles[i].name.m);
|
|
if (thisch == lastch)
|
|
thisch = highc(thisch);
|
|
Strcpy(rolenamebuf, roles[i].name.m);
|
|
if (roles[i].name.f) {
|
|
/* role has distinct name for female (C,P) */
|
|
if (gend == 1) {
|
|
/* female already chosen; replace male name */
|
|
Strcpy(rolenamebuf, roles[i].name.f);
|
|
} else if (gend < 0) {
|
|
/* not chosen yet; append slash+female name */
|
|
Strcat(rolenamebuf, "/");
|
|
Strcat(rolenamebuf, roles[i].name.f);
|
|
}
|
|
}
|
|
/* !filtering implies reset_role_filtering() where we want to
|
|
mark this role as preseleted if current filter excludes it */
|
|
add_menu(win, &nul_glyphinfo, &any, thisch, 0,
|
|
ATR_NONE, clr, an(rolenamebuf),
|
|
(!filtering && !role_ok)
|
|
? MENU_ITEMFLAGS_SELECTED : MENU_ITEMFLAGS_NONE);
|
|
lastch = thisch;
|
|
}
|
|
}
|
|
|
|
static void
|
|
setup_racemenu(
|
|
winid win,
|
|
boolean filtering,
|
|
int role, int gend, int algn)
|
|
{
|
|
anything any;
|
|
boolean race_ok;
|
|
int i;
|
|
char this_ch;
|
|
int clr = 0;
|
|
|
|
any = cg.zeroany;
|
|
for (i = 0; races[i].noun; i++) {
|
|
/* no ok_gend(); race isn't constrained by gender */
|
|
race_ok = (ok_race(role, i, gend, algn)
|
|
&& ok_role(role, i, gend, algn)
|
|
&& ok_align(role, i, gend, algn));
|
|
if (filtering && !race_ok)
|
|
continue;
|
|
if (filtering)
|
|
any.a_int = i + 1;
|
|
else
|
|
any.a_string = races[i].noun;
|
|
this_ch = *races[i].noun;
|
|
/* filtering: picking race, so choose by first letter, with
|
|
capital letter as unseen accelerator;
|
|
!filtering: resetting filter rather than picking, choose by
|
|
capital letter since lowercase role letters will be present */
|
|
add_menu(win, &nul_glyphinfo, &any,
|
|
filtering ? this_ch : highc(this_ch),
|
|
filtering ? highc(this_ch) : 0,
|
|
ATR_NONE, clr, races[i].noun,
|
|
(!filtering && !race_ok)
|
|
? MENU_ITEMFLAGS_SELECTED : MENU_ITEMFLAGS_NONE);
|
|
}
|
|
}
|
|
|
|
static void
|
|
setup_gendmenu(
|
|
winid win,
|
|
boolean filtering,
|
|
int role, int race, int algn)
|
|
{
|
|
anything any;
|
|
boolean gend_ok;
|
|
int i;
|
|
char this_ch;
|
|
int clr = 0;
|
|
|
|
any = cg.zeroany;
|
|
for (i = 0; i < ROLE_GENDERS; i++) {
|
|
/* no ok_align(); gender isn't constrained by alignment */
|
|
gend_ok = (ok_gend(role, race, i, algn)
|
|
&& ok_role(role, race, i, algn)
|
|
&& ok_race(role, race, i, algn));
|
|
if (filtering && !gend_ok)
|
|
continue;
|
|
if (filtering)
|
|
any.a_int = i + 1;
|
|
else
|
|
any.a_string = genders[i].adj;
|
|
this_ch = *genders[i].adj;
|
|
/* (see setup_racemenu for explanation of selector letters
|
|
and setup_rolemenu for preselection) */
|
|
add_menu(win, &nul_glyphinfo, &any,
|
|
filtering ? this_ch : highc(this_ch),
|
|
filtering ? highc(this_ch) : 0,
|
|
ATR_NONE, clr, genders[i].adj,
|
|
(!filtering && !gend_ok)
|
|
? MENU_ITEMFLAGS_SELECTED : MENU_ITEMFLAGS_NONE);
|
|
}
|
|
}
|
|
|
|
static void
|
|
setup_algnmenu(
|
|
winid win,
|
|
boolean filtering,
|
|
int role, int race, int gend)
|
|
{
|
|
anything any;
|
|
boolean algn_ok;
|
|
int i;
|
|
char this_ch;
|
|
int clr = 0;
|
|
|
|
any = cg.zeroany;
|
|
for (i = 0; i < ROLE_ALIGNS; i++) {
|
|
/* no ok_gend(); alignment isn't constrained by gender */
|
|
algn_ok = (ok_align(role, race, gend, i)
|
|
&& ok_role(role, race, gend, i)
|
|
&& ok_race(role, race, gend, i));
|
|
if (filtering && !algn_ok)
|
|
continue;
|
|
if (filtering)
|
|
any.a_int = i + 1;
|
|
else
|
|
any.a_string = aligns[i].adj;
|
|
this_ch = *aligns[i].adj;
|
|
/* (see setup_racemenu for explanation of selector letters
|
|
and setup_rolemenu for preselection) */
|
|
add_menu(win, &nul_glyphinfo, &any,
|
|
filtering ? this_ch : highc(this_ch),
|
|
filtering ? highc(this_ch) : 0,
|
|
ATR_NONE, clr, aligns[i].adj,
|
|
(!filtering && !algn_ok)
|
|
? MENU_ITEMFLAGS_SELECTED : MENU_ITEMFLAGS_NONE);
|
|
}
|
|
}
|
|
|
|
#else /* !TTY_GRAPHICS */
|
|
|
|
int
|
|
genl_player_setup(int screenheight UNUSED)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
#endif /* ?TTY_GRAPHICS */
|
|
|
|
/* role.c */
|