From b23927580a70265fc7445a0674cfceebf6ed57e7 Mon Sep 17 00:00:00 2001 From: kinther Date: Fri, 3 Oct 2025 13:02:24 -0700 Subject: [PATCH] Food update --- lib/world/obj/0.obj | 11 +- lib/world/obj/1.obj | 54 +++++++++- lib/world/rsv/0.rsv | 19 ++++ lib/world/rsv/1.rsv | 14 ++- lib/world/shp/1.shp | 4 + lib/world/wld/0.wld | 2 +- lib/world/wld/1.wld | 23 ++++- src/act.item.c | 247 ++++++++++++++++++++++++++++++++++++++------ src/oedit.c | 3 +- src/structs.h | 12 ++- 10 files changed, 348 insertions(+), 41 deletions(-) diff --git a/lib/world/obj/0.obj b/lib/world/obj/0.obj index 857f13a..0c17fa8 100644 --- a/lib/world/obj/0.obj +++ b/lib/world/obj/0.obj @@ -1 +1,10 @@ -$ +#1 +board immortal~ +the immortal board~ +The immortal bulletin board is here.~ +Made specifically for immortals to post on. +~ +13 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 +0 0 0 0 0 +$~ diff --git a/lib/world/obj/1.obj b/lib/world/obj/1.obj index 0275468..a41527a 100644 --- a/lib/world/obj/1.obj +++ b/lib/world/obj/1.obj @@ -331,5 +331,57 @@ of liquid. ~ 17 0 0 0 0 a 0 0 0 0 0 0 0 4 4 0 0 -1 15 0 0 0 +9 8 0 0 0 +#139 +skewer erdlu meat~ +an erdlu meat skewer~ +A skewer with some browned meat is here.~ + A thin piece of wood has been carved into a long skewer, with one end tied +into a small hook. Pieces of dark erdlu meat have been pressed into the skewer +and cooked over an open fire. +~ +19 0 0 0 0 ap 0 0 0 0 0 0 0 +2 2 1 0 +1 12 0 0 0 +#140 +steak carru thick~ +a thick carru steak~ +A thick, cooked steak is here.~ + This steak is a very large piece of meat that has been cooked over an open +fire. Grill marks can be seen on both sides which form small X's. +~ +19 0 0 0 0 a 0 0 0 0 0 0 0 +8 8 1 0 +2 20 0 0 0 +#141 +bacon aprig plate~ +a plate of aprig bacon~ +A plate with several slices of cooked bacon is here.~ + This plate is small, but piled high with pieces of bacon. They are cooked to +the point that they are still chewy but not quite crunchy. +~ +19 0 0 0 0 a 0 0 0 0 0 0 0 +4 4 1 0 +1 18 0 0 0 +#142 +bread hunk~ +a hunk of bread~ +A piece of bread has been torn from a loaf and left here.~ + Torn from a larger piece of bread, this hunk is a still a decent size. The +crust is brown and the inside is fluffy. +~ +19 0 0 0 0 a 0 0 0 0 0 0 0 +2 2 1 0 +1 14 0 0 0 +#143 +cot small~ +a small cot~ +A small cot has been set up here.~ + This cot is made primarily from pieces of wood that are lashed together with +rope. Pieces of hide have been tied down, providing a soft yet supportive place +for someone to rest upon. +~ +6 0 0 0 0 0 0 0 0 0 0 0 0 +2 0 0 0 +10 100 0 0 0 $~ diff --git a/lib/world/rsv/0.rsv b/lib/world/rsv/0.rsv index e69de29..bdd50d9 100644 --- a/lib/world/rsv/0.rsv +++ b/lib/world/rsv/0.rsv @@ -0,0 +1,19 @@ +#R 1 1759515981 +O 1 0 0 0 0 +X 0 0 +X 1 0 +X 2 0 +X 3 0 +W 0 0 +W 1 0 +W 2 0 +W 3 0 +V 0 0 +V 1 0 +V 2 0 +V 3 0 +V 4 0 +V 5 0 +V 6 0 +V 7 0 +. diff --git a/lib/world/rsv/1.rsv b/lib/world/rsv/1.rsv index 800bddd..b315990 100644 --- a/lib/world/rsv/1.rsv +++ b/lib/world/rsv/1.rsv @@ -24,15 +24,19 @@ E 15 117 E 16 117 E 17 127 . -#R 134 1759515738 +#R 134 1759521685 +M 103 M 102 E 9 112 E 14 113 -G 138 -G 137 -G 136 +G 142 +G 141 +G 140 +G 139 G 135 -M 103 +G 136 +G 137 +G 138 O 132 0 200 0 0 X 0 0 X 1 0 diff --git a/lib/world/shp/1.shp b/lib/world/shp/1.shp index e770c2a..2c73428 100644 --- a/lib/world/shp/1.shp +++ b/lib/world/shp/1.shp @@ -4,6 +4,10 @@ CircleMUD v3.0 Shop File~ 136 137 138 +139 +140 +141 +142 -1 1.00 1.00 diff --git a/lib/world/wld/0.wld b/lib/world/wld/0.wld index c5727f7..f0fe065 100644 --- a/lib/world/wld/0.wld +++ b/lib/world/wld/0.wld @@ -1,6 +1,6 @@ #1 Limbo~ -You are in an unfinished room. +Floating in the void, you lose all sense of time and space. ~ 0 0 0 0 0 0 S diff --git a/lib/world/wld/1.wld b/lib/world/wld/1.wld index 50955e3..51a6c0c 100644 --- a/lib/world/wld/1.wld +++ b/lib/world/wld/1.wld @@ -534,11 +534,15 @@ from a curtained doorway. In a corner is an L-shaped counter made of rough-hewn stone which has a selection of drinks behind it. Near the eastern wall, a stairway heads upward toward an open dormitory. ~ -1 131080 0 0 0 0 +1 8 0 0 0 0 D0 ~ ~ 0 0 108 +D4 +~ +~ +0 0 200 S #135 Wall Road~ @@ -1474,4 +1478,21 @@ D3 ~ 0 0 196 S +#200 +A Cramped Dormitory~ + While this room is rather large, the amount of cots crammed into it makes it +feel quite cramped. Between the cots are small pathways that one can walk, +though backpacks and other items spill out from below their respective cots to +occasionally block the way. The walls have small hooks on them and some appear +to have cloaks attached. An opening in the wall lets light in from the north +and allows for fresh air to circulate. A pair of sconces are on either side of +the room to provide light during the evening hours. On the south side of the +room, a staircase descends to the room below. +~ +1 131080 0 0 0 0 +D5 +~ +~ +0 0 134 +S $~ diff --git a/src/act.item.c b/src/act.item.c index b7318d0..6036951 100644 --- a/src/act.item.c +++ b/src/act.item.c @@ -1062,9 +1062,9 @@ ACMD(do_drink) ACMD(do_eat) { char arg[MAX_INPUT_LENGTH]; - struct obj_data *food; + struct obj_data *obj = NULL; struct affected_type af; - int amount; + int amount = 0; one_argument(argument, arg); @@ -1075,60 +1075,247 @@ ACMD(do_eat) send_to_char(ch, "Eat what?\r\n"); return; } - if (!(food = get_obj_in_list_vis(ch, arg, NULL, ch->carrying))) { - send_to_char(ch, "You don't seem to have %s %s.\r\n", AN(arg), arg); - return; + + /* Find in inventory first, then in room */ + if (!(obj = get_obj_in_list_vis(ch, arg, NULL, ch->carrying))) { + if (!(obj = get_obj_in_list_vis(ch, arg, NULL, world[IN_ROOM(ch)].contents))) { + send_to_char(ch, "You can't find it!\r\n"); + return; + } } - if (subcmd == SCMD_TASTE && ((GET_OBJ_TYPE(food) == ITEM_DRINKCON) || - (GET_OBJ_TYPE(food) == ITEM_FOUNTAIN))) { - do_drink(ch, argument, 0, SCMD_SIP); - return; + + /* If the player used 'taste', we handle both food and drink here (no delegation). */ + if (subcmd == SCMD_TASTE) { + /* DRINKS: sip logic (inline; do not call do_drink) */ + if (GET_OBJ_TYPE(obj) == ITEM_DRINKCON || GET_OBJ_TYPE(obj) == ITEM_FOUNTAIN) { + int cap = GET_OBJ_VAL(obj, 0); + int rem = GET_OBJ_VAL(obj, 1); + int liq = GET_OBJ_VAL(obj, 2); /* liquid type */ + + if (GET_COND(ch, DRUNK) > 10 && GET_COND(ch, THIRST) > 0) { + send_to_char(ch, "You can't seem to get close enough to your mouth.\r\n"); + act("$n tries to drink but misses $s mouth!", TRUE, ch, 0, 0, TO_ROOM); + return; + } + + if (rem < 1) { + send_to_char(ch, "It is empty.\r\n"); + + /* Update sdesc to "an empty " like our drink update */ + if (GET_OBJ_TYPE(obj) == ITEM_DRINKCON) { + obj_rnum rnum = GET_OBJ_RNUM(obj); + const char *proto_sd = (rnum != NOTHING) ? obj_proto[rnum].short_description : NULL; + const char *base = obj->short_description ? obj->short_description : proto_sd; + const char *noun = base ? base : "container"; + + /* strip leading article */ + if (!strn_cmp(noun, "a ", 2)) noun += 2; + else if (!strn_cmp(noun, "an ", 3)) noun += 3; + else if (!strn_cmp(noun, "the ", 4)) noun += 4; + + char sbuf[MAX_STRING_LENGTH]; + const char *ofp = strstr(noun, " of "); + size_t noun_len = ofp ? (size_t)(ofp - noun) : strlen(noun); + snprintf(sbuf, sizeof(sbuf), "an empty %.*s", (int)MIN(noun_len, sizeof(sbuf) - 10), noun); + + if (obj->short_description && obj->short_description != proto_sd) + free(obj->short_description); + obj->short_description = strdup(sbuf); + } + return; + } + + /* Take a single sip */ + amount = 1; + rem = MAX(0, rem - amount); + GET_OBJ_VAL(obj, 1) = rem; + + /* Condition effects: use HUNGER (not FULL) in this codebase */ + gain_condition(ch, DRUNK, (drink_aff[liq][DRUNK] * amount) / 4); + gain_condition(ch, HUNGER,(drink_aff[liq][HUNGER] * amount) / 4); /* <-- FIX */ + gain_condition(ch, THIRST,(drink_aff[liq][THIRST] * amount) / 4); + + if (GET_COND(ch, DRUNK) > 10) + send_to_char(ch, "You feel drunk.\r\n"); + + if (GET_COND(ch, THIRST) > 20) + send_to_char(ch, "You don't feel thirsty.\r\n"); + + if (GET_OBJ_VAL(obj, 3) && GET_LEVEL(ch) < LVL_IMMORT) { + send_to_char(ch, "It tastes strange!\r\n"); + act("$n tastes something strange and coughs.", FALSE, ch, 0, 0, TO_ROOM); + + memset(&af, 0, sizeof(af)); + af.spell = SPELL_POISON; /* <-- FIX: use spell, not type */ + af.duration = amount * 3; + af.location = APPLY_NONE; + af.modifier = 0; + /* no af.bonus in this codebase */ + af.bitvector[0] = af.bitvector[1] = af.bitvector[2] = af.bitvector[3] = 0; + SET_BIT_AR(af.bitvector, AFF_POISON); + affect_join(ch, &af, FALSE, FALSE, FALSE, FALSE); + } + + act("You sip from $p.", FALSE, ch, obj, 0, TO_CHAR); + act("$n sips from $p.", TRUE, ch, obj, 0, TO_ROOM); + + /* Update dynamic sdesc band like drink code ("partially filled", etc.) */ + if (GET_OBJ_TYPE(obj) == ITEM_DRINKCON) { + obj_rnum rnum = GET_OBJ_RNUM(obj); + const char *proto_sd = (rnum != NOTHING) ? obj_proto[rnum].short_description : NULL; + const char *base = obj->short_description ? obj->short_description : proto_sd; + const char *noun = base ? base : "container"; + + /* Strip leading article */ + if (!strn_cmp(noun, "a ", 2)) noun += 2; + else if (!strn_cmp(noun, "an ", 3)) noun += 3; + else if (!strn_cmp(noun, "the ", 4)) noun += 4; + + char sbuf[MAX_STRING_LENGTH]; + + if (rem <= 0) { + const char *ofp = strstr(noun, " of "); + size_t noun_len = ofp ? (size_t)(ofp - noun) : strlen(noun); + snprintf(sbuf, sizeof(sbuf), "an empty %.*s", (int)MIN(noun_len, sizeof(sbuf) - 10), noun); + } else { + const char *status = "partially filled"; + if (cap > 0) { + int pct = (rem * 100) / cap; + if (pct >= 75) + status = "partially filled"; + else if (pct >= 50) + status = "half-filled"; + else + status = "nearly empty"; + } + bool use_an = FALSE; + if (status && *status) { + char first = LOWER((unsigned char)status[0]); + use_an = (first == 'a' || first == 'e' || first == 'i' || first == 'o' || first == 'u'); + } + const char *article = use_an ? "an" : "a"; + + size_t prefix_len = strlen(article) + 1 + strlen(status) + 1; + size_t max_noun = (sizeof(sbuf) > (prefix_len + 1)) + ? (sizeof(sbuf) - prefix_len - 1) : 0; + snprintf(sbuf, sizeof(sbuf), "%s %s %.*s", article, status, (int)max_noun, noun); + } + + if (obj->short_description && obj->short_description != proto_sd) + free(obj->short_description); + obj->short_description = strdup(sbuf); + } + + return; + } /* end taste of drink container */ + /* otherwise fall through to taste FOOD below */ } - if ((GET_OBJ_TYPE(food) != ITEM_FOOD) && (GET_LEVEL(ch) < LVL_IMMORT)) { + + /* From here: regular EAT/TASTE handling for FOOD (multi-bite) */ + if (GET_OBJ_TYPE(obj) != ITEM_FOOD && GET_LEVEL(ch) < LVL_IMMORT) { send_to_char(ch, "You can't eat THAT!\r\n"); return; } - if (GET_COND(ch, HUNGER) > 20) { /* Stomach full */ + + /* Prevent overstuffing */ + if (GET_COND(ch, HUNGER) > 20) { send_to_char(ch, "You are too full to eat more!\r\n"); return; } - if (!consume_otrigger(food, ch, OCMD_EAT)) /* check trigger */ + if (!consume_otrigger(obj, ch, OCMD_EAT)) /* check trigger */ return; - if (subcmd == SCMD_EAT) { - act("You eat $p.", FALSE, ch, food, 0, TO_CHAR); - act("$n eats $p.", TRUE, ch, food, 0, TO_ROOM); - } else { - act("You nibble a little bit of $p.", FALSE, ch, food, 0, TO_CHAR); - act("$n tastes a little bit of $p.", TRUE, ch, food, 0, TO_ROOM); + /* Determine bites & nutrition */ + int cap = GET_OBJ_VAL(obj, VAL_FOOD_BITE_CAP); + int left = GET_OBJ_VAL(obj, VAL_FOOD_BITES_LEFT); + int per = GET_OBJ_VAL(obj, VAL_FOOD_HOURS_PER_BITE); + bool poisoned = (GET_OBJ_VAL(obj, VAL_FOOD_POISONED) != 0); + + if (left < 1) { + send_to_char(ch, "There's nothing left of it.\r\n"); + return; } - amount = (subcmd == SCMD_EAT ? GET_OBJ_VAL(food, 0) : 1); + /* Messaging differs for taste vs eat, nutrition identical per bite */ + if (subcmd == SCMD_EAT) { + act("You eat a bite of $p.", FALSE, ch, obj, 0, TO_CHAR); + act("$n eats a bite of $p.", TRUE, ch, obj, 0, TO_ROOM); + } else { + act("You nibble a little bit of $p.", FALSE, ch, obj, 0, TO_CHAR); + act("$n tastes a little bit of $p.", TRUE, ch, obj, 0, TO_ROOM); + } + /* One bite per action (simple & consistent). */ + left = MAX(0, left - 1); + GET_OBJ_VAL(obj, VAL_FOOD_BITES_LEFT) = left; + + /* Apply hunger gain from one bite */ + amount = MAX(0, per); gain_condition(ch, HUNGER, amount); if (GET_COND(ch, HUNGER) > 20) send_to_char(ch, "You are full.\r\n"); - if (GET_OBJ_VAL(food, 3) && (GET_LEVEL(ch) < LVL_IMMORT)) { - /* The crap was poisoned ! */ + if (poisoned && GET_LEVEL(ch) < LVL_IMMORT) { send_to_char(ch, "Oops, that tasted rather strange!\r\n"); act("$n coughs and utters some strange sounds.", FALSE, ch, 0, 0, TO_ROOM); - new_affect(&af); - af.spell = SPELL_POISON; - af.duration = amount * 2; + memset(&af, 0, sizeof(af)); + af.spell = SPELL_POISON; /* <-- FIX: use spell, not type */ + af.duration = MAX(1, amount * 2); + af.location = APPLY_NONE; + af.modifier = 0; + /* no af.bonus in this codebase */ + af.bitvector[0] = af.bitvector[1] = af.bitvector[2] = af.bitvector[3] = 0; SET_BIT_AR(af.bitvector, AFF_POISON); affect_join(ch, &af, FALSE, FALSE, FALSE, FALSE); } - if (subcmd == SCMD_EAT) - extract_obj(food); - else { - if (!(--GET_OBJ_VAL(food, 0))) { - send_to_char(ch, "There's nothing left now.\r\n"); - extract_obj(food); + + /* If no bites remain, consume the object. + * Otherwise, update the instance sdesc with an "eaten" status band. */ + if (left <= 0) { + send_to_char(ch, "There's nothing left now.\r\n"); + extract_obj(obj); + } else { + /* Build " " using the instance/proto sdesc as noun */ + obj_rnum rnum = GET_OBJ_RNUM(obj); + const char *proto_sd = (rnum != NOTHING) ? obj_proto[rnum].short_description : NULL; + const char *base = obj->short_description ? obj->short_description : proto_sd; + const char *noun = base ? base : "food"; + + /* Strip leading article from noun */ + if (!strn_cmp(noun, "a ", 2)) noun += 2; + else if (!strn_cmp(noun, "an ", 3)) noun += 3; + else if (!strn_cmp(noun, "the ", 4)) noun += 4; + + const char *status = "partially eaten"; + if (cap > 0) { + int pct = (left * 100) / cap; + if (pct >= 75) + status = "partially eaten"; + else if (pct >= 50) + status = "half-eaten"; + else + status = "nearly eaten"; } + + bool use_an = FALSE; + if (status && *status) { + char first = LOWER((unsigned char)status[0]); + use_an = (first == 'a' || first == 'e' || first == 'i' || first == 'o' || first == 'u'); + } + const char *article = use_an ? "an" : "a"; + + char sbuf[MAX_STRING_LENGTH]; + size_t prefix_len = strlen(article) + 1 + strlen(status) + 1; + size_t max_noun = (sizeof(sbuf) > (prefix_len + 1)) + ? (sizeof(sbuf) - prefix_len - 1) : 0; + snprintf(sbuf, sizeof(sbuf), "%s %s %.*s", article, status, (int)max_noun, noun); + + if (obj->short_description && obj->short_description != proto_sd) + free(obj->short_description); + obj->short_description = strdup(sbuf); } } diff --git a/src/oedit.c b/src/oedit.c index 6f88f3d..fa42ec4 100644 --- a/src/oedit.c +++ b/src/oedit.c @@ -90,7 +90,7 @@ static const char *drink_val_labels[NUM_OBJ_VAL_POSITIONS] = { /* Food */ static const char *food_val_labels[NUM_OBJ_VAL_POSITIONS] = { - "hours_full", "unused1", "unused2", "poisoned", + "bites_capacity", "bites_left", "hours_full_per_bite", "poisoned", "Value[4]", "Value[5]", "Value[6]", "Value[7]" }; @@ -519,6 +519,7 @@ static void oedit_disp_values_menu(struct descriptor_data *d) case ITEM_FOUNTAIN: labels = drink_val_labels; break; case ITEM_CONTAINER: labels = container_val_labels; break; case ITEM_FURNITURE: labels = furniture_val_labels; break; + case ITEM_FOOD: labels = food_val_labels; break; default: labels = generic_val_labels; break; } diff --git a/src/structs.h b/src/structs.h index ddc0377..dc4da59 100644 --- a/src/structs.h +++ b/src/structs.h @@ -1331,13 +1331,23 @@ void loadout_free_list(struct mob_loadout **head); void loadout_add_entry(struct mob_loadout **head, obj_vnum vnum, sh_int wear_pos, int qty); struct mob_loadout *loadout_deep_copy(const struct mob_loadout *src); - /* Furniture defines for object values */ /* Furniture object values (obj_flags.value[x]) */ #define VAL_FURN_CAPACITY 0 /* maximum number of people who can use furniture */ #define VAL_FURN_MAX_OCC 1 /* current number of people using furniture (runtime only, managed by game engine) */ #define VAL_FURN_POSITIONS 2 /* allowed positions bitvector: bit 0=STAND(1), bit 1=SIT(2), bit 2=REST(4), bit 3=SLEEP(8) */ +/* Food value indices (mirrors drinkcon layout for multi-bite foods) + * 0: bites_capacity - total bite capacity + * 1: bites_left - remaining bites + * 2: hours_full_per_bite- hunger gain per bite (like hours_full) + * 3: poisoned - 1 if poisoned + */ +#define VAL_FOOD_BITE_CAP 0 +#define VAL_FOOD_BITES_LEFT 1 +#define VAL_FOOD_HOURS_PER_BITE 2 +#define VAL_FOOD_POISONED 3 + /* Config structs */ /** The game configuration structure used for configurating the game play