diff --git a/src/act.informative.c b/src/act.informative.c index 86498cc..4611ac1 100644 --- a/src/act.informative.c +++ b/src/act.informative.c @@ -828,26 +828,25 @@ ACMD(do_score) send_to_char(ch, "STR %2d (%+d) DEX %2d (%+d) CON %2d (%+d)\r\n" "INT %2d (%+d) WIS %2d (%+d) CHA %2d (%+d)\r\n", - GET_STR(ch), ability_mod(GET_STR(ch)), - GET_DEX(ch), ability_mod(GET_DEX(ch)), - GET_CON(ch), ability_mod(GET_CON(ch)), - GET_INT(ch), ability_mod(GET_INT(ch)), - GET_WIS(ch), ability_mod(GET_WIS(ch)), - GET_CHA(ch), ability_mod(GET_CHA(ch))); + GET_STR(ch), GET_ABILITY_MOD(GET_STR(ch)), + GET_DEX(ch), GET_ABILITY_MOD(GET_DEX(ch)), + GET_CON(ch), GET_ABILITY_MOD(GET_CON(ch)), + GET_INT(ch), GET_ABILITY_MOD(GET_INT(ch)), + GET_WIS(ch), GET_ABILITY_MOD(GET_WIS(ch)), + GET_CHA(ch), GET_ABILITY_MOD(GET_CHA(ch))); /* Ascending AC breakdown */ send_to_char(ch, "\r\n" "Armor Class (ascending): %d\r\n" " base: %d, armor: %d, armor magic: +%d, DEX (cap %d): %+d,\r\n" - " shield: +%d, situational: %+d, bulk score: %d\r\n", + " situational: %+d, bulk score: %d\r\n", acb.total, acb.base, acb.armor_piece_sum, acb.armor_magic_sum, acb.dex_cap, acb.dex_mod_applied, - acb.shield_bonus, acb.situational, acb.total_bulk); diff --git a/src/class.c b/src/class.c index 9496e9f..4195a01 100644 --- a/src/class.c +++ b/src/class.c @@ -886,6 +886,7 @@ void do_start(struct char_data *ch) case CLASS_THIEF: SET_SKILL(ch, SKILL_SNEAK, 5); SET_SKILL(ch, SKILL_HIDE, 5); + SET_SKILL(ch, SKILL_TRACK, 5); SET_SKILL(ch, SKILL_STEAL, 5); SET_SKILL(ch, SKILL_BACKSTAB, 5); SET_SKILL(ch, SKILL_PICK_LOCK, 5); diff --git a/src/constants.c b/src/constants.c index 394c59e..e484349 100644 --- a/src/constants.c +++ b/src/constants.c @@ -950,7 +950,9 @@ const char *ibt_bits[] = { /* 5e system helpers */ -/* Armor slot table for ascending AC rules */ +/* Armor slot table for ascending AC rules +* Fields are AC, bulk, magic cap +*/ const struct armor_slot armor_slots[] = { { "head", 2, 1, 1 }, { "body", 3, 3, 3 }, @@ -958,19 +960,22 @@ const struct armor_slot armor_slots[] = { { "arms", 1, 1, 1 }, { "hands", 1, 1, 1 }, { "feet", 1, 1, 1 }, - /* shield handled separately in compute_ascending_ac() */ + { "right wrist", 1, 1, 1 }, + { "left wrist", 1, 1, 1 }, }; const int NUM_ARMOR_SLOTS = sizeof(armor_slots) / sizeof(armor_slots[0]); /* Wear-position mapping for armor_slots[] order */ const int ARMOR_WEAR_POSITIONS[] = { - WEAR_HEAD, /* "head" */ - WEAR_BODY, /* "body" */ - WEAR_LEGS, /* "legs" */ - WEAR_ARMS, /* "arms" */ - WEAR_HANDS, /* "hands" */ - WEAR_FEET /* "feet" */ + WEAR_HEAD, /* "head" */ + WEAR_BODY, /* "body" */ + WEAR_LEGS, /* "legs" */ + WEAR_ARMS, /* "arms" */ + WEAR_HANDS, /* "hands" */ + WEAR_FEET, /* "feet" */ + WEAR_WRIST_R, /* "right wrist" */ + WEAR_WRIST_L /* "left wrist" */ }; /* Armor flag names for obj->value[3] */ diff --git a/src/fight.c b/src/fight.c index 9c81131..6ccfe10 100644 --- a/src/fight.c +++ b/src/fight.c @@ -75,11 +75,11 @@ static int roll_damage(struct char_data *ch, struct char_data *victim, int ndice = GET_OBJ_VAL(wielded, 1); /* #dice */ int sdice = GET_OBJ_VAL(wielded, 2); /* sides */ dam = dice(ndice, sdice); - dam += ability_mod(GET_STR(ch)); /* STR adds to weapon damage */ + dam += GET_ABILITY_MOD(GET_STR(ch)); /* STR adds to weapon damage */ } else { /* unarmed */ dam = dice(1, 2); - dam += ability_mod(GET_STR(ch)); + dam += GET_ABILITY_MOD(GET_STR(ch)); } if (dam < 0) dam = 0; @@ -848,7 +848,7 @@ void hit(struct char_data *ch, struct char_data *victim, int type) d20 = rand_number(1, 20); /* Ability modifier: STR only (no ranged types yet) */ - attack_mod += ability_mod(GET_STR(ch)); + attack_mod += GET_ABILITY_MOD(GET_STR(ch)); /* Skill family & proficiency */ { @@ -856,7 +856,7 @@ void hit(struct char_data *ch, struct char_data *victim, int type) const char *skillname = skill_name_for_gain(skillnum); /* maps to "unarmed", "piercing weapons", etc. */ /* proficiency from current % */ - attack_mod += prof_from_skill(GET_SKILL(ch, skillnum)); + attack_mod += GET_PROFICIENCY(GET_SKILL(ch, skillnum)); /* Weapon magic (cap +3) */ if (wielded && GET_OBJ_TYPE(wielded) == ITEM_WEAPON) { diff --git a/src/tests/sim_5e.c b/src/tests/sim_5e.c index a980b03..86c2ab3 100644 --- a/src/tests/sim_5e.c +++ b/src/tests/sim_5e.c @@ -1,4 +1,4 @@ -/* tests/sim_5e.c — quick simulations for 5e-like tuning (fixed RNG + bounds) */ +/* tests/sim_5e.c — quick simulations for 5e-like tuning */ #include #include #include @@ -30,7 +30,7 @@ static void init_test_char(struct char_data *ch) { memset(ch, 0, sizeof(*ch)); /* ensure skills and other saved fields exist if GET_SKILL() dereferences */ ch->player_specials = calloc(1, sizeof(struct player_special_data)); - /* if your tree uses player_specials->saved, calloc above keeps it zeroed */ + /* if the tree uses player_specials->saved, calloc above keeps it zeroed */ ch->in_room = 0; /* park them in room #0 (we'll make a stub room below) */ } @@ -98,36 +98,43 @@ static int duel_rounds(int atk_mod, int ndice, int sdice, int att_str_mod, return (int) floor((double)rounds_sum / (double)trials); } -/* build three defenders with your real slot weights and caps */ +/* build three defenders with real slot weights and caps */ static void build_light(struct char_data *ch) { init_test_char(ch); set_ability_scores(ch, 10, 18, 10, 10, 10, 10); - equip_at(ch, WEAR_HEAD, make_armor(1,1,1,0)); - equip_at(ch, WEAR_BODY, make_armor(2,1,2,0)); + equip_at(ch, WEAR_HEAD, make_armor(1,1,0,0)); + equip_at(ch, WEAR_BODY, make_armor(1,1,0,0)); + equip_at(ch, WEAR_LEGS, make_armor(1,2,0,0)); + equip_at(ch, WEAR_FEET, make_armor(1,1,0,0)); } static void build_medium(struct char_data *ch) { init_test_char(ch); set_ability_scores(ch, 10, 18, 10, 10, 10, 10); - equip_at(ch, WEAR_LEGS, make_armor(2,2,2,0)); - equip_at(ch, WEAR_HANDS, make_armor(1,1,0,0)); - equip_at(ch, WEAR_FEET, make_armor(1,1,0,0)); + equip_at(ch, WEAR_HEAD, make_armor(2,1,0,0)); + equip_at(ch, WEAR_BODY, make_armor(2,2,1,0)); + equip_at(ch, WEAR_LEGS, make_armor(2,2,0,0)); + equip_at(ch, WEAR_HANDS, make_armor(1,1,0,0)); + equip_at(ch, WEAR_FEET, make_armor(1,1,0,0)); } -static void build_heavy(struct char_data *ch, int shield_magic) { +static void build_heavy(struct char_data *ch) { init_test_char(ch); set_ability_scores(ch, 10, 18, 10, 10, 10, 10); - equip_at(ch, WEAR_BODY, make_armor(3,3,3,0)); - equip_at(ch, WEAR_LEGS, make_armor(2,2,3,0)); - struct obj_data *shield = make_armor(0,0,shield_magic,0); - equip_at(ch, WEAR_SHIELD, shield); + equip_at(ch, WEAR_HEAD, make_armor(2,1,1,0)); + equip_at(ch, WEAR_BODY, make_armor(3,3,1,0)); + equip_at(ch, WEAR_LEGS, make_armor(2,1,1,0)); + equip_at(ch, WEAR_ARMS, make_armor(1,1,0,0)); + equip_at(ch, WEAR_HANDS, make_armor(1,1,0,0)); + equip_at(ch, WEAR_FEET, make_armor(1,1,0,0)); + equip_at(ch, WEAR_WRIST_L, make_armor(1,1,0,0)); + equip_at(ch, WEAR_WRIST_R, make_armor(1,1,0,0)); } - /* attacker profiles: compute attack_mod = STRmod + prof(skill%) + weapon_magic */ static int compute_attack_mod(int str_score, int skill_pct, int weapon_magic) { - int mod = ability_mod(str_score); - mod += prof_from_skill(skill_pct); + int mod = GET_ABILITY_MOD(str_score); + mod += GET_PROFICIENCY(skill_pct); if (weapon_magic > MAX_WEAPON_MAGIC) weapon_magic = MAX_WEAPON_MAGIC; mod += weapon_magic; if (mod > MAX_TOTAL_ATTACK_BONUS) mod = MAX_TOTAL_ATTACK_BONUS; @@ -152,32 +159,27 @@ int main(void) { } printf("\n"); - /* 2) Real defenders AC using your compute_ac_breakdown */ - struct char_data light, medium, heavy0, heavy3; + /* 2) Real defenders AC using compute_ac_breakdown */ + struct char_data light, medium, heavy; build_light(&light); build_medium(&medium); - build_heavy(&heavy0, 0); - build_heavy(&heavy3, 5); /* requests +5 but shield path clamps to +3 */ + build_heavy(&heavy); - struct ac_breakdown bl, bm, bh0, bh3; + struct ac_breakdown bl, bm, bh0; compute_ac_breakdown(&light, &bl); compute_ac_breakdown(&medium, &bm); - compute_ac_breakdown(&heavy0, &bh0); - compute_ac_breakdown(&heavy3, &bh3); + compute_ac_breakdown(&heavy, &bh0); printf("Defender AC breakdowns:\n"); - printf(" Light : total=%d (base=%d armor=%d magic=%d dexCap=%d dex=%+d shield=%d situ=%+d bulk=%d)\n", + printf(" Light : total=%d (base=%d armor=%d magic=%d dexCap=%d dex=%+d situ=%+d bulk=%d)\n", bl.total, bl.base, bl.armor_piece_sum, bl.armor_magic_sum, bl.dex_cap, - bl.dex_mod_applied, bl.shield_bonus, bl.situational, bl.total_bulk); - printf(" Medium: total=%d (base=%d armor=%d magic=%d dexCap=%d dex=%+d shield=%d situ=%+d bulk=%d)\n", + bl.dex_mod_applied, bl.situational, bl.total_bulk); + printf(" Medium: total=%d (base=%d armor=%d magic=%d dexCap=%d dex=%+d situ=%+d bulk=%d)\n", bm.total, bm.base, bm.armor_piece_sum, bm.armor_magic_sum, bm.dex_cap, - bm.dex_mod_applied, bm.shield_bonus, bm.situational, bm.total_bulk); - printf(" Heavy : total=%d (base=%d armor=%d magic=%d dexCap=%d dex=%+d shield=%d situ=%+d bulk=%d)\n", + bm.dex_mod_applied, bm.situational, bm.total_bulk); + printf(" Heavy : total=%d (base=%d armor=%d magic=%d dexCap=%d dex=%+d situ=%+d bulk=%d)\n", bh0.total, bh0.base, bh0.armor_piece_sum, bh0.armor_magic_sum, bh0.dex_cap, - bh0.dex_mod_applied, bh0.shield_bonus, bh0.situational, bh0.total_bulk); - printf(" Heavy+(sh+3 cap): total=%d (base=%d armor=%d magic=%d dexCap=%d dex=%+d shield=%d situ=%+d bulk=%d)\n\n", - bh3.total, bh3.base, bh3.armor_piece_sum, bh3.armor_magic_sum, bh3.dex_cap, - bh3.dex_mod_applied, bh3.shield_bonus, bh3.situational, bh3.total_bulk); + bh0.dex_mod_applied, bh0.situational, bh0.total_bulk); /* 3) Attacker profiles vs defenders (TTK & hit%, 1d8 weapon) */ struct { int str; int skill; int wmag; const char *name; } atk[] = { @@ -188,16 +190,15 @@ int main(void) { struct { struct char_data *def; const char *name; int hp; } def[] = { { &light, "Light", 40 }, { &medium, "Medium", 50 }, - { &heavy0, "Heavy", 60 }, - { &heavy3, "Heavy+Shield+3", 60 }, + { &heavy, "Heavy", 60 }, }; printf("Matchups (trials=20000, 1d8 weapon):\n"); for (size_t i=0;i attack_mod %+d (STRmod %+d, prof %+d, wm %+d)\n", - atk[i].name, atk_mod, str_mod, prof_from_skill(atk[i].skill), MIN(atk[i].wmag, MAX_WEAPON_MAGIC)); + atk[i].name, atk_mod, str_mod, GET_PROFICIENCY(atk[i].skill), MIN(atk[i].wmag, MAX_WEAPON_MAGIC)); for (size_t j=0;j #include "conf.h" @@ -53,9 +44,8 @@ static void equip_at(struct char_data *ch, int wear_pos, struct obj_data *o) { ch->equipment[wear_pos] = o; } -/* Set an ability score (helpers for readability) — adjust if your tree uses different fields. */ +/* Set an ability score (helpers for readability) */ static void set_ability_scores(struct char_data *ch, int str, int dex, int con, int intel, int wis, int cha) { - /* real_abils is standard in Circle; if your tree differs, tweak these lines */ ch->real_abils.str = str; ch->real_abils.dex = dex; ch->real_abils.con = con; @@ -81,29 +71,29 @@ static double simulate_hit_rate(int attack_mod, int target_ac, int trials) { /* Dump a breakdown (useful when a test fails) */ static void dbg_dump_ac(const char *label, struct ac_breakdown *b) { - fprintf(stderr, "%s: total=%d (base=%d armor=%d magic=%d dexCap=%d dex=%d shield=%d situ=%d bulk=%d)\n", + fprintf(stderr, "%s: total=%d (base=%d armor=%d magic=%d dexCap=%d dex=%d situ=%d bulk=%d)\n", label, b->total, b->base, b->armor_piece_sum, b->armor_magic_sum, - b->dex_cap, b->dex_mod_applied, b->shield_bonus, b->situational, b->total_bulk); + b->dex_cap, b->dex_mod_applied, b->situational, b->total_bulk); } /* ---------- TESTS ---------- */ -static void test_ability_mod(void) { +static void test_GET_ABILITY_MOD(void) { /* Spot-check classic 5e values and a sweep */ - T_EQI(ability_mod(10), 0, "ability_mod(10)"); - T_EQI(ability_mod(8), -1, "ability_mod(8)"); - T_EQI(ability_mod(12), 1, "ability_mod(12)"); - T_EQI(ability_mod(18), 4, "ability_mod(18)"); - T_EQI(ability_mod(1), -5, "ability_mod(1)"); + T_EQI(GET_ABILITY_MOD(10), 0, "GET_ABILITY_MOD(10)"); + T_EQI(GET_ABILITY_MOD(8), -1, "GET_ABILITY_MOD(8)"); + T_EQI(GET_ABILITY_MOD(12), 1, "GET_ABILITY_MOD(12)"); + T_EQI(GET_ABILITY_MOD(18), 4, "GET_ABILITY_MOD(18)"); + T_EQI(GET_ABILITY_MOD(1), -5, "GET_ABILITY_MOD(1)"); /* sweep 1..30 vs floor((s-10)/2) */ for (int s = 1; s <= 30; ++s) { int expect = (int)floor((s - 10) / 2.0); - T_EQI(ability_mod(s), expect, "ability_mod sweep"); + T_EQI(GET_ABILITY_MOD(s), expect, "GET_ABILITY_MOD sweep"); } } -static void test_prof_from_skill(void) { - /* Boundaries for your <= mapping: 0..14→0, 15..29→+1, ... 91..100→+6 */ +static void test_GET_PROFICIENCY(void) { + /* Boundaries for <= mapping: 0..14→0, 15..29→+1, ... 91..100→+6 */ struct { int pct, expect; const char *lbl; } cases[] = { { 0, 0, "0→0"}, {14,0,"14→0"}, {15,1,"15→1"}, {29,1,"29→1"}, @@ -114,7 +104,7 @@ static void test_prof_from_skill(void) { {91,6,"91→6"}, {100,6,"100→6"} }; for (size_t i=0;i +1), body bulk 1 (wt 3 => +3), total 4 => Light. - * Dex 18 => +4 fully applied. - * Armor piece AC: head=1, body=2 => sum = 3. - * Magic: head+1, body+2 => per-slot ok, total 3 (at global cap). + * Bulk target: Light (<=5) + * Uses: + * Armor AC: head=1, body=1, total=2 + * Bulk: head=1, body=1, total=2 + * Magic: total=0 + * Dex +4 * No shield. - * Expect: base 10 + piece 3 + magic 3 + Dex 4 = 20. + * Expect: base 10 + piece 2 + magic 0 + dex 4 = 16 */ set_ability_scores(&ch, 10, 18, 10, 10, 10, 10); - equip_at(&ch, WEAR_HEAD, make_armor(1, 1, 1, 0)); - equip_at(&ch, WEAR_BODY, make_armor(2, 1, 2, 0)); + equip_at(&ch, WEAR_HEAD, make_armor(1,1,0,0)); + equip_at(&ch, WEAR_BODY, make_armor(1,1,0,0)); + equip_at(&ch, WEAR_LEGS, make_armor(1,2,0,0)); + equip_at(&ch, WEAR_FEET, make_armor(1,1,0,0)); struct ac_breakdown b1; compute_ac_breakdown(&ch, &b1); /* Sanity checks */ - if (b1.total != 20) dbg_dump_ac("LIGHT", &b1); + if (b1.total != 18) dbg_dump_ac("LIGHT", &b1); T_EQI(b1.dex_cap, 5, "Light dex cap 5"); T_EQI(b1.dex_mod_applied, 4, "Light dex +4 applied"); - T_EQI(b1.armor_magic_sum, 3, "Light magic cap (global) 3"); - T_EQI(b1.total, 20, "Light total AC"); + T_EQI(b1.armor_magic_sum, 0, "Light magic cap (global) 3"); + T_EQI(b1.total, 18, "Light total AC"); /* MEDIUM SETUP: - * Clear equipment and rebuild: - * Bulk target: Medium (6..10). - * Use legs bulk 2 (wt 2 => +4), hands bulk 1 (wt 1 => +1), feet bulk 1 (wt 1 => +1), total 6 => Medium. - * Dex 18 => +4, but cap at +2. - * Armor piece AC: legs=2, hands=1, feet=1 => sum = 4. - * Magic: legs +2 only (still under global cap). - * Expect: base 10 + piece 4 + magic 2 + Dex 2 = 18. + * Bulk target: Medium (6..10) + * Uses: + * Armor AC: legs=2, hands=1, feet=1, total=4 + * Bulk: legs=2, hands=2, feet=2, total=6 + * Magic: legs=1, hands=1, total=2 + * Dex +4, but cap at +2 + * Expect: base 10 + piece 4 + magic 2 + dex 2 = 18. */ memset(ch.equipment, 0, sizeof(ch.equipment)); - equip_at(&ch, WEAR_LEGS, make_armor(2, 2, 2, 0)); /* wt 2 -> bulk 4 */ - equip_at(&ch, WEAR_HANDS, make_armor(1, 1, 0, 0)); /* wt 1 -> bulk 1 */ - equip_at(&ch, WEAR_FEET, make_armor(1, 1, 0, 0)); /* wt 1 -> bulk 1 */ + equip_at(&ch, WEAR_HEAD, make_armor(2,1,0,0)); + equip_at(&ch, WEAR_BODY, make_armor(2,2,1,0)); + equip_at(&ch, WEAR_LEGS, make_armor(2,2,0,0)); + equip_at(&ch, WEAR_HANDS, make_armor(1,1,0,0)); + equip_at(&ch, WEAR_FEET, make_armor(1,1,0,0)); struct ac_breakdown b2; compute_ac_breakdown(&ch, &b2); - if (b2.total != 18) dbg_dump_ac("MEDIUM", &b2); + if (b2.total != 21) dbg_dump_ac("MEDIUM", &b2); T_EQI(b2.dex_cap, 2, "Medium dex cap 2"); T_EQI(b2.dex_mod_applied, 2, "Medium dex +2 applied"); - T_EQI(b2.total_bulk, 6, "Medium bulk score 6"); - T_EQI(b2.total, 18, "Medium total AC"); + T_EQI(b2.total_bulk, 7, "Medium bulk score 7"); + T_EQI(b2.total, 21, "Medium total AC"); /* HEAVY SETUP: - * Bulk target: Heavy (>=11). - * Use body bulk 3 (wt 3 => +9), legs bulk 2 (wt 2 => +4), total 13 => Heavy. - * Dex 18 => +4 but cap 0. - * Armor piece AC: body=3, legs=2 => sum = 5. - * Magic: body +3 (per-slot allows up to 3), legs +3 (per-slot 1 -> runtime clamps to 1), global cap 3 -> total 3. - * Shield: base 2 + magic +5 (clamped to +3) + prof 0 => +5 total. - * Expect: base 10 + piece 5 + armorMagic 3 + Dex 0 + shield 5 = 23. + * Bulk target: Heavy (>=11) + * Uses: + * Armor AC: body=3, legs=2. total=5 + * Bulk: body=3, legs=2, total=13 + * Magic: body=3, legs=3, total=6 (max cap of 3, so total=3) + * Dex +4 but cap 0 due to bulk + * Shield: base 2 + magic +5 (clamped to +3) + prof 0 => +5 total + * Expect: base 10 + piece 5 + armorMagic 3 + Dex 0 + shield 5 = 23 */ memset(ch.equipment, 0, sizeof(ch.equipment)); - equip_at(&ch, WEAR_BODY, make_armor(3, 3, 3, 0)); /* bulk 3*wt3 => 9 */ - equip_at(&ch, WEAR_LEGS, make_armor(2, 2, 3, 0)); /* magic will clamp via slot+global; bulk 2*wt2 => 4 */ - /* Shield: test magic cap on shield and zero prof */ - struct obj_data *shield = make_armor(0, 0, 5, 0); - equip_at(&ch, WEAR_SHIELD, shield); + equip_at(&ch, WEAR_HEAD, make_armor(2,1,1,0)); + equip_at(&ch, WEAR_BODY, make_armor(3,3,1,0)); + equip_at(&ch, WEAR_LEGS, make_armor(2,1,1,0)); + equip_at(&ch, WEAR_ARMS, make_armor(1,1,0,0)); + equip_at(&ch, WEAR_HANDS, make_armor(1,1,0,0)); + equip_at(&ch, WEAR_FEET, make_armor(1,1,0,0)); + equip_at(&ch, WEAR_WRIST_L, make_armor(1,1,0,0)); + equip_at(&ch, WEAR_WRIST_R, make_armor(1,1,0,0)); struct ac_breakdown b3; compute_ac_breakdown(&ch, &b3); - if (b3.total != 23) dbg_dump_ac("HEAVY", &b3); + if (b3.total != 25) dbg_dump_ac("HEAVY", &b3); T_EQI(b3.dex_cap, 0, "Heavy dex cap 0"); T_EQI(b3.dex_mod_applied, 0, "Heavy dex applied 0"); - T_EQI(b3.total_bulk, 13, "Heavy bulk score 13"); - T_EQI(b3.armor_piece_sum, 5, "Heavy piece sum 5"); + T_EQI(b3.total_bulk, 10, "Heavy bulk score 10"); + T_EQI(b3.armor_piece_sum, 12, "Heavy piece sum 12"); T_EQI(b3.armor_magic_sum, 3, "Heavy armor magic at global cap 3"); - T_EQI(b3.shield_bonus, 5, "Shield: base 2 + magic 3 (cap) + prof 0 = 5"); - T_EQI(b3.total, 23, "Heavy total AC"); + T_EQI(b3.total, 25, "Heavy total AC"); } static void test_hit_probability_sanity(void) { /* Sanity envelope checks (Monte Carlo with seed) */ - srand(42); + circle_srandom(42); /* Even-ish fight: attack_mod = +5 vs AC 16 => expect about 55–65% */ double p1 = simulate_hit_rate(/*atk*/5, /*AC*/16, 200000); - T_IN_RANGE(p1, 0.55, 0.65, "Hit rate ~60% (atk+5 vs AC16)"); + T_IN_RANGE(p1, 0.45, 0.55, "Hit rate ~50% (atk+5 vs AC16)"); /* Slightly behind: atk +3 vs AC 17 => expect about 35–50% */ double p2 = simulate_hit_rate(3, 17, 200000); - T_IN_RANGE(p2, 0.35, 0.50, "Hit rate ~40% (atk+3 vs AC17)"); + T_IN_RANGE(p2, 0.30, 0.40, "Hit rate ~35% (atk+3 vs AC17)"); /* Way ahead: atk +8 vs AC 14 => expect ≳80% but < 95% (nat1 auto-miss) */ double p3 = simulate_hit_rate(8, 14, 200000); - T_IN_RANGE(p3, 0.80, 0.95, "Hit rate high (atk+8 vs AC14)"); + T_IN_RANGE(p3, 0.75, 0.85, "Hit rate high (atk+8 vs AC14)"); /* Way behind: atk +0 vs AC 20 => expect ≲15% but > 5% (nat20 auto-hit) */ double p4 = simulate_hit_rate(0, 20, 200000); @@ -214,8 +212,8 @@ static void test_hit_probability_sanity(void) { } int main(void) { - test_ability_mod(); - test_prof_from_skill(); + test_GET_ABILITY_MOD(); + test_GET_PROFICIENCY(); test_ac_light_medium_heavy(); test_hit_probability_sanity(); diff --git a/src/utils.c b/src/utils.c index 7e4b7d4..d03c455 100644 --- a/src/utils.c +++ b/src/utils.c @@ -1589,7 +1589,7 @@ bool percent_success_disadv(int chance_pct) { static int dex_cap_from_bulk(int total_bulk) { if (total_bulk <= 5) /* Light */ return 5; - else if (total_bulk <= 10) /* Medium */ + else if (total_bulk <= 9) /* Medium */ return 2; else /* Heavy */ return 0; @@ -1631,7 +1631,7 @@ bool has_stealth_disadv(struct char_data *ch) { } /* Returns the 5e-style ability modifier for a given ability score. */ -int ability_mod(int score) { +int GET_ABILITY_MOD(int score) { int mod = (score - 10) / 2; if ((score - 10) < 0 && ((score - 10) % 2 != 0)) mod -= 1; /* adjust for C truncation toward zero */ @@ -1639,7 +1639,7 @@ int ability_mod(int score) { } /* Converts a skill percentage (0-100) into a 5e-like proficiency bonus. */ -int prof_from_skill(int pct) { +int GET_PROFICIENCY(int pct) { if (pct <= 14) return 0; if (pct <= 29) return 1; if (pct <= 44) return 2; @@ -1660,7 +1660,7 @@ void compute_ac_breakdown(struct char_data *ch, struct ac_breakdown *out) memset(out, 0, sizeof(*out)); out->base = 10; - /* Armor pieces: head/body/legs/arms/hands/feet (no shield here) */ + /* Armor pieces: head/body/legs/arms/hands/feet */ for (int i = 0; i < NUM_ARMOR_SLOTS; i++) { int wear_pos = ARMOR_WEAR_POSITIONS[i]; struct obj_data *obj = GET_EQ(ch, wear_pos); @@ -1684,35 +1684,21 @@ void compute_ac_breakdown(struct char_data *ch, struct ac_breakdown *out) /* bulk contribution */ int piece_bulk = GET_OBJ_VAL(obj, VAL_ARMOR_BULK); if (piece_bulk < 0) piece_bulk = 0; - out->total_bulk += piece_bulk * armor_slots[i].bulk_weight; + out->total_bulk += piece_bulk; } - /* global armor magic cap (armor only; shield handled separately) */ + /* global armor magic cap (armor only) */ if (total_magic > MAX_TOTAL_ARMOR_MAGIC) total_magic = MAX_TOTAL_ARMOR_MAGIC; out->armor_magic_sum = total_magic; /* Dex cap from bulk and applied dex mod */ { - int dexmod = ability_mod(GET_DEX(ch)); + int dexmod = GET_ABILITY_MOD(GET_DEX(ch)); out->dex_cap = dex_cap_from_bulk(out->total_bulk); /* Light<=5:5 / <=10:2 / else:0 */ out->dex_mod_applied = (dexmod > out->dex_cap) ? out->dex_cap : dexmod; } - /* Shield: base +2, magic (capped), +Shield Use proficiency */ - { - struct obj_data *shield = GET_EQ(ch, WEAR_SHIELD); - if (shield && GET_OBJ_TYPE(shield) == ITEM_ARMOR) { - int shield_bonus = 2; - int shield_magic = GET_OBJ_VAL(shield, VAL_ARMOR_MAGIC_BONUS); - if (shield_magic < 0) shield_magic = 0; - if (shield_magic > MAX_SHIELD_MAGIC) shield_magic = MAX_SHIELD_MAGIC; - shield_bonus += shield_magic; - shield_bonus += prof_from_skill(GET_SKILL(ch, SKILL_SHIELD_USE)); - out->shield_bonus = shield_bonus; - } - } - /* Situational */ out->situational = situational_ac_mods(ch); @@ -1721,7 +1707,6 @@ void compute_ac_breakdown(struct char_data *ch, struct ac_breakdown *out) + out->armor_piece_sum + out->armor_magic_sum + out->dex_mod_applied - + out->shield_bonus + out->situational; } diff --git a/src/utils.h b/src/utils.h index 1314861..f1a8a83 100644 --- a/src/utils.h +++ b/src/utils.h @@ -78,25 +78,21 @@ void remove_from_string(char *string, const char *to_remove); /* --- Ascending AC breakdown --- */ struct ac_breakdown { int base; /* always 10 */ - int armor_piece_sum; /* sum of clamped per-piece AC (no shield) */ + int armor_piece_sum; /* sum of clamped per-piece AC */ int armor_magic_sum; /* sum of clamped per-piece magic (capped globally) */ int total_bulk; /* sum of bulk * weight across armor pieces */ int dex_cap; /* cap derived from bulk (Light 5 / Med 2 / Heavy 0) */ int dex_mod_applied; /* min(DEX_mod, dex_cap) */ - int shield_bonus; /* base 2 + magic (cap) + Shield Use proficiency */ int situational; /* cover, spells, etc. */ int total; /* final AC */ }; -int ability_mod(int score); -int prof_from_skill(int pct); -int ability_mod(int score); -int prof_from_skill(int pct); +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 compute_armor_class_asc(struct char_data *ch); void compute_ac_breakdown(struct char_data *ch, struct ac_breakdown *out); -int compute_ascending_ac(struct char_data *ch); /* still available */ /* Advantage/Disadvantage helpers */ int roll_d20(void);