diff --git a/src/act.h b/src/act.h index cf98b1b..fc32587 100644 --- a/src/act.h +++ b/src/act.h @@ -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); diff --git a/src/act.offensive.c b/src/act.offensive.c index 2ba3662..9577923 100644 --- a/src/act.offensive.c +++ b/src/act.offensive.c @@ -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 1–4 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 don’t 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); } diff --git a/src/act.other.c b/src/act.other.c index ec5e6c0..73e8f3f 100644 --- a/src/act.other.c +++ b/src/act.other.c @@ -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 Hide’s field) */ + /* If you’ve 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: it’s 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 don’t 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) diff --git a/src/class.c b/src/class.c index 4195a01..f648b3c 100644 --- a/src/class.c +++ b/src/class.c @@ -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 diff --git a/src/config.c b/src/config.c index 2690559..217894f 100644 --- a/src/config.c +++ b/src/config.c @@ -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; diff --git a/src/db.c b/src/db.c index 063d456..d7b2bf1 100644 --- a/src/db.c +++ b/src/db.c @@ -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); diff --git a/src/fight.c b/src/fight.c index 8cfe6f6..70e4b62 100644 --- a/src/fight.c +++ b/src/fight.c @@ -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) diff --git a/src/interpreter.c b/src/interpreter.c index 7ceb6a9..2ebd735 100644 --- a/src/interpreter.c +++ b/src/interpreter.c @@ -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 }, diff --git a/src/spell_parser.c b/src/spell_parser.c index c1d2e1b..41b939d 100644 --- a/src/spell_parser.c +++ b/src/spell_parser.c @@ -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"); } diff --git a/src/spells.h b/src/spells.h index e40a59e..c539f36 100644 --- a/src/spells.h +++ b/src/spells.h @@ -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) */ diff --git a/src/structs.h b/src/structs.h index bd96033..4e35bc3 100644 --- a/src/structs.h +++ b/src/structs.h @@ -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 diff --git a/src/utils.c b/src/utils.c index 21de05f..309f005 100644 --- a/src/utils.c +++ b/src/utils.c @@ -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; diff --git a/src/utils.h b/src/utils.h index f1a8a83..6b18df5 100644 --- a/src/utils.h +++ b/src/utils.h @@ -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);