Skinning update

This commit is contained in:
kinther 2025-12-17 15:12:23 -08:00
parent 4ea7abca56
commit 1278c31e89
15 changed files with 464 additions and 22 deletions

View file

@ -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
$

View file

@ -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

View file

@ -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);
}

View file

@ -486,6 +486,8 @@ const char *extra_bits[] = {
"ANTI_DRUID",
"NO_SELL",
"QUEST_ITEM",
"HOOD_UP",
"SKINNED",
"\n"
};

View file

@ -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 <obj_vnum> <dc>).", 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 <obj_vnum> <dc>).", 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;
}

View file

@ -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);

View file

@ -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");

View file

@ -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);

View file

@ -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 },

View file

@ -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, " <none>\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) {

View file

@ -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;
}

View file

@ -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

View file

@ -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

View file

@ -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<b)?a:b; }
int roll_survival_check(struct char_data *ch, int mode, int *out_d20)
{
int d20, total;
if (!ch) {
if (out_d20) *out_d20 = 0;
return 0;
}
if (mode > 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;

View file

@ -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 */