diff --git a/lib/world/mob/1.mob b/lib/world/mob/1.mob index 404b494..85453a2 100644 --- a/lib/world/mob/1.mob +++ b/lib/world/mob/1.mob @@ -12,6 +12,7 @@ call his stature bulky, as he has quite a bit of muscle. B + ~ 6218 0 0 0 0 0 0 0 0 E 1 3d20+40 @@ -30,17 +31,17 @@ Skill 145 5 Skill 146 5 Skill 147 5 E -L 3 118 1 -L 5 131 1 -L 6 110 1 -L 7 108 1 -L 8 115 1 -L 9 124 1 -L 10 107 1 -L 11 111 1 -L 15 117 1 -L 16 117 1 L 17 127 1 +L 16 117 1 +L 15 117 1 +L 11 111 1 +L 10 107 1 +L 9 124 1 +L 8 115 1 +L 7 108 1 +L 6 110 1 +L 5 131 1 +L 3 118 1 #101 Sally~ slim lanky human soldier guard~ @@ -56,6 +57,7 @@ her nose. B + ~ 6218 0 0 0 0 0 0 0 0 E 1 3d20+40 @@ -71,17 +73,17 @@ Skill 145 5 Skill 146 5 Skill 147 5 E -L 17 127 1 -L 16 117 1 -L 15 117 1 -L 11 111 1 -L 10 107 1 -L 9 124 1 -L 8 115 1 -L 7 108 1 -L 6 110 1 -L 5 131 1 L 3 118 1 +L 5 131 1 +L 6 110 1 +L 7 108 1 +L 8 115 1 +L 9 124 1 +L 10 107 1 +L 11 111 1 +L 15 117 1 +L 16 117 1 +L 17 127 1 #102 Baldy~ barkeep stocky bald~ @@ -97,13 +99,14 @@ others. B + ~ 10 0 0 0 0 0 0 0 0 E 1 3d12+60 8 8 1 E -L 14 113 1 L 9 112 1 +L 14 113 1 #103 Lanky~ woman lanky scarred~ @@ -120,6 +123,7 @@ appear slightly bloodshot. B + ~ 10 0 0 0 0 0 0 0 0 E 1 3d8+60 @@ -135,4 +139,22 @@ Skill 143 5 Skill 144 5 Skill 147 5 E +#104 +Rat~ +rat small furry~ +a small, furry rat~ +Keeping low to the ground, a small, furry rat wanders around here. +~ + This small rat is covered in thick fur. The fur itself appears matted and +has grime coating it. Two beady black eyes look around, constantly shifting. +Its tail is four, perhaps five inches long and grey in color. Both hindlings +appear thick and ready to propel the animal if it feels threatened. +~ +B +It's a rat. +~ +8 0 0 0 0 0 0 0 0 E +1 0d0+10 +8 8 2 +E $ diff --git a/src/act.h b/src/act.h index 079b3c0..f4694d1 100644 --- a/src/act.h +++ b/src/act.h @@ -158,6 +158,7 @@ ACMD(do_put); ACMD(do_remove); ACMD(do_wear); ACMD(do_wield); +ACMD(do_skin); /***************************************************************************** * Begin Functions and defines for act.movement.c diff --git a/src/act.item.c b/src/act.item.c index fd24ddf..8132166 100644 --- a/src/act.item.c +++ b/src/act.item.c @@ -1927,3 +1927,124 @@ ACMD(do_raise_lower_hood) send_to_char(ch, "You lower your hood.\r\n"); act("$n lowers $s hood.", FALSE, ch, 0, 0, TO_ROOM); } + +static void dump_obj_contents_to_room(struct obj_data *container, room_rnum room) +{ + struct obj_data *obj, *next_obj; + + if (!container || room == NOWHERE) + return; + + for (obj = container->contains; obj; obj = next_obj) { + next_obj = obj->next_content; + obj_from_obj(obj); + obj_to_room(obj, room); + } +} + +static int is_corpse_obj(struct obj_data *obj) +{ + if (!obj) + return 0; + return (GET_OBJ_TYPE(obj) == ITEM_CONTAINER && GET_OBJ_VAL(obj, 3) == 1); +} + +ACMD(do_skin) +{ + char arg[MAX_INPUT_LENGTH]; + struct obj_data *corpse = NULL; + struct skin_yield_entry *y; + room_rnum room; + mob_vnum mvnum; + mob_rnum mrnum; + int d20, total, successes = 0; + int number = 1; + + one_argument(argument, arg); + + if (!*arg) { + send_to_char(ch, "Skin what?\r\n"); + return; + } + + /* Prefer room first, then inventory. */ + number = 1; + corpse = get_obj_in_list_vis(ch, arg, &number, world[IN_ROOM(ch)].contents); + + if (!corpse) { + number = 1; + corpse = get_obj_in_list_vis(ch, arg, &number, ch->carrying); + } + + if (!corpse) { + send_to_char(ch, "You don't see that here.\r\n"); + return; + } + + if (!is_corpse_obj(corpse)) { + send_to_char(ch, "You can't skin that.\r\n"); + return; + } + + room = IN_ROOM(ch); + if (room == NOWHERE) { + send_to_char(ch, "You can't do that here.\r\n"); + return; + } + + mvnum = corpse->corpse_mob_vnum; + if (mvnum <= 0) { + send_to_char(ch, "You aren't able to cut anything useful from the corpse.\r\n"); + dump_obj_contents_to_room(corpse, room); + extract_obj(corpse); + return; + } + + mrnum = real_mobile(mvnum); + if (mrnum < 0 || !mob_index[mrnum].skin_yields) { + send_to_char(ch, "You aren't able to cut anything useful from the corpse.\r\n"); + dump_obj_contents_to_room(corpse, room); + extract_obj(corpse); + return; + } + + d20 = dice(1, 20); + + if (d20 == 1) { + send_to_char(ch, "You aren't able to cut anything useful from the corpse.\r\n"); + dump_obj_contents_to_room(corpse, room); + extract_obj(corpse); + return; + } + + total = roll_survival_check(ch, 0, &d20); + + if (d20 == 1) { + send_to_char(ch, "You aren't able to cut anything useful from the corpse.\r\n"); + dump_obj_contents_to_room(corpse, room); + extract_obj(corpse); + return; + } + + /* Evaluate configured yields (if any). */ + for (y = mob_index[mrnum].skin_yields; y; y = y->next) { + if (total >= y->dc) { + struct obj_data *o = read_object(y->obj_vnum, VIRTUAL); + if (o) { + obj_to_room(o, room); + successes++; + } + } + } + + if (successes == 0) { + send_to_char(ch, "You aren't able to cut anything useful from the corpse.\r\n"); + } else { + act("You skin $p, cutting away anything useful.", FALSE, ch, corpse, 0, TO_CHAR); + act("$n skins $p, cutting away anything useful.", FALSE, ch, corpse, 0, TO_ROOM); + } + + dump_obj_contents_to_room(corpse, room); + + extract_obj(corpse); +} diff --git a/src/constants.c b/src/constants.c index bbbe111..be4bf97 100644 --- a/src/constants.c +++ b/src/constants.c @@ -486,6 +486,8 @@ const char *extra_bits[] = { "ANTI_DRUID", "NO_SELL", "QUEST_ITEM", + "HOOD_UP", + "SKINNED", "\n" }; diff --git a/src/db.c b/src/db.c index 86523b8..c5b4992 100644 --- a/src/db.c +++ b/src/db.c @@ -1765,6 +1765,7 @@ void parse_mobile(FILE *mob_f, int nr) mob_index[i].vnum = nr; mob_index[i].number = 0; mob_index[i].func = NULL; + mob_index[i].skin_yields = NULL; clear_char(mob_proto + i); @@ -1926,6 +1927,39 @@ void parse_mobile(FILE *mob_f, int nr) /* look ahead to see if there is another 'L' */ letter = fread_letter(mob_f); } + ungetc(letter, mob_f); + + /* ---- Skinning yields (Y block): allow before triggers ---- */ + letter = fread_letter(mob_f); + while (letter == 'Y') { + obj_vnum ovnum; + int dc; + + for (;;) { + if (!get_line(mob_f, line)) { + log("SYSERR: Unexpected EOF while reading 'Y' block in mob #%d.", nr); + break; + } + if (sscanf(line, "%d %d", &ovnum, &dc) != 2) { + log("SYSERR: Bad 'Y' line in mob #%d: '%s' (need ).", nr, line); + continue; + } + if (ovnum == 0 && dc == 0) + break; + + /* add entry to mob_index[i].skin_yields */ + struct skin_yield_entry *e; + CREATE(e, struct skin_yield_entry, 1); + e->mob_vnum = mob_index[i].vnum; + e->obj_vnum = ovnum; + e->dc = dc; + e->next = mob_index[i].skin_yields; + mob_index[i].skin_yields = e; + } + + /* look ahead for another Y block (rare but harmless to support) */ + letter = fread_letter(mob_f); + } ungetc(letter, mob_f); /* ---- DG triggers: script info follows mob S/E section ---- */ @@ -1936,6 +1970,38 @@ void parse_mobile(FILE *mob_f, int nr) } ungetc(letter, mob_f); + /* ---- Skinning yields (Y block): allow after triggers ---- */ + letter = fread_letter(mob_f); + while (letter == 'Y') { + obj_vnum ovnum; + int dc; + + for (;;) { + if (!get_line(mob_f, line)) { + log("SYSERR: Unexpected EOF while reading 'Y' block in mob #%d.", nr); + break; + } + if (sscanf(line, "%d %d", &ovnum, &dc) != 2) { + log("SYSERR: Bad 'Y' line in mob #%d: '%s' (need ).", nr, line); + continue; + } + if (ovnum == 0 && dc == 0) + break; + + struct skin_yield_entry *e; + CREATE(e, struct skin_yield_entry, 1); + e->mob_vnum = mob_index[i].vnum; + e->obj_vnum = ovnum; + e->dc = dc; + e->next = mob_index[i].skin_yields; + mob_index[i].skin_yields = e; + } + + /* look ahead for another Y block (optional) */ + letter = fread_letter(mob_f); + } + ungetc(letter, mob_f); + /* ---- And allow loadout lines AFTER triggers, too ---- */ letter = fread_letter(mob_f); while (letter == 'L') { @@ -4348,3 +4414,28 @@ void load_config( void ) fclose(fl); } + +void free_skin_yields(struct skin_yield_entry *list) +{ + struct skin_yield_entry *e, *next; + for (e = list; e; e = next) { + next = e->next; + free(e); + } +} + +struct skin_yield_entry *copy_skin_yields(struct skin_yield_entry *src) +{ + struct skin_yield_entry *head = NULL, *tail = NULL, *e; + + for (; src; src = src->next) { + CREATE(e, struct skin_yield_entry, 1); + *e = *src; + e->next = NULL; + + if (!head) head = e; + else tail->next = e; + tail = e; + } + return head; +} diff --git a/src/db.h b/src/db.h index 7cb864f..bfb0d91 100644 --- a/src/db.h +++ b/src/db.h @@ -258,6 +258,8 @@ void free_player_index(void); void load_help(FILE *fl, char *name); void new_mobile_data(struct char_data *ch); void equip_mob_from_loadout(struct char_data *mob); +void free_skin_yields(struct skin_yield_entry *list); +struct skin_yield_entry *copy_skin_yields(struct skin_yield_entry *src); zone_rnum real_zone(zone_vnum vnum); room_rnum real_room(room_vnum vnum); diff --git a/src/fight.c b/src/fight.c index a97bef9..df31dd8 100644 --- a/src/fight.c +++ b/src/fight.c @@ -238,6 +238,8 @@ static void make_corpse(struct char_data *ch) corpse = create_obj(); + corpse->corpse_mob_vnum = IS_NPC(ch) ? GET_MOB_VNUM(ch) : 0; + corpse->item_number = NOTHING; IN_ROOM(corpse) = NOWHERE; corpse->name = strdup("corpse"); diff --git a/src/genmob.c b/src/genmob.c index b4cd84b..a884d82 100644 --- a/src/genmob.c +++ b/src/genmob.c @@ -479,6 +479,19 @@ int write_mobile_record(mob_vnum mvnum, struct char_data *mob, FILE *fd) /* --- DG Scripts --- */ script_save_to_disk(fd, mob, MOB_TRIGGER); + /* --- Skinning yields --- */ + { + mob_rnum rmob = real_mobile(mvnum); + struct skin_yield_entry *sy; + + if (rmob != NOBODY && mob_index[rmob].skin_yields) { + fprintf(fd, "Y\n"); + for (sy = mob_index[rmob].skin_yields; sy; sy = sy->next) + fprintf(fd, "%d %d\n", sy->obj_vnum, sy->dc); + fprintf(fd, "0 0\n"); + } + } + #if CONFIG_GENOLC_MOBPROG if (write_mobile_mobprog(mvnum, mob, fd) < 0) log("SYSERR: GenOLC: Error writing MobProgs for mobile #%d.", mvnum); diff --git a/src/interpreter.c b/src/interpreter.c index a6be498..2517f44 100644 --- a/src/interpreter.c +++ b/src/interpreter.c @@ -281,6 +281,7 @@ cpp_extern const struct command_info cmd_info[] = { { "set" , "set" , POS_DEAD , do_set , LVL_IMMORT, 0 }, { "shout" , "sho" , POS_RESTING , do_gen_comm , 0, SCMD_SHOUT }, { "skills" , "sk" , POS_SLEEPING, do_skills , 0, 0 }, + { "skin" , "skin" , POS_STANDING, do_skin, 0, 0 }, { "show" , "show" , POS_DEAD , do_show , LVL_IMMORT, 0 }, { "shutdow" , "shutdow" , POS_DEAD , do_shutdown , LVL_IMPL, 0 }, { "shutdown" , "shutdown", POS_DEAD , do_shutdown , LVL_IMPL, SCMD_SHUTDOWN }, diff --git a/src/medit.c b/src/medit.c index ee4dc9f..2d402d9 100644 --- a/src/medit.c +++ b/src/medit.c @@ -115,6 +115,7 @@ ACMD(do_oasis_medit) } CREATE(d->olc, struct oasis_olc_data, 1); + d->olc->skin_yields = NULL; /* Find the zone. */ OLC_ZNUM(d) = save ? real_zone(number) : real_zone_by_thing(number); @@ -221,6 +222,9 @@ void medit_setup_existing(struct descriptor_data *d, int rmob_num) */ SCRIPT(mob) = NULL; OLC_MOB(d)->proto_script = NULL; + + /* Copy skinning yields from the prototype index into OLC working storage. */ + d->olc->skin_yields = copy_skin_yields(mob_index[rmob_num].skin_yields); } /* Ideally, this function should be in db.c, but I'll put it here for portability. */ @@ -448,6 +452,7 @@ static void medit_disp_menu(struct descriptor_data *d) "%s8%s) Default : %s%s\r\n" "%s9%s) Attack : %s%s\r\n" "%sD%s) Class : %s%s\r\n" + "%sK%s) Skinning Menu...\r\n" "%s0%s) Stats Menu...\r\n" "%s-%s) Skills Menu...\r\n" "%sA%s) NPC Flags : %s%s\r\n" @@ -463,6 +468,7 @@ static void medit_disp_menu(struct descriptor_data *d) grn, nrm, yel, position_types[(int)GET_DEFAULT_POS(mob)], grn, nrm, yel, attack_hit_text[(int)GET_ATTACK(mob)].singular, grn, nrm, yel, classname, + grn, nrm, grn, nrm, grn, nrm, grn, nrm, cyn, flags, @@ -618,6 +624,27 @@ static void medit_disp_skill_menu(struct descriptor_data *d) OLC_MODE(d) = MEDIT_SKILL_MENU; } +static void medit_disp_skin_menu(struct descriptor_data *d) +{ + struct skin_yield_entry *e; + int n = 1; + + write_to_output(d, "\r\n-- Skinning Yields --\r\n"); + + if (!d->olc->skin_yields) { + write_to_output(d, " \r\n"); + } else { + for (e = d->olc->skin_yields; e; e = e->next) + write_to_output(d, "%2d) obj %d dc %d\r\n", n++, e->obj_vnum, e->dc); + } + + write_to_output(d, + "\r\nA) Add yield\r\n" + "D) Delete yield\r\n" + "Q) Quit to main menu\r\n" + "Enter choice: "); +} + void medit_parse(struct descriptor_data *d, char *arg) { int i = -1, j; @@ -640,6 +667,14 @@ void medit_parse(struct descriptor_data *d, char *arg) switch (*arg) { case 'y': case 'Y': + /* Commit skinning yields from OLC working copy into the prototype index. */ + { + mob_rnum rmob = real_mobile(OLC_NUM(d)); + if (rmob != NOBODY) { + free_skin_yields(mob_index[rmob].skin_yields); + mob_index[rmob].skin_yields = copy_skin_yields(d->olc->skin_yields); + } + } /* Save the mob in memory and to disk. */ medit_save_internally(d); mudlog(CMP, MAX(LVL_BUILDER, GET_INVIS_LEV(d->character)), TRUE, "OLC: %s edits mob %d", GET_NAME(d->character), OLC_NUM(d)); @@ -752,6 +787,11 @@ void medit_parse(struct descriptor_data *d, char *arg) string_write(d, &OLC_MOB(d)->player.background, MAX_MOB_DESC, 0, oldtext); OLC_VAL(d) = 1; return; + case 'k': + case 'K': + medit_disp_skin_menu(d); + OLC_MODE(d) = MEDIT_SKIN_MENU; + return; case 'w': case 'W': write_to_output(d, "Copy what mob? "); @@ -781,6 +821,92 @@ void medit_parse(struct descriptor_data *d, char *arg) write_to_output(d, "Oops...\r\n"); return; + case MEDIT_SKIN_MENU: + switch (UPPER(*arg)) { + case 'A': + write_to_output(d, "Enter object vnum: "); + OLC_MODE(d) = MEDIT_SKIN_ADD_VNUM; + return; + + case 'D': + write_to_output(d, "Delete which entry number? "); + OLC_MODE(d) = MEDIT_SKIN_DELETE; + return; + + case 'Q': + medit_disp_menu(d); + OLC_MODE(d) = MEDIT_MAIN_MENU; + return; + + default: + medit_disp_skin_menu(d); + return; + } + /* not reached */ + + case MEDIT_SKIN_ADD_VNUM: { + obj_vnum ovnum = (obj_vnum)atoi(arg); + + if (ovnum <= 0) { + write_to_output(d, "Invalid object vnum. Enter object vnum: "); + return; + } + + OLC_VAL(d) = (int)ovnum; /* stash temporarily (note: OLC_VAL is also your dirty flag) */ + write_to_output(d, "Enter DC required: "); + OLC_MODE(d) = MEDIT_SKIN_ADD_DC; + return; + } + + case MEDIT_SKIN_ADD_DC: { + int dc = atoi(arg); + struct skin_yield_entry *e; + + CREATE(e, struct skin_yield_entry, 1); + e->mob_vnum = OLC_NUM(d); /* mob vnum being edited */ + e->obj_vnum = (obj_vnum)OLC_VAL(d); /* vnum captured in prior step */ + e->dc = MAX(0, dc); + e->next = d->olc->skin_yields; + d->olc->skin_yields = e; + + /* Mark the mob as changed */ + OLC_VAL(d) = TRUE; + + medit_disp_skin_menu(d); + OLC_MODE(d) = MEDIT_SKIN_MENU; + return; + } + + case MEDIT_SKIN_DELETE: { + int target = atoi(arg); + struct skin_yield_entry *e, *prev = NULL; + int n = 1; + + if (target < 1) { + medit_disp_skin_menu(d); + OLC_MODE(d) = MEDIT_SKIN_MENU; + return; + } + + for (e = d->olc->skin_yields; e; prev = e, e = e->next, n++) { + if (n == target) { + if (prev) + prev->next = e->next; + else + d->olc->skin_yields = e->next; + free(e); + + /* Mark the mob as changed */ + OLC_VAL(d) = TRUE; + break; + } + } + + medit_disp_skin_menu(d); + OLC_MODE(d) = MEDIT_SKIN_MENU; + return; + } + case MEDIT_STATS_MENU: i=0; switch(*arg) { diff --git a/src/oasis.c b/src/oasis.c index 9edc2ac..359c65d 100644 --- a/src/oasis.c +++ b/src/oasis.c @@ -213,6 +213,12 @@ void cleanup_olc(struct descriptor_data *d, byte cleanup_type) STATE(d) = CON_PLAYING; } + /* Free Skinning Yield working list (medit). */ + if (d->olc->skin_yields) { + free_skin_yields(d->olc->skin_yields); + d->olc->skin_yields = NULL; + } + free(d->olc); d->olc = NULL; } diff --git a/src/oasis.h b/src/oasis.h index 11e07aa..8dc8e23 100644 --- a/src/oasis.h +++ b/src/oasis.h @@ -13,6 +13,7 @@ #define _OASIS_H_ #include "utils.h" /* for ACMD macro */ +#include "structs.h" #define _OASISOLC 0x206 /* 2.0.6 */ @@ -110,6 +111,7 @@ struct oasis_olc_data { int item_type; struct trig_proto_list *script; /* for assigning triggers in [r|o|m]edit*/ struct help_index_element*help; /* Hedit uses this */ + struct skin_yield_entry *skin_yields; }; /* Exported globals. */ @@ -294,6 +296,12 @@ extern const char *nrm, *grn, *cyn, *yel; #define MEDIT_SAVE_CHA 37 #define MEDIT_SKILL_VALUE 38 +/* Skinning yield editor */ +#define MEDIT_SKIN_MENU 39 +#define MEDIT_SKIN_ADD_VNUM 40 +#define MEDIT_SKIN_ADD_DC 41 +#define MEDIT_SKIN_DELETE 42 + /* Submodes of SEDIT connectedness. */ #define SEDIT_MAIN_MENU 0 #define SEDIT_CONFIRM_SAVESTRING 1 diff --git a/src/structs.h b/src/structs.h index c1a98e3..b95c76e 100644 --- a/src/structs.h +++ b/src/structs.h @@ -445,8 +445,9 @@ #define ITEM_NOSELL 20 /**< Shopkeepers won't touch it */ #define ITEM_QUEST 21 /**< Item is a quest item */ #define ITEM_HOOD_UP 22 /**< WORN item hood is currently up */ +#define ITEM_SKINNED 23 /* Item/corpse can be skinned */ /** Total number of item flags */ -#define NUM_ITEM_FLAGS 23 +#define NUM_ITEM_FLAGS 24 /* Modifier constants used with obj affects ('A' fields) */ #define APPLY_NONE 0 /**< No effect */ @@ -753,6 +754,8 @@ struct obj_data struct char_data *sitting_here; /**< For furniture, who is sitting in it */ struct list_data *events; /**< Used for object events */ + + mob_vnum corpse_mob_vnum; }; /** Instance info for an object that gets saved to disk. @@ -1262,6 +1265,7 @@ struct index_data char *farg; /**< String argument for special function. */ struct trig_data *proto; /**< Points to the trigger prototype. */ + struct skin_yield_entry *skin_yields; }; /** Master linked list for the mob/object prototype trigger lists. */ @@ -1338,6 +1342,14 @@ struct mob_loadout *loadout_deep_copy(const struct mob_loadout *src); #define VAL_FOOD_HOURS_PER_BITE 2 #define VAL_FOOD_POISONED 3 +/* For skinning/survival skill usage */ +struct skin_yield_entry { + mob_vnum mob_vnum; /* redundant but useful for debugging */ + obj_vnum obj_vnum; /* object to create on success */ + int dc; /* DC required */ + struct skin_yield_entry *next; +}; + /* Config structs */ /** The game configuration structure used for configurating the game play diff --git a/src/utils.c b/src/utils.c index 2471e5c..ace7b07 100644 --- a/src/utils.c +++ b/src/utils.c @@ -1707,6 +1707,40 @@ int roll_d20(void) { return rand_number(1, 20); } int roll_d20_adv(void) { int a=roll_d20(), b=roll_d20(); return (a>b)?a:b; } int roll_d20_disadv(void) { int a=roll_d20(), b=roll_d20(); return (a 0) + d20 = roll_d20_adv(); + else if (mode < 0) + d20 = roll_d20_disadv(); + else + d20 = roll_d20(); + + if (out_d20) + *out_d20 = d20; + + /* Base: d20 + WIS mod */ + total = d20 + GET_ABILITY_MOD(GET_WIS(ch)); + + /* + * Proficiency: Fighters/Rangers/Druids are proficient in Survival. + * This uses your existing proficiency bonus helper. + */ + if (GET_CLASS(ch) == CLASS_FIGHTER || + GET_CLASS(ch) == CLASS_RANGER || + GET_CLASS(ch) == CLASS_DRUID) + total += get_total_proficiency_bonus(ch); + + return total; +} + /* Percent style (for legacy percent-based skill checks) */ bool percent_success(int chance_pct) { if (chance_pct <= 0) return FALSE; diff --git a/src/utils.h b/src/utils.h index 18462b0..20a3d05 100644 --- a/src/utils.h +++ b/src/utils.h @@ -82,6 +82,7 @@ const char *const *obj_value_labels(int item_type); const char *get_char_sdesc(const struct char_data *ch); int obj_is_storage(const struct obj_data *obj); int obj_storage_is_closed(const struct obj_data *obj); +int roll_survival_check(struct char_data *ch, int mode, int *out_d20); /* 5e system helpers */