Skip to content

Commit

Permalink
Crafting traps, part 1: initial implementation.
Browse files Browse the repository at this point in the history
With this commit, it is now possible for the player to build their own
traps using a new object called a 'trap kit'. A trap kit comes with
10-30 'charges', and Rogue's will always start the game with one in
their inventory. These kits can spawn randomly as well, but are very
rare. These can be 'recharged' with a scroll of charging as many times
as needed. The charges simulate the materials and tools needed to build
a trap, much like the charges in a tinning kit simulate the number of
tins available to preserve corpses.

The initial implementation, only arrow traps can be built for now -
there's a LOT more to do here, but this commit basically sets the
framework, and shows that this actually works.

How it works: the player applies their trap kit, they're asked to supply
a component. If the correct component (type and quantity) is provided,
a trap is created and will appear in inventory (in this case, an 'arrow
trap'). The player can then apply the newly created trap to activate it
on the tile space they are standing on, same rules apply as with setting
a bear trap or a land mine.

Because of the changes made here, if the player were to wish for an
arrow trap, they'll actually receive an arrow trap in inventory that
they can use. In wizard mode, typically you'd wish for 'arrow trap' to
make a set trap on the tile space you're standing on - now you'd use
'arrow trap set' instead to create a set trap on the current tile space.

Traps I had in mind that could actually be crafted: arrow traps
(implemented), land mines, bear traps, bolt traps, dart traps, sleeping
gas traps, rust traps, fire traps, ice traps, teleportation traps (not
levelport), magic traps, antimagic traps, polymorph traps, spear traps,
magic beam traps, squeaky board traps, and falling rock traps. Trap
doors, maybe? Turning a pit into a spiked pit, also maybe? Would need to
figure out suitable components, and in what quantities. Case in point -
right now, it only takes 20 arrows to craft an arrow trap, but depending
on your luck, disarming it could yield twice as many arrows, or more.
Will need to think on that.

Other to-do's - create a new skill for building/setting/disarming traps.
All traps could be possible to create, but the more advanced traps (poly
traps, magic beam traps, etc) would require higher skill to ensure a
decent chance of success. Then figure out which roles should have access
to this skill (some are obvious choice, others not so much). Also have a
look at xNetHack's trap ammo revamp, this would allow using a specific
material type for a component to create a trap using that material (use
silver arrows to make an arrow trap, it'll fire silver arrows).
  • Loading branch information
k21971 committed Mar 12, 2025
1 parent 89fcebe commit c4d6db6
Show file tree
Hide file tree
Showing 16 changed files with 279 additions and 157 deletions.
1 change: 1 addition & 0 deletions doc/evilhack-changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -4378,4 +4378,5 @@ The following changes to date are:
- Initial preparation for new version (0.9.1)
- Properly display fractured altars in dungeon overview
- Druids can pass directly through trees
- Crafting traps, part 1: initial implementation

70 changes: 35 additions & 35 deletions include/trap.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,47 +55,47 @@ extern struct trap *ftrap;

/* unconditional traps */
enum trap_types {
NO_TRAP = 0,
ARROW_TRAP = 1,
BOLT_TRAP = 2,
DART_TRAP = 3,
ROCKTRAP = 4,
SQKY_BOARD = 5,
BEAR_TRAP = 6,
LANDMINE = 7,
ROLLING_BOULDER_TRAP = 8,
SLP_GAS_TRAP = 9,
RUST_TRAP = 10,
FIRE_TRAP = 11,
ICE_TRAP = 12,
PIT = 13,
SPIKED_PIT = 14,
HOLE = 15,
TRAPDOOR = 16,
TELEP_TRAP = 17,
LEVEL_TELEP = 18,
MAGIC_PORTAL = 19,
WEB = 20,
STATUE_TRAP = 21,
MAGIC_TRAP = 22,
ANTI_MAGIC = 23,
POLY_TRAP = 24,
SPEAR_TRAP = 25,
MAGIC_BEAM_TRAP = 26,
VIBRATING_SQUARE = 27,
NO_TRAP = 0,
ARROW_TRAP_SET = 1,
BOLT_TRAP_SET = 2,
DART_TRAP_SET = 3,
ROCKTRAP = 4,
SQKY_BOARD = 5,
BEAR_TRAP = 6,
LANDMINE = 7,
ROLLING_BOULDER_TRAP = 8,
SLP_GAS_TRAP_SET = 9,
RUST_TRAP_SET = 10,
FIRE_TRAP_SET = 11,
ICE_TRAP_SET = 12,
PIT = 13,
SPIKED_PIT = 14,
HOLE = 15,
TRAPDOOR = 16,
TELEP_TRAP_SET = 17,
LEVEL_TELEP = 18,
MAGIC_PORTAL = 19,
WEB = 20,
STATUE_TRAP = 21,
MAGIC_TRAP_SET = 22,
ANTI_MAGIC = 23,
POLY_TRAP_SET = 24,
SPEAR_TRAP_SET = 25,
MAGIC_BEAM_TRAP_SET = 26,
VIBRATING_SQUARE = 27,

TRAPNUM = 28
TRAPNUM = 28
};

#define is_pit(ttyp) ((ttyp) == PIT || (ttyp) == SPIKED_PIT)
#define is_hole(ttyp) ((ttyp) == HOLE || (ttyp) == TRAPDOOR)
#define undestroyable_trap(ttyp) ((ttyp) == MAGIC_PORTAL \
|| (ttyp) == VIBRATING_SQUARE)
#define is_magical_trap(ttyp) ((ttyp) == TELEP_TRAP \
|| (ttyp) == LEVEL_TELEP \
|| (ttyp) == MAGIC_TRAP \
|| (ttyp) == ANTI_MAGIC \
|| (ttyp) == POLY_TRAP \
|| (ttyp) == MAGIC_BEAM_TRAP)
#define is_magical_trap(ttyp) ((ttyp) == TELEP_TRAP_SET \
|| (ttyp) == LEVEL_TELEP \
|| (ttyp) == MAGIC_TRAP_SET \
|| (ttyp) == ANTI_MAGIC \
|| (ttyp) == POLY_TRAP_SET \
|| (ttyp) == MAGIC_BEAM_TRAP_SET)

