character_race() was going out of bounds when scanning the races[] array, relying on a field value that the fencepost entry didn't set. This incorporates the previous fix for UNDEFINED_RACE but also changes character_race() to not care about that anymore.
3002 lines
100 KiB
C
3002 lines
100 KiB
C
/* NetHack 3.7 role.c $NHDT-Date: 1701132222 2023/11/28 00:43:42 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.94 $ */
|
|
/* 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 */
|
|
UNDEFINED_ROLE,
|
|
};
|
|
|
|
/* 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 */
|
|
UNDEFINED_RACE,
|
|
};
|
|
|
|
/* 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) - 1; ++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) - 1; ++i) {
|
|
if (gr.rfilter.roles[i])
|
|
Sprintf(eos(outbuf), " !%.3s", roles[i].name.m);
|
|
}
|
|
break;
|
|
case RS_RACE:
|
|
for (i = 0; i < SIZE(races) - 1; ++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) - 1; ++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) - 1; ++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) - 1; ++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 = NO_COLOR;
|
|
|
|
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) - 1; ++i)
|
|
if (i != f && !gr.rfilter.roles[i])
|
|
break;
|
|
if (i == SIZE(roles) - 1) {
|
|
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_str(where, buf);
|
|
} 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; if pmindex is for some
|
|
other type of monster which isn't a player race, return Null */
|
|
const struct Race *
|
|
character_race(short pmindex)
|
|
{
|
|
const struct Race *r;
|
|
|
|
for (r = races; r->noun != NULL; ++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 = NO_COLOR;
|
|
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_str(win, "");
|
|
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_str(win, "");
|
|
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_str(win, "");
|
|
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_str(win, "");
|
|
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;
|
|
int i, n;
|
|
char filterprompt[QBUFSZ];
|
|
menu_item *selected = 0;
|
|
|
|
win = create_nhwindow(NHW_MENU);
|
|
start_menu(win, MENU_BEHAVE_STANDARD);
|
|
|
|
/* no extra blank line preceding this entry; end_menu supplies one */
|
|
add_menu_str(win, "Unacceptable roles");
|
|
setup_rolemenu(win, FALSE, ROLE_NONE, ROLE_NONE, ROLE_NONE);
|
|
|
|
add_menu_str(win, "");
|
|
add_menu_str(win, "Unacceptable races");
|
|
setup_racemenu(win, FALSE, ROLE_NONE, ROLE_NONE, ROLE_NONE);
|
|
|
|
add_menu_str(win, "");
|
|
add_menu_str(win, "Unacceptable genders");
|
|
setup_gendmenu(win, FALSE, ROLE_NONE, ROLE_NONE, ROLE_NONE);
|
|
|
|
add_menu_str(win, "");
|
|
add_menu_str(win, "Unacceptable alignments");
|
|
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;
|
|
const char *rolename;
|
|
|
|
/* 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);
|
|
|
|
add_menu_str(win, qbuf);
|
|
if (maybe_skip_seps(ttyrows, aspect) != 2)
|
|
add_menu_str(win, "");
|
|
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 = NO_COLOR;
|
|
|
|
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 = NO_COLOR;
|
|
|
|
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 = NO_COLOR;
|
|
|
|
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 = NO_COLOR;
|
|
|
|
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 */
|