Fix saving throw bonus logic

This commit is contained in:
kinther 2025-12-14 14:56:09 -08:00
parent 6a743b5276
commit ff5f254fce
7 changed files with 109 additions and 36 deletions

View file

@ -1274,14 +1274,15 @@ static void do_stat_character(struct char_data *ch, struct char_data *k)
CCCYN(ch, C_NRM), GET_DEX(k), CCNRM(ch, C_NRM),
CCCYN(ch, C_NRM), GET_CON(k), CCNRM(ch, C_NRM),
CCCYN(ch, C_NRM), GET_CHA(k), CCNRM(ch, C_NRM));
send_to_char(ch, "Saving Throws: Str: [%s%d%s] Int: [%s%d%s] Wis: [%s%d%s] "
"Dex: [%s%d%s] Con: [%s%d%s] Cha: [%s%d%s]\r\n",
CCCYN(ch, C_NRM), GET_SAVE(k, ABIL_STR), CCNRM(ch, C_NRM),
CCCYN(ch, C_NRM), GET_SAVE(k, ABIL_DEX), CCNRM(ch, C_NRM),
CCCYN(ch, C_NRM), GET_SAVE(k, ABIL_CON), CCNRM(ch, C_NRM),
CCCYN(ch, C_NRM), GET_SAVE(k, ABIL_INT), CCNRM(ch, C_NRM),
CCCYN(ch, C_NRM), GET_SAVE(k, ABIL_WIS), CCNRM(ch, C_NRM),
CCCYN(ch, C_NRM), GET_SAVE(k, ABIL_CHA), CCNRM(ch, C_NRM));
send_to_char(ch, "Saving Throws: Str: [%s%+d%s (%+d)] Dex: [%s%+d%s (%+d)] "
"Con: [%s%+d%s (%+d)] Int: [%s%+d%s (%+d)] Wis: [%s%+d%s (%+d)] "
"Cha: [%s%+d%s (%+d)]\r\n",
CCCYN(ch, C_NRM), get_save_mod(k, ABIL_STR), CCNRM(ch, C_NRM), GET_SAVE(k, ABIL_STR),
CCCYN(ch, C_NRM), get_save_mod(k, ABIL_DEX), CCNRM(ch, C_NRM), GET_SAVE(k, ABIL_DEX),
CCCYN(ch, C_NRM), get_save_mod(k, ABIL_CON), CCNRM(ch, C_NRM), GET_SAVE(k, ABIL_CON),
CCCYN(ch, C_NRM), get_save_mod(k, ABIL_INT), CCNRM(ch, C_NRM), GET_SAVE(k, ABIL_INT),
CCCYN(ch, C_NRM), get_save_mod(k, ABIL_WIS), CCNRM(ch, C_NRM), GET_SAVE(k, ABIL_WIS),
CCCYN(ch, C_NRM), get_save_mod(k, ABIL_CHA), CCNRM(ch, C_NRM), GET_SAVE(k, ABIL_CHA));
send_to_char(ch, "Hit p.:[%s%d/%d+%d%s] Mana p.:[%s%d/%d+%d%s] Move p.:[%s%d/%d+%d%s]\r\n",
CCGRN(ch, C_NRM), GET_HIT(k), GET_MAX_HIT(k), hit_gain(k), CCNRM(ch, C_NRM),

View file

@ -21,6 +21,7 @@ int invalid_class(struct char_data *ch, struct obj_data *obj);
int level_exp(int chclass, int level);
int parse_class(char arg);
void roll_real_abils(struct char_data *ch);
bool has_save_proficiency(int class_num, int ability);
/* Global variables */

View file

@ -29,29 +29,22 @@ static int mag_materials(struct char_data *ch, IDXTYPE item0, IDXTYPE item1, IDX
static void perform_mag_groups(int level, struct char_data *ch, struct char_data *tch, int spellnum, int savetype);
/* Negative apply_saving_throw[] values make saving throws better! So do
* negative modifiers. Though people may be used to the reverse of that.
* It's due to the code modifying the target saving throw instead of the
* random number of the character as in some other systems. */
int mag_savingthrow(struct char_data *ch, int type, int modifier)
/* Roll a 5e-style saving throw: d20 + ability mod + proficiency vs DC. */
int mag_savingthrow(struct char_data *ch, int ability, int dc)
{
/* NPCs use fighter tables according to some book */
int class_sav = CLASS_FIGHTER;
int save;
int roll, total, target;
if (!IS_NPC(ch))
class_sav = GET_CLASS(ch);
if (!ch)
return FALSE;
save = GET_SAVE(ch, type);
save += GET_SAVE(ch, type);
save += modifier;
roll = roll_d20();
total = roll + get_save_mod(ch, ability);
target = MAX(1, dc);
/* Throwing a 0 is always a failure. */
if (MAX(1, save) < rand_number(0, 99))
return (TRUE);
if (total >= target)
return TRUE;
/* Oops, failed. Sorry. */
return (FALSE);
return FALSE;
}
/* affect_update: called from comm.c (causes spells to wear off) */
@ -287,7 +280,7 @@ int mag_damage(int level, struct char_data *ch, struct char_data *victim,
/* divide damage by two if victim makes his saving throw */
if (mag_savingthrow(victim, savetype, 0))
if (mag_savingthrow(victim, savetype, compute_save_dc(ch, level, spellnum)))
dam /= 2;
/* and finally, inflict the damage */
@ -307,11 +300,14 @@ void mag_affects(int level, struct char_data *ch, struct char_data *victim,
bool accum_affect = FALSE, accum_duration = FALSE;
const char *to_vict = NULL, *to_room = NULL;
int i, j;
int save_dc;
if (victim == NULL || ch == NULL)
return;
save_dc = compute_save_dc(ch, level, spellnum);
for (i = 0; i < MAX_SPELL_AFFECTS; i++) {
new_affect(&(af[i]));
af[i].spell = spellnum;
@ -321,7 +317,7 @@ void mag_affects(int level, struct char_data *ch, struct char_data *victim,
case SPELL_CHILL_TOUCH:
af[0].location = APPLY_STR;
if (mag_savingthrow(victim, ABIL_CON, savetype))
if (mag_savingthrow(victim, ABIL_CON, save_dc))
af[0].duration = 1; /* resisted: brief weakening */
else
af[0].duration = 4; /* failed: longer effect */
@ -354,7 +350,7 @@ void mag_affects(int level, struct char_data *ch, struct char_data *victim,
case SPELL_BLINDNESS:
if (MOB_FLAGGED(victim, MOB_NOBLIND) || GET_LEVEL(victim) >= LVL_IMMORT ||
mag_savingthrow(victim, ABIL_CON, savetype)) {
mag_savingthrow(victim, ABIL_CON, save_dc)) {
send_to_char(ch, "You fail.\r\n");
return;
}
@ -374,7 +370,7 @@ void mag_affects(int level, struct char_data *ch, struct char_data *victim,
break;
case SPELL_CURSE:
if (mag_savingthrow(victim, ABIL_WIS, savetype)) {
if (mag_savingthrow(victim, ABIL_WIS, save_dc)) {
send_to_char(ch, "%s", CONFIG_NOEFFECT);
return;
}
@ -445,7 +441,7 @@ void mag_affects(int level, struct char_data *ch, struct char_data *victim,
break;
case SPELL_POISON:
if (mag_savingthrow(victim, ABIL_CON, savetype)) {
if (mag_savingthrow(victim, ABIL_CON, save_dc)) {
send_to_char(ch, "%s", CONFIG_NOEFFECT);
return;
}
@ -478,7 +474,7 @@ void mag_affects(int level, struct char_data *ch, struct char_data *victim,
return;
if (MOB_FLAGGED(victim, MOB_NOSLEEP))
return;
if (mag_savingthrow(victim, ABIL_WIS, savetype))
if (mag_savingthrow(victim, ABIL_WIS, save_dc))
return;
af[0].duration = 4 + (GET_LEVEL(ch) / 4);

View file

@ -106,9 +106,13 @@ ASPELL(spell_teleport)
#define SUMMON_FAIL "You failed.\r\n"
ASPELL(spell_summon)
{
int save_dc;
if (ch == NULL || victim == NULL)
return;
save_dc = compute_save_dc(ch, level, SPELL_SUMMON);
if (GET_LEVEL(victim) > MIN(LVL_IMMORT - 1, level + 3)) {
send_to_char(ch, "%s", SUMMON_FAIL);
return;
@ -143,7 +147,7 @@ ASPELL(spell_summon)
}
if (MOB_FLAGGED(victim, MOB_NOSUMMON) ||
(IS_NPC(victim) && mag_savingthrow(victim, SAVING_CHA, 0))) {
(IS_NPC(victim) && mag_savingthrow(victim, SAVING_CHA, save_dc))) {
send_to_char(ch, "%s", SUMMON_FAIL);
return;
}
@ -248,10 +252,13 @@ ASPELL(spell_locate_object)
ASPELL(spell_charm)
{
struct affected_type af;
int save_dc;
if (victim == NULL || ch == NULL)
return;
save_dc = compute_save_dc(ch, level, SPELL_CHARM);
if (victim == ch)
send_to_char(ch, "You like yourself even better!\r\n");
else if (!IS_NPC(victim) && !PRF_FLAGGED(victim, PRF_SUMMONABLE))
@ -269,7 +276,7 @@ ASPELL(spell_charm)
send_to_char(ch, "You fail - shouldn't be doing it anyway.\r\n");
else if (circle_follow(victim, ch))
send_to_char(ch, "Sorry, following in circles is not allowed.\r\n");
else if (mag_savingthrow(victim, SAVING_WIS, 0))
else if (mag_savingthrow(victim, SAVING_WIS, save_dc))
send_to_char(ch, "Your victim resists!\r\n");
else {
if (victim->master)

View file

@ -286,7 +286,7 @@ void init_spell_levels(void);
const char *skill_name(int num);
/* From magic.c */
int mag_savingthrow(struct char_data *ch, int type, int modifier);
int mag_savingthrow(struct char_data *ch, int ability, int dc);
void affect_update(void);
/* from spell_parser.c */

View file

@ -1640,6 +1640,70 @@ int GET_ABILITY_MOD(int score) {
return mod;
}
/* Helper: derive a class-level proficiency bonus (no situational modifiers). */
int get_level_proficiency_bonus(struct char_data *ch)
{
int level;
int bonus;
if (!ch)
return 0;
level = MAX(1, GET_LEVEL(ch));
bonus = 2 + ((level - 1) / 4);
if (bonus > 6)
bonus = 6;
return bonus;
}
int get_total_proficiency_bonus(struct char_data *ch)
{
if (!ch)
return 0;
return get_level_proficiency_bonus(ch) + GET_PROF_MOD(ch);
}
static int get_ability_mod_from_index(struct char_data *ch, int ability)
{
if (!ch)
return 0;
switch (ability) {
case ABIL_STR: return GET_ABILITY_MOD(GET_STR(ch));
case ABIL_DEX: return GET_ABILITY_MOD(GET_DEX(ch));
case ABIL_CON: return GET_ABILITY_MOD(GET_CON(ch));
case ABIL_INT: return GET_ABILITY_MOD(GET_INT(ch));
case ABIL_WIS: return GET_ABILITY_MOD(GET_WIS(ch));
case ABIL_CHA: return GET_ABILITY_MOD(GET_CHA(ch));
default: return 0;
}
}
int get_save_mod(struct char_data *ch, int ability)
{
int mod;
if (!ch)
return 0;
mod = GET_SAVE(ch, ability);
mod += get_ability_mod_from_index(ch, ability);
if (has_save_proficiency(GET_CLASS(ch), ability))
mod += get_total_proficiency_bonus(ch);
return mod;
}
int compute_save_dc(struct char_data *caster, int level, int spellnum)
{
if (caster)
return GET_SPELL_SAVE_DC(caster, spellnum, 0);
/* Non-caster fallback for scrolls/wands/etc. */
return MAX(1, 8 + (level / 2));
}
/* Converts a skill percentage (0-100) into a 5e-like proficiency bonus. */
int GET_PROFICIENCY(int pct) {
if (pct <= 14) return 0;
@ -1799,4 +1863,4 @@ struct mob_loadout *loadout_deep_copy(const struct mob_loadout *src) {
else { tail->next = n; tail = n; }
}
return head;
}
}

View file

@ -95,6 +95,10 @@ struct ac_breakdown {
int GET_ABILITY_MOD(int score);
int GET_PROFICIENCY(int pct);
int get_level_proficiency_bonus(struct char_data *ch);
int get_total_proficiency_bonus(struct char_data *ch);
int get_save_mod(struct char_data *ch, int ability);
int compute_save_dc(struct char_data *caster, int level, int spellnum);
int compute_ascending_ac(struct char_data *ch);
int GET_SITUATIONAL_AC(struct char_data *ch);
int compute_armor_class_asc(struct char_data *ch);