#endif /* TRAP_H */
115 changes: 113 additions & 2 deletions src/apply.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ STATIC_DCL void FDECL(light_cocktail, (struct obj **));
STATIC_PTR void FDECL(display_jump_positions, (int));
STATIC_DCL void FDECL(use_tinning_kit, (struct obj *));
STATIC_DCL void FDECL(use_grease, (struct obj *));
STATIC_DCL void FDECL(use_trap_kit, (struct obj *));
STATIC_DCL void FDECL(use_trap, (struct obj *));
STATIC_DCL void FDECL(apply_flint, (struct obj **));
STATIC_DCL void FDECL(use_stone, (struct obj **));
Expand Down Expand Up @@ -2925,6 +2926,107 @@ reset_trapset()
trapinfo.force_bungle = 0;
}

static const struct trap_recipe {
short result_typ;
short typ;
short quan;
} final[] = {
/* trap type, components, component quantity */
{ ARROW_TRAP, ARROW, 20 },
{ 0, 0, 0 }
};

STATIC_OVL void
use_trap_kit(obj)
struct obj *obj; /* actual trap kit */
{
const struct trap_recipe *recipe;
struct obj* otmp; /* components needed to make a trap */
struct obj* output; /* final product (crafted trap) */
char allowall[2];
int trap_type = 0;

allowall[0] = ALL_CLASSES;
allowall[1] = '\0';

/* various player conditions can prevent successful crafting */
if (!u_handsy()) {
return;
} else if (Stunned || Confusion) {
You_cant("build a trap while incapacitated.");
return;
} else if (u.uhunger < 50) { /* weak */
You("are too weak from hunger to build a trap.");
return;
} else if (ACURR(A_DEX) < 4) {
You("lack the dexterity to build a trap.");
return;
}

/* trap kit requires 'charges' to function */
if (obj->spe <= 0) {
You("seem to be out of materials to build a trap.");
return;
}

/* setup the base components for the trap */
otmp = getobj(allowall, "use as a component");
if (!otmp) {
You("need a base component to build a trap.");
return;
}

/* start the build process */
for (recipe = final; recipe->result_typ; recipe++) {
if (otmp->otyp == recipe->typ
&& otmp->quan >= recipe->quan) {
trap_type = recipe->result_typ;
break;
}
}

if (!trap_type) {
You("fail to build the trap.");
return;
} else if (trap_type) {
/* success */
output = mksobj(trap_type, TRUE, FALSE);
consume_obj_charge(obj, TRUE);
}

/* feedback for successful build */
pline("Using your %s, you craft %s to build %s.",
simpleonames(obj), yobjnam(otmp, (char *) 0),
doname(output));

/* ensure the final product is not degraded or coated
with anything in any way */
output->cursed = output->blessed = 0;
output->oeroded = output->oeroded2 = 0;
output->opoisoned = 0;
output->otainted = 0;
output->greased = 0;

/* toss out old objects, add new one */
if (otmp->otyp == recipe->typ)
otmp->quan -= recipe->quan;

/* recalculate weight of the recipe objects if
using a stack */
if (otmp->quan > 0)
otmp->owt = weight(otmp);

/* delete recipe objects if quantity reaches zero */
if (otmp->quan <= 0)
delobj(otmp);

/* trap is created */
output = addinv(output);
output->owt = weight(output);

update_inventory();
}

/* Place a landmine/bear trap. Helge Hafting */
STATIC_OVL void
use_trap(otmp)
Expand Down Expand Up @@ -2970,7 +3072,9 @@ struct obj *otmp;
reset_trapset();
return;
}
ttyp = (otmp->otyp == LAND_MINE) ? LANDMINE : BEAR_TRAP;
ttyp = (otmp->otyp == LAND_MINE) ? LANDMINE
: (otmp->otyp == BEARTRAP) ? BEAR_TRAP
: ARROW_TRAP_SET;
if (otmp == trapinfo.tobj && u.ux == trapinfo.tx && u.uy == trapinfo.ty) {
You("resume setting %s%s.", shk_your(buf, otmp),
defsyms[trap_to_defsym(what_trap(ttyp, rn2))].explanation);
Expand Down Expand Up @@ -3007,6 +3111,7 @@ struct obj *otmp;
trapinfo.time_needed = 0;
trapinfo.force_bungle = TRUE;
break;
case ARROW_TRAP_SET:
case BEAR_TRAP: /* drop it without arming it */
reset_trapset();
You("drop %s!",
Expand Down Expand Up @@ -3045,7 +3150,9 @@ set_trap()
if (--trapinfo.time_needed > 0)
return 1; /* still busy */

ttyp = (otmp->otyp == LAND_MINE) ? LANDMINE : BEAR_TRAP;
ttyp = (otmp->otyp == LAND_MINE) ? LANDMINE
: (otmp->otyp == BEARTRAP) ? BEAR_TRAP
: ARROW_TRAP_SET;
ttmp = maketrap(u.ux, u.uy, ttyp);
if (ttmp) {
ttmp->madeby_u = 1;
Expand Down Expand Up @@ -4402,6 +4509,9 @@ doapply()
case DWARVISH_MATTOCK:
res = use_pick_axe(obj);
break;
case TRAP_KIT:
use_trap_kit(obj);
break;
case TINNING_KIT:
use_tinning_kit(obj);
break;
Expand Down Expand Up @@ -4519,6 +4629,7 @@ doapply()
break;
case LAND_MINE:
case BEARTRAP:
case ARROW_TRAP:
use_trap(obj);
break;
case FLINT:
Expand Down
4 changes: 2 additions & 2 deletions src/dothrow.c
Original file line number Diff line number Diff line change
Expand Up @@ -847,8 +847,8 @@ int x, y;
} else if (ttmp->ttyp == VIBRATING_SQUARE) {
pline("The ground vibrates as you pass it.");
dotrap(ttmp, 0); /* doesn't print messages */
} else if (ttmp->ttyp == FIRE_TRAP
|| ttmp->ttyp == ICE_TRAP) {
} else if (ttmp->ttyp == FIRE_TRAP_SET
|| ttmp->ttyp == ICE_TRAP_SET) {
dotrap(ttmp, 0);
} else if ((is_pit(ttmp->ttyp) || is_hole(ttmp->ttyp))
&& Sokoban) {
Expand Down
4 changes: 2 additions & 2 deletions src/hack.c
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ moverock()
newsym(rx, ry);
return sobj_at(BOULDER, sx, sy) ? -1 : 0;
case LEVEL_TELEP:
case TELEP_TRAP: {
case TELEP_TRAP_SET: {
int newlev = 0; /* lint suppression */
d_level dest;

Expand All @@ -275,7 +275,7 @@ moverock()
else
You("push %s and suddenly it disappears!",
the(xname(otmp)));
if (ttmp->ttyp == TELEP_TRAP) {
if (ttmp->ttyp == TELEP_TRAP_SET) {
(void) rloco(otmp);
} else {
obj_extract_self(otmp);
Expand Down
Loading

0 comments on commit c4d6db6

Please sign in to comment.