You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
NetHack/src/NetHack_3.7/src/shknam.c

934 lines
29 KiB

/* NetHack 3.7 shknam.c $NHDT-Date: 1596498209 2020/08/03 23:43:29 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.57 $ */
/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
/*-Copyright (c) Robert Patrick Rankin, 2011. */
/* NetHack may be freely redistributed. See license for details. */
/* shknam.c -- initialize a shop */
/*stock_room_goodpos(struct mkroom *, int, int, int, int): 检查指定房间是否适合放置货物。
veggy_item(struct obj * obj, int): 判断指定物品是否是蔬菜类物品。
shkveg(): 生成一个随机的蔬菜类物品。
mkveggy_at(int, int): 在指定位置生成一个蔬菜。
mkshobj_at(const struct shclass *, int, int, boolean): 在指定位置生成一个商店物品。
nameshk(struct monst *, const char *const *): 为商人生成一个特定名字。
good_shopdoor(struct mkroom *, coordxy *, coordxy *): 检查指定房间中是否有适合作为商店入口的门。
shkinit(const struct shclass *, struct mkroom *): 初始化商店。
VEGETARIAN_CLASS (MAXOCLASSES + 1): 定义蔬菜类物品的类别。*/
#include "hack.h"
static boolean stock_room_goodpos(struct mkroom *, int, int, int, int);
static boolean veggy_item(struct obj * obj, int);
static int shkveg(void);
static void mkveggy_at(int, int);
static void mkshobj_at(const struct shclass *, int, int, boolean);
static void nameshk(struct monst *, const char *const *);
static int good_shopdoor(struct mkroom *, coordxy *, coordxy *);
static int shkinit(const struct shclass *, struct mkroom *);
#define VEGETARIAN_CLASS (MAXOCLASSES + 1)
/*
* Name prefix codes:
* dash - female, personal name
* underscore _ female, general name
* plus + male, personal name
* vertical bar | male, general name (implied for most of shktools)
* equals = gender not specified, personal name
*
* Personal names do not receive the honorific prefix "Mr." or "Ms.".
*/
static const char *const shkliquors[] = {
/* Ukraine */
"Njezjin", "Tsjernigof", "Ossipewsk", "Gorlowka",
/* Belarus */
"Gomel",
/* N. Russia */
"Konosja", "Weliki Oestjoeg", "Syktywkar", "Sablja", "Narodnaja", "Kyzyl",
/* Silezie */
"Walbrzych", "Swidnica", "Klodzko", "Raciborz", "Gliwice", "Brzeg",
"Krnov", "Hradec Kralove",
/* Schweiz */
"Leuk", "Brig", "Brienz", "Thun", "Sarnen", "Burglen", "Elm", "Flims",
"Vals", "Schuls", "Zum Loch", 0
};
static const char *const shkbooks[] = {
/* Eire */
"Skibbereen", "Kanturk", "Rath Luirc", "Ennistymon",
"Lahinch", "Kinnegad", "Lugnaquillia", "Enniscorthy",
"Gweebarra", "Kittamagh", "Nenagh", "Sneem",
"Ballingeary", "Kilgarvan", "Cahersiveen", "Glenbeigh",
"Kilmihil", "Kiltamagh", "Droichead Atha", "Inniscrone",
"Clonegal", "Lisnaskea", "Culdaff", "Dunfanaghy",
"Inishbofin", "Kesh", 0
};
static const char *const shkarmors[] = {
/* Turquie */
"Demirci", "Kalecik", "Boyabai", "Yildizeli", "Gaziantep",
"Siirt", "Akhalataki", "Tirebolu", "Aksaray", "Ermenak",
"Iskenderun", "Kadirli", "Siverek", "Pervari", "Malasgirt",
"Bayburt", "Ayancik", "Zonguldak", "Balya", "Tefenni",
"Artvin", "Kars", "Makharadze", "Malazgirt", "Midyat",
"Birecik", "Kirikkale", "Alaca", "Polatli", "Nallihan",
0
};
static const char *const shkwands[] = {
/* Wales */
"Yr Wyddgrug", "Trallwng", "Mallwyd", "Pontarfynach", "Rhaeader",
"Llandrindod", "Llanfair-ym-muallt", "Y-Fenni", "Maesteg", "Rhydaman",
"Beddgelert", "Curig", "Llanrwst", "Llanerchymedd", "Caergybi",
/* Scotland */
"Nairn", "Turriff", "Inverurie", "Braemar", "Lochnagar", "Kerloch",
"Beinn a Ghlo", "Drumnadrochit", "Morven", "Uist", "Storr",
"Sgurr na Ciche", "Cannich", "Gairloch", "Kyleakin", "Dunvegan", 0
};
static const char *const shkrings[] = {
/* Hollandse familienamen */
"Feyfer", "Flugi", "Gheel", "Havic", "Haynin",
"Hoboken", "Imbyze", "Juyn", "Kinsky", "Massis",
"Matray", "Moy", "Olycan", "Sadelin", "Svaving",
"Tapper", "Terwen", "Wirix", "Ypey",
/* Skandinaviske navne */
"Rastegaisa", "Varjag Njarga", "Kautekeino", "Abisko", "Enontekis",
"Rovaniemi", "Avasaksa", "Haparanda", "Lulea", "Gellivare",
"Oeloe", "Kajaani", "Fauske", 0
};
static const char *const shkfoods[] = {
/* Indonesia */
"Djasinga", "Tjibarusa", "Tjiwidej", "Pengalengan",
"Bandjar", "Parbalingga", "Bojolali", "Sarangan",
"Ngebel", "Djombang", "Ardjawinangun", "Berbek",
"Papar", "Baliga", "Tjisolok", "Siboga",
"Banjoewangi", "Trenggalek", "Karangkobar", "Njalindoeng",
"Pasawahan", "Pameunpeuk", "Patjitan", "Kediri",
"Pemboeang", "Tringanoe", "Makin", "Tipor",
"Semai", "Berhala", "Tegal", "Samoe",
0
};
static const char *const shkweapons[] = {
/* Perigord */
"Voulgezac", "Rouffiac", "Lerignac", "Touverac", "Guizengeard",
"Melac", "Neuvicq", "Vanzac", "Picq", "Urignac",
"Corignac", "Fleac", "Lonzac", "Vergt", "Queyssac",
"Liorac", "Echourgnac", "Cazelon", "Eypau", "Carignan",
"Monbazillac", "Jonzac", "Pons", "Jumilhac", "Fenouilledes",
"Laguiolet", "Saujon", "Eymoutiers", "Eygurande", "Eauze",
"Labouheyre", 0
};
static const char *const shktools[] = {
/* Spmi */
"Ymla", "Eed-morra", "Elan Lapinski", "Cubask", "Nieb", "Bnowr Falr",
"Sperc", "Noskcirdneh", "Yawolloh", "Hyeghu", "Niskal", "Trahnil",
"Htargcm", "Enrobwem", "Kachzi Rellim", "Regien", "Donmyar", "Yelpur",
"Nosnehpets", "Stewe", "Renrut", "Senna Hut", "-Zlaw", "Nosalnef",
"Rewuorb", "Rellenk", "Yad", "Cire Htims", "Y-crad", "Nenilukah",
"Corsh", "Aned", "Dark Eery", "Niknar", "Lapu", "Lechaim",
"Rebrol-nek", "AlliWar Wickson", "Oguhmk", "Telloc Cyaj",
#ifdef OVERLAY
"Erreip", "Nehpets", "Mron", "Snivek", "Kahztiy",
#endif
#ifdef WIN32
"Lexa", "Niod",
#endif
#ifdef MAC
"Nhoj-lee", "Evad\'kh", "Ettaw-noj", "Tsew-mot", "Ydna-s", "Yao-hang",
"Tonbar", "Kivenhoug", "Llardom",
#endif
#ifdef AMIGA
"Falo", "Nosid-da\'r", "Ekim-p", "Noslo", "Yl-rednow", "Mured-oog",
"Ivrajimsal",
#endif
#ifdef TOS
"Nivram",
#endif
#ifdef OS2
"Nedraawi-nav",
#endif
#ifdef VMS
"Lez-tneg", "Ytnu-haled",
#endif
0
};
static const char *const shklight[] = {
/* Romania */
"Zarnesti", "Slanic", "Nehoiasu", "Ludus", "Sighisoara", "Nisipitu",
"Razboieni", "Bicaz", "Dorohoi", "Vaslui", "Fetesti", "Tirgu Neamt",
"Babadag", "Zimnicea", "Zlatna", "Jiu", "Eforie", "Mamaia",
/* Bulgaria */
"Silistra", "Tulovo", "Panagyuritshte", "Smolyan", "Kirklareli", "Pernik",
"Lom", "Haskovo", "Dobrinishte", "Varvara", "Oryahovo", "Troyan",
"Lovech", "Sliven", 0
};
static const char *const shkgeneral[] = {
/* Suriname */
"Hebiwerie", "Possogroenoe", "Asidonhopo", "Manlobbi",
"Adjama", "Pakka Pakka", "Kabalebo", "Wonotobo",
"Akalapi", "Sipaliwini",
/* Greenland */
"Annootok", "Upernavik", "Angmagssalik",
/* N. Canada */
"Aklavik", "Inuvik", "Tuktoyaktuk", "Chicoutimi",
"Ouiatchouane", "Chibougamau", "Matagami", "Kipawa",
"Kinojevis", "Abitibi", "Maganasipi",
/* Iceland */
"Akureyri", "Kopasker", "Budereyri", "Akranes",
"Bordeyri", "Holmavik", 0
};
static const char *const shkhealthfoods[] = {
/* Tibet */
"Ga'er", "Zhangmu", "Rikaze", "Jiangji", "Changdu",
"Linzhi", "Shigatse", "Gyantse", "Ganden", "Tsurphu",
"Lhasa", "Tsedong", "Drepung",
/* Hippie names */
"=Azura", "=Blaze", "=Breanna", "=Breezy", "=Dharma",
"=Feather", "=Jasmine", "=Luna", "=Melody", "=Moonjava",
"=Petal", "=Rhiannon", "=Starla", "=Tranquilla", "=Windsong",
"=Zennia", "=Zoe", "=Zora", 0
};
/*
* To add new shop types, all that is necessary is to edit the shtypes[]
* array. See mkroom.h for the structure definition. Typically, you'll
* have to lower some or all of the probability fields in old entries to
* free up some percentage for the new type.
*
* The placement type field is not yet used but will be in the near future.
*
* The iprobs array in each entry defines the probabilities for various kinds
* of objects to be present in the given shop type. You can associate with
* each percentage either a generic object type (represented by one of the
* *_CLASS enum value) or a specific object enum value.
* In the latter case, prepend it with a unary minus so the code can know
* (by testing the sign) whether to use mkobj() or mksobj().
*/
const struct shclass shtypes[] = {
{ "general store",
RANDOM_CLASS,
42,
D_SHOP,
{ { 100, RANDOM_CLASS },
{ 0, 0 },
{ 0, 0 },
{ 0, 0 },
{ 0, 0 },
{ 0, 0 } },
shkgeneral },
{ "used armor dealership",
ARMOR_CLASS,
14,
D_SHOP,
{ { 90, ARMOR_CLASS },
{ 10, WEAPON_CLASS },
{ 0, 0 },
{ 0, 0 },
{ 0, 0 },
{ 0, 0 } },
shkarmors },
{ "second-hand bookstore",
SCROLL_CLASS,
10,
D_SHOP,
{ { 90, SCROLL_CLASS },
{ 10, SPBOOK_CLASS },
{ 0, 0 },
{ 0, 0 },
{ 0, 0 },
{ 0, 0 } },
shkbooks },
{ "liquor emporium",
POTION_CLASS,
10,
D_SHOP,
{ { 100, POTION_CLASS },
{ 0, 0 },
{ 0, 0 },
{ 0, 0 },
{ 0, 0 },
{ 0, 0 } },
shkliquors },
{ "antique weapons outlet",
WEAPON_CLASS,
5,
D_SHOP,
{ { 90, WEAPON_CLASS },
{ 10, ARMOR_CLASS },
{ 0, 0 },
{ 0, 0 },
{ 0, 0 },
{ 0, 0 } },
shkweapons },
{ "delicatessen",
FOOD_CLASS,
5,
D_SHOP,
{ { 83, FOOD_CLASS },
{ 5, -POT_FRUIT_JUICE },
{ 4, -POT_BOOZE },
{ 5, -POT_WATER },
{ 3, -ICE_BOX },
{ 0, 0 } },
shkfoods },
{ "jewelers",
RING_CLASS,
3,
D_SHOP,
{ { 85, RING_CLASS },
{ 10, GEM_CLASS },
{ 5, AMULET_CLASS },
{ 0, 0 },
{ 0, 0 },
{ 0, 0 } },
shkrings },
{ "quality apparel and accessories",
WAND_CLASS,
3,
D_SHOP,
{ { 90, WAND_CLASS },
{ 5, -LEATHER_GLOVES },
{ 5, -ELVEN_CLOAK },
{ 0, 0 } },
shkwands },
{ "hardware store",
TOOL_CLASS,
3,
D_SHOP,
{ { 100, TOOL_CLASS },
{ 0, 0 },
{ 0, 0 },
{ 0, 0 },
{ 0, 0 },
{ 0, 0 } },
shktools },
{ "rare books",
SPBOOK_CLASS,
3,
D_SHOP,
{ { 90, SPBOOK_CLASS },
{ 10, SCROLL_CLASS },
{ 0, 0 },
{ 0, 0 },
{ 0, 0 },
{ 0, 0 } },
shkbooks },
{ "health food store",
FOOD_CLASS,
2,
D_SHOP,
{ { 70, VEGETARIAN_CLASS },
{ 20, -POT_FRUIT_JUICE },
{ 4, -POT_HEALING },
{ 3, -POT_FULL_HEALING },
{ 2, -SCR_FOOD_DETECTION },
{ 1, -LUMP_OF_ROYAL_JELLY } },
shkhealthfoods },
/* Shops below this point are "unique". That is they must all have a
* probability of zero. They are only created via the special level
* loader.
*/
{ "lighting store",
TOOL_CLASS,
0,
D_SHOP,
{ { 30, -WAX_CANDLE },
{ 44, -TALLOW_CANDLE },
{ 5, -BRASS_LANTERN },
{ 9, -OIL_LAMP },
{ 3, -MAGIC_LAMP },
{ 5, -POT_OIL },
{ 2, -WAN_LIGHT },
{ 1, -SCR_LIGHT },
{ 1, -SPE_LIGHT } },
shklight },
/* sentinel */
{ (char *) 0,
0,
0,
0,
{ { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } },
0 }
};
#if 0
/* validate shop probabilities; otherwise incorrect local changes could
end up provoking infinite loops or wild subscripts fetching garbage */
void
init_shop_selection()
{
register int i, j, item_prob, shop_prob;
for (shop_prob = 0, i = 0; i < SIZE(shtypes); i++) {
shop_prob += shtypes[i].prob;
for (item_prob = 0, j = 0; j < SIZE(shtypes[0].iprobs); j++)
item_prob += shtypes[i].iprobs[j].iprob;
if (item_prob != 100)
panic("item probabilities total to %d for %s shops!",
item_prob, shtypes[i].name);
}
if (shop_prob != 100)
panic("shop probabilities total to %d!", shop_prob);
}
#endif /*0*/
/* decide whether an object or object type is considered vegetarian;
for types, items which might go either way are assumed to be veggy */
static boolean
veggy_item(struct obj* obj, int otyp /* used iff obj is null */)
{
int corpsenm;
char oclass;
if (obj) {
/* actual object; will check tin content and corpse species */
otyp = (int) obj->otyp;
oclass = obj->oclass;
corpsenm = obj->corpsenm;
} else {
/* just a type; caller will have to handle tins and corpses */
oclass = objects[otyp].oc_class;
corpsenm = PM_LICHEN; /* veggy standin */
}
if (oclass == FOOD_CLASS) {
if (objects[otyp].oc_material == VEGGY || otyp == EGG)
return TRUE;
if (otyp == TIN && corpsenm == NON_PM) /* implies obj is non-null */
return (boolean) (obj->spe == 1); /* 0 = empty, 1 = spinach */
if (otyp == TIN || otyp == CORPSE)
return (boolean) (corpsenm >= LOW_PM
&& vegetarian(&mons[corpsenm]));
}
return FALSE;
}
static int
shkveg(void)
{
int i, j, maxprob, prob;
char oclass = FOOD_CLASS;
int ok[NUM_OBJECTS];
j = maxprob = 0;
ok[0] = 0; /* lint suppression */
for (i = gb.bases[(int) oclass]; i < NUM_OBJECTS; ++i) {
if (objects[i].oc_class != oclass)
break;
if (veggy_item((struct obj *) 0, i)) {
ok[j++] = i;
maxprob += objects[i].oc_prob;
}
}
if (maxprob < 1)
panic("shkveg no veggy objects");
prob = rnd(maxprob);
j = 0;
i = ok[0];
while ((prob -= objects[i].oc_prob) > 0) {
j++;
i = ok[j];
}
if (objects[i].oc_class != oclass || !OBJ_NAME(objects[i]))
panic("shkveg probtype error, oclass=%d i=%d", (int) oclass, i);
return i;
}
/* make a random item for health food store */
static void
mkveggy_at(int sx, int sy)
{
struct obj *obj = mksobj_at(shkveg(), sx, sy, TRUE, TRUE);
if (obj && obj->otyp == TIN)
set_tin_variety(obj, HEALTHY_TIN);
return;
}
/* make an object of the appropriate type for a shop square */
static void
mkshobj_at(const struct shclass* shp, int sx, int sy, boolean mkspecl)
{
struct monst *mtmp;
struct permonst *ptr;
int atype;
/* 3.6 tribute */
if (mkspecl && (!strcmp(shp->name, "rare books")
|| !strcmp(shp->name, "second-hand bookstore"))) {
struct obj *novel = mksobj_at(SPE_NOVEL, sx, sy, FALSE, FALSE);
if (novel)
gc.context.tribute.bookstock = TRUE;
return;
}
if (rn2(100) < depth(&u.uz) && !MON_AT(sx, sy)
&& (ptr = mkclass(S_MIMIC, 0)) != 0
&& (mtmp = makemon(ptr, sx, sy, NO_MM_FLAGS)) != 0) {
/* nothing */
} else {
atype = get_shop_item((int) (shp - shtypes));
if (atype == VEGETARIAN_CLASS)
mkveggy_at(sx, sy);
else if (atype < 0)
(void) mksobj_at(-atype, sx, sy, TRUE, TRUE);
else
(void) mkobj_at(atype, sx, sy, TRUE);
}
}
/* extract a shopkeeper name for the given shop type */
static void
nameshk(struct monst* shk, const char* const* nlp)
{
int i, trycnt, names_avail;
const char *shname = 0;
struct monst *mtmp;
int name_wanted = shk->m_id;
s_level *sptr;
if (nlp == shklight && In_mines(&u.uz)
&& (sptr = Is_special(&u.uz)) != 0 && sptr->flags.town) {
/* special-case minetown lighting shk */
shname = "+Izchak";
shk->female = FALSE;
} else {
/* We want variation from game to game, without needing the save
and restore support which would be necessary for randomization;
try not to make too many assumptions about time_t's internals;
use ledger_no rather than depth to keep minetown distinct. */
int nseed = (int) ((long) ubirthday / 257L);
name_wanted += ledger_no(&u.uz) + (nseed % 13) - (nseed % 5);
if (name_wanted < 0)
name_wanted += (13 + 5);
shk->female = name_wanted & 1;
for (names_avail = 0; nlp[names_avail]; names_avail++)
continue;
name_wanted = name_wanted % names_avail;
for (trycnt = 0; trycnt < 50; trycnt++) {
if (nlp == shktools) {
shname = shktools[rn2(names_avail)];
shk->female = 0; /* reversed below for '_' prefix */
} else if (name_wanted < names_avail) {
shname = nlp[name_wanted];
} else if ((i = rn2(names_avail)) != 0) {
shname = nlp[i - 1];
} else if (nlp != shkgeneral) {
nlp = shkgeneral; /* try general names */
for (names_avail = 0; nlp[names_avail]; names_avail++)
continue;
continue; /* next `trycnt' iteration */
} else {
shname = shk->female ? "-Lucrezia" : "+Dirk";
}
if (*shname == '_' || *shname == '-')
shk->female = 1;
else if (*shname == '|' || *shname == '+')
shk->female = 0;
/* is name already in use on this level? */
for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) {
if (DEADMONSTER(mtmp) || (mtmp == shk) || !mtmp->isshk)
continue;
if (strcmp(ESHK(mtmp)->shknam, shname))
continue;
name_wanted = names_avail; /* try a random name */
break;
}
if (!mtmp)
break; /* new name */
}
}
(void) strncpy(ESHK(shk)->shknam, shname, PL_NSIZ);
ESHK(shk)->shknam[PL_NSIZ - 1] = 0;
}
void
neweshk(struct monst* mtmp)
{
if (!mtmp->mextra)
mtmp->mextra = newmextra();
if (!ESHK(mtmp))
ESHK(mtmp) = (struct eshk *) alloc(sizeof(struct eshk));
(void) memset((genericptr_t) ESHK(mtmp), 0, sizeof(struct eshk));
ESHK(mtmp)->bill_p = (struct bill_x *) 0;
}
void
free_eshk(struct monst* mtmp)
{
if (mtmp->mextra && ESHK(mtmp)) {
free((genericptr_t) ESHK(mtmp));
ESHK(mtmp) = (struct eshk *) 0;
}
mtmp->isshk = 0;
}
/* find a door in room sroom which is good for shop entrance.
returns -1 if no good door found, or the gd.doors index
and the door coordinates in sx, sy */
static int
good_shopdoor(struct mkroom *sroom, coordxy *sx, coordxy *sy)
{
int i;
for (i = 0; i < sroom->doorct; i++) {
int di = sroom->fdoor + i;
*sx = gd.doors[di].x;
*sy = gd.doors[di].y;
/* check that the shopkeeper placement is sane */
if (sroom->irregular) {
int rmno = (int) ((sroom - gr.rooms) + ROOMOFFSET);
if (isok(*sx - 1, *sy) && !levl[*sx - 1][*sy].edge
&& (int) levl[*sx - 1][*sy].roomno == rmno)
(*sx)--;
else if (isok(*sx + 1, *sy) && !levl[*sx + 1][*sy].edge
&& (int) levl[*sx + 1][*sy].roomno == rmno)
(*sx)++;
else if (isok(*sx, *sy - 1) && !levl[*sx][*sy - 1].edge
&& (int) levl[*sx][*sy - 1].roomno == rmno)
(*sy)--;
else if (isok(*sx, *sy + 1) && !levl[*sx][*sy + 1].edge
&& (int) levl[*sx][*sy + 1].roomno == rmno)
(*sy)++;
else
continue;
} else if (*sx == sroom->lx - 1) {
(*sx)++;
} else if (*sx == sroom->hx + 1) {
(*sx)--;
} else if (*sy == sroom->ly - 1) {
(*sy)++;
} else if (*sy == sroom->hy + 1) {
(*sy)--;
} else {
continue;
}
return di;
}
return -1;
}
/* create a new shopkeeper in the given room */
static int
shkinit(const struct shclass* shp, struct mkroom* sroom)
{
int sh;
coordxy sx, sy;
struct monst *shk;
struct eshk *eshkp;
/* place the shopkeeper in the given room */
sh = good_shopdoor(sroom, &sx, &sy);
if (sh < 0) {
#ifdef DEBUG
/* Said to happen sometimes, but I have never seen it. */
/* Supposedly fixed by fdoor change in mklev.c */
if (wizard) {
register int j = sroom->doorct;
impossible("Where is shopdoor?");
pline("Room at (%d,%d),(%d,%d).", sroom->lx, sroom->ly, sroom->hx,
sroom->hy);
pline("doormax=%d doorct=%d fdoor=%d", gd.doorindex, sroom->doorct,
sh);
while (j--) {
pline("door [%d,%d]", gd.doors[sh].x, gd.doors[sh].y);
sh++;
}
display_nhwindow(WIN_MESSAGE, FALSE);
}
#endif
return -1;
}
if (MON_AT(sx, sy))
(void) rloc(m_at(sx, sy), RLOC_NOMSG); /* insurance */
/* now initialize the shopkeeper monster structure */
if (!(shk = makemon(&mons[PM_SHOPKEEPER], sx, sy, MM_ESHK)))
return -1;
eshkp = ESHK(shk); /* makemon(...,MM_ESHK) allocates this */
shk->isshk = shk->mpeaceful = 1;
set_malign(shk);
shk->msleeping = 0;
mon_learns_traps(shk, ALL_TRAPS); /* we know all the traps already */
eshkp->shoproom = (schar) ((sroom - gr.rooms) + ROOMOFFSET);
sroom->resident = shk;
eshkp->shoptype = sroom->rtype;
assign_level(&eshkp->shoplevel, &u.uz);
eshkp->shd = gd.doors[sh];
eshkp->shk.x = sx;
eshkp->shk.y = sy;
eshkp->robbed = eshkp->credit = eshkp->debit = eshkp->loan = 0L;
eshkp->following = eshkp->surcharge = eshkp->dismiss_kops = FALSE;
eshkp->billct = eshkp->visitct = 0;
eshkp->bill_p = (struct bill_x *) 0;
eshkp->customer[0] = '\0';
mkmonmoney(shk, 1000L + 30L * (long) rnd(100)); /* initial capital */
if (shp->shknms == shkrings)
(void) mongets(shk, TOUCHSTONE);
nameshk(shk, shp->shknms);
return sh;
}
static boolean
stock_room_goodpos(struct mkroom* sroom, int rmno, int sh, int sx, int sy)
{
if (sroom->irregular) {
if (levl[sx][sy].edge
|| (int) levl[sx][sy].roomno != rmno
|| distmin(sx, sy, gd.doors[sh].x, gd.doors[sh].y) <= 1)
return FALSE;
} else if ((sx == sroom->lx && gd.doors[sh].x == sx - 1)
|| (sx == sroom->hx && gd.doors[sh].x == sx + 1)
|| (sy == sroom->ly && gd.doors[sh].y == sy - 1)
|| (sy == sroom->hy && gd.doors[sh].y == sy + 1))
return FALSE;
/* only generate items on solid floor squares */
if (!IS_ROOM(levl[sx][sy].typ)) {
return FALSE;
}
return TRUE;
}
/* stock a newly-created room with objects */
void
stock_room(int shp_indx, register struct mkroom* sroom)
{
/*
* Someday soon we'll dispatch on the shdist field of shclass to do
* different placements in this routine. Currently it only supports
* shop-style placement (all squares except a row nearest the first
* door get objects).
*/
int sx, sy, sh;
int stockcount = 0, specialspot = 0;
char buf[BUFSZ];
int rmno = (int) ((sroom - gr.rooms) + ROOMOFFSET);
const struct shclass *shp = &shtypes[shp_indx];
/* first, try to place a shopkeeper in the room */
if ((sh = shkinit(shp, sroom)) < 0)
return;
/* make sure no doorways without doors, and no trapped doors, in shops */
sx = gd.doors[sroom->fdoor].x;
sy = gd.doors[sroom->fdoor].y;
if (levl[sx][sy].doormask == D_NODOOR) {
levl[sx][sy].doormask = D_ISOPEN;
newsym(sx, sy);
}
if (levl[sx][sy].typ == SDOOR) {
cvt_sdoor_to_door(&levl[sx][sy]); /* .typ = DOOR */
newsym(sx, sy);
}
if (levl[sx][sy].doormask & D_TRAPPED)
levl[sx][sy].doormask = D_LOCKED;
if (levl[sx][sy].doormask == D_LOCKED) {
register int m = sx, n = sy;
if (inside_shop(sx + 1, sy))
m--;
else if (inside_shop(sx - 1, sy))
m++;
if (inside_shop(sx, sy + 1))
n--;
else if (inside_shop(sx, sy - 1))
n++;
Sprintf(buf, "Closed for inventory");
make_engr_at(m, n, buf, 0L, DUST);
if (levl[m][n].typ != CORR && levl[m][n].typ != ROOM)
levl[m][n].typ = (Is_special(&u.uz)
|| *in_rooms(m, n, 0)) ? ROOM : CORR;
}
if (gc.context.tribute.enabled && !gc.context.tribute.bookstock) {
/*
* Out of the number of spots where we're actually
* going to put stuff, randomly single out one in particular.
*/
for (sx = sroom->lx; sx <= sroom->hx; sx++)
for (sy = sroom->ly; sy <= sroom->hy; sy++)
if (stock_room_goodpos(sroom, rmno, sh, sx,sy))
stockcount++;
specialspot = rnd(stockcount);
stockcount = 0;
}
for (sx = sroom->lx; sx <= sroom->hx; sx++)
for (sy = sroom->ly; sy <= sroom->hy; sy++)
if (stock_room_goodpos(sroom, rmno, sh, sx,sy)) {
stockcount++;
mkshobj_at(shp, sx, sy,
((stockcount) && (stockcount == specialspot)));
}
/*
* Special monster placements (if any) should go here: that way,
* monsters will sit on top of objects and not the other way around.
*/
/* Hack for Orcus's level: it's a ghost town, get rid of shopkeepers */
if (on_level(&u.uz, &orcus_level)) {
struct monst* mtmp = shop_keeper(rmno);
mongone(mtmp);
}
gl.level.flags.has_shop = TRUE;
}
/* does shkp's shop stock this item type? */
boolean
saleable(struct monst* shkp, struct obj* obj)
{
int i, shp_indx = ESHK(shkp)->shoptype - SHOPBASE;
const struct shclass *shp = &shtypes[shp_indx];
if (shp->symb == RANDOM_CLASS)
return TRUE;
for (i = 0; i < SIZE(shtypes[0].iprobs) && shp->iprobs[i].iprob; i++) {
/* pseudo-class needs special handling */
if (shp->iprobs[i].itype == VEGETARIAN_CLASS) {
if (veggy_item(obj, 0))
return TRUE;
} else if ((shp->iprobs[i].itype < 0)
? shp->iprobs[i].itype == -obj->otyp
: shp->iprobs[i].itype == obj->oclass)
return TRUE;
}
/* not found */
return FALSE;
}
/* positive value: class; negative value: specific object type.
can also return non-existing object class (eg. VEGETARIAN_CLASS) */
int
get_shop_item(int type)
{
const struct shclass *shp = shtypes + type;
register int i, j;
/* select an appropriate object type at random */
for (j = rnd(100), i = 0; (j -= shp->iprobs[i].iprob) > 0; i++)
continue;
return shp->iprobs[i].itype;
}
/* version of shkname() for beginning of sentence */
char *
Shknam(struct monst* mtmp)
{
char *nam = shkname(mtmp);
/* 'nam[]' is almost certainly already capitalized, but be sure */
nam[0] = highc(nam[0]);
return nam;
}
/* shopkeeper's name, without any visibility constraint; if hallucinating,
will yield some other shopkeeper's name (not necessarily one residing
in the current game's dungeon, or who keeps same type of shop) */
char *
shkname(struct monst* mtmp)
{
char *nam;
unsigned save_isshk = mtmp->isshk;
mtmp->isshk = 0; /* don't want mon_nam() calling shkname() */
/* get a modifiable name buffer along with fallback result */
nam = noit_mon_nam(mtmp);
mtmp->isshk = save_isshk;
if (!mtmp->isshk) {
impossible("shkname: \"%s\" is not a shopkeeper.", nam);
} else if (!has_eshk(mtmp)) {
panic("shkname: shopkeeper \"%s\" lacks 'eshk' data.", nam);
} else {
const char *shknm = ESHK(mtmp)->shknam;
if (Hallucination && !gp.program_state.gameover) {
const char *const *nlp;
int num;
/* count the number of non-unique shop types;
pick one randomly, ignoring shop generation probabilities;
pick a name at random from that shop type's list */
for (num = 0; num < SIZE(shtypes); num++)
if (shtypes[num].prob == 0)
break;
if (num > 0) {
nlp = shtypes[rn2(num)].shknms;
for (num = 0; nlp[num]; num++)
continue;
if (num > 0)
shknm = nlp[rn2(num)];
}
}
/* strip prefix if present */
if (!letter(*shknm))
++shknm;
Strcpy(nam, shknm);
}
return nam;
}
boolean
shkname_is_pname(struct monst* mtmp)
{
const char *shknm = ESHK(mtmp)->shknam;
return (boolean) (*shknm == '-' || *shknm == '+' || *shknm == '=');
}
boolean
is_izchak(struct monst* shkp, boolean override_hallucination)
{
const char *shknm;
if (Hallucination && !override_hallucination)
return FALSE;
if (!shkp->isshk)
return FALSE;
/* outside of town, Izchak becomes just an ordinary shopkeeper */
if (!in_town(shkp->mx, shkp->my))
return FALSE;
shknm = ESHK(shkp)->shknam;
/* skip "+" prefix */
if (!letter(*shknm))
++shknm;
return (boolean) !strcmp(shknm, "Izchak");
}
/*shknam.c*/