Stealth skill update

This commit is contained in:
kinther 2025-12-15 14:34:45 -08:00
parent d7aec2e6c2
commit 06d581b011
10 changed files with 194 additions and 67 deletions

View file

@ -242,6 +242,7 @@ bool perform_scan_sweep(struct char_data *ch);
void clear_scan_results(struct char_data *ch);
bool scan_can_target(struct char_data *ch, struct char_data *tch);
bool scan_confirm_target(struct char_data *ch, struct char_data *tch);
void stealth_process_room_movement(struct char_data *ch, room_rnum room, int dir, bool leaving);
/*****************************************************************************

View file

@ -412,12 +412,12 @@ static void list_one_char(struct char_data *i, struct char_data *ch)
/* Otherwise, use short description (PC or NPC) if present, else name. */
{
const char *sdesc = GET_SHORT_DESC(i);
const char *sdesc = get_char_sdesc(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));
send_to_char(ch, "Someone");
}
}

View file

@ -274,8 +274,9 @@ int do_simple_move(struct char_data *ch, int dir, int need_specials_check)
GET_MOVE(ch) -= need_movement;
/* Generate the leave message and display to others in the was_in room. */
if (!AFF_FLAGGED(ch, AFF_SNEAK))
{
if (AFF_FLAGGED(ch, AFF_SNEAK)) {
stealth_process_room_movement(ch, was_in, dir, TRUE);
} else {
snprintf(leave_message, sizeof(leave_message), "$n leaves %s.", dirs[dir]);
act(leave_message, TRUE, ch, 0, 0, TO_ROOM);
}
@ -299,7 +300,9 @@ int do_simple_move(struct char_data *ch, int dir, int need_specials_check)
}
/* Display arrival information to anyone in the destination room... */
if (!AFF_FLAGGED(ch, AFF_SNEAK))
if (AFF_FLAGGED(ch, AFF_SNEAK))
stealth_process_room_movement(ch, going_to, dir, FALSE);
else
act("$n has arrived.", TRUE, ch, 0, 0, TO_ROOM);
/* ... and the room description to the character. */

View file

