Update skills for 5e system

This commit is contained in:
kinther 2025-08-28 10:47:05 -07:00
parent 0815cd6a96
commit 17d6221510
13 changed files with 474 additions and 143 deletions

View file

@ -234,6 +234,7 @@ ACMD(do_report);
ACMD(do_save);
ACMD(do_skills);
ACMD(do_sneak);
ACMD(do_perception);
ACMD(do_split);
ACMD(do_steal);
ACMD(do_title);

View file

@ -126,7 +126,9 @@ ACMD(do_backstab)
{
char buf[MAX_INPUT_LENGTH];
struct char_data *vict;
int percent, prob;
struct obj_data *weap;
int roll, atk_bonus, total, target_ac;
bool crit_success = FALSE, crit_fail = FALSE;
if (IS_NPC(ch) || !GET_SKILL(ch, SKILL_BACKSTAB)) {
send_to_char(ch, "You have no idea how to do that.\r\n");
@ -143,11 +145,12 @@ ACMD(do_backstab)
send_to_char(ch, "How can you sneak up on yourself?\r\n");
return;
}
if (!GET_EQ(ch, WEAR_WIELD)) {
if (!(weap = GET_EQ(ch, WEAR_WIELD))) {
send_to_char(ch, "You need to wield a weapon to make it a success.\r\n");
return;
}
if (GET_OBJ_VAL(GET_EQ(ch, WEAR_WIELD), 3) != TYPE_PIERCE - TYPE_HIT) {
/* Only piercing weapons allowed */
if (GET_OBJ_VAL(weap, 3) != TYPE_PIERCE - TYPE_HIT) {
send_to_char(ch, "Only piercing weapons can be used for backstabbing.\r\n");
return;
}
@ -164,15 +167,50 @@ ACMD(do_backstab)
return;
}
percent = rand_number(1, 101); /* 101% is a complete failure */
prob = GET_SKILL(ch, SKILL_BACKSTAB);
/* --- d20 vs ascending AC --- */
atk_bonus = GET_ABILITY_MOD(GET_DEX(ch)) +
GET_PROFICIENCY(GET_SKILL(ch, SKILL_BACKSTAB));
roll = rand_number(1, 20);
crit_fail = (roll == 1);
crit_success = (roll == 20);
total = roll + atk_bonus;
target_ac = compute_ascending_ac(vict);
if (!crit_fail && (crit_success || total >= target_ac)) {
/* Successful backstab */
act("You slip behind $N and drive your weapon home!", TRUE, ch, 0, vict, TO_CHAR);
act("$n slips behind you and drives a weapon into your back!", TRUE, ch, 0, vict, TO_VICT);
act("$n slips behind $N and drives a weapon home!", TRUE, ch, 0, vict, TO_NOTVICT);
/* Keeping this logic really simple so it can be adjusted later if need be */
if (crit_success) {
/* Simple crit = 2x damage */
int base = dice(GET_OBJ_VAL(weap, 1), GET_OBJ_VAL(weap, 2));
int dmg = base + GET_ABILITY_MOD(GET_DEX(ch));
if (dmg < 1) dmg = 1;
dmg *= 2;
damage(ch, vict, dmg, SKILL_BACKSTAB);
} else {
/* Hit but not crit = 1.5x damage */
int base = dice(GET_OBJ_VAL(weap, 1), GET_OBJ_VAL(weap, 2));
int dmg = base + GET_ABILITY_MOD(GET_DEX(ch));
if (dmg < 1) dmg = 1;
dmg *= 1.5;
damage(ch, vict, dmg, SKILL_BACKSTAB);
}
gain_skill(ch, "backstab", TRUE);
} else {
/* Missed backstab */
act("You lunge at $N, but miss the mark.", TRUE, ch, 0, vict, TO_CHAR);
act("$n lunges at you, but misses the mark!", TRUE, ch, 0, vict, TO_VICT);
act("$n lunges at $N, but misses the mark!", TRUE, ch, 0, vict, TO_NOTVICT);
if (AWAKE(vict) && (percent > prob)) {
damage(ch, vict, 0, SKILL_BACKSTAB);
gain_skill(ch, "backstab", FALSE);
} else {
hit(ch, vict, SKILL_BACKSTAB);
gain_skill(ch, "backstab", TRUE);
}
WAIT_STATE(ch, 2 * PULSE_VIOLENCE);
@ -271,7 +309,8 @@ ACMD(do_bash)
{
char arg[MAX_INPUT_LENGTH];
struct char_data *vict;
int percent, prob;
int roll, atk_bonus, total, target_ac;
bool crit_success = FALSE, crit_fail = FALSE;
one_argument(argument, arg);
@ -304,31 +343,59 @@ ACMD(do_bash)
return;
}
percent = rand_number(1, 101); /* 101% is a complete failure */
prob = GET_SKILL(ch, SKILL_BASH);
/* --- 5e-like attack roll vs ascending AC --- */
atk_bonus = GET_ABILITY_MOD(GET_STR(ch)) +
GET_PROFICIENCY(GET_SKILL(ch, SKILL_BASH));
roll = rand_number(1, 20);
crit_success = (roll == 1);
crit_fail = (roll == 20);
total = roll + atk_bonus;
target_ac = compute_ascending_ac(vict);
/* Some mobs simply can't be bashed: force a miss like legacy code did. */
if (MOB_FLAGGED(vict, MOB_NOBASH))
percent = 101;
crit_fail = TRUE;
if (percent > prob) {
damage(ch, vict, 0, SKILL_BASH);
GET_POS(ch) = POS_SITTING;
gain_skill(ch, "bash", FALSE);
} else {
/*
* If we bash a player and they wimp out, they will move to the previous
* room before we set them sitting. If we try to set the victim sitting
* first to make sure they don't flee, then we can't bash them! So now
* we only set them sitting if they didn't flee. -gg 9/21/98
*/
if (damage(ch, vict, 1, SKILL_BASH) > 0) { /* -1 = dead, 0 = miss */
WAIT_STATE(vict, PULSE_VIOLENCE);
if (!crit_fail && (crit_success || total >= target_ac)) {
/* ---- HIT: small damage + knockdown ---- */
/* Damage = 1 + STR mod (min 1); double on crit */
int dmg = 1 + GET_ABILITY_MOD(GET_STR(ch));
if (dmg < 1) dmg = 1;
if (crit_success) dmg *= 2;
if (crit_success) {
act("You slam into $N with a crushing bash!", TRUE, ch, 0, vict, TO_CHAR);
act("$n slams into you with a crushing bash!", TRUE, ch, 0, vict, TO_VICT);
act("$n slams into $N with a crushing bash!", TRUE, ch, 0, vict, TO_NOTVICT);
} else {
act("You bash $N off balance.", TRUE, ch, 0, vict, TO_CHAR);
act("$n bashes you off balance.", TRUE, ch, 0, vict, TO_VICT);
act("$n bashes $N off balance.", TRUE, ch, 0, vict, TO_NOTVICT);
}
/* Apply damage; legacy: >0 means still alive & not a pure miss */
if (damage(ch, vict, dmg, SKILL_BASH) > 0) {
WAIT_STATE(vict, PULSE_VIOLENCE); /* brief stun */
if (IN_ROOM(ch) == IN_ROOM(vict)) {
GET_POS(vict) = POS_SITTING;
GET_POS(vict) = POS_SITTING; /* knockdown */
gain_skill(ch, "bash", TRUE);
}
}
} else {
/* ---- MISS: you eat the floor (attacker sits) ---- */
act("You miss your bash at $N and lose your footing!", TRUE, ch, 0, vict, TO_CHAR);
act("$n misses a bash at you and loses $s footing!", TRUE, ch, 0, vict, TO_VICT);
act("$n misses a bash at $N and loses $s footing!", TRUE, ch, 0, vict, TO_NOTVICT);
damage(ch, vict, 0, SKILL_BASH);
GET_POS(ch) = POS_SITTING;
gain_skill(ch, "bash", FALSE);
}
WAIT_STATE(ch, PULSE_VIOLENCE * 2);
}
@ -336,7 +403,7 @@ ACMD(do_rescue)
{
char arg[MAX_INPUT_LENGTH];
struct char_data *vict, *tmp_ch;
int percent, prob;
int roll, bonus, total, dc;
if (IS_NPC(ch) || !GET_SKILL(ch, SKILL_RESCUE)) {
send_to_char(ch, "You have no idea how to do that.\r\n");
@ -357,29 +424,40 @@ ACMD(do_rescue)
send_to_char(ch, "How can you rescue someone you are trying to kill?\r\n");
return;
}
for (tmp_ch = world[IN_ROOM(ch)].people; tmp_ch &&
(FIGHTING(tmp_ch) != vict); tmp_ch = tmp_ch->next_in_room);
/* Find someone who is fighting the victim */
for (tmp_ch = world[IN_ROOM(ch)].people; tmp_ch && (FIGHTING(tmp_ch) != vict); tmp_ch = tmp_ch->next_in_room)
;
/* Handle the “already rescued” edge case from your original */
if ((FIGHTING(vict) != NULL) && (FIGHTING(ch) == FIGHTING(vict)) && (tmp_ch == NULL)) {
tmp_ch = FIGHTING(vict);
if (FIGHTING(tmp_ch) == ch) {
send_to_char(ch, "You have already rescued %s from %s.\r\n", GET_NAME(vict), GET_NAME(FIGHTING(ch)));
return;
}
tmp_ch = FIGHTING(vict);
if (FIGHTING(tmp_ch) == ch) {
send_to_char(ch, "You have already rescued %s from %s.\r\n", GET_NAME(vict), GET_NAME(FIGHTING(ch)));
return;
}
}
if (!tmp_ch) {
act("But nobody is fighting $M!", FALSE, ch, 0, vict, TO_CHAR);
return;
}
percent = rand_number(1, 101); /* 101% is a complete failure */
prob = GET_SKILL(ch, SKILL_RESCUE);
if (percent > prob) {
/* --- STR + proficiency ability check vs DC (no nat 1/20 rules) --- */
bonus = GET_ABILITY_MOD(GET_STR(ch)) + GET_PROFICIENCY(GET_SKILL(ch, SKILL_RESCUE));
dc = 10;
if (FIGHTING(ch)) dc += 5; /* harder to pull off while already in melee */
roll = rand_number(1, 20);
total = roll + bonus;
if (total < dc) {
send_to_char(ch, "You fail the rescue!\r\n");
gain_skill(ch, "rescue", FALSE);
return;
}
/* Success: swap aggro */
send_to_char(ch, "Banzai! To the rescue...\r\n");
act("You are rescued by $N, you are confused!", FALSE, vict, 0, ch, TO_CHAR);
act("$n heroically rescues $N!", FALSE, ch, 0, vict, TO_NOTVICT);
@ -388,77 +466,109 @@ ACMD(do_rescue)
stop_fighting(vict);
if (FIGHTING(tmp_ch))
stop_fighting(tmp_ch);
if (FIGHTING(ch)) {
if (FIGHTING(ch))
stop_fighting(ch);
gain_skill(ch, "rescue", TRUE);
}
set_fighting(ch, tmp_ch);
set_fighting(tmp_ch, ch);
gain_skill(ch, "rescue", TRUE);
WAIT_STATE(vict, 2 * PULSE_VIOLENCE);
}
/* 5e-like whirlwind tick: random characters (PCs & NPCs), d20 vs AC, friendly fire possible */
EVENTFUNC(event_whirlwind)
{
struct char_data *ch, *tch;
struct mud_event_data *pMudEvent;
struct list_data *room_list;
int count;
int roll;
/* This is just a dummy check, but we'll do it anyway */
if (event_obj == NULL)
return 0;
/* For the sake of simplicity, we will place the event data in easily
* referenced pointers */
pMudEvent = (struct mud_event_data *) event_obj;
ch = (struct char_data *) pMudEvent->pStruct;
/* When using a list, we have to make sure to allocate the list as it
* uses dynamic memory */
ch = (struct char_data *) pMudEvent->pStruct;
if (!ch || IN_ROOM(ch) == NOWHERE || GET_POS(ch) < POS_FIGHTING)
return 0;
room_list = create_list();
/* We search through the "next_in_room", and grab all NPCs and add them
* to our list */
for (tch = world[IN_ROOM(ch)].people; tch; tch = tch->next_in_room)
if (IS_NPC(tch))
add_to_list(tch, room_list);
/* If our list is empty or has "0" entries, we free it from memory and
* close off our event */
/* === Target pool: everyone except self; skip protected mobs === */
for (tch = world[IN_ROOM(ch)].people; tch; tch = tch->next_in_room) {
if (tch == ch) continue;
if (IS_NPC(tch) && MOB_FLAGGED(tch, MOB_NOKILL)) continue;
if (GET_POS(tch) <= POS_DEAD) continue;
add_to_list(tch, room_list);
}
if (room_list->iSize == 0) {
free_list(room_list);
send_to_char(ch, "There is no one in the room to whirlwind!\r\n");
send_to_char(ch, "There is no one here to whirlwind!\r\n");
return 0;
}
/* We spit out some ugly colour, making use of the new colour options,
* to let the player know they are performing their whirlwind strike */
send_to_char(ch, "\t[f313]You deliver a vicious \t[f014]\t[b451]WHIRLWIND!!!\tn\r\n");
/* Lets grab some a random NPC from the list, and hit() them up */
send_to_char(ch, "\t[f313]You deliver a vicious \t[f014]\t[b451]attack, spinning wildly!!!\tn\r\n");
/* Up to 14 rapid strikes (friendly fire possible) */
for (count = dice(1, 4); count > 0; count--) {
tch = random_from_list(room_list);
hit(ch, tch, TYPE_UNDEFINED);
roll = rand_number(1, 20);
if (roll >= 20) {
gain_skill(ch, "whirlwind", FALSE); /* on a critical success, give whirlwind a chance to gain */
int roll, atk_bonus, total, target_ac, dmg;
bool crit_success = FALSE, crit_fail = FALSE;
tch = (struct char_data *) random_from_list(room_list);
if (!tch || IN_ROOM(tch) != IN_ROOM(ch) || GET_POS(tch) <= POS_DEAD)
continue;
/* Attack roll vs ascending AC */
atk_bonus = GET_ABILITY_MOD(GET_STR(ch)) + GET_PROFICIENCY(GET_SKILL(ch, SKILL_WHIRLWIND));
roll = rand_number(1, 20);
crit_fail = (roll == 1);
crit_success = (roll == 20);
total = roll + atk_bonus;
target_ac = compute_ascending_ac(tch);
if (!crit_fail && (crit_success || total >= target_ac)) {
dmg = dice(1, 4) + GET_ABILITY_MOD(GET_STR(ch)) + GET_PROFICIENCY(GET_SKILL(ch, SKILL_WHIRLWIND));
if (dmg < 1) dmg = 1;
if (crit_success) dmg *= 2;
if (crit_success) {
act("Your whirlwind catches $N with a devastating slash!", TRUE, ch, 0, tch, TO_CHAR);
act("$n's whirlwind catches you with a devastating slash!", TRUE, ch, 0, tch, TO_VICT | TO_SLEEP);
act("$n's whirlwind catches $N with a devastating slash!", TRUE, ch, 0, tch, TO_NOTVICT);
} else {
act("Your whirlwind slices into $N.", TRUE, ch, 0, tch, TO_CHAR);
act("$n's whirlwind slices into you.", TRUE, ch, 0, tch, TO_VICT | TO_SLEEP);
act("$n's whirlwind slices into $N.", TRUE, ch, 0, tch, TO_NOTVICT);
}
damage(ch, tch, dmg, SKILL_WHIRLWIND);
/* Learning tick on a strong (crit) hit */
if (crit_success)
gain_skill(ch, "whirlwind", TRUE);
} else {
act("Your whirlwind arcs wide past $N.", TRUE, ch, 0, tch, TO_CHAR);
act("$n's whirlwind arcs wide past you.", TRUE, ch, 0, tch, TO_VICT | TO_SLEEP);
act("$n's whirlwind arcs wide past $N.", TRUE, ch, 0, tch, TO_NOTVICT);
damage(ch, tch, 0, SKILL_WHIRLWIND);
gain_skill(ch, "whirlwind", FALSE);
}
}
/* Now that our attack is done, let's free out list */
free_list(room_list);
/* The "return" of the event function is the time until the event is called
* again. If we return 0, then the event is freed and removed from the list, but
* any other numerical response will be the delay until the next call */
/* Continue spinning or stop */
if (GET_SKILL(ch, SKILL_WHIRLWIND) < rand_number(1, 101)) {
send_to_char(ch, "You stop spinning.\r\n");
send_to_char(ch, "You stop spinning, but the world around you doesn't.\r\n");
return 0;
} else
return 1.5 * PASSES_PER_SEC;
}
return (int)(1.5 * PASSES_PER_SEC);
}
/* The "Whirlwind" skill is designed to provide a basic understanding of the
@ -471,7 +581,7 @@ ACMD(do_whirlwind)
return;
}
if ROOM_FLAGGED(IN_ROOM(ch), ROOM_PEACEFUL) {
if (ROOM_FLAGGED(IN_ROOM(ch), ROOM_PEACEFUL)) {
send_to_char(ch, "This room just has such a peaceful, easy feeling...\r\n");
return;
}
@ -505,7 +615,8 @@ ACMD(do_kick)
{
char arg[MAX_INPUT_LENGTH];
struct char_data *vict;
int percent, prob;
int roll, atk_bonus, total, target_ac;
bool crit_success = FALSE, crit_miss = FALSE;
if (IS_NPC(ch) || !GET_SKILL(ch, SKILL_KICK)) {
send_to_char(ch, "You have no idea how.\r\n");
@ -522,20 +633,53 @@ ACMD(do_kick)
return;
}
}
if (vict == ch) {
send_to_char(ch, "Aren't we funny today...\r\n");
return;
}
/* 101% is a complete failure */
percent = ((10 - (compute_armor_class(vict) / 10)) * 2) + rand_number(1, 101);
prob = GET_SKILL(ch, SKILL_KICK);
if (percent > prob) {
/* --- 5e-like attack roll vs ascending AC --- */
atk_bonus = GET_ABILITY_MOD(GET_STR(ch)) +
GET_PROFICIENCY(GET_SKILL(ch, SKILL_KICK));
roll = rand_number(1, 20);
crit_miss = (roll == 1);
crit_success = (roll == 20);
total = roll + atk_bonus;
target_ac = compute_ascending_ac(vict);
if (!crit_miss && (crit_success || total >= target_ac)) {
/* HIT */
/* Damage = 1 + STR mod, floored at 1 */
int dmg = 1 + GET_ABILITY_MOD(GET_STR(ch));
if (dmg < 1)
dmg = 1;
if (crit_success) {
dmg *= 2; /* simple crit rule: double damage */
act("You land a brutal, bone-jarring kick on $N!", TRUE, ch, 0, vict, TO_CHAR);
act("$n lands a brutal, bone-jarring kick on you!", TRUE, ch, 0, vict, TO_VICT);
act("$n lands a brutal, bone-jarring kick on $N!", TRUE, ch, 0, vict, TO_NOTVICT);
} else {
act("You kick $N solidly.", TRUE, ch, 0, vict, TO_CHAR);
act("$n kicks you solidly.", TRUE, ch, 0, vict, TO_VICT);
act("$n kicks $N solidly.", TRUE, ch, 0, vict, TO_NOTVICT);
}
damage(ch, vict, dmg, SKILL_KICK);
gain_skill(ch, "kick", TRUE);
} else {
/* MISS */
act("You miss your kick at $N.", TRUE, ch, 0, vict, TO_CHAR);
act("$n misses a kick at you.", TRUE, ch, 0, vict, TO_VICT);
act("$n misses a kick at $N.", TRUE, ch, 0, vict, TO_NOTVICT);
damage(ch, vict, 0, SKILL_KICK);
gain_skill(ch, "kick", FALSE);
} else {
damage(ch, vict, GET_LEVEL(ch) / 2, SKILL_KICK);
gain_skill(ch, "kick", TRUE);
}
WAIT_STATE(ch, PULSE_VIOLENCE * 3);
@ -544,11 +688,10 @@ ACMD(do_kick)
ACMD(do_bandage)
{
char arg[MAX_INPUT_LENGTH];
struct char_data * vict;
int percent, prob;
struct char_data *vict;
int roll, bonus, total, dc;
if (!GET_SKILL(ch, SKILL_BANDAGE))
{
if (!GET_SKILL(ch, SKILL_BANDAGE)) {
send_to_char(ch, "You are unskilled in the art of bandaging.\r\n");
return;
}
@ -565,30 +708,63 @@ ACMD(do_bandage)
return;
}
if (GET_HIT(vict) >= 0) {
send_to_char(ch, "You can only bandage someone who is close to death.\r\n");
if (AFF_FLAGGED(vict, AFF_BANDAGED)) {
send_to_char(ch, "That person has already been bandaged recently.\r\n");
return;
}
if (GET_HIT(vict) >= GET_MAX_HIT(vict)) {
send_to_char(ch, "They dont need bandaging right now.\r\n");
return;
}
WAIT_STATE(ch, PULSE_VIOLENCE * 2);
percent = rand_number(1, 101); /* 101% is a complete failure */
prob = GET_SKILL(ch, SKILL_BANDAGE);
/* --- WIS + proficiency ability check vs DC --- */
bonus = GET_ABILITY_MOD(GET_WIS(ch)) + GET_PROFICIENCY(GET_SKILL(ch, SKILL_BANDAGE));
dc = 10;
if (FIGHTING(ch)) dc += 2; /* harder to bandage in combat */
roll = rand_number(1, 20);
total = roll + bonus;
if (total < dc) {
/* Failure: hurt the patient slightly */
act("Your attempt to bandage fails.", FALSE, ch, 0, vict, TO_CHAR);
act("$n tries to bandage $N, but fails miserably.", TRUE, ch, 0, vict, TO_NOTVICT);
act("$n fumbles the bandage work on you. That hurts!", TRUE, ch, 0, vict, TO_VICT);
if (percent <= prob) {
act("Your attempt to bandage fails.", FALSE, ch, 0, 0, TO_CHAR);
act("$n tries to bandage $N, but fails miserably.", TRUE, ch,
0, vict, TO_NOTVICT);
damage(vict, vict, 2, TYPE_SUFFERING);
gain_skill(ch, "bandage", FALSE);
return;
}
/* Success: heal 1d4 + WIS mod + proficiency */
int heal = dice(1, 4) + GET_ABILITY_MOD(GET_WIS(ch)) +
GET_PROFICIENCY(GET_SKILL(ch, SKILL_BANDAGE));
if (heal < 1) heal = 1;
GET_HIT(vict) = MIN(GET_MAX_HIT(vict), GET_HIT(vict) + heal);
act("You successfully bandage $N.", FALSE, ch, 0, vict, TO_CHAR);
act("$n bandages $N, who looks a bit better now.", TRUE, ch, 0,
vict, TO_NOTVICT);
act("Someone bandages you, and you feel a bit better now.",
FALSE, ch, 0, vict, TO_VICT);
GET_HIT(vict) = 0;
act("$n bandages $N, who looks a bit better now.", TRUE, ch, 0, vict, TO_NOTVICT);
act("Someone bandages you, and you feel a bit better now.", FALSE, ch, 0, vict, TO_VICT);
/* Apply the bandaged cooldown: 30 minutes real-time (1800 sec) */
struct affected_type af;
new_affect(&af);
/* Field name is 'spell' in your headers (not 'type') */
af.spell = SKILL_BANDAGE;
af.duration = 30 RL_SEC; /* 30 minutes real time */
af.modifier = 0;
af.location = APPLY_NONE;
/* bitvector is an array; clear then set the AFF_BANDAGED bit */
memset(af.bitvector, 0, sizeof(af.bitvector)); /* clear all bits; macro usually zeros the array */
SET_BIT_AR(af.bitvector, AFF_BANDAGED);
affect_to_char(vict, &af);
gain_skill(ch, "bandage", TRUE);
}

View file

@ -99,49 +99,72 @@ ACMD(do_not_here)
ACMD(do_sneak)
{
struct affected_type af;
int chance;
bool ok;
int rolla, rollb, roll, bonus, total, dc;
bool disadv = FALSE;
if (IS_NPC(ch) || !GET_SKILL(ch, SKILL_SNEAK)) {
send_to_char(ch, "You have no idea how to do that.\r\n");
return;
}
if (FIGHTING(ch)){
send_to_char(ch, "While fighting!?\r\n");
return;
} /* you can't sneak while in active melee */
send_to_char(ch, "Okay, you'll try to move silently for a while.\r\n");
/* Remove prior sneak affect if present (refresh logic) */
if (AFF_FLAGGED(ch, AFF_SNEAK))
affect_from_char(ch, SKILL_SNEAK);
/* Base chance: skill % + Dex-based adjustment */
chance = GET_SKILL(ch, SKILL_SNEAK) + dex_app_skill[GET_DEX(ch)].sneak;
if (chance < 0) chance = 0;
if (chance > 100) chance = 100;
/* --- 5e-style Stealth check (DEX + proficiency) --- */
bonus = GET_ABILITY_MOD(GET_DEX(ch)) +
GET_PROFICIENCY(GET_SKILL(ch, SKILL_SNEAK));
/* Apply disadvantage if heavy/bulky armor or flagged pieces */
if (has_stealth_disadv(ch))
ok = percent_success_disadv(chance);
else
ok = percent_success(chance);
dc = 10;
if (!ok) {
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;
if (total < dc) {
gain_skill(ch, "sneak", FALSE);
WAIT_STATE(ch, PULSE_VIOLENCE / 2);
GET_MOVE(ch) -= 10;
return;
}
/* Success: apply Sneak affect */
new_affect(&af);
af.spell = SKILL_SNEAK;
af.duration = GET_LEVEL(ch);
af.spell = SKILL_SNEAK;
af.location = APPLY_NONE;
af.modifier = 0;
af.duration = GET_LEVEL(ch); /* keep stock duration; adjust if desired */
memset(af.bitvector, 0, sizeof(af.bitvector));
SET_BIT_AR(af.bitvector, AFF_SNEAK);
affect_to_char(ch, &af);
/* Store a stealth check value for movement contests (reuse Hides field) */
/* 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);
GET_MOVE(ch) -= 10;
}
ACMD(do_hide)
{
int chance;
bool ok;
int rolla, rollb, roll, bonus, total, dc;
bool disadv = FALSE;
if (IS_NPC(ch) || !GET_SKILL(ch, SKILL_HIDE)) {
send_to_char(ch, "You have no idea how to do that.\r\n");
@ -150,29 +173,135 @@ ACMD(do_hide)
send_to_char(ch, "You attempt to hide yourself.\r\n");
if (AFF_FLAGGED(ch, AFF_HIDE))
/* If already hidden, drop it before re-attempting */
if (AFF_FLAGGED(ch, AFF_HIDE)) {
REMOVE_BIT_AR(AFF_FLAGS(ch), AFF_HIDE);
GET_STEALTH_CHECK(ch) = 0;
}
/* Base chance: skill % + Dex-based adjustment */
chance = GET_SKILL(ch, SKILL_HIDE) + dex_app_skill[GET_DEX(ch)].hide;
if (chance < 0) chance = 0;
if (chance > 100) chance = 100;
if (FIGHTING(ch)){
send_to_char(ch, "While fighting!?\r\n");
return;
} /* you can't hide while in active melee */
/* Apply disadvantage if heavy/bulky armor or flagged pieces */
if (has_stealth_disadv(ch))
ok = percent_success_disadv(chance);
else
ok = percent_success(chance);
/* --- 5e Stealth (DEX) ability check --- */
bonus = GET_ABILITY_MOD(GET_DEX(ch)) + GET_PROFICIENCY(GET_SKILL(ch, SKILL_HIDE));
if (!ok) {
/* 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;
if (total < dc) {
/* Failure */
gain_skill(ch, "hide", FALSE);
WAIT_STATE(ch, PULSE_VIOLENCE / 2);
GET_MOVE(ch) -= 10;
return;
}
/* Success */
/* Success: set flag and store this specific Stealth result */
SET_BIT_AR(AFF_FLAGS(ch), AFF_HIDE);
GET_STEALTH_CHECK(ch) = total;
send_to_char(ch, "You hide yourself as best you can.\r\n");
gain_skill(ch, "hide", TRUE);
WAIT_STATE(ch, PULSE_VIOLENCE / 2);
GET_MOVE(ch) -= 10;
}
/* Perception: scan the room for hidden creatures and objects */
ACMD(do_perception)
{
struct char_data *tch;
struct obj_data *obj, *next_obj;
int roll, bonus, total;
int found_chars = 0, found_objs = 0;
if (IS_NPC(ch) || !GET_SKILL(ch, SKILL_PERCEPTION)) {
send_to_char(ch, "You have no idea how to do that.\r\n");
return;
}
/* Roll once for this scan (active check) */
bonus = GET_ABILITY_MOD(GET_WIS(ch)) +
GET_PROFICIENCY(GET_SKILL(ch, SKILL_PERCEPTION));
roll = rand_number(1, 20);
total = roll + bonus;
/* Optional: its harder to actively scan while in melee */
if (FIGHTING(ch))
total -= 4;
/* --- Scan characters in the room (PCs & NPCs) --- */
for (tch = world[IN_ROOM(ch)].people; tch; tch = tch->next_in_room) {
if (tch == ch)
continue;
if (!AFF_FLAGGED(tch, AFF_HIDE))
continue;
/* Safety default if some legacy code set AFF_HIDE without a stored check */
if (GET_STEALTH_CHECK(tch) <= 0)
SET_STEALTH_CHECK(tch, 5);
if (total >= GET_STEALTH_CHECK(tch)) {
/* Spotted! Reveal them. */
REMOVE_BIT_AR(AFF_FLAGS(tch), AFF_HIDE);
SET_STEALTH_CHECK(tch, 0);
++found_chars;
act("You spot $N hiding!", FALSE, ch, 0, tch, TO_CHAR);
act("$n seems to look right at you — you've been spotted!", FALSE, ch, 0, tch, TO_VICT);
act("$n spots $N hiding nearby!", FALSE, ch, 0, tch, TO_NOTVICT);
}
}
/* --- Scan objects in the room (requires an ITEM_HIDDEN extra flag) --- */
for (obj = world[IN_ROOM(ch)].contents; obj; obj = next_obj) {
next_obj = obj->next_content;
/* If you don't have ITEM_HIDDEN yet, add it to your extra flags table and OBJ flag names. */
#ifdef ITEM_HIDDEN
if (OBJ_FLAGGED(obj, ITEM_HIDDEN)) {
/* Simple baseline DC for hidden objects; tune as desired or add per-object difficulty. */
int obj_dc = 12;
if (FIGHTING(ch)) obj_dc += 2;
if (total >= obj_dc) {
/* Reveal the object. */
REMOVE_BIT_AR(GET_OBJ_EXTRA(obj), ITEM_HIDDEN);
++found_objs;
act("You spot $p tucked out of sight.", FALSE, ch, obj, 0, TO_CHAR);
act("$n notices $p tucked out of sight.", FALSE, ch, obj, 0, TO_ROOM);
}
}
#endif
}
if (!found_chars && !found_objs) {
send_to_char(ch, "You search carefully but dont uncover anything hidden.\r\n");
gain_skill(ch, "perception", FALSE);
} else {
gain_skill(ch, "perception", TRUE);
}
/* Small action taxes do disincline players from spamming perception */
WAIT_STATE(ch, PULSE_VIOLENCE / 2);
GET_MOVE(ch) -= 10;
}
ACMD(do_steal)

View file

@ -893,6 +893,7 @@ void do_start(struct char_data *ch)
SET_SKILL(ch, SKILL_UNARMED, 5);
SET_SKILL(ch, SKILL_SHIELD_USE, 5);
SET_SKILL(ch, SKILL_PIERCING_WEAPONS, 5);
SET_SKILL(ch, SKILL_PERCEPTION, 5);
break;
case CLASS_WARRIOR:
@ -905,6 +906,7 @@ void do_start(struct char_data *ch)
SET_SKILL(ch, SKILL_PIERCING_WEAPONS, 5);
SET_SKILL(ch, SKILL_BLUDGEONING_WEAPONS, 5);
SET_SKILL(ch, SKILL_SHIELD_USE, 5);
SET_SKILL(ch, SKILL_PERCEPTION, 5);
break;
case CLASS_BARBARIAN:
@ -915,6 +917,7 @@ void do_start(struct char_data *ch)
SET_SKILL(ch, SKILL_UNARMED, 5);
SET_SKILL(ch, SKILL_PIERCING_WEAPONS, 5);
SET_SKILL(ch, SKILL_BLUDGEONING_WEAPONS, 5);
SET_SKILL(ch, SKILL_PERCEPTION, 5);
break;
case CLASS_RANGER:
@ -927,6 +930,7 @@ void do_start(struct char_data *ch)
SET_SKILL(ch, SKILL_SLASHING_WEAPONS, 5);
SET_SKILL(ch, SKILL_PIERCING_WEAPONS, 5);
SET_SKILL(ch, SKILL_SHIELD_USE, 5);
SET_SKILL(ch, SKILL_PERCEPTION, 5);
break;
case CLASS_BARD:
@ -938,6 +942,7 @@ void do_start(struct char_data *ch)
SET_SKILL(ch, SKILL_UNARMED, 5);
SET_SKILL(ch, SKILL_PIERCING_WEAPONS, 5);
SET_SKILL(ch, SKILL_SHIELD_USE, 5);
SET_SKILL(ch, SKILL_PERCEPTION, 5);
break;
case CLASS_DRUID:
@ -949,6 +954,7 @@ void do_start(struct char_data *ch)
SET_SKILL(ch, SKILL_UNARMED, 5);
SET_SKILL(ch, SKILL_PIERCING_WEAPONS, 5);
SET_SKILL(ch, SKILL_SHIELD_USE, 5);
SET_SKILL(ch, SKILL_PERCEPTION, 5);
break;
}
@ -1165,6 +1171,7 @@ void init_spell_levels(void)
spell_level(SKILL_UNARMED, CLASS_THIEF, 1);
spell_level(SKILL_PIERCING_WEAPONS, CLASS_THIEF, 1);
spell_level(SKILL_SHIELD_USE, CLASS_THIEF, 1);
spell_level(SKILL_PERCEPTION, CLASS_THIEF, 1);
/* WARRIORS */
spell_level(SKILL_KICK, CLASS_WARRIOR, 1);
@ -1176,6 +1183,7 @@ void init_spell_levels(void)
spell_level(SKILL_PIERCING_WEAPONS, CLASS_WARRIOR, 1);
spell_level(SKILL_BLUDGEONING_WEAPONS, CLASS_WARRIOR, 1);
spell_level(SKILL_SHIELD_USE, CLASS_WARRIOR, 1);
spell_level(SKILL_PERCEPTION, CLASS_WARRIOR, 1);
/* BARBARIANS */
spell_level(SKILL_KICK, CLASS_BARBARIAN, 1);
@ -1185,6 +1193,7 @@ void init_spell_levels(void)
spell_level(SKILL_UNARMED, CLASS_BARBARIAN, 1);
spell_level(SKILL_SLASHING_WEAPONS, CLASS_BARBARIAN, 1);
spell_level(SKILL_BLUDGEONING_WEAPONS, CLASS_BARBARIAN, 1);
spell_level(SKILL_PERCEPTION, CLASS_BARBARIAN, 1);
/* RANGERS */
spell_level(SKILL_SNEAK, CLASS_RANGER, 1);
@ -1196,6 +1205,7 @@ void init_spell_levels(void)
spell_level(SKILL_SLASHING_WEAPONS, CLASS_RANGER, 1);
spell_level(SKILL_PIERCING_WEAPONS, CLASS_RANGER, 1);
spell_level(SKILL_SHIELD_USE, CLASS_RANGER, 1);
spell_level(SKILL_PERCEPTION, CLASS_RANGER, 1);
/* BARDS */
spell_level(SPELL_ARMOR, CLASS_BARD, 1);
@ -1206,6 +1216,7 @@ void init_spell_levels(void)
spell_level(SKILL_UNARMED, CLASS_BARD, 1);
spell_level(SKILL_PIERCING_WEAPONS, CLASS_BARD, 1);
spell_level(SKILL_SHIELD_USE, CLASS_BARD, 1);
spell_level(SKILL_PERCEPTION, CLASS_BARD, 1);
/* DRUIDS */
spell_level(SPELL_DETECT_INVIS, CLASS_DRUID, 1);
@ -1216,6 +1227,7 @@ void init_spell_levels(void)
spell_level(SKILL_UNARMED, CLASS_DRUID, 1);
spell_level(SKILL_PIERCING_WEAPONS, CLASS_DRUID, 1);
spell_level(SKILL_SHIELD_USE, CLASS_DRUID, 1);
spell_level(SKILL_PERCEPTION, CLASS_DRUID, 1);
}
/* This is the exp given to implementors -- it must always be greater than the

View file

@ -167,10 +167,10 @@ int selfdelete_fastwipe = YES;
/* ROOM NUMBERS */
/* Virtual number of room that mortals should enter at. */
room_vnum mortal_start_room = 3001;
room_vnum mortal_start_room = 131;
/* Virtual number of room that immorts should enter at by default. */
room_vnum immort_start_room = 1204;
room_vnum immort_start_room = 1;
/* Virtual number of room that frozen players should enter at. */
room_vnum frozen_start_room = 1202;

View file

@ -3480,6 +3480,8 @@ void clear_char(struct char_data *ch)
GET_AC(ch) = 100; /* Basic Armor */
if (ch->points.max_mana < 100)
ch->points.max_mana = 100;
SET_STEALTH_CHECK(ch, 0);
}
void clear_object(struct obj_data *obj)
@ -3568,6 +3570,8 @@ void init_char(struct char_data *ch)
ch->real_abils.con = 25;
ch->real_abils.cha = 25;
SET_STEALTH_CHECK(ch, 0);
for (i = 0; i < 3; i++)
GET_COND(ch, i) = (GET_LEVEL(ch) == LVL_IMPL ? -1 : 24);

View file

@ -133,7 +133,6 @@ static const char *skill_name_for_gain(int skillnum) {
}
}
#define IS_WEAPON(type) (((type) >= TYPE_HIT) && ((type) < TYPE_SUFFERING))
/* The Fight related routines */
void appear(struct char_data *ch)

View file

@ -226,6 +226,7 @@ cpp_extern const struct command_info cmd_info[] = {
{ "pick" , "pi" , POS_STANDING, do_gen_door , 1, SCMD_PICK },
{ "page" , "pag" , POS_DEAD , do_page , 1, 0 },
{ "pardon" , "pardon" , POS_DEAD , do_wizutil , LVL_GOD, SCMD_PARDON },
{ "perception","per" , POS_RESTING , do_perception, 0, 0 },
{ "plist" , "plist" , POS_DEAD , do_plist , LVL_GOD, 0 },
{ "policy" , "pol" , POS_DEAD , do_gen_ps , 0, SCMD_POLICIES },
{ "pour" , "pour" , POS_STANDING, do_pour , 0, SCMD_POUR },

View file

@ -934,5 +934,6 @@ void mag_assign_spells(void) {
skillo(SKILL_PIERCING_WEAPONS, "piercing weapons");
skillo(SKILL_SLASHING_WEAPONS, "slashing weapons");
skillo(SKILL_BLUDGEONING_WEAPONS, "bludgeoning weapons");
skillo(SKILL_PERCEPTION, "perception");
}

View file

@ -116,6 +116,7 @@
#define SKILL_PIERCING_WEAPONS 144 /* Reserved Skill[] DO NOT CHANGE */
#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 */
/* New skills may be added here up to MAX_SKILLS (200) */

View file

@ -290,8 +290,9 @@
#define AFF_HIDE 20 /**< Char is hidden */
#define AFF_FREE 21 /**< Room for future expansion */
#define AFF_CHARM 22 /**< Char is charmed */
#define AFF_BANDAGED 23 /**< Character was bandaged recently */
/** Total number of affect flags */
#define NUM_AFF_FLAGS 23
#define NUM_AFF_FLAGS 24
/* Modes of connectedness: used by descriptor_data.state */
#define CON_PLAYING 0 /**< Playing - Nominal state */
@ -936,6 +937,7 @@ struct char_special_data
int carry_weight; /**< Carried weight */
byte carry_items; /**< Number of items carried */
int timer; /**< Timer for update */
int stealth_check; /* last rolled Stealth value for Hide; 0 = not hiding/opposed */
struct char_special_data_saved saved; /**< Constants saved for PCs. */
};
@ -1296,6 +1298,10 @@ struct recent_player
#define VAL_ARMOR_DURABILITY 4
#define VAL_ARMOR_STR_REQ 5
/* Helper macros */
#define GET_STEALTH_CHECK(ch) ((ch)->char_specials.stealth_check)
#define SET_STEALTH_CHECK(ch,v) ((ch)->char_specials.stealth_check = (v))
/* Config structs */
/** The game configuration structure used for configurating the game play

View file

@ -1638,7 +1638,7 @@ int GET_PROFICIENCY(int pct) {
}
/* Forward declaration */
int situational_ac_mods(struct char_data *ch);
int GET_SITUATIONAL_AC(struct char_data *ch);
void compute_ac_breakdown(struct char_data *ch, struct ac_breakdown *out)
{
@ -1688,7 +1688,7 @@ void compute_ac_breakdown(struct char_data *ch, struct ac_breakdown *out)
}
/* Situational */
out->situational = situational_ac_mods(ch);
out->situational = GET_SITUATIONAL_AC(ch);
/* Total */
out->total = out->base
@ -1707,7 +1707,8 @@ int compute_ascending_ac(struct char_data *ch)
}
/* Stub: situational AC mods */
int situational_ac_mods(struct char_data *ch)
/* Placeholder for future skill/spell enhancements such as Haste */
int GET_SITUATIONAL_AC(struct char_data *ch)
{
int mod = 0;

View file

@ -90,7 +90,7 @@ struct ac_breakdown {
int GET_ABILITY_MOD(int score);
int GET_PROFICIENCY(int pct);
int compute_ascending_ac(struct char_data *ch);
int situational_ac_mods(struct char_data *ch);
int GET_SITUATIONAL_AC(struct char_data *ch);
int compute_armor_class_asc(struct char_data *ch);
void compute_ac_breakdown(struct char_data *ch, struct ac_breakdown *out);