From ae3e5d281f333f4d2f7f22dda14adb6665babc8c Mon Sep 17 00:00:00 2001 From: Alex Smith Date: Sat, 2 Dec 2023 06:17:51 +0000 Subject: [PATCH] Add bonus items to some early-game Dungeons levels These are primarily in chests (apart from one guaranteed good food item on the Mines branch level), and are quite likely to be potions of healing, although other items that are useful for early-game survivability are also possibilities. This is part of a series of commits that aim to make the early game less about waiting to heal up and more about pressing forwards. In particular, this means that characters need likely access to healing sources other than waiting/backtracking/hiding in closets. In a future commit, I plan to make permafood generate primarily through exploration (rather than drops from monsters) in order to deter waiting around or grinding; the early guaranteed food drop is present to give the more nutrition-intensive characters (e.g. orc wizard or vegetarian Monk) a fair chance to reach the more abundant food sources in Minetown or Sokoban. --- doc/fixes3-7-0.txt | 4 +- src/mklev.c | 148 ++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 144 insertions(+), 8 deletions(-) diff --git a/doc/fixes3-7-0.txt b/doc/fixes3-7-0.txt index ed3668efe..bf8932620 100644 --- a/doc/fixes3-7-0.txt +++ b/doc/fixes3-7-0.txt @@ -1296,8 +1296,10 @@ mimics sometimes woke up and came out of hiding when they shouldn't have if Wizards no longer have a bonus to writing unknown spellbooks, but now learn what spellbooks look like as they gain skill in their spell schools (allowing a guaranteed write with enough skill) -Starting inventory: magic markers are more likely (guaranteed for +starting inventory: magic markers are more likely (guaranteed for Wizards), but have fewer charges +early dungeon (pre-Sokoban) generates extra useful survivability items + Fixes to 3.7.0-x General Problems Exposed Via git Repository diff --git a/src/mklev.c b/src/mklev.c index 25fa0be04..6264cacf2 100644 --- a/src/mklev.c +++ b/src/mklev.c @@ -19,7 +19,7 @@ static void mkaltar(struct mkroom *); static void mkgrave(struct mkroom *); static void makevtele(void); void clear_level_structures(void); -static void fill_ordinary_room(struct mkroom *); +static void fill_ordinary_room(struct mkroom *, boolean); static void makelevel(void); static boolean bydoor(coordxy, coordxy); static void mktrap_victim(struct trap *); @@ -830,16 +830,22 @@ clear_level_structures(void) } } +#define ROOM_IS_FILLABLE(croom) \ + ((croom->rtype == OROOM || croom->rtype == THEMEROOM) && croom->needfill == FILL_NORMAL) + /* Fill a "random" room (i.e. a typical non-special room in the Dungeons of - * Doom) with random monsters, objects, and dungeon features. - */ + Doom) with random monsters, objects, and dungeon features. + + If bonus_items is TRUE, there may be an additional special item + generated, depending on depth. */ static void -fill_ordinary_room(struct mkroom *croom) +fill_ordinary_room(struct mkroom *croom, boolean bonus_items) { int trycnt = 0; coord pos; struct monst *tmonst; /* always put a web with a spider */ coordxy x, y; + boolean skip_chests = FALSE; if (croom->rtype != OROOM && croom->rtype != THEMEROOM) return; @@ -848,7 +854,7 @@ fill_ordinary_room(struct mkroom *croom) * that's specified to be unfilled to block an inner subroom that's * specified to be filled. */ for (x = 0; x < croom->nsubrooms; ++x) { - fill_ordinary_room(croom->sbrooms[x]); + fill_ordinary_room(croom->sbrooms[x], FALSE); } if (croom->needfill != FILL_NORMAL) @@ -893,12 +899,123 @@ fill_ordinary_room(struct mkroom *croom) (void) mkcorpstat(STATUE, (struct monst *) 0, (struct permonst *) 0, pos.x, pos.y, CORPSTAT_INIT); + + /* + * bonus_items means that this is the room where the bonus item + * should be placed, if there is one; but there might not be a + * bonus item on any given level. + * + * Bonus items are currently as follows: + * a) on the Mines branch level, 100% chance of a fairly filling + * comestible; + * b) on other levels above the Oracle, 2/3 chance of a "supply + * chest" that contains an early-game survivability item + * (there are therefore more of these when Sokoban is deep, + * which is intentional as those games are harder). + * This mechanism could be expanded in the future to place + * near-guaranteed items on particular levels (but, it is possible + * that no room will be given a bonus item if there is no suitable + * room to place it in, so it should not be used for plot-critical + * items). + */ + if (bonus_items && somexyspace(croom, &pos)) { + branch *uz_branch = Is_branchlev(&u.uz); + + if (uz_branch && u.uz.dnum != mines_dnum && + (uz_branch->end1.dnum == mines_dnum || + uz_branch->end2.dnum == mines_dnum)) { + (void) mksobj_at( + rn2(5) < 3 ? FOOD_RATION : rn2(2) ? CRAM_RATION : LEMBAS_WAFER, + pos.x, pos.y, TRUE, FALSE); + } else if (u.uz.dnum == oracle_level.dnum && + u.uz.dlevel < oracle_level.dlevel && rn2(3)) { + struct obj *otmp; + /* reverse probabilities compared to non-supply chests; + these are twice as likely to be chests than large + boxes, rather than vice versa */ + struct obj *supply_chest = mksobj_at( + rn2(3) ? CHEST : LARGE_BOX, pos.x, pos.y, FALSE, FALSE); + supply_chest->olocked = !!(rn2(6)); + + int tryct = 0; + do { + int otyp; + /* 50% this is a potion of healing */ + if (rn2(2)) + otyp = POT_HEALING; + else { + static const int supply_items[] = { + POT_EXTRA_HEALING, + POT_SPEED, + POT_GAIN_ENERGY, + SCR_ENCHANT_WEAPON, + SCR_ENCHANT_ARMOR, + SCR_CONFUSE_MONSTER, + SCR_SCARE_MONSTER, + WAN_DIGGING, + SPE_HEALING, + }; + otyp = supply_items[rn2(SIZE(supply_items))]; + } + otmp = mksobj(otyp, TRUE, FALSE); + if (otyp == POT_HEALING && rn2(2)) + otmp->quan = 2; + add_to_container(supply_chest, otmp); + + ++tryct; + if (tryct == 50) { + impossible("couldn't generate supply chest item"); + break; + } + /* guarantee at least one noncursed item, with a small + probability of more; if we generate a cursed item, it's + added to the supply chest but we reroll for a noncursed + item and add that too */ + } while (otmp->cursed || !rn2(5)); + + /* maybe put a random item into the supply chest, biased + slightly towards low-level spellbooks; avoid tools + because chests don't fit into other chests */ + if (rn2(3)) { + static const int extra_classes[] = { + FOOD_CLASS, + WEAPON_CLASS, + ARMOR_CLASS, + GEM_CLASS, + SCROLL_CLASS, + POTION_CLASS, + RING_CLASS, + SPBOOK_no_NOVEL, + SPBOOK_no_NOVEL, + SPBOOK_no_NOVEL + }; + int oclass = extra_classes[rn2(SIZE(extra_classes))]; + otmp = mkobj(oclass, FALSE); + if (oclass == SPBOOK_no_NOVEL) { + /* bias towards lower level by generating again + and taking the lower-level book */ + struct obj *otmp2 = mkobj(oclass, FALSE); + if (objects[otmp->otyp].oc_level <= objects[otmp2->otyp].oc_level) { + dealloc_obj(otmp2); + } else { + dealloc_obj(otmp); + otmp = otmp2; + } + } + add_to_container(supply_chest, otmp); + } + + skip_chests = TRUE; /* don't want a second chest in this room */ + } + } + + /* put box/chest inside; * 40% chance for at least 1 box, regardless of number * of rooms; about 5 - 7.5% for 2 boxes, least likely * when few rooms; chance for 3 or more is negligible. */ - if (!rn2(gn.nroom * 5 / 2) && somexyspace(croom, &pos)) + if (!rn2(gn.nroom * 5 / 2) && somexyspace(croom, &pos) && !skip_chests) (void) mksobj_at((rn2(3)) ? LARGE_BOX : CHEST, pos.x, pos.y, TRUE, FALSE); @@ -1065,9 +1182,26 @@ makelevel(void) if (u.uz.dnum == 0 && u.uz.dlevel == 1 && gs.stairs != prevstairs) gs.stairs->u_traversed = TRUE; + /* some levels have specially generated items in ordinary + rooms (intended to be indistinguishable from the normally + generated items); work out which room these will be placed in */ + int fillable_room_count = 0; + for (croom = gr.rooms; croom->hx > 0; croom++) { + if (ROOM_IS_FILLABLE(croom)) + fillable_room_count++; + } + /* choose a random fillable room to be the one that gets the + bonus items, if there are any; if there aren't any we don't + generate the bonus items (but levels with no fillable rooms + typically don't have any bonus items to generate anyway) */ + signed bonus_item_room_countdown = + fillable_room_count ? rn2(fillable_room_count) : -1; + /* for each room: put things inside */ for (croom = gr.rooms; croom->hx > 0; croom++) { - fill_ordinary_room(croom); + boolean fillable = ROOM_IS_FILLABLE(croom); + fill_ordinary_room(croom, fillable && bonus_item_room_countdown == 0); + if (fillable) --bonus_item_room_countdown; } } /* Fill all special rooms now, regardless of whether this is a special