diff --git a/lib/text/help/help.hlp b/lib/text/help/help.hlp index 923b259..d9f2fad 100644 --- a/lib/text/help/help.hlp +++ b/lib/text/help/help.hlp @@ -296,6 +296,25 @@ to list all forms. i. e. the keyword rumble could be used to cover anyone who types rumble rumbl rumb rum ru. #31 +AC ARMOR-CLASS ASCENDING-AC DEFENSE + + Summary: We use an ascending AC system (higher is better). A typical +unarmored character has AC 10. Attacks roll 1d20 + attack modifiers and +hit if the total your AC. Natural 1 always misses; natural 20 always hits. + +How AC is built: + +Base: 10 + +Armor pieces: each worn slot contributes 03 AC (clamped by slot caps) +Armor magic: total armor magic across all pieces is capped at +3 +Dexterity: add min(DEX modifier, Dex cap); Dex cap depends on total armor bulk +Shield: base +2 (tower +3 if present), plus shield magic (capped at +3), plus +Shield Use skill proficiency +Situational: cover (+2/+5), spells (Shield, Haste, etc.) + +See Also: SCORE +#0 ACAUDIT ARMOR-AUDIT AUDIT-ARMOR IMMORTAL Summary: Imm-only tool that scans all ITEM\_ARMOR prototypes and reports @@ -338,117 +357,6 @@ Heavy : AC \~16–20 See Also: ARMOR PIECES, BULK, SHIELDS, MAGIC CAPS, SCORE, OEDIT ARMOR #32 -AC ARMOR-CLASS ASCENDING-AC DEFENSE - - Summary: We use an ascending AC system (higher is better). A typical -unarmored character has AC 10. Attacks roll 1d20 + attack modifiers and -hit if the total your AC. Natural 1 always misses; natural 20 always hits. - -How AC is built: - -Base: 10 - -Armor pieces: each worn slot contributes 03 AC (clamped by slot caps) -Armor magic: total armor magic across all pieces is capped at +3 -Dexterity: add min(DEX modifier, Dex cap); Dex cap depends on total armor bulk -Shield: base +2 (tower +3 if present), plus shield magic (capped at +3), plus -Shield Use skill proficiency -Situational: cover (+2/+5), spells (Shield, Haste, etc.) - -See Also: SCORE -#0 -ARMOR SLOTS - - Summary: Armor is split across six slots: head, body, legs, arms, hands, -feet. Each piece has: - -Piece AC (value[0]): 0–3 (per-slot hard cap) -Bulk (value[1]): 0–3 (drives Dex cap & stealth) -Magic (value[2]): 0–3 (per-slot cap; global armor magic cap +3) -Flags (value[3]): special rules (e.g., Stealth Disadvantage) -Slot caps (defaults) -Head: AC ≤2, Magic ≤1 -Body: AC ≤3, Magic ≤3 -Legs: AC ≤2, Magic ≤1 -Arms/Hands/Feet: AC ≤1, Magic ≤1 -Shield is handled separately - -SEE ALSO: SHIELDS -#0 -BULK DEX-CAP LIGHT MEDIUM HEAVY - - Summary: Armor bulk limits how much of your Dex modifier you can apply -to AC. - -Light (bulk ≤ 5): Dex cap +5 -Medium (bulk 6–10): Dex cap +2 -Heavy (bulk ≥ 11): Dex cap +0 and imposes Stealth Disadvantage - -Bulk is computed by summing each piece’s bulk × slot weight. -Slot weights: head 1, body 3, legs 2, arms 1, hands 1, feet 1. - -SEE ALSO: ARMOR -#0 -SHIELD SHIELDS SHIELD-USE - - Summary: A shield adds to AC: - -Base +2 (tower +3 if applicable) -Shield magic (capped at +3) -Shield Use proficiency (based on your skill%) - -Shield Use proficiency (from skill%) -<=14:+0 -<=29:+1 -<=44:+2 -<=59:+3 -<=74:+4 -<=90:+5 -<=100:+6 - -SEE ALSO: AC ARMOR SLOTS -#0 -MAGIC-CAP ENCHANTED-ITEMS - - Summary: Sum of magic across all worn pieces is capped at +3 (after -slot caps) - -Shield: magic is capped at +3 (separate from armor) -Weapons: magic is capped at +3 -Total attack bonus (stats + proficiency + magic + situational) is gently -capped around +10 for balance - -These caps are enforced automatically in calculations. - -SEE ALSO: AC ARMOR SLOTS SHIELDS -#0 -PROFICIENCY WEAPON SKILL SAVING THROWS - - Summary: We map your skill % to a 5e-like proficiency bonus: - -<=14:+0 -<=29:+1 -<=44:+2 -<=59:+3 -<=74:+4 -<=90:+5 -<=100:+6 - -This is used for weapon attacks, shields, and (when applicable) saving throws. - -SEE ALSO: SHIELDS -#0 -STEALTH SNEAK HIDE DISADVTANGE - - Summary: You have Stealth Disadvantage if certain conditions are met, -such as: - -Any worn piece has the Stealth Disadvantage armor flag, or -Your total armor bulk puts you in Heavy (Dex cap 0) - -With Stealth Disadvantage, Sneak and Hide roll twice and take the worse result. -Both success and failure can grant training progress. -#0 ACRONYMS TERMINOLOGY VOCABULARY Here are some common terms used in building, and TBA zone: @@ -777,6 +685,24 @@ Any areas that 'overlap' the specified range are shown in red. See Also: ZONES #0 +ARMOR SLOTS + + Summary: Armor is split across six slots: head, body, legs, arms, hands, +feet. Each piece has: + +Piece AC (value[0]): 0–3 (per-slot hard cap) +Bulk (value[1]): 0–3 (drives Dex cap & stealth) +Magic (value[2]): 0–3 (per-slot cap; global armor magic cap +3) +Flags (value[3]): special rules (e.g., Stealth Disadvantage) +Slot caps (defaults) +Head: AC ≤2, Magic ≤1 +Body: AC ≤3, Magic ≤3 +Legs: AC ≤2, Magic ≤1 +Arms/Hands/Feet: AC ≤1, Magic ≤1 +Shield is handled separately + +SEE ALSO: SHIELDS +#0 ARMOR-SPELL Usage : cast 'armor' [target] @@ -1303,6 +1229,20 @@ NOTE: Buildwalk does not autosave on exit. You must type SAVEALL. This way if See also: TOGGLE, SAVEALL, REDIT, OLC, RLIST, DIG #31 +BULK DEX-CAP LIGHT MEDIUM HEAVY + + Summary: Armor bulk limits how much of your Dex modifier you can apply +to AC. + +Light (bulk ≤ 5): Dex cap +5 +Medium (bulk 6–10): Dex cap +2 +Heavy (bulk ≥ 11): Dex cap +0 and imposes Stealth Disadvantage + +Bulk is computed by summing each piece’s bulk × slot weight. +Slot weights: head 1, body 3, legs 2, arms 1, hands 1, feet 1. + +SEE ALSO: ARMOR +#0 BULLETINS BOARDS BULLETIN-BOARDS MESSAGE-BOARDS POSTING Bulletin boards are the forum of inter-player communication on the MUD. @@ -4801,6 +4741,20 @@ LOVE MARRIAGE WEDDINGS SEX Seek professional counseling if you need help on these topics. +#0 +MAGIC-CAP ENCHANTED-ITEMS + + Summary: Sum of magic across all worn pieces is capped at +3 (after +slot caps) + +Shield: magic is capped at +3 (separate from armor) +Weapons: magic is capped at +3 +Total attack bonus (stats + proficiency + magic + situational) is gently +capped around +10 for balance + +These caps are enforced automatically in calculations. + +SEE ALSO: AC ARMOR SLOTS SHIELDS #0 MAGIC-MISSILE @@ -4982,20 +4936,6 @@ be carrying 100 gold. Well, maybe it is a carrier pigeon, ;-) be reasonable. There are other ways to reward players than with gold or experience. Try objects and quests. -See also: AUTOROLL -#31 -MEDIT-NUMDAMDICE MEDIT-SIZEDAMDICE MEDIT-BHD - -Bare Hand Damage (xdy+z): -(6) BHD NumDice: [ 5] -(7) BHD SizeDice: [ 5] - For BHD (bare hand damage), xdy specifies the dice rolls and z is the -strength bonus added both to BHD and weapon-inflicted damage. For example, -a monster with a BHD of 1d4+10 will do between 11 and 14 hit points each -round without a weapon. If the monster picks up and wields a tiny stick -which gives 1d2 damage, then the monster will do 1d2 + 10 points of damage -per round with the stick. - See also: AUTOROLL #31 MEDIT-KEYWORDS MEDIT-ALIAS MEDIT-SEX MEDITNAME MOB-NAME MEDIT-ALIAS MOB-SEX @@ -5157,6 +5097,20 @@ Instead of gold reward the player with a pelt, teeth, claws, etc. Always have a mob carry *something*. It might be low-grade piece of food, trash, or a bad rash. Be creative. +#31 +MEDIT-NUMDAMDICE MEDIT-SIZEDAMDICE MEDIT-BHD + +Bare Hand Damage (xdy+z): +(6) BHD NumDice: [ 5] +(7) BHD SizeDice: [ 5] + For BHD (bare hand damage), xdy specifies the dice rolls and z is the +strength bonus added both to BHD and weapon-inflicted damage. For example, +a monster with a BHD of 1d4+10 will do between 11 and 14 hit points each +round without a weapon. If the monster picks up and wields a tiny stick +which gives 1d2 damage, then the monster will do 1d2 + 10 points of damage +per round with the stick. + +See also: AUTOROLL #31 MEDIT-POSITIONS MEDIT-DEFAULT MOB-POSITIONS MOBILE-POSITIONS @@ -7112,6 +7066,22 @@ AUTOASSIST - Player has Autoassist enabled. See also: FLAGS, PLR #31 +PROFICIENCY WEAPON SKILL SAVING THROWS + + Summary: We map your skill % to a 5e-like proficiency bonus: + +<=14:+0 +<=29:+1 +<=44:+2 +<=59:+3 +<=74:+4 +<=90:+5 +<=100:+6 + +This is used for weapon attacks, shields, and (when applicable) saving throws. + +SEE ALSO: SHIELDS +#0 PROMOTE PROMOTIONS ADVANCEMENTS RAISES LVLS LEVELS GAINS 31 32 33 34 RANKS RANKING HIRING JOBS STAFFING Here at The Builder Academy, the level of an immortal generally reflects that @@ -8642,6 +8612,25 @@ bit further along. I am oftentimes in another window or near my computer so page me if you really need something, if I don't respond I'm probably sleeping or in class or just don't want to be bothered :-) +#0 +SHIELD SHIELDS SHIELD-USE + + Summary: A shield adds to AC: + +Base +2 (tower +3 if applicable) +Shield magic (capped at +3) +Shield Use proficiency (based on your skill%) + +Shield Use proficiency (from skill%) +<=14:+0 +<=29:+1 +<=44:+2 +<=59:+3 +<=74:+4 +<=90:+5 +<=100:+6 + +SEE ALSO: AC ARMOR SLOTS #0 SHOCKING-GRASP @@ -9258,6 +9247,17 @@ Examples: See also: FLAGS #0 +STEALTH SNEAK HIDE DISADVTANGE + + Summary: You have Stealth Disadvantage if certain conditions are met, +such as: + +Any worn piece has the Stealth Disadvantage armor flag, or +Your total armor bulk puts you in Heavy (Dex cap 0) + +With Stealth Disadvantage, Sneak and Hide roll twice and take the worse result. +Both success and failure can grant training progress. +#0 STOCK This is a listing of stock areas. @@ -13524,4 +13524,60 @@ zlock - Locks one or all zones. Type zlock for usage. zunlock - Locks one or all zones. Type zunlock for usage. #31 +EMOTE EMOTING + +Emoting allows you to describe your characters actions, expressions, and +nuances in freeform text, bringing scenes to life in ways that predefined +commands cannot. These tools let you craft rich, reactive roleplay that +feels immediate and personal to everyone who sees it. + +The emoting engine in this game supports dynamic references which can be used +in your emotes to help you make the world feel vibrant: + ++----------+----------------------------+----------------+-----------------+ +| Operator | Actor Types | Target Sees | Others See | ++----------+----------------------------+----------------+-----------------+ +| ~ | target's name | you | target's name | +| ! | him/her/them | you | him/her/them | +| % | target's possessive | your | name's | +| ^ | his/her/their | your | his/her/their | +| # | he/she/they | you | he/she/they | +| & | himself/herself/themself | yourself | himself/etc. | +| = | target's possessive (abs) | yours | name's | +| + | his/hers/theirs (abs) | yours | his/hers/theirs | +| @ | actor's name or possessive | actor name/pos | actor name/pos | ++----------+----------------------------+----------------+-----------------+ + +Examples of using these operators in an emote: + +>emote scans the room, looking for ~amos + +The room sees: + +The bald, pudgy man scans the room, looking for the tall, muscular man. + +Amos sees: + +The bald, pudgy man scans the room, looking for you. + +>pemote after scanning the room, @ eyes drop to ~mug in his hand as he sighs. + +The room sees: + +After scanning the room, the bald, pudgy man's eyes drop to a clay mug in his +hand as he sighs. + +>emote gesturing to %amos empty mug, @ waves !amos over with %me right hand, +pointing to an empty seat at ^me table. + +The room sees: + +Gesturing to the tall, muscular man's empty mug, the bald, budgy man waves +him over with his right hand, pointing to an empty seat at his table. + +Amos sees: + +Gesturing to your empty mug, the bald, pudgy man waves you over with his right +hand, pointing to an empty seat at his table. +#0 $~ diff --git a/lib/world/mob/1.mob b/lib/world/mob/1.mob index 3bd895f..a0a08a4 100644 --- a/lib/world/mob/1.mob +++ b/lib/world/mob/1.mob @@ -19,21 +19,21 @@ Skill 142 60 Skill 143 60 Skill 145 60 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~ -a slim, lanky human soldier~ +the slim, lanky human soldier~ A slim, lanky human soldier stands here eyeing passerbys. ~ This woman looks rather thin, with her darkly tanned skin hugging her frame @@ -50,17 +50,17 @@ Skill 142 60 Skill 143 60 Skill 145 60 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~ @@ -77,8 +77,8 @@ others. 1 3d12+60 8 8 1 E -L 14 113 1 L 9 112 1 +L 14 113 1 #103 Lanky~ woman lanky scarred~ diff --git a/src/act.informative.c b/src/act.informative.c index 49b48d3..ea3f270 100644 --- a/src/act.informative.c +++ b/src/act.informative.c @@ -388,6 +388,7 @@ static void list_one_char(struct char_data *i, struct char_data *ch) CCNRM(ch, C_NRM)); } + /* NPCs with a full long description at default position: print that and bail. */ if (IS_NPC(i) && i->player.long_descr && GET_POS(i) == GET_DEFAULT_POS(i)) { if (AFF_FLAGGED(i, AFF_INVISIBLE)) send_to_char(ch, "*"); @@ -398,6 +399,7 @@ static void list_one_char(struct char_data *i, struct char_data *ch) else if (IS_GOOD(i)) send_to_char(ch, "(Blue Aura) "); } + send_to_char(ch, "%s", i->player.long_descr); if (AFF_FLAGGED(i, AFF_SANCTUARY)) @@ -408,10 +410,16 @@ static void list_one_char(struct char_data *i, struct char_data *ch) return; } - if (IS_NPC(i)) - send_to_char(ch, "%c%s", UPPER(*i->player.short_descr), i->player.short_descr + 1); - else - send_to_char(ch, "%s", i->player.name); + /* Otherwise, use short description (PC or NPC) if present, else name. */ + { + const char *sdesc = GET_SHORT_DESC(i); + if (sdesc && *sdesc) { + /* Capitalize first letter for room list */ + send_to_char(ch, "%c%s", UPPER(*sdesc), sdesc + 1); + } else { + send_to_char(ch, "%s", GET_NAME(i)); + } + } if (AFF_FLAGGED(i, AFF_INVISIBLE)) send_to_char(ch, " (invisible)"); @@ -456,6 +464,7 @@ static void list_one_char(struct char_data *i, struct char_data *ch) else if (IS_GOOD(i)) send_to_char(ch, " (Blue Aura)"); } + send_to_char(ch, "\r\n"); if (AFF_FLAGGED(i, AFF_SANCTUARY)) @@ -654,7 +663,20 @@ static void look_in_obj(struct char_data *ch, char *arg) if (OBJVAL_FLAGGED(obj, CONT_CLOSED) && (GET_LEVEL(ch) < LVL_IMMORT || !PRF_FLAGGED(ch, PRF_NOHASSLE))) send_to_char(ch, "It is closed.\r\n"); else { - send_to_char(ch, "%s", fname(obj->name)); + /* Choose a label for the container: + * - For corpses (GET_OBJ_VAL(obj,3) == 1), use the short_description, + * e.g. "the corpse of the tall, muscular man" + * - Otherwise, fall back to the first keyword (fname(obj->name)) + */ + const char *label; + + if (GET_OBJ_VAL(obj, 3) == 1 && obj->short_description && *obj->short_description) + label = obj->short_description; + else + label = fname(obj->name); + + send_to_char(ch, "%s", label); + switch (bits) { case FIND_OBJ_INV: send_to_char(ch, " (carried): \r\n"); diff --git a/src/act.wizard.c b/src/act.wizard.c index 79f472e..22a7d20 100644 --- a/src/act.wizard.c +++ b/src/act.wizard.c @@ -145,9 +145,20 @@ static void collapse_spaces(char *s) { *dst = '\0'; } -static void build_actor_name(struct char_data *actor, bool possessive, char *out, size_t outsz) { - if (!possessive) strlcpy(out, GET_NAME(actor), outsz); - else make_possessive(GET_NAME(actor), out, outsz); +static void build_actor_name(struct char_data *actor, + bool possessive, + char *out, size_t outsz) +{ + /* Prefer short description if present; fall back to personal name */ + const char *base = + (GET_SHORT_DESC(actor) && *GET_SHORT_DESC(actor)) + ? GET_SHORT_DESC(actor) + : GET_NAME(actor); + + if (!possessive) + strlcpy(out, base, outsz); + else + make_possessive(base, out, outsz); } /* Replace all occurrences of 'needle' in 'hay' with 'repl'. */ @@ -273,23 +284,69 @@ static void build_replacement(const struct emote_tok *tok, out[0] = '\0'; if (tok->op == '@') { - if (!actor_possessive_for_at) strlcpy(out, GET_NAME(actor), outsz); - else make_possessive(GET_NAME(actor), out, outsz); + /* Use actor's sdesc if present, otherwise their name */ + const char *ref = (GET_SHORT_DESC(actor) && *GET_SHORT_DESC(actor)) + ? GET_SHORT_DESC(actor) + : GET_NAME(actor); + + if (!actor_possessive_for_at) + strlcpy(out, ref, outsz); + else + make_possessive(ref, out, outsz); return; } if (tok->tch) { bool you = (viewer == tok->tch); + /* For non-you views, prefer sdesc over name */ + const char *ref = (GET_SHORT_DESC(tok->tch) && *GET_SHORT_DESC(tok->tch)) + ? GET_SHORT_DESC(tok->tch) + : GET_NAME(tok->tch); + switch (tok->op) { - case '~': if (you) strlcpy(out, "you", outsz); else strlcpy(out, GET_NAME(tok->tch), outsz); break; - case '!': if (you) strlcpy(out, "you", outsz); else strlcpy(out, pron_obj(tok->tch), outsz); break; - case '%': if (you) strlcpy(out, "your", outsz); else make_possessive(GET_NAME(tok->tch), out, outsz); break; - case '^': if (you) strlcpy(out, "your", outsz); else strlcpy(out, pron_pos_adj(tok->tch), outsz); break; - case '#': if (you) strlcpy(out, "you", outsz); else strlcpy(out, pron_subj(tok->tch), outsz); break; - case '&': if (you) strlcpy(out, "yourself", outsz); else strlcpy(out, pron_refl(tok->tch), outsz); break; - case '=': if (you) strlcpy(out, "yours", outsz); else make_possessive(GET_NAME(tok->tch), out, outsz); break; - case '+': if (you) strlcpy(out, "yours", outsz); else strlcpy(out, pron_pos_pron(tok->tch), outsz); break; - default: strlcpy(out, GET_NAME(tok->tch), outsz); break; + case '~': + if (you) strlcpy(out, "you", outsz); + else strlcpy(out, ref, outsz); + break; + + case '!': + if (you) strlcpy(out, "you", outsz); + else strlcpy(out, pron_obj(tok->tch), outsz); + break; + + case '%': + if (you) strlcpy(out, "your", outsz); + else make_possessive(ref, out, outsz); + break; + + case '^': + if (you) strlcpy(out, "your", outsz); + else strlcpy(out, pron_pos_adj(tok->tch), outsz); + break; + + case '#': + if (you) strlcpy(out, "you", outsz); + else strlcpy(out, pron_subj(tok->tch), outsz); + break; + + case '&': + if (you) strlcpy(out, "yourself", outsz); + else strlcpy(out, pron_refl(tok->tch), outsz); + break; + + case '=': + if (you) strlcpy(out, "yours", outsz); + else make_possessive(ref, out, outsz); + break; + + case '+': + if (you) strlcpy(out, "yours", outsz); + else strlcpy(out, pron_pos_pron(tok->tch), outsz); + break; + + default: + strlcpy(out, ref, outsz); + break; } return; } diff --git a/src/fight.c b/src/fight.c index 579b79a..a97bef9 100644 --- a/src/fight.c +++ b/src/fight.c @@ -242,14 +242,22 @@ static void make_corpse(struct char_data *ch) IN_ROOM(corpse) = NOWHERE; corpse->name = strdup("corpse"); - snprintf(buf2, sizeof(buf2), "The corpse of %s is lying here.", GET_NAME(ch)); + /* Use short description if available, otherwise fall back to name */ + const char *who = NULL; + + if (GET_SHORT_DESC(ch) && *GET_SHORT_DESC(ch)) + who = GET_SHORT_DESC(ch); + else + who = GET_NAME(ch); + + snprintf(buf2, sizeof(buf2), "The corpse of %s is lying here.", who); corpse->description = strdup(buf2); - snprintf(buf2, sizeof(buf2), "the corpse of %s", GET_NAME(ch)); + snprintf(buf2, sizeof(buf2), "the corpse of %s", who); corpse->short_description = strdup(buf2); GET_OBJ_TYPE(corpse) = ITEM_CONTAINER; - for(x = y = 0; x < EF_ARRAY_MAX || y < TW_ARRAY_MAX; x++, y++) { + for (x = y = 0; x < EF_ARRAY_MAX || y < TW_ARRAY_MAX; x++, y++) { if (x < EF_ARRAY_MAX) GET_OBJ_EXTRA_AR(corpse, x) = 0; if (y < TW_ARRAY_MAX) @@ -257,8 +265,8 @@ static void make_corpse(struct char_data *ch) } SET_BIT_AR(GET_OBJ_WEAR(corpse), ITEM_WEAR_TAKE); SET_BIT_AR(GET_OBJ_EXTRA(corpse), ITEM_NODONATE); - GET_OBJ_VAL(corpse, 0) = 0; /* You can't store stuff in a corpse */ - GET_OBJ_VAL(corpse, 3) = 1; /* corpse identifier */ + GET_OBJ_VAL(corpse, 0) = 0; /* You can't store stuff in a corpse */ + GET_OBJ_VAL(corpse, 3) = 1; /* corpse identifier */ GET_OBJ_WEIGHT(corpse) = GET_WEIGHT(ch) + IS_CARRYING_W(ch); GET_OBJ_RENT(corpse) = 100000; if (IS_NPC(ch)) @@ -281,11 +289,6 @@ static void make_corpse(struct char_data *ch) /* transfer gold */ if (GET_GOLD(ch) > 0) { - /* following 'if' clause added to fix gold duplication loophole. The above - * line apparently refers to the old "partially log in, kill the game - * character, then finish login sequence" duping bug. The duplication has - * been fixed (knock on wood) but the test below shall live on, for a - * while. -gg 3/3/2002 */ if (IS_NPC(ch) || ch->desc) { money = create_money(GET_GOLD(ch)); obj_to_obj(money, corpse); diff --git a/src/handler.c b/src/handler.c index 234bd88..127b2f9 100644 --- a/src/handler.c +++ b/src/handler.c @@ -1078,35 +1078,66 @@ struct char_data *get_char_room_vis(struct char_data *ch, char *name, int *numbe /* JE */ if (!str_cmp(name, "self") || !str_cmp(name, "me")) - return (ch); + return ch; /* 0. means PC with name */ if (*number == 0) - return (get_player_vis(ch, name, NULL, FIND_CHAR_ROOM)); + return get_player_vis(ch, name, NULL, FIND_CHAR_ROOM); for (i = world[IN_ROOM(ch)].people; i && *number; i = i->next_in_room) { - const char *namelist = NULL; bool match = FALSE; if (IS_NPC(i)) { - /* NPCs match either keywords or their name */ + /* NPCs: match either keywords or their name (unchanged) */ const char *keywords = GET_KEYWORDS(i); const char *proper = GET_NAME(i); - if ((keywords && isname(name, keywords)) || (proper && isname(name, proper))) + + if ((keywords && isname(name, keywords)) || + (proper && isname(name, proper))) match = TRUE; + } else { - /* PCs match only their name */ - namelist = GET_NAME(i); - if (namelist && isname(name, namelist)) + /* PCs: match against name + sanitized short description */ + const char *proper = GET_NAME(i); + const char *sdesc = GET_SHORT_DESC(i); + + if (sdesc && *sdesc) { + char clean_sdesc[MAX_INPUT_LENGTH]; + char tmp[MAX_INPUT_LENGTH * 2]; + int w = 0; + + /* Turn punctuation etc. into spaces so "tall," -> "tall" */ + for (int r = 0; sdesc[r] && w < (int)sizeof(clean_sdesc) - 1; r++) { + unsigned char c = (unsigned char)sdesc[r]; + + if (isalnum(c) || c == '\'' || c == '-') { + clean_sdesc[w++] = c; + } else { + /* normalize anything else (spaces, commas, etc.) to a single space */ + clean_sdesc[w++] = ' '; + } + } + clean_sdesc[w] = '\0'; + + if (proper && *proper) + snprintf(tmp, sizeof(tmp), "%s %s", proper, clean_sdesc); + else + snprintf(tmp, sizeof(tmp), "%s", clean_sdesc); + + if (isname(name, tmp)) + match = TRUE; + } else if (proper && isname(name, proper)) { + /* Fallback: no sdesc yet, use name only */ match = TRUE; + } } if (match && CAN_SEE(ch, i)) if (--(*number) == 0) - return (i); + return i; } - return (NULL); + return NULL; } struct char_data *get_char_world_vis(struct char_data *ch, char *name, int *number) diff --git a/src/interpreter.c b/src/interpreter.c index 431232d..908dc61 100644 --- a/src/interpreter.c +++ b/src/interpreter.c @@ -1608,58 +1608,108 @@ void nanny(struct descriptor_data *d, char *arg) STATE(d) = CON_QCLASS; break; - case CON_QCLASS: - load_result = parse_class(*arg); - if (load_result == CLASS_UNDEFINED) { - write_to_output(d, "\r\nThat's not a class.\r\nClass: "); - return; - } else { - GET_CLASS(d->character) = load_result; - } - - /* Create player entry and initialize character now so file exists */ - if (d->olc) { - free(d->olc); - d->olc = NULL; - } - if (GET_PFILEPOS(d->character) < 0) - GET_PFILEPOS(d->character) = create_entry(GET_PC_NAME(d->character)); - - /* Initialize base stats, starting level, etc. */ - init_char(d->character); - save_char(d->character); - save_player_index(); - - /* Log and register early so new players are tracked immediately */ - GET_PREF(d->character) = rand_number(1, 128000); - GET_HOST(d->character) = strdup(d->host); - mudlog(NRM, LVL_GOD, TRUE, "%s [%s] new player created (awaiting description).", - GET_NAME(d->character), d->host); - - if (AddRecentPlayer(GET_NAME(d->character), d->host, TRUE, FALSE) == FALSE) - mudlog(BRF, MAX(LVL_IMMORT, GET_INVIS_LEV(d->character)), TRUE, - "Failure to AddRecentPlayer (returned FALSE)."); - - /* Now move to mandatory description entry */ - write_to_output(d, - "\r\nBefore entering the world, please describe your character.\r\n" - "Focus on what others can immediately see — height, build, complexion,\r\n" - "facial structure, hair, eyes, and other physical details. Avoid names,\r\n" - "clothing, or personal information that someone would not know meeting\r\n" - "you for the first time.\r\n\r\n" - "Example:\r\n" - " This broad-shouldered human stands with a relaxed but watchful bearing.\r\n" - " Weather and sun have darkened their skin, and faint scars trace the backs\r\n" - " of their hands. Their eyes are a pale, gray-green hue, steady and alert\r\n" - " beneath a low brow. Thick, uneven hair falls around a strong jaw and\r\n" - " angular features.\r\n\r\n"); - - d->backstr = NULL; - d->str = &d->character->player.description; - d->max_str = PLR_DESC_LENGTH; - STATE(d) = CON_PLR_DESC; - send_editor_help(d); +case CON_QCLASS: + load_result = parse_class(*arg); + if (load_result == CLASS_UNDEFINED) { + write_to_output(d, "\r\nThat's not a class.\r\nClass: "); return; + } else { + GET_CLASS(d->character) = load_result; + } + + /* Create player entry and initialize character now so file exists */ + if (d->olc) { + free(d->olc); + d->olc = NULL; + } + if (GET_PFILEPOS(d->character) < 0) + GET_PFILEPOS(d->character) = create_entry(GET_PC_NAME(d->character)); + + /* Initialize base stats, starting level, etc. */ + init_char(d->character); + save_char(d->character); + save_player_index(); + + /* Log and register early so new players are tracked immediately */ + GET_PREF(d->character) = rand_number(1, 128000); + GET_HOST(d->character) = strdup(d->host); + mudlog(NRM, LVL_GOD, TRUE, "%s [%s] new player created (awaiting description).", + GET_NAME(d->character), d->host); + + if (AddRecentPlayer(GET_NAME(d->character), d->host, TRUE, FALSE) == FALSE) + mudlog(BRF, MAX(LVL_IMMORT, GET_INVIS_LEV(d->character)), TRUE, + "Failure to AddRecentPlayer (returned FALSE)."); + + /* === NEW: mandatory short description before main description === */ + write_to_output(d, + "\r\nBefore entering the world, you must choose a short description.\r\n" + "This is what others see in the room list and messages instead of your name.\r\n" + "It should describe your appearance, not identity.\r\n\r\n" + "Examples:\r\n" + " the tall, muscular man\r\n" + " the lanky, sharp-eyed elf\r\n" + " the short, bald dwarf\r\n\r\n" + "Do not include your character's name here.\r\n" + "Short description: "); + + STATE(d) = CON_QSHORTDESC; + return; + + case CON_QSHORTDESC: { + skip_spaces(&arg); + + if (!*arg) { + write_to_output(d, "\r\nA short description cannot be empty.\r\n" + "Short description: "); + return; + } + + /* Do not allow their character name in the sdesc */ + if (str_str(arg, GET_NAME(d->character))) { + write_to_output(d, "\r\nYour short description must not include your name.\r\n" + "Short description: "); + return; + } + + /* Enforce length limit (optional, but recommended) */ + if (strlen(arg) > MAX_NAME_LENGTH * 2) { + write_to_output(d, "\r\nThat description is too long.\r\n" + "Short description: "); + return; + } + + /* Store as player's short description */ + if (GET_SHORT_DESC(d->character)) + free(GET_SHORT_DESC(d->character)); + + GET_SHORT_DESC(d->character) = strdup(arg); + + /* Immediately save it to disk */ + save_char(d->character); + save_player_index(); + + /* Now transition to full description input */ + write_to_output(d, + "\r\nBefore entering the world, please describe your character.\r\n" + "Focus on what others can immediately see — height, build, complexion,\r\n" + "facial structure, hair, eyes, and other physical details. Avoid names,\r\n" + "clothing, or personal information that someone would not know meeting\r\n" + "you for the first time.\r\n\r\n" + "Example:\r\n" + " This broad-shouldered human stands with a relaxed but watchful bearing.\r\n" + " Weather and sun have darkened their skin, and faint scars trace the backs\r\n" + " of their hands. Their eyes are a pale, gray-green hue, steady and alert\r\n" + " beneath a low brow. Thick, uneven hair falls around a strong jaw and\r\n" + " angular features.\r\n\r\n"); + + d->backstr = NULL; + d->str = &d->character->player.description; + d->max_str = PLR_DESC_LENGTH; + STATE(d) = CON_PLR_DESC; + + send_editor_help(d); + return; + } case CON_PLR_DESC: /* If the player canceled or has no description, prompt again */ diff --git a/src/players.c b/src/players.c index e81f85c..f438442 100644 --- a/src/players.c +++ b/src/players.c @@ -247,6 +247,7 @@ int load_char(const char *name, struct char_data *ch) /* Character initializations. Necessary to keep some things straight. */ ch->affected = NULL; + ch->player.short_descr = NULL; /* ensure a clean start */ for (i = 1; i <= MAX_SKILLS; i++) if (IS_NPC(ch)) ch->mob_specials.skills[i] = 0; @@ -430,6 +431,13 @@ int load_char(const char *name, struct char_data *ch) case 'S': if (!strcmp(tag, "Sex ")) GET_SEX(ch) = atoi(line); + else if (!strcmp(tag, "Sdsc")) { + /* Clear any existing sdesc to avoid leaks */ + if (GET_SHORT_DESC(ch)) + free(GET_SHORT_DESC(ch)); + /* 'line' is the remainder of the line after "Sdsc" + space */ + GET_SHORT_DESC(ch) = strdup(line); + } else if (!strcmp(tag, "ScrW")) GET_SCREEN_WIDTH(ch) = atoi(line); else if (!strcmp(tag, "Skil")) load_skills(fl, ch); else if (!strcmp(tag, "SkGt")) { /* Skill Gain Timers */ @@ -570,6 +578,7 @@ void save_char(struct char_data * ch) /* end char_to_store code */ if (GET_NAME(ch)) fprintf(fl, "Name: %s\n", GET_NAME(ch)); + if (GET_SHORT_DESC(ch) && *GET_SHORT_DESC(ch)) fprintf(fl, "Sdsc: %s\n", GET_SHORT_DESC(ch)); if (GET_PASSWD(ch)) fprintf(fl, "Pass: %s\n", GET_PASSWD(ch)); if (ch->player.description && *ch->player.description) { strcpy(buf, ch->player.description); diff --git a/src/structs.h b/src/structs.h index 2219602..51c13c0 100644 --- a/src/structs.h +++ b/src/structs.h @@ -314,30 +314,31 @@ #define CON_CNFPASSWD 6 /**< New character, confirm password */ #define CON_QSEX 7 /**< Choose character sex */ #define CON_QCLASS 8 /**< Choose character class */ -#define CON_RMOTD 9 /**< Reading the message of the day */ -#define CON_MENU 10 /**< At the main menu */ -#define CON_PLR_DESC 11 /**< Enter a new character description prompt */ -#define CON_CHPWD_GETOLD 12 /**< Changing passwd: Get old */ -#define CON_CHPWD_GETNEW 13 /**< Changing passwd: Get new */ -#define CON_CHPWD_VRFY 14 /**< Changing passwd: Verify new password */ -#define CON_DELCNF1 15 /**< Character Delete: Confirmation 1 */ -#define CON_DELCNF2 16 /**< Character Delete: Confirmation 2 */ -#define CON_DISCONNECT 17 /**< In-game link loss (leave character) */ -#define CON_OEDIT 18 /**< OLC mode - object editor */ -#define CON_REDIT 19 /**< OLC mode - room editor */ -#define CON_ZEDIT 20 /**< OLC mode - zone info editor */ -#define CON_MEDIT 21 /**< OLC mode - mobile editor */ -#define CON_SEDIT 22 /**< OLC mode - shop editor */ -#define CON_TEDIT 23 /**< OLC mode - text editor */ -#define CON_CEDIT 24 /**< OLC mode - conf editor */ -#define CON_AEDIT 25 /**< OLC mode - social (action) edit */ -#define CON_TRIGEDIT 26 /**< OLC mode - trigger edit */ -#define CON_HEDIT 27 /**< OLC mode - help edit */ -#define CON_QEDIT 28 /**< OLC mode - quest edit */ -#define CON_PREFEDIT 29 /**< OLC mode - preference edit */ -#define CON_IBTEDIT 30 /**< OLC mode - idea/bug/typo edit */ -#define CON_MSGEDIT 31 /**< OLC mode - message editor */ -#define CON_GET_PROTOCOL 32 /**< Used at log-in while attempting to get protocols > */ +#define CON_QSHORTDESC 9 /**< Enter a new character short description prompt */ +#define CON_RMOTD 10 /**< Reading the message of the day */ +#define CON_MENU 11 /**< At the main menu */ +#define CON_PLR_DESC 12 /**< Enter a new character description prompt */ +#define CON_CHPWD_GETOLD 13 /**< Changing passwd: Get old */ +#define CON_CHPWD_GETNEW 14 /**< Changing passwd: Get new */ +#define CON_CHPWD_VRFY 15 /**< Changing passwd: Verify new password */ +#define CON_DELCNF1 16 /**< Character Delete: Confirmation 1 */ +#define CON_DELCNF2 17 /**< Character Delete: Confirmation 2 */ +#define CON_DISCONNECT 18 /**< In-game link loss (leave character) */ +#define CON_OEDIT 19 /**< OLC mode - object editor */ +#define CON_REDIT 20 /**< OLC mode - room editor */ +#define CON_ZEDIT 21 /**< OLC mode - zone info editor */ +#define CON_MEDIT 22 /**< OLC mode - mobile editor */ +#define CON_SEDIT 23 /**< OLC mode - shop editor */ +#define CON_TEDIT 24 /**< OLC mode - text editor */ +#define CON_CEDIT 25 /**< OLC mode - conf editor */ +#define CON_AEDIT 26 /**< OLC mode - social (action) edit */ +#define CON_TRIGEDIT 27 /**< OLC mode - trigger edit */ +#define CON_HEDIT 28 /**< OLC mode - help edit */ +#define CON_QEDIT 29 /**< OLC mode - quest edit */ +#define CON_PREFEDIT 30 /**< OLC mode - preference edit */ +#define CON_IBTEDIT 31 /**< OLC mode - idea/bug/typo edit */ +#define CON_MSGEDIT 32 /**< OLC mode - message editor */ +#define CON_GET_PROTOCOL 33 /**< Used at log-in while attempting to get protocols > */ /* OLC States range - used by IS_IN_OLC and IS_PLAYING */ #define FIRST_OLC_STATE CON_OEDIT /**< The first CON_ state that is an OLC */ @@ -876,9 +877,9 @@ struct char_player_data char passwd[MAX_PWD_LENGTH+1]; /**< PC's password */ char *name; /**< Display name (PC/NPC personal name) */ char *keywords; /**< Parsing keywords (for NPCs and parsing lookup) */ - char *short_descr; /**< NPC 'actions' */ - char *long_descr; /**< PC / NPC look description */ - char *description; /**< NPC Extra descriptions */ + char *short_descr; /**< PC / NPC short description */ + char *long_descr; /**< PC / NPC long description */ + char *description; /**< PC / NPC main descriptions */ byte sex; /**< PC / NPC sex */ byte chclass; /**< PC / NPC class */ byte level; /**< PC / NPC level */ diff --git a/src/utils.h b/src/utils.h index f2b650f..8575190 100644 --- a/src/utils.h +++ b/src/utils.h @@ -881,15 +881,17 @@ do \ (CAN_WEAR((obj), ITEM_WEAR_TAKE) && CAN_CARRY_OBJ((ch),(obj)) && \ CAN_SEE_OBJ((ch),(obj))) -/** - * If vict can see ch, return visible name. - * For NPCs: use short_descr (e.g. "a burly guard"). - * For PCs: use proper name. +/* Display name for a character as seen by 'vict'. + * - If vict can’t see ch: "someone" + * - If NPC: use short_descr if set, else personal name + * - If PC: use short_descr if set, else personal name */ -#define PERS(ch, vict) \ - (CAN_SEE((vict), (ch)) ? \ - (IS_NPC(ch) ? GET_SHORT_DESC(ch) : GET_NAME(ch)) : \ - ((GET_LEVEL(ch) > LVL_IMMORT) ? "an immortal" : "someone")) +#define PERS(ch, vict) \ + (CAN_SEE((vict), (ch)) ? \ + ((GET_SHORT_DESC(ch) && *GET_SHORT_DESC(ch)) ? \ + GET_SHORT_DESC(ch) : \ + GET_NAME(ch)) : \ + "someone") /** If vict can see obj, return obj short description, else return * "something". */