Skip to content

Commit

Permalink
Give pit fiends a pit-creation attack.
Browse files Browse the repository at this point in the history
From xNetHack git commit d027e7b (modified) - pit fiends now have a
super-cool appropriate damage type that allows them to create a pit,
grab you and throw you down into the pit. There's a small chance that
the pit can become a hole instead. I've modified this slightly - in
xNetHack, pit fiends will use this attack on every turn they get, which
felt like too much during play testing. I've adjusted the attack to
occur randomly 50% of the time, but also increased the damage output to
compensate. Love this, this is exactly the kind of 'flavor' monsters
like these need.
  • Loading branch information
k21971 committed Apr 8, 2021
1 parent e09e1fc commit 8a2d4cd
Show file tree
Hide file tree
Showing 10 changed files with 206 additions and 12 deletions.
1 change: 1 addition & 0 deletions doc/evilhack-changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -1533,4 +1533,5 @@ The following changes to date are:
- Remove #give command
- Implement giving and taking pets' items via #loot
- Fix: findgold always picked first gold item in the object chain
- Give pit fiends a pit-creation attack

1 change: 1 addition & 0 deletions include/extern.h
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,7 @@ E schar FDECL(fillholetyp, (int, int, BOOLEAN_P));
E void FDECL(liquid_flow,
(XCHAR_P, XCHAR_P, SCHAR_P, struct trap *, const char *));
E boolean FDECL(conjoined_pits, (struct trap *, struct trap *, BOOLEAN_P));
E boolean FDECL(create_pit_under, (struct monst *, struct monst *));
#if 0
E void FDECL(bury_monst, (struct monst *));
E void NDECL(bury_you);
Expand Down
1 change: 1 addition & 0 deletions include/monattk.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
#define AD_CLOB 49 /* knock-back attack */
#define AD_POLY 50 /* polymorph the target (genetic engineer) */
#define AD_WTHR 51 /* withering attack (mummies) */
#define AD_PITS 52 /* create pit under target (pit fiend) */

#define AD_CLRC 240 /* random clerical spell */
#define AD_SPEL 241 /* random magic spell */
Expand Down
162 changes: 162 additions & 0 deletions src/dig.c
Original file line number Diff line number Diff line change
Expand Up @@ -2178,4 +2178,166 @@ wiz_debug_cmd_bury()
}
#endif /* DEBUG */

/* magr is attempting to create a pit under mdef via an AD_PITS attack.
* This may fail on certain terrains, or if there is a trap there, and do
* nothing. With other terrains and traps it has other effects, but all of them
* aimed towards getting the target in the bottom of a pit.
* Return value: TRUE if AD_PITS damage dice should still be applied; FALSE if
* not (because of falling down a hole mainly; it would be nice to also exempt
* further damage if the monster is lifesaved, but there's sadly no good way to
* check that)
*/
boolean
create_pit_under(mdef, magr)
struct monst *mdef, *magr;
{
boolean youdefend = (mdef == &youmonst);
boolean youattack = (magr == &youmonst);
int x = youdefend ? u.ux : mdef->mx;
int y = youdefend ? u.uy : mdef->my;
const int typ = levl[x][y].typ;
struct trap *trap = t_at(x, y);
const boolean canseexy = cansee(x, y);
struct obj *boulder = sobj_at(BOULDER, x, y);
boolean sent_down_hole = FALSE;

/* check for illegalities: out of bounds, terrain unsuitable for traps,
* or trap types that should not be deleted and replaced with pits */
if (!isok(x, y) || !SPACE_POS(typ) || IS_FURNITURE(typ) || IS_AIR(typ)
|| (trap &&
(trap->ttyp == MAGIC_PORTAL || trap->ttyp == VIBRATING_SQUARE))
|| (In_endgame(&u.uz) && !Is_earthlevel(&u.uz))) {
if (youattack) {
You("fail to create a pit on the %s under %s.", surface(x, y),
mon_nam(mdef));
} else if (canseemon(magr)) {
pline("%s looks rather piteous.", Monnam(magr));
}
return FALSE;
}
/* player should only be able to do this while polyselfed */
if (youattack && !Upolyd) {
impossible("player creating pit while not polyselfed?");
return FALSE;
}

if (flags.verbose) {
if (youattack)
pline("You stomp the ground!");
else
pline("%s stomps the ground!", Monnam(magr));
}

/* First, do terrain modifications. mdef doesn't react until after this. */
if (trap && (trap->ttyp == PIT || trap->ttyp == SPIKED_PIT
|| trap->ttyp == HOLE)) {
/* The existing chasm grows larger. A pit creator that is low on health
* is more likely to dig all the way through and turn it into a hole. */
if (canseexy) {
pline("The %s below %s grows deeper!",
trap->ttyp == HOLE ? "chasm" : "pit",
youdefend ? "you" : mon_nam(mdef));
}
boolean make_hole = !rn2(40);
if ((youattack && u.mh * 5 <= u.mhmax)
|| (!youattack && mdef->mhp * 5 <= mdef->mhpmax)) {
make_hole = !rn2(10);
}
if (!Can_dig_down(&u.uz) || trap->ttyp == HOLE)
make_hole = FALSE;
if (make_hole) {
deltrap(trap);
trap = maketrap(x, y, HOLE);
}
} else if (trap && trap->ttyp == TRAPDOOR) {
/* There's already a hole under this, so the attack just enlarges it and
* removes the door (making it HOLE).
* Assume that since there's a trap door already, Can_dig_down is true.
*/
if (canseexy)
pline("%s trap door breaks apart as a chasm widens beneath it!",
trap->tseen ? "The" : "A");
deltrap(trap);
trap = maketrap(x, y, HOLE);
} else if (trap && trap->ttyp == LANDMINE) {
/* Blow it up. It will become a pit (but nothing falls in yet). */
if (!canseexy && !Deaf) {
pline("Kaablamm! You hear an explosion in the distance!");
} else if (canseexy) {
pline("%sA land mine blows up!", !Deaf ? "KAABLAMM!!! " : "");
}
blow_up_landmine(trap);
} else { /* also includes case of no trap there in the first place */
if (trap) {
/* Silently delete whatever other sort of trap this is. */
deltrap(trap);
}
/* Now we know there is no trap; create a pit. */
if (canseexy)
pline("A pit opens up beneath %s!",
youdefend ? "you" : mon_nam(mdef));
trap = maketrap(x, y, PIT);
}
if (canseexy || youdefend)
trap->tseen = 1;
if (youattack)
trap->madeby_u = 1;

/* Now that terrain has been modified, take care of mdef.
* We have guaranteed that (x,y) now contains either a pit, spiked pit, or
* hole. */
const char *to_the_bottom = is_pit(trap->ttyp) ? " to the bottom" : "";
if (youdefend) {
reset_utrap(FALSE);
pline("%s hurls you down%s!", Monnam(magr), to_the_bottom);
/* Use FORCETRAP to avoid messages about not falling into the pit if
* flying or levitating; however, like the !youdefend case below, either
* will cause you to skip the pit's actual effects (but you will take
* the regular damage from the hurling attack). */
dotrap(trap, FORCETRAP);
if (u.utotype) { /* nonzero = will goto_level after this */
sent_down_hole = TRUE;
} else if (Levitation || Flying) {
You("%s back up.", Flying ? "fly" : "float");
/* utrap should not have been set at all */
}
} else {
wakeup(mdef, FALSE);
/* We want to make them be in the trap anew - they won't fall into holes
* and such if this is left as 1. */
mdef->mtrapped = 0;
pline("%s hurl%s %s down%s!", youattack ? "You" : Monnam(magr),
youattack ? "" : "s", mon_nam(mdef), to_the_bottom);
/* This does not set force_mintrap - which for some reason causes
* flying monsters not to fall into a pit if true. Thus, they will not
* get extra damage for the trap, but will still take the normal damage
* from being hurled in. */
if (mintrap(mdef) == 3) { /* 3 == went off level */
sent_down_hole = TRUE;
} else if (is_flyer(mdef->data) || is_floater(mdef->data)) {
pline("%s %s back up.", Monnam(mdef),
is_flyer(mdef->data) ? "flies" : "floats");
mdef->mtrapped = 0; /* maybe still held, but not stuck in pit */
}
}

if (boulder) {
/* Boulder falls in, plugging the newly created pit and possibly
* damaging whoever might have gotten trapped inside. */
obj_extract_self(boulder);
if (!(youdefend && sent_down_hole)) {
/* We still always want to plug the hole with the boulder, but it'd
* be weird to print this after the player gets messages about
* falling through the hole. */
pline("KADOOM!"); /* ... "The boulder falls into the pit" */
}
flooreffects(boulder, x, y, "fall");
}

if (sent_down_hole || (!youdefend && DEADMONSTER(mdef))) {
return FALSE;
}
return TRUE;
}

/*dig.c*/
19 changes: 9 additions & 10 deletions src/mhitm.c
Original file line number Diff line number Diff line change
Expand Up @@ -2191,16 +2191,7 @@ struct obj **ootmp; /* to return worn armor for caller to disintegrate */
for (otmp = mdef->minvent; otmp; otmp = otmp2) {
otmp2 = otmp->nobj;
if (!oresist_disintegration(otmp)) {
if (otmp->owornmask) {
/* in case monster's life gets saved */
mdef->misc_worn_check &= ~otmp->owornmask;
if (otmp->owornmask & W_WEP)
setmnotwielded(mdef, otmp);
/* also dismounts hero if this object is steed's saddle */
update_mon_intrinsics(mdef, otmp, FALSE, TRUE);
otmp->owornmask = 0L;
}
obj_extract_self(otmp);
extract_from_minvent(mdef, otmp, TRUE, TRUE);
obfree(otmp, (struct obj *) 0);
}
}
Expand Down Expand Up @@ -2244,6 +2235,14 @@ struct obj **ootmp; /* to return worn armor for caller to disintegrate */
(void) cancel_monst(mdef, (struct obj *) 0, FALSE, TRUE, FALSE);
}
break;
case AD_PITS:
if (rn2(2)) {
if (!magr->mcan) {
if (!create_pit_under(mdef, magr))
tmp = 0;
}
}
break;
default:
tmp = 0;
break;
Expand Down
12 changes: 12 additions & 0 deletions src/mhitu.c
Original file line number Diff line number Diff line change
Expand Up @@ -2260,6 +2260,18 @@ register struct attack *mattk;
incr_itimeout(&HWithering, rnd(dmg) + 3);
}
break;
case AD_PITS:
/* For some reason, the uhitm code calls this for any AT_HUGS attack,
* but the mhitu code doesn't. */
if (rn2(2)) {
if (mattk->aatyp == AT_HUGS)
u.ustuck = mtmp;
if (!mtmp->mcan) {
if (!create_pit_under(&youmonst, mtmp))
dmg = 0;
}
}
break;
default:
dmg = 0;
break;
Expand Down
2 changes: 1 addition & 1 deletion src/monst.c
Original file line number Diff line number Diff line change
Expand Up @@ -3213,7 +3213,7 @@ struct permonst _mons2[] = {
MON("pit fiend", S_DEMON, LVL(13, 9, -3, 65, -13),
(G_HELL | G_NOCORPSE | 2),
A(ATTK(AT_WEAP, AD_PHYS, 4, 2), ATTK(AT_WEAP, AD_PHYS, 4, 2),
ATTK(AT_HUGS, AD_PHYS, 2, 4), NO_ATTK, NO_ATTK, NO_ATTK),
ATTK(AT_HUGS, AD_PITS, 4, 4), NO_ATTK, NO_ATTK, NO_ATTK),
SIZ(WT_HUMAN, 400, MS_GROWL, MZ_LARGE), MR_FIRE | MR_POISON, 0,
M1_SEE_INVIS | M1_POIS,
M2_STALK | M2_HOSTILE | M2_NASTY | M2_COLLECT,
Expand Down
6 changes: 5 additions & 1 deletion src/teleport.c
Original file line number Diff line number Diff line change
Expand Up @@ -1455,8 +1455,12 @@ int in_sight;
{
int tt = (trap ? trap->ttyp : NO_TRAP);

if (mtmp == u.ustuck) /* probably a vortex */
if (mtmp == u.ustuck && context.mon_moving) {
/* ustuck case is probably a vortex, but check mon_moving so that if
* hero is forcing a monster into a trap (i.e. with an AD_PITS attack),
* this will proc correctly. */
return 0; /* temporary? kludge */
}
if (resists_magm(mtmp))
return 0;
if (teleport_pet(mtmp, force_it)) {
Expand Down
6 changes: 6 additions & 0 deletions src/trap.c
Original file line number Diff line number Diff line change
Expand Up @@ -1028,6 +1028,12 @@ unsigned trflags;

nomul(0);

/* Correct conj_pit and adj_pit if the player isn't moving; this function
* can also be called by a pit fiend hurling you into a pit on its turn,
* which has nothing to do with moving between pits */
if (!context.mon_moving)
conj_pit = adj_pit = FALSE;

/* KMH -- You can't escape the Sokoban level traps */
if (Sokoban && (is_pit(ttype) || is_hole(ttype))) {
/* The "air currents" message is still appropriate -- even when
Expand Down
8 changes: 8 additions & 0 deletions src/uhitm.c
Original file line number Diff line number Diff line change
Expand Up @@ -2620,6 +2620,14 @@ int specialdmg; /* blessed and/or silver bonus against various things */
mdef->mwither = 1;
}
break;
case AD_PITS:
if (rn2(2)) {
if (!u.uswallow) {
if (!create_pit_under(mdef, &youmonst))
tmp = 0;
}
}
break;
default:
tmp = 0;
break;
Expand Down

0 comments on commit 8a2d4cd

Please sign in to comment.