@ -189,13 +189,74 @@ ACMD(do_not_here)
send_to_char(ch, "Sorry, but you cannot do that here!\r\n");
}
#define STEALTH_BASE_DC 10
static int get_stealth_skill_value(struct char_data *ch)
{
int skill = GET_SKILL(ch, SKILL_STEALTH);
int legacy = MAX(GET_SKILL(ch, SKILL_HIDE), GET_SKILL(ch, SKILL_SNEAK));
if (skill <= 0 && legacy > 0) {
skill = MIN(legacy, 100);
SET_SKILL(ch, SKILL_STEALTH, skill);
}
return skill;
}
static int roll_stealth_check(struct char_data *ch)
{
int skill = get_stealth_skill_value(ch);
int bonus = GET_ABILITY_MOD(GET_DEX(ch)) + GET_PROFICIENCY(skill);
bool disadv = has_stealth_disadv(ch) ? TRUE : FALSE;
int rolla = rand_number(1, 20);
int rollb = rand_number(1, 20);
int roll = disadv ? MIN(rolla, rollb) : rolla;
return roll + bonus;
}
static int sneak_effect_duration(struct char_data *ch)
{
int skill = get_stealth_skill_value(ch);
if (skill <= 0)
return 1;
return MAX(1, skill / 10);
}
static bool can_scan_for_sneak(struct char_data *ch)
{
if (!AFF_FLAGGED(ch, AFF_SCAN))
return FALSE;
if (!GET_SKILL(ch, SKILL_PERCEPTION))
return FALSE;
if (AFF_FLAGGED(ch, AFF_BLIND))
return FALSE;
if (IS_DARK(IN_ROOM(ch)) && !CAN_SEE_IN_DARK(ch))
return FALSE;
return TRUE;
}
static int roll_scan_perception(struct char_data *ch)
{
int bonus = GET_ABILITY_MOD(GET_WIS(ch)) +
GET_PROFICIENCY(GET_SKILL(ch, SKILL_PERCEPTION));
int total = rand_number(1, 20) + bonus;
if (FIGHTING(ch))
total -= 4;
return total;
}
ACMD(do_sneak)
{
struct affected_type af;
int rolla, rollb, roll, bonus, total, dc;
bool disadv = FALSE;
int total, dc;
int stealth_skill = get_stealth_skill_value(ch);
if (!GET_SKILL(ch, SKILL_SNEAK)) {
if (!stealth_skill) {
send_to_char(ch, "You have no idea how to do that.\r\n");
return;
}
@ -212,25 +273,11 @@ ACMD(do_sneak)
affect_from_char(ch, SKILL_SNEAK);
/* --- 5e-style Stealth check (DEX + proficiency) --- */
bonus = GET_ABILITY_MOD(GET_DEX(ch)) +
GET_PROFICIENCY(GET_SKILL(ch, SKILL_SNEAK));
dc = 10;
disadv = has_stealth_disadv(ch) ? TRUE : FALSE;
rolla = rand_number(1, 20);
if (disadv) {
rollb = rand_number(1, 20);
roll = MIN(rolla, rollb); /* disadvantage: take lower roll */
} else {
roll = rolla;
}
total = roll + bonus;
total = roll_stealth_check(ch);
dc = STEALTH_BASE_DC;
if (total < dc) {
gain_skill(ch, "sneak", FALSE);
gain_skill(ch, "stealth", FALSE);
WAIT_STATE(ch, PULSE_VIOLENCE / 2);
GET_MOVE(ch) -= 10;
return;
@ -241,7 +288,7 @@ ACMD(do_sneak)
af.spell = SKILL_SNEAK;
af.location = APPLY_NONE;
af.modifier = 0;
af.duration = GET_LEVEL(ch); /* keep stock duration; adjust if desired */
af.duration = sneak_effect_duration(ch);
memset(af.bitvector, 0, sizeof(af.bitvector));
SET_BIT_AR(af.bitvector, AFF_SNEAK);
affect_to_char(ch, &af);
@ -250,16 +297,16 @@ ACMD(do_sneak)
/* If youve already hidden with a higher roll, keep the stronger value. */
SET_STEALTH_CHECK(ch, MAX(GET_STEALTH_CHECK(ch), total));
gain_skill(ch, "sneak", TRUE);
gain_skill(ch, "stealth", TRUE);
GET_MOVE(ch) -= 10;
}
ACMD(do_hide)
{
int rolla, rollb, roll, bonus, total, dc;
bool disadv = FALSE;
int total, dc;
int stealth_skill = get_stealth_skill_value(ch);
if (!GET_SKILL(ch, SKILL_HIDE)) {
if (!stealth_skill) {
send_to_char(ch, "You have no idea how to do that.\r\n");
return;
}
@ -278,28 +325,14 @@ ACMD(do_hide)
} /* you can't hide while in active melee */
/* --- 5e Stealth (DEX) ability check --- */
bonus = GET_ABILITY_MOD(GET_DEX(ch)) + GET_PROFICIENCY(GET_SKILL(ch, SKILL_HIDE));
/* Baseline difficulty: hiding in general */
/* TODO: Maybe change dc based on terrain/populated rooms in the future */
dc = 10;
/* Armor/gear can impose disadvantage */
disadv = has_stealth_disadv(ch) ? TRUE : FALSE;
rolla = rand_number(1, 20);
if (disadv) {
rollb = rand_number(1, 20);
roll = MIN(rolla, rollb); /* disadvantage: take the lower */
} else {
roll = rolla;
}
total = roll + bonus;
dc = STEALTH_BASE_DC;
total = roll_stealth_check(ch);
if (total < dc) {
/* Failure */
gain_skill(ch, "hide", FALSE);
gain_skill(ch, "stealth", FALSE);
WAIT_STATE(ch, PULSE_VIOLENCE / 2);
GET_MOVE(ch) -= 10;
return;
@ -310,7 +343,7 @@ ACMD(do_hide)
GET_STEALTH_CHECK(ch) = total;
send_to_char(ch, "You hide yourself as best you can.\r\n");
gain_skill(ch, "hide", TRUE);
gain_skill(ch, "stealth", TRUE);
WAIT_STATE(ch, PULSE_VIOLENCE / 2);
GET_MOVE(ch) -= 10;
}
@ -365,6 +398,78 @@ static void forget_scan_target(struct char_data *ch, struct char_data *tch)
}
}
void stealth_process_room_movement(struct char_data *ch, room_rnum room, int dir, bool leaving)
{
struct char_data *viewer;
int stealth_total;
bool base_failure;
const char *dir_word;
char msg[MAX_INPUT_LENGTH];
char sdesc_buf[MAX_INPUT_LENGTH];
const char *name_desc;
const char *format;
if (!ch || room == NOWHERE)
return;
stealth_total = roll_stealth_check(ch);
base_failure = (stealth_total < STEALTH_BASE_DC);
if (dir >= 0 && dir < NUM_OF_DIRS) {
dir_word = leaving ? dirs[dir] : dirs[rev_dir[dir]];
} else {
dir_word = "somewhere";
}
if (get_char_sdesc(ch) && *get_char_sdesc(ch)) {
strlcpy(sdesc_buf, get_char_sdesc(ch), sizeof(sdesc_buf));
if (*sdesc_buf)
sdesc_buf[0] = UPPER(sdesc_buf[0]);
name_desc = sdesc_buf;
} else {
name_desc = "Someone";
}
format = leaving ?
"%s tries to stealthily move to the %s." :
"%s stealthily moves in from the %s.";
snprintf(msg, sizeof(msg), format, name_desc, dir_word);
for (viewer = world[room].people; viewer; viewer = viewer->next_in_room) {
bool viewer_can_scan, saw_with_scan = FALSE, send_echo = FALSE;
if (viewer == ch)
continue;
viewer_can_scan = can_scan_for_sneak(viewer);
if (viewer_can_scan) {
int perception_total = roll_scan_perception(viewer);
if (perception_total >= stealth_total) {
saw_with_scan = TRUE;
send_echo = TRUE;
remember_scan_target(viewer, ch);
} else if (!base_failure) {
forget_scan_target(viewer, ch);
}
gain_skill(viewer, "perception", saw_with_scan ? TRUE : FALSE);
}
if (!send_echo && base_failure) {
if (!viewer_can_scan && !CAN_SEE(viewer, ch))
continue;
send_echo = TRUE;
if (viewer_can_scan)
remember_scan_target(viewer, ch);
}
if (send_echo)
send_to_char(viewer, "%s\r\n", msg);
}
}
void clear_scan_results(struct char_data *ch)
{
struct scan_result_data *node, *next;

View file

@ -349,8 +349,7 @@ void grant_class_skills(struct char_data *ch, bool reset)
break;
case CLASS_ROGUE:
SET_SKILL(ch, SKILL_SNEAK, 5);
SET_SKILL(ch, SKILL_HIDE, 5);
SET_SKILL(ch, SKILL_STEALTH, 5);
SET_SKILL(ch, SKILL_TRACK, 5);
SET_SKILL(ch, SKILL_STEAL, 5);
SET_SKILL(ch, SKILL_BACKSTAB, 5);
@ -386,8 +385,7 @@ void grant_class_skills(struct char_data *ch, bool reset)
break;
case CLASS_RANGER:
SET_SKILL(ch, SKILL_SNEAK, 5);
SET_SKILL(ch, SKILL_HIDE, 5);
SET_SKILL(ch, SKILL_STEALTH, 5);
SET_SKILL(ch, SKILL_BANDAGE, 5);
SET_SKILL(ch, SKILL_TRACK, 5);
SET_SKILL(ch, SKILL_BASH, 5);
@ -642,16 +640,15 @@ void init_spell_levels(void)
spell_level(SKILL_SHIELD_USE, CLASS_CLERIC, 1);
/* ROGUES */
spell_level(SKILL_SNEAK, CLASS_ROGUE, 1);
spell_level(SKILL_PICK_LOCK, CLASS_ROGUE, 1);
spell_level(SKILL_BACKSTAB, CLASS_ROGUE, 1);
spell_level(SKILL_STEAL, CLASS_ROGUE, 1);
spell_level(SKILL_HIDE, CLASS_ROGUE, 1);
spell_level(SKILL_TRACK, CLASS_ROGUE, 1);
spell_level(SKILL_UNARMED, CLASS_ROGUE, 1);
spell_level(SKILL_PIERCING_WEAPONS, CLASS_ROGUE, 1);
spell_level(SKILL_SHIELD_USE, CLASS_ROGUE, 1);
spell_level(SKILL_PERCEPTION, CLASS_ROGUE, 1);
spell_level(SKILL_STEALTH, CLASS_ROGUE, 1);
/* FIGHTERS */
spell_level(SKILL_KICK, CLASS_FIGHTER, 1);
@ -676,8 +673,6 @@ void init_spell_levels(void)
spell_level(SKILL_PERCEPTION, CLASS_BARBARIAN, 1);
/* RANGERS */
spell_level(SKILL_SNEAK, CLASS_RANGER, 1);
spell_level(SKILL_HIDE, CLASS_RANGER, 1);
spell_level(SKILL_BANDAGE, CLASS_RANGER, 1);
spell_level(SKILL_TRACK, CLASS_RANGER, 1);
spell_level(SKILL_BASH, CLASS_RANGER, 1);
@ -686,6 +681,7 @@ void init_spell_levels(void)
spell_level(SKILL_PIERCING_WEAPONS, CLASS_RANGER, 1);
spell_level(SKILL_SHIELD_USE, CLASS_RANGER, 1);
spell_level(SKILL_PERCEPTION, CLASS_RANGER, 1);
spell_level(SKILL_STEALTH, CLASS_RANGER, 1);
/* BARDS */
spell_level(SPELL_ARMOR, CLASS_BARD, 1);

View file

@ -442,10 +442,22 @@ int load_char(const char *name, struct char_data *ch)
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 */
char *p = line;
for (int i = 1; i <= MAX_SKILLS; i++) {
long t = 0;
if (fscanf(fl, " %ld", &t) != 1)
t = 0;
while (*p && isspace((unsigned char)*p))
++p;
if (*p) {
char *endptr = p;
t = strtol(p, &endptr, 10);
if (endptr == p)
t = 0;
else
p = endptr;
}
GET_SKILL_NEXT_GAIN(ch, i) = (time_t)t;
}
}

View file

@ -935,5 +935,5 @@ void mag_assign_spells(void) {
skillo(SKILL_SLASHING_WEAPONS, "slashing weapons");
skillo(SKILL_BLUDGEONING_WEAPONS, "bludgeoning weapons");
skillo(SKILL_PERCEPTION, "perception");
skillo(SKILL_STEALTH, "stealth");
}

View file

@ -117,6 +117,7 @@
#define SKILL_SLASHING_WEAPONS 145 /* Reserved Skill[] DO NOT CHANGE */
#define SKILL_BLUDGEONING_WEAPONS 146 /* Reserved Skill[] DO NOT CHANGE */
#define SKILL_PERCEPTION 147 /* Reserved Skill[] DO NOT CHANGE */
#define SKILL_STEALTH 148 /* Shared stealth skill for hide/sneak */
/* New skills may be added here up to MAX_SKILLS (200) */

View file

@ -1642,7 +1642,20 @@ void remove_from_string(char *string, const char *to_remove)
i--;
}
}
}
const char *get_char_sdesc(const struct char_data *ch)
{
if (!ch)
return "someone";
if (GET_SHORT_DESC(ch) && *GET_SHORT_DESC(ch))
return GET_SHORT_DESC(ch);
if (GET_NAME(ch))
return GET_NAME(ch);
return "someone";
}
/* 5e system helpers */

View file

@ -79,6 +79,7 @@ int count_non_protocol_chars(char * str);
char *right_trim_whitespace(const char *string);
void remove_from_string(char *string, const char *to_remove);
const char *const *obj_value_labels(int item_type);
const char *get_char_sdesc(const struct char_data *ch);
/* 5e system helpers */
@ -892,15 +893,10 @@ do \
/* Display name for a character as seen by 'vict'.
* - If vict cant see ch: "someone"
* - If NPC: use short_descr if set, else personal name
* - If PC: use short_descr if set, else personal name
* - Otherwise: prefer short_descr, fall back to NPC name or a generic label
*/
#define PERS(ch, vict) \
(CAN_SEE((vict), (ch)) ? \
((GET_SHORT_DESC(ch) && *GET_SHORT_DESC(ch)) ? \
GET_SHORT_DESC(ch) : \
GET_NAME(ch)) : \
"someone")
(CAN_SEE((vict), (ch)) ? get_char_sdesc(ch) : "someone")
/** If vict can see obj, return obj short description, else return
* "something". */