From b23ff20c6a03b1991c0c55c572584330a00a2f58 Mon Sep 17 00:00:00 2001 From: Pasi Kallinen Date: Sun, 19 Sep 2021 13:51:51 +0300 Subject: [PATCH 1/3] Gas clouds expand around terrain rather than being rhomboid The gas will expand from its chosen center point via breadth-first search instead of hardcoding a diagonal shape. The search is performed with a randomized list of directions, and has 50% chance of not spreading to a space it otherwise would have spread to. This has the effect of fuzzing the cloud edges in open areas, helping the clouds on, for instance, the Plane of Fire not be big rhombuses. Also some other code refactoring related to stinking clouds in read.c This comes from xNetHack by copperwater --- doc/fixes37.0 | 1 + include/extern.h | 1 + src/mkmaze.c | 2 +- src/read.c | 81 ++++++++++++++++++++++++----------------- src/region.c | 93 +++++++++++++++++++++++++++++++++++++++++------- src/zap.c | 4 +-- 6 files changed, 135 insertions(+), 47 deletions(-) diff --git a/doc/fixes37.0 b/doc/fixes37.0 index a4f773b27..5ce9ff879 100644 --- a/doc/fixes37.0 +++ b/doc/fixes37.0 @@ -617,6 +617,7 @@ similar "The ogre lord yanks Cleaver from your corpses!" due to caching the update clobber that; fixed by having inventory display release the obuf used for each item so that the same one will be reused for the next item, to avoid churning through the whole pool of obufs +gas clouds are a little random in how they spread out from a point Fixes to 3.7.0-x Problems that Were Exposed Via git Repository diff --git a/include/extern.h b/include/extern.h index 5e328c24b..f6f508ed1 100644 --- a/include/extern.h +++ b/include/extern.h @@ -2141,6 +2141,7 @@ extern void assign_candy_wrapper(struct obj *); extern int doread(void); extern int charge_ok(struct obj *); extern void recharge(struct obj *, int); +extern boolean valid_cloud_pos(int, int); extern int seffects(struct obj *); extern void drop_boulder_on_player(boolean, boolean, boolean, boolean); extern boolean drop_boulder_on_monster(int, int, boolean, boolean); diff --git a/src/mkmaze.c b/src/mkmaze.c index 1180821b9..67f5901ac 100644 --- a/src/mkmaze.c +++ b/src/mkmaze.c @@ -1315,7 +1315,7 @@ fumaroles(void) xchar y = rn1(ROWNO - 4, 3); if (levl[x][y].typ == LAVAPOOL) { - NhRegion *r = create_gas_cloud(x, y, 4 + rn2(5), rn1(10, 5)); + NhRegion *r = create_gas_cloud(x, y, rn1(30, 20), rn1(10, 5)); clear_heros_fault(r); snd = TRUE; diff --git a/src/read.c b/src/read.c index 66f4b4a45..2d8c4ecba 100644 --- a/src/read.c +++ b/src/read.c @@ -22,8 +22,7 @@ static void p_glow1(struct obj *); static void p_glow2(struct obj *, const char *); static void forget(int); static int maybe_tame(struct monst *, struct obj *); -static boolean get_valid_stinking_cloud_pos(int, int); -static boolean is_valid_stinking_cloud_pos(int, int, boolean); +static boolean can_center_cloud(int, int); static void display_stinking_cloud_positions(int); static void seffect_enchant_armor(struct obj **); static void seffect_destroy_armor(struct obj **); @@ -52,6 +51,7 @@ static void seffect_mail(struct obj **); #endif /* MAIL_STRUCTURES */ static void set_lit(int, int, genericptr); static void do_class_genocide(void); +static void do_stinking_cloud(struct obj *, boolean); static boolean create_particular_parse(char *, struct _create_particular_data *); static boolean create_particular_creation(struct _create_particular_data *); @@ -989,23 +989,26 @@ maybe_tame(struct monst* mtmp, struct obj* sobj) return 0; } -static boolean -get_valid_stinking_cloud_pos(int x,int y) +/* Can a stinking cloud physically exist at a certain position? + * NOT the same thing as can_center_cloud. + */ +boolean +valid_cloud_pos(int x, int y) { - return (!(!isok(x,y) || !cansee(x, y) - || !ACCESSIBLE(levl[x][y].typ) - || distu(x, y) >= 32)); + if (!isok(x,y)) + return FALSE; + return ACCESSIBLE(levl[x][y].typ) || is_pool(x, y) || is_lava(x, y); } -static boolean -is_valid_stinking_cloud_pos(int x, int y, boolean showmsg) +/* Callback for getpos_sethilite, also used in determining whether a scroll + * should have its regular effects, or not because it was out of range. + */ +boolean +can_center_cloud(int x, int y) { - if (!get_valid_stinking_cloud_pos(x,y)) { - if (showmsg) - You("smell rotten eggs."); + if (!valid_cloud_pos(x, y)) return FALSE; - } - return TRUE; + return (cansee(x, y) && distu(x, y) < 32); } static void @@ -1021,7 +1024,7 @@ display_stinking_cloud_positions(int state) for (dy = -dist; dy <= dist; dy++) { x = u.ux + dx; y = u.uy + dy; - if (get_valid_stinking_cloud_pos(x,y)) + if (can_center_cloud(x,y)) tmp_at(x, y); } } else { @@ -1702,9 +1705,9 @@ seffect_fire(struct obj **sobjp) dam *= 5; pline("Where do you want to center the explosion?"); getpos_sethilite(display_stinking_cloud_positions, - get_valid_stinking_cloud_pos); + can_center_cloud); (void) getpos(&cc, TRUE, "the desired position"); - if (!is_valid_stinking_cloud_pos(cc.x, cc.y, FALSE)) { + if (!can_center_cloud(cc.x, cc.y)) { /* try to reach too far, get burned */ cc.x = u.ux; cc.y = u.uy; @@ -1786,25 +1789,11 @@ seffect_stinking_cloud(struct obj **sobjp) int otyp = sobj->otyp; boolean already_known = (sobj->oclass == SPBOOK_CLASS /* spell */ || objects[otyp].oc_name_known); - coord cc; if (!already_known) You("have found a scroll of stinking cloud!"); g.known = TRUE; - pline("Where do you want to center the %scloud?", - already_known ? "stinking " : ""); - cc.x = u.ux; - cc.y = u.uy; - getpos_sethilite(display_stinking_cloud_positions, - get_valid_stinking_cloud_pos); - if (getpos(&cc, TRUE, "the desired position") < 0) { - pline1(Never_mind); - return; - } - if (!is_valid_stinking_cloud_pos(cc.x, cc.y, TRUE)) - return; - (void) create_gas_cloud(cc.x, cc.y, 3 + bcsign(sobj), - 8 + 4 * bcsign(sobj)); + do_stinking_cloud(sobj, already_known); } static void @@ -2759,6 +2748,34 @@ unpunish(void) setworn((struct obj *) 0, W_BALL); /* sets 'uball' to Null */ } +/* Prompt the player to create a stinking cloud and then create it if they give + * a location. */ +static void +do_stinking_cloud(struct obj *sobj, boolean mention_stinking) +{ + coord cc; + + pline("Where do you want to center the %scloud?", + mention_stinking ? "stinking " : ""); + cc.x = u.ux; + cc.y = u.uy; + getpos_sethilite(display_stinking_cloud_positions, can_center_cloud); + if (getpos(&cc, TRUE, "the desired position") < 0) { + pline(Never_mind); + return; + } else if (!can_center_cloud(cc.x, cc.y)) { + if (Hallucination) + pline("Ugh... someone cut the cheese."); + else + pline("%s a whiff of rotten eggs.", + sobj->oclass == SCROLL_CLASS ? "The scroll crumbles with" + : "You smell"); + return; + } + (void) create_gas_cloud(cc.x, cc.y, 15 + 10 * bcsign(sobj), + 8 + 4 * bcsign(sobj)); +} + /* some creatures have special data structures that only make sense in their * normal locations -- if the player tries to create one elsewhere, or to * revive one, the disoriented creature becomes a zombie diff --git a/src/region.c b/src/region.c index 9f08c0b0d..e18785b0a 100644 --- a/src/region.c +++ b/src/region.c @@ -1034,27 +1034,96 @@ inside_gas_cloud(genericptr_t p1, genericptr_t p2) return FALSE; /* Monster is still alive */ } +/* Create a gas cloud which starts at (x,y) and grows outward from it via + * breadth-first search. + * cloudsize is the number of squares the cloud will attempt to fill. + * damage is how much it deals to afflicted creatures. */ +#define MAX_CLOUD_SIZE 150 NhRegion * -create_gas_cloud(xchar x, xchar y, int radius, int damage) +create_gas_cloud(xchar x, xchar y, int cloudsize, int damage) { NhRegion *cloud; - int i, nrect; + int i, j; NhRect tmprect; + /* store visited coords */ + xchar xcoords[MAX_CLOUD_SIZE]; + xchar ycoords[MAX_CLOUD_SIZE]; + xcoords[0] = x; + ycoords[0] = y; + int curridx; + int newidx = 1; /* initial spot is already taken */ + + if (cloudsize > MAX_CLOUD_SIZE) { + impossible("create_gas_cloud: cloud too large (%d)!", cloudsize); + cloudsize = MAX_CLOUD_SIZE; + } + + for (curridx = 0; curridx < newidx; curridx++) { + if (newidx >= cloudsize) + break; + int xx = xcoords[curridx]; + int yy = ycoords[curridx]; + /* Do NOT check for if there is already a gas cloud created at some + * other time at this position. They can overlap. */ + + /* Primitive Fisher-Yates-Knuth shuffle to randomize the order of + * directions chosen. */ + coord dirs[4] = { {0, -1}, {0, 1}, {-1, 0}, {1, 0} }; + for (i = 4; i > 0; --i) { + xchar swapidx = rn2(i); + coord tmp = dirs[swapidx]; + dirs[swapidx] = dirs[i-1]; + dirs[i-1] = tmp; + } + int nvalid = 0; /* # of valid adjacent spots */ + for (i = 0; i < 4; ++i) { + /* try all 4 directions */ + + int dx = dirs[i].x, dy = dirs[i].y; + boolean isunpicked = TRUE; + + if (valid_cloud_pos(xx + dx, yy + dy)) { + nvalid++; + /* don't pick a location we've already picked */ + for (j = 0; j < newidx; ++j) { + if (xcoords[j] == xx + dx && ycoords[j] == yy + dy) { + isunpicked = FALSE; + break; + } + } + /* randomly disrupt the natural breadth-first search, so that + * clouds released in open spaces don't always tend towards a + * rhombus shape */ + if (nvalid == 4 && !rn2(2)) + continue; + + if (isunpicked) { + xcoords[newidx] = xx + dx; + ycoords[newidx] = yy + dy; + newidx++; + } + } + if (newidx >= cloudsize) { + /* don't try further directions */ + break; + } + } + } + /* we have now either filled up xcoord and ycoord entirely or run out of + * space. In either case, newidx is the correct total number of coordinates + * inserted. */ cloud = create_region((NhRect *) 0, 0); - nrect = radius; - tmprect.lx = x; - tmprect.hx = x; - tmprect.ly = y - (radius - 1); - tmprect.hy = y + (radius - 1); - for (i = 0; i < nrect; i++) { + for (i = 0; i < newidx; ++i) { + tmprect.lx = tmprect.hx = xcoords[i]; + tmprect.ly = tmprect.hy = ycoords[i]; add_rect_to_reg(cloud, &tmprect); - tmprect.lx--; - tmprect.hx++; - tmprect.ly++; - tmprect.hy--; } cloud->ttl = rn1(3, 4); + /* If the cloud was constrained in a small space, give it more time to + * live. */ + cloud->ttl = (cloud->ttl * cloudsize) / newidx; + if (!g.in_mklev && !g.context.mon_moving) set_heros_fault(cloud); /* assume player has created it */ cloud->inside_f = INSIDE_GAS_CLOUD; diff --git a/src/zap.c b/src/zap.c index ae4c111ad..e1c40f4cb 100644 --- a/src/zap.c +++ b/src/zap.c @@ -4599,7 +4599,7 @@ zap_over_floor(xchar x, xchar y, int type, boolean *shopdamage, /* don't create steam clouds on Plane of Water; air bubble movement and gas regions don't understand each other */ if (!on_water_level) - create_gas_cloud(x, y, rnd(3), 0); /* radius 1..3, no damg */ + create_gas_cloud(x, y, rnd(5), 0); /* 1..5, no damg */ if (lev->typ != POOL) { /* MOAT or DRAWBRIDGE_UP or WATER */ if (on_water_level) @@ -4629,7 +4629,7 @@ zap_over_floor(xchar x, xchar y, int type, boolean *shopdamage, newsym(x, y); } } else if (IS_FOUNTAIN(lev->typ)) { - create_gas_cloud(x, y, rnd(2), 0); /* radius 1..2, no damage */ + create_gas_cloud(x, y, rnd(3), 0); /* 1..3, no damage */ if (see_it) pline("Steam billows from the fountain."); rangemod -= 1; From 5d3e237ffc6e39bf53ae006cd6d610973f8ffd77 Mon Sep 17 00:00:00 2001 From: Pasi Kallinen Date: Sun, 19 Sep 2021 20:52:01 +0300 Subject: [PATCH 2/3] Izchak occasionally stocks wands/scrolls/spellbooks of light This comes from xNetHack by copperwater --- doc/fixes37.0 | 1 + include/mkroom.h | 2 +- src/shknam.c | 7 +++++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/doc/fixes37.0 b/doc/fixes37.0 index 5ce9ff879..bad9788b1 100644 --- a/doc/fixes37.0 +++ b/doc/fixes37.0 @@ -618,6 +618,7 @@ similar "The ogre lord yanks Cleaver from your corpses!" due to caching the obuf used for each item so that the same one will be reused for the next item, to avoid churning through the whole pool of obufs gas clouds are a little random in how they spread out from a point +Izchak occasionally stocks wands/scrolls/spellbooks of light Fixes to 3.7.0-x Problems that Were Exposed Via git Repository diff --git a/include/mkroom.h b/include/mkroom.h index 8f184ab67..e54d6f5b2 100644 --- a/include/mkroom.h +++ b/include/mkroom.h @@ -35,7 +35,7 @@ struct shclass { struct itp { int iprob; /* probability of an item type */ int itype; /* item type: if >=0 a class, if < 0 a specific item */ - } iprobs[6]; + } iprobs[9]; const char *const *shknms; /* list of shopkeeper names for this type */ }; diff --git a/src/shknam.c b/src/shknam.c index 2b151f09f..97dcc7a45 100644 --- a/src/shknam.c +++ b/src/shknam.c @@ -330,11 +330,14 @@ const struct shclass shtypes[] = { 0, D_SHOP, { { 30, -WAX_CANDLE }, - { 48, -TALLOW_CANDLE }, + { 44, -TALLOW_CANDLE }, { 5, -BRASS_LANTERN }, { 9, -OIL_LAMP }, { 3, -MAGIC_LAMP }, - { 5, -POT_OIL } }, + { 5, -POT_OIL }, + { 2, -WAN_LIGHT }, + { 1, -SCR_LIGHT }, + { 1, -SPE_LIGHT } }, shklight }, /* sentinel */ { (char *) 0, From 176d5b846371dcfd9a694063bfdc33bf3b584383 Mon Sep 17 00:00:00 2001 From: Pasi Kallinen Date: Sun, 19 Sep 2021 21:20:13 +0300 Subject: [PATCH 3/3] Bones piles can be ransacked by adjacent monsters If a bones file is created, any object-liking monster next to where hero died has a chance of grabbing objects from hero's inventory. This comes from xNetHack by copperwater . --- doc/fixes37.0 | 1 + src/bones.c | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/doc/fixes37.0 b/doc/fixes37.0 index bad9788b1..c9df2bc72 100644 --- a/doc/fixes37.0 +++ b/doc/fixes37.0 @@ -1161,6 +1161,7 @@ vomiting on an altar provokes the deities wrath branch stairs have a different glyph, show up in yellow color in tty duration of confusion when drinking booze depends upon hunger state using 'D' allows dropping items picked up previously +bones piles can be ransacked by adjacent monsters Platform- and/or Interface-Specific New Features diff --git a/src/bones.c b/src/bones.c index 22755289f..c34bd1449 100644 --- a/src/bones.c +++ b/src/bones.c @@ -8,6 +8,7 @@ static boolean no_bones_level(d_level *); static void goodfruit(int); static void resetobjs(struct obj *, boolean); +static void give_to_nearby_mon(struct obj *, int, int); static boolean fixuporacle(struct monst *); static boolean @@ -213,6 +214,41 @@ sanitize_name(char *namebuf) } } +/* Give object to a random object-liking monster on or adjacent to x,y + but skipping hero's location. + If no such monster, place object on floor at x,y. */ +static void +give_to_nearby_mon(struct obj *otmp, int x, int y) +{ + struct monst *mtmp; + struct monst *selected = (struct monst *) 0; + int nmon = 0, xx, yy; + + for (xx = x - 1; xx <= x + 1; ++xx) { + for (yy = y - 1; yy <= y + 1; ++yy) { + if (!isok(xx, yy)) + continue; + if (xx == u.ux && yy == u.uy) + continue; + if (!(mtmp = m_at(xx, yy))) + continue; + /* This doesn't do any checks on otmp to see that it matches the + * likes_* property, intentionally. Assume that the monster is + * rifling through and taking things that look interesting. */ + if (!(likes_gold(mtmp->data) || likes_gems(mtmp->data) + || likes_objs(mtmp->data) || likes_magic(mtmp->data))) + continue; + nmon++; + if (!rn2(nmon)) + selected = mtmp; + } + } + if (selected && can_carry(selected, otmp)) + add_to_minv(selected, otmp); + else + place_object(otmp, x, y); +} + /* called by savebones(); also by finish_paybill(shk.c) */ void drop_upon_death(struct monst *mtmp, /* monster if hero turned into one (other than ghost) */ @@ -249,6 +285,8 @@ drop_upon_death(struct monst *mtmp, /* monster if hero turned into one (other th (void) add_to_minv(mtmp, otmp); else if (cont) (void) add_to_container(cont, otmp); + else if (!rn2(8)) + give_to_nearby_mon(otmp, x, y); else place_object(otmp, x, y); }