Skip to content

Commit

Permalink
Give pit fiends a pit-creation attack
Browse files Browse the repository at this point in the history
Inspired by Fourk, but this implementation is quite different. The
motivation is the same: have pit fiends live up to their name (and
become a more distinct monster) by actually having some pit-related
mechanic, as well as buffing them to be more of a late game threat.

They only create one pit (unlike random multiple pits in Fourk, which
seemed like it'd turn into way too many pits after a few attacks)
beneath their target. Preexisting pits may rarely turn into a hole.
The attack only works on "normal" terrain types, but preexisting traps
generally do not block the attack.

This had a number of weird and tricky interactions with levitating and
flying targets. What I ended up settling on was: levitation or flying
will generally protect you from the smallish damage of a pit (including
avoiding poison from any spikes), and prevent you from getting stuck in
the pit, but will not prevent you from taking 4d4 damage by being hurled
into the bottom of the pit, nor will it prevent you from being grappled
by the pit fiend.
  • Loading branch information
copperwater committed Oct 7, 2020
1 parent 4d8c7ec commit d027e7b
Show file tree
Hide file tree
Showing 11 changed files with 229 additions and 6 deletions.
6 changes: 6 additions & 0 deletions doc/xnh-changelog-6.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,10 @@ changes:
- Any monster that generates with a spear can generate with a stack of them.
- If a monster dies to damage from a trap on the player's turn, the player is
considered responsible.
- Pit fiends' hug attack now creates a pit underneath their target, followed by
them hurling the target down into the pit. Sometimes a pit may be enlarged
into a hole. Levitation and flying prevent getting trapped in the pit, but
do not prevent damage dealt by being hurled down to the bottom.

### Interface changes

Expand Down Expand Up @@ -339,3 +343,5 @@ changes:
1/1/0000.
- Externify the inside_region function.
- Externify the cant_wield_corpse function.
- New function create_pit_under(), an xhity-compatible function that has an
aggressor create a pit beneath a target monster and throw them down into it.
1 change: 1 addition & 0 deletions include/extern.h
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,7 @@ E void FDECL(bury_obj, (struct obj *));
#ifdef DEBUG
extern int NDECL(wiz_debug_cmd_bury);
#endif
E boolean FDECL(create_pit_under, (struct monst *, struct monst *));


/* ### display.c ### */
Expand Down
9 changes: 5 additions & 4 deletions include/monattk.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,11 @@
#define AD_CORR 42 /* corrode armor (black pudding) */
#define AD_CURS 43 /* random curse (ex. gremlin) */
#define AD_POLY 44 /* polymorph the target (genetic engineer) */
#define AD_CLRC 45 /* random clerical spell */
#define AD_SPEL 46 /* random magic spell */
#define AD_RBRE 47 /* random breath weapon */
#define AD_SAMU 48 /* hits, may steal Amulet (Wizard) */
#define AD_PITS 45 /* create pit under target */
#define AD_CLRC 46 /* random clerical spell */
#define AD_SPEL 47 /* random magic spell */
#define AD_RBRE 48 /* random breath weapon */
#define AD_SAMU 49 /* hits, may steal Amulet (Wizard) */
#define LAST_AD AD_SAMU

/*
Expand Down
177 changes: 177 additions & 0 deletions src/dig.c
Original file line number Diff line number Diff line change
Expand Up @@ -2215,4 +2215,181 @@ 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 == &g.youmonst);
boolean youattack = (magr == &g.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_with_ammo(trap, DELTRAP_DESTROY_AMMO);
}
/* 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 g.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*/
7 changes: 7 additions & 0 deletions src/mhitm.c
Original file line number Diff line number Diff line change
Expand Up @@ -1448,6 +1448,13 @@ int dieroll;
if (!magr->mcan && tmp < mdef->mhp)
tmp = mon_poly(magr, mdef, tmp);
break;
case AD_PITS:
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 @@ -1812,6 +1812,18 @@ register struct attack *mattk;
if (uncancelled && Maybe_Half_Phys(dmg) < (Upolyd ? u.mh : u.uhp))
dmg = mon_poly(mtmp, &g.youmonst, dmg);
break;
case AD_PITS:
/* For some reason, the uhitm code calls this for any AT_HUGS attack,
* but the mhitu code doesn't. */
if (mattk->aatyp == AT_HUGS) {
set_ustuck(mtmp);
}
if (!mtmp->mcan) {
if (!create_pit_under(&g.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 @@ -2577,7 +2577,7 @@ struct permonst _mons2[] = {
MON("pit fiend", S_DEMON, LVL(13, 6, -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_DEMON | M2_STALK | M2_HOSTILE | M2_NASTY | M2_COLLECT,
Expand Down
1 change: 1 addition & 0 deletions src/pager.c
Original file line number Diff line number Diff line change
Expand Up @@ -625,6 +625,7 @@ static const char * damagetypes[] = {
"corrode",
"steal intrinsic",
"polymorph",
"create pit",
"clerical",
"arcane",
"random breath",
Expand Down
6 changes: 5 additions & 1 deletion src/teleport.c
Original file line number Diff line number Diff line change
Expand Up @@ -1395,8 +1395,12 @@ int in_sight;
{
int tt = (trap ? trap->ttyp : NO_TRAP);

if (mtmp == u.ustuck) /* probably a vortex */
if (mtmp == u.ustuck && g.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 (teleport_pet(mtmp, force_it)) {
d_level tolevel;
int migrate_typ = MIGR_RANDOM;
Expand Down
7 changes: 7 additions & 0 deletions src/trap.c
Original file line number Diff line number Diff line change
Expand Up @@ -1122,6 +1122,13 @@ 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 (!g.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
7 changes: 7 additions & 0 deletions src/uhitm.c
Original file line number Diff line number Diff line change
Expand Up @@ -2221,6 +2221,13 @@ int specialdmg; /* blessed and/or silver bonus against various things */
if (!negated && tmp < mdef->mhp)
tmp = mon_poly(&g.youmonst, mdef, tmp);
break;
case AD_PITS:
if (!u.uswallow) {
if (!create_pit_under(mdef, &g.youmonst)) {
tmp = 0;
}
}
break;
default:
tmp = 0;
break;
Expand Down

0 comments on commit d027e7b

Please sign in to comment.