From 1d530ef1c692526f3cd12305c8da25b6eeec16c2 Mon Sep 17 00:00:00 2001 From: Lucien Date: Sun, 15 Mar 2026 08:36:47 -0700 Subject: [PATCH 01/13] Deduplicate newfight code --- src/newfight.cpp | 87 ++++++++++++++++++++++-------------------------- 1 file changed, 40 insertions(+), 47 deletions(-) diff --git a/src/newfight.cpp b/src/newfight.cpp index 79c94761e..3df4cfb2e 100644 --- a/src/newfight.cpp +++ b/src/newfight.cpp @@ -59,6 +59,13 @@ SPECIAL(weapon_dominator); bool hit_with_multiweapon_toggle(struct char_data *attacker, struct char_data *victim, struct obj_data *weap, struct obj_data *vict_weap, struct obj_data *weap_ammo, bool multi_weapon_modifier) { + // We use this struct to reduce code duplication by iterating with braced-init ranged loops instead of copy-pasting and swapping vars around. + struct AttDef { + combat_data *fighter; + combat_data *victim; + const char *msg; + }; + int net_successes, successes_for_use_in_monowhip_test_check; assert(attacker != NULL); assert(victim != NULL); @@ -75,8 +82,7 @@ bool hit_with_multiweapon_toggle(struct char_data *attacker, struct char_data *v struct combat_data *att = &attacker_data; struct combat_data *def = &defender_data; - char rbuf[MAX_STRING_LENGTH]; - memset(rbuf, 0, sizeof(rbuf)); + char rbuf[MAX_STRING_LENGTH] = {0}; snprintf(rbuf, sizeof(rbuf), ">> ^cCombat eval: %s vs %s.", GET_CHAR_NAME(attacker), GET_CHAR_NAME(victim)); SEND_RBUF_TO_ROLLS_FOR_BOTH_ATTACKER_AND_DEFENDER; @@ -85,28 +91,23 @@ bool hit_with_multiweapon_toggle(struct char_data *attacker, struct char_data *v if (def->is_surprised) AFF_FLAGS(def->ch).RemoveBit(AFF_SURPRISE); - // Precondition: If your foe is astral (ex: a non-manifested projection, a dematerialized spirit), you don't belong here. - if (IS_ASTRAL(def->ch)) { - if (SEES_ASTRAL(att->ch)) { - return astral_fight(att->ch, def->ch); - } else { - mudlog("SYSERR: Entered hit() with an non-astrally-reachable character attacking an astral character.", att->ch, LOG_SYSLOG, TRUE); - act("Unable to hit $N- $E's astral and $n can't touch that.", FALSE, att->ch, 0, def->ch, TO_ROLLS); - stop_fighting(att->ch); - } - return FALSE; - } - // Precondition: Same for if you're an astral being and your target isn't. - if (IS_ASTRAL(att->ch)) { - if (SEES_ASTRAL(def->ch)) { - astral_fight(att->ch, def->ch); - } else { - mudlog("SYSERR: Entered hit() with an astral character attacking a non-astrally-reachable character.", att->ch, LOG_SYSLOG, TRUE); - act("Unable to hit $N- $E's unreachable from the astral plane and $n can't touch that.", FALSE, att->ch, 0, def->ch, TO_ROLLS); - stop_fighting(att->ch); + // Precondition: Prevent astral state mismatch (non-manifested projection or dematerialized spirit etc fighting a meatspace body or v/v) + for (auto &c : {AttDef{att, def, ""}, AttDef{def, att, ""}}) { + if (IS_ASTRAL(c.victim->ch)) { + if (SEES_ASTRAL(c.fighter->ch)) { + return astral_fight(c.fighter->ch, c.victim->ch); + } else { + mudlog_vfprintf(c.fighter->ch, LOG_SYSLOG, "SYSERR: Entered hit() with %sastral (%s) attacking %sastral (%s).", + IS_ASTRAL(c.fighter->ch) ? "" : "non-", + GET_CHAR_NAME(c.fighter->ch), + IS_ASTRAL(c.victim->ch) ? "" : "non-", + GET_CHAR_NAME(c.victim->ch)); + act("Unable to hit $N- astral state mismatch with $n.", FALSE, c.fighter->ch, 0, c.victim->ch, TO_ROLLS); + stop_fighting(att->ch); // <- not a mistake, we always want to stop att here since it's their combat loop. + } + return FALSE; } - return FALSE; } // Precondition: If you're in melee combat and your foe isn't present, stop fighting. @@ -161,21 +162,15 @@ bool hit_with_multiweapon_toggle(struct char_data *attacker, struct char_data *v return FALSE; } - // (ATT) Precondition: If you're wielding a non-weapon, take a penalty. - if (att->weapon && (GET_OBJ_TYPE(att->weapon) != ITEM_WEAPON || GET_WEAPON_ATTACK_TYPE(att->weapon) == WEAP_GRENADE)) { - send_to_char(att->ch, "You struggle to figure out how to attack while holding %s!\r\n", decapitalize_a_an(GET_OBJ_NAME(att->weapon))); - att->weapon = NULL; - att->ranged_combat_mode = FALSE; - // Your fists / claws / etc are less effective when you're holding something. - att->melee->modifiers[COMBAT_MOD_WIELDING_A_NON_WEAPON] = 2; - } - // (DEF) Precondition: If you're wielding a non-weapon, take a penalty. - if (def->weapon && (GET_OBJ_TYPE(def->weapon) != ITEM_WEAPON || GET_WEAPON_ATTACK_TYPE(def->weapon) == WEAP_GRENADE)) { - send_to_char(def->ch, "You struggle to figure out how to defend yourself while holding %s!\r\n", decapitalize_a_an(GET_OBJ_NAME(def->weapon))); - def->weapon = NULL; - def->ranged_combat_mode = FALSE; - // Your fists / claws / etc are less effective when you're holding something. - def->melee->modifiers[COMBAT_MOD_WIELDING_A_NON_WEAPON] = 2; + for (auto &c : {AttDef{att, def, "attack"}, AttDef{def, att, "defend yourself"}}) { + // (ATT) Precondition: If you're wielding a non-weapon, take a penalty. + if (c.fighter->weapon && (GET_OBJ_TYPE(c.fighter->weapon) != ITEM_WEAPON || GET_WEAPON_ATTACK_TYPE(c.fighter->weapon) == WEAP_GRENADE)) { + send_to_char(c.fighter->ch, "You struggle to figure out how to %s while holding %s!\r\n", c.msg, decapitalize_a_an(GET_OBJ_NAME(c.fighter->weapon))); + c.fighter->weapon = NULL; + c.fighter->ranged_combat_mode = FALSE; + // Your fists / claws / etc are less effective when you're holding something. + c.fighter->melee->modifiers[COMBAT_MOD_WIELDING_A_NON_WEAPON] = 2; + } } // Precondition: If you're asleep or paralyzed, you don't get to fight. @@ -429,18 +424,16 @@ bool hit_with_multiweapon_toggle(struct char_data *attacker, struct char_data *v // Deduct ammo. THIS MUST BE THE LAST PRECONDITION CHECK. if (!MOB_FLAGGED(att->ch, MOB_EMPLACED)) { + // Subtract the full ammo count. NPCs are the exception: Their manned weapons have unlimited ammo. if (att->ranged->burst_count) { - if (weap_ammo || att->ranged->magazine) { - // Subtract the full ammo count. NPCs are the exception: Their manned weapons have unlimited ammo. - if (weap_ammo) { - if (!IS_NPC(att->ch)) { - update_ammobox_ammo_quantity(weap_ammo, -(att->ranged->burst_count), "newfight burst deduction"); - AMMOTRACK(att->ch, GET_AMMOBOX_WEAPON(weap_ammo), GET_AMMOBOX_TYPE(weap_ammo), AMMOTRACK_COMBAT, -(att->ranged->burst_count)); - } - } else { - GET_MAGAZINE_AMMO_COUNT(att->ranged->magazine) -= (att->ranged->burst_count); - AMMOTRACK(att->ch, GET_MAGAZINE_BONDED_ATTACKTYPE(att->ranged->magazine), GET_MAGAZINE_AMMO_TYPE(att->ranged->magazine), AMMOTRACK_COMBAT, -(att->ranged->burst_count)); + if (weap_ammo) { + if (!IS_NPC(att->ch)) { + update_ammobox_ammo_quantity(weap_ammo, -(att->ranged->burst_count), "newfight burst deduction"); + AMMOTRACK(att->ch, GET_AMMOBOX_WEAPON(weap_ammo), GET_AMMOBOX_TYPE(weap_ammo), AMMOTRACK_COMBAT, -(att->ranged->burst_count)); } + } else if (att->ranged->magazine) { + GET_MAGAZINE_AMMO_COUNT(att->ranged->magazine) -= (att->ranged->burst_count); + AMMOTRACK(att->ch, GET_MAGAZINE_BONDED_ATTACKTYPE(att->ranged->magazine), GET_MAGAZINE_AMMO_TYPE(att->ranged->magazine), AMMOTRACK_COMBAT, -(att->ranged->burst_count)); } } // Just deduct one round from their total. From 09a8e6ab35e25e8aa221240a3ad29fdc7d8cf6a2 Mon Sep 17 00:00:00 2001 From: Lucien Date: Sun, 15 Mar 2026 09:29:47 -0700 Subject: [PATCH 02/13] Checkpointing changes --- src/newfight.cpp | 72 +++++++++++++++++++++++++++--------------------- src/newfight.hpp | 10 +++++-- src/structs.hpp | 3 +- src/utils.hpp | 6 +++- 4 files changed, 55 insertions(+), 36 deletions(-) diff --git a/src/newfight.cpp b/src/newfight.cpp index 3df4cfb2e..f58023d1b 100644 --- a/src/newfight.cpp +++ b/src/newfight.cpp @@ -63,7 +63,7 @@ bool hit_with_multiweapon_toggle(struct char_data *attacker, struct char_data *v struct AttDef { combat_data *fighter; combat_data *victim; - const char *msg; + const char *msg = nullptr; }; int net_successes, successes_for_use_in_monowhip_test_check; @@ -93,7 +93,7 @@ bool hit_with_multiweapon_toggle(struct char_data *attacker, struct char_data *v // Precondition: Prevent astral state mismatch (non-manifested projection or dematerialized spirit etc fighting a meatspace body or v/v) - for (auto &c : {AttDef{att, def, ""}, AttDef{def, att, ""}}) { + for (auto &c : {AttDef{att, def}, AttDef{def, att}}) { if (IS_ASTRAL(c.victim->ch)) { if (SEES_ASTRAL(c.fighter->ch)) { return astral_fight(c.fighter->ch, c.victim->ch); @@ -173,26 +173,32 @@ bool hit_with_multiweapon_toggle(struct char_data *attacker, struct char_data *v } } - // Precondition: If you're asleep or paralyzed, you don't get to fight. - if (att->is_paralyzed_or_insensate) { - act("$n unable to fight $N: Paralyzed or insensate.", TRUE, att->ch, 0, def->ch, TO_ROLLS); - return FALSE; - } - // Precondition: If you're asleep or paralyzed, you don't get to fight, and also your opponent sets combat to their desired range. - if (def->is_paralyzed_or_insensate) { - if (att->ranged_combat_mode) { - AFF_FLAGS(def->ch).SetBit(AFF_APPROACH); - } else { - AFF_FLAGS(def->ch).RemoveBit(AFF_APPROACH); - } + for (auto &c : {AttDef{att, def, "fight"}, AttDef{def, att, "react"}}) { + if (c.fighter->is_paralyzed || c.fighter->is_insensate) { + // Fighter having AFF_APPROACH set is always disadvantageous for them. + AFF_FLAGS(c.fighter->ch).SetBit(AFF_APPROACH); + + // If the victim wants ranged combat, set their approach flag (distant). Otherwise, remove it (close). + if (c.victim->ranged_combat_mode) { + AFF_FLAGS(c.victim->ch).SetBit(AFF_APPROACH); + } else { + AFF_FLAGS(c.victim->ch).RemoveBit(AFF_APPROACH); + } + + // Only message if you're not insensate. + if (!c.fighter->is_insensate) { + send_to_char(c.fighter->ch, "You can't %s-- you're paralyzed!\r\n", c.msg); + } - if (AWAKE(def->ch)) { - send_to_char("You can't react-- you're paralyzed!\r\n", def->ch); + // Only bail out if we're evaluating the attacker. + if (c.fighter == att) + return FALSE; } } // Precondition: If you're out of ammo, you don't get to fight. + // We specifically don't do melee combat instead since the gun-users tend to get wrecked when switched to that unexpectedly. if (att->weapon && !has_ammo_no_deduct(att->ch, att->weapon)) { act("$n unable to fight $N: No ammo", TRUE, att->ch, 0, def->ch, TO_ROLLS); return FALSE; @@ -207,12 +213,11 @@ bool hit_with_multiweapon_toggle(struct char_data *attacker, struct char_data *v } // Setup: Calculate sight penalties. - int attacker_vision_penalty = calculate_vision_penalty(att->ch, def->ch); - int defender_vision_penalty = calculate_vision_penalty(def->ch, att->ch); - att->melee->modifiers[COMBAT_MOD_VISIBILITY] += attacker_vision_penalty; - att->ranged->modifiers[COMBAT_MOD_VISIBILITY] += attacker_vision_penalty; - def->melee->modifiers[COMBAT_MOD_VISIBILITY] += defender_vision_penalty; - def->ranged->modifiers[COMBAT_MOD_VISIBILITY] += defender_vision_penalty; + for (auto &c : {AttDef{att, def}, AttDef{def, att}}) { + int vision_penalty = calculate_vision_penalty(c.fighter->ch, c.victim->ch); + c.fighter->melee->modifiers[COMBAT_MOD_VISIBILITY] += vision_penalty; + c.fighter->ranged->modifiers[COMBAT_MOD_VISIBILITY] += vision_penalty; + } // Setup: If the character is rigging a vehicle or is in a vehicle, set veh to that vehicle. RIG_VEH(att->ch, att->veh); @@ -223,6 +228,11 @@ bool hit_with_multiweapon_toggle(struct char_data *attacker, struct char_data *v att->ranged->modifiers[COMBAT_MOD_SMARTLINK] = 0; } + // Setup: If you're somehow attacking the meatspace form of someone whose mind is elsewhere (remote riggers, deckers, projectors, etc), they are treated as insensate. + if (IS_JACKED_IN(def->ch)) { + def->is_insensate = true; + } + if (att->veh && !att->weapon) { mudlog("SYSERR: Somehow, we ended up in a vehicle attacking someone with no weapon!", att->ch, LOG_SYSLOG, TRUE); send_to_char("You'll have to leave your vehicle for that.\r\n", att->ch); @@ -245,7 +255,7 @@ bool hit_with_multiweapon_toggle(struct char_data *attacker, struct char_data *v } // Setup: Limit the burst of the weapon to the available ammo. Ammo is deducted later. - // Emplaced mobs (turrets, etc) have unlimited ammo and no recoil. + // Emplaced mobs (turrets, etc) have unlimited ammo and no recoil, so they're skipped over with the outermost if-check. if (!MOB_FLAGGED(att->ch, MOB_EMPLACED)) { if (att->ranged->burst_count) { if (weap_ammo || att->ranged->magazine) { @@ -299,8 +309,10 @@ bool hit_with_multiweapon_toggle(struct char_data *attacker, struct char_data *v } } + // TODO: Review the rolls act code and figure out how to split things up between meatspace and decking rolls. A new TO_MATRIX_ROLLS maybe? + // Setup: Compute modifiers to the TN based on the def->ch's current state. - if (def->is_paralyzed_or_insensate) + if (def->is_paralyzed || def->is_insensate) att->ranged->modifiers[COMBAT_MOD_POSITION] -= 6; else if (AFF_FLAGGED(def->ch, AFF_PRONE)) { // Prone next to you is a bigger / easier target, prone far away is a smaller / harder one. @@ -374,8 +386,8 @@ bool hit_with_multiweapon_toggle(struct char_data *attacker, struct char_data *v if (!IS_NPC(att->ch)) { att->ranged->modifiers[COMBAT_MOD_DISTANCE] += SAME_ROOM_SNIPER_RIFLE_PENALTY; - // Specific 1-round single-shot snipers get an extra +1. - if (GET_OBJ_VNUM(att->weapon) == 33600 && GET_WEAPON_FIREMODE(att->weapon) == MODE_SS && GET_WEAPON_MAX_AMMO(att->weapon) == 1) { + // Specific rifles get an extra penalty for being extremely large, even for sniper rifles. + if (GET_OBJ_VNUM(att->weapon) == 33600) { att->ranged->modifiers[COMBAT_MOD_DISTANCE] += 1; } } @@ -384,10 +396,8 @@ bool hit_with_multiweapon_toggle(struct char_data *attacker, struct char_data *v else { att->ranged->modifiers[COMBAT_MOD_DISTANCE] -= 2; - // Specific 1-round single-shot snipers get an extra -1, but only if proned with a bipod or tripod. + // Specific rifles get an extra bonus, but only if proned with a bipod or tripod. if (GET_OBJ_VNUM(att->weapon) == 33600 - && GET_WEAPON_FIREMODE(att->weapon) == MODE_SS - && GET_WEAPON_MAX_AMMO(att->weapon) == 1 && AFF_FLAGGED(att->ch, AFF_PRONE) && weapon_has_usable_bipod_or_tripod(att->ch, att->weapon, att->using_gyro)) { @@ -399,10 +409,10 @@ bool hit_with_multiweapon_toggle(struct char_data *attacker, struct char_data *v // Setup: If your attacker is closing the distance (running), take a penalty per Core p112. // We can't avoid setting AFF_APPROACH in set_fighting for surprised targets since it also // indicates not-in-melee-range, so make an exception since they're probably not moving yet. - if (!def->is_paralyzed_or_insensate && !def->is_surprised) { + if (!(def->is_paralyzed || def->is_insensate || def->is_surprised)) { if (AFF_FLAGGED(def->ch, AFF_APPROACH)) att->ranged->modifiers[COMBAT_MOD_DEFENDER_MOVING] += 2; - else if (!def->ranged_combat_mode && def->ch->in_room == att->ch->in_room && !IS_JACKED_IN(def->ch)) + else if (!def->ranged_combat_mode && def->ch->in_room == att->ch->in_room) att->ranged->modifiers[COMBAT_MOD_IN_MELEE_COMBAT] += 2; // technically supposed to be +2 per attacker, but ehhhh. } diff --git a/src/newfight.hpp b/src/newfight.hpp index d5ce3518a..6ff6c76c1 100644 --- a/src/newfight.hpp +++ b/src/newfight.hpp @@ -396,7 +396,8 @@ struct combat_data int standard_impact_rating; int hardened_armor_ballistic_rating; int hardened_armor_impact_rating; - bool is_paralyzed_or_insensate; + bool is_paralyzed; + bool is_insensate; bool is_surprised; // Pool data for the things that can be temporarily overwritten mid-fight. @@ -440,8 +441,11 @@ struct combat_data hardened_armor_ballistic_rating = get_hardened_ballistic_armor_rating(ch); hardened_armor_impact_rating = get_hardened_impact_armor_rating(ch); - // Figure out if they're unable to move at all (paralyzed, asleep, jacked in, etc) - is_paralyzed_or_insensate = !AWAKE(ch) || GET_QUI(ch) <= 0; + // Figure out if they're unable to move at all (paralyzed, asleep/stunned/morted, projecting, jacked in, etc) + is_paralyzed = GET_QUI(ch) <= 0; + is_insensate = (GET_POS(ch) <= POS_SLEEPING // We don't use AWAKE() here since that evaluates to true when qui <= 0. + || PLR_FLAGS(ch).AreAnySet(PLR_MATRIX, PLR_PROJECT, ENDBIT) // We don't use IS_JACKED_IN here since that would be true for all our riggers. + ); // Check if they're using a gyromount. using_gyro = (ranged->gyro || cyber->cyberarm_gyromount); diff --git a/src/structs.hpp b/src/structs.hpp index de17a395e..11c37fa69 100644 --- a/src/structs.hpp +++ b/src/structs.hpp @@ -556,13 +556,14 @@ struct char_point_data ubyte reach[2]; int extras[2]; sh_int projection_ticks; + sh_int taser_stun_rounds; /* Adding a field to this struct? If it's a pointer, or if it's important, add it to utils.cpp's copy_over_necessary_info() to avoid breaking mdelete etc. */ char_point_data() : mental(0), max_mental(0), physical(0), max_physical(10), nuyen(0), bank(0), karma(0), rep(0), noto(0), tke(0), sig(0), init_dice(0), init_roll(0), grade(0), extrapp(0), extra(0), extra2(0), - magic_loss(0), ess_loss(0), domain(0), resistpain(0), lastdamage(0), projection_ticks(0) + magic_loss(0), ess_loss(0), domain(0), resistpain(0), lastdamage(0), projection_ticks(0), taser_stun_rounds(0) { ZERO_OUT_ARRAY(ballistic, 3); ZERO_OUT_ARRAY(impact, 3); diff --git a/src/utils.hpp b/src/utils.hpp index c5f2ce7f7..82af64726 100644 --- a/src/utils.hpp +++ b/src/utils.hpp @@ -833,8 +833,12 @@ bool _mob_is_alert(struct char_data *npc); #define CAN_CARRY_W(ch) ((GET_STR(ch) * 10) + 30) #define CAN_CARRY_N(ch) (8 + GET_QUI(ch) + (GET_REAL_LEVEL(ch)>=LVL_BUILDER?50:0)) #define AWAKE(ch) (GET_POS(ch) > POS_SLEEPING && GET_QUI(ch) > 0 && !PLR_FLAGGED(ch, PLR_PROJECT)) +#define TASER_STUNNED_ROUNDS(ch) ((ch)->points.taser_stun_rounds) + +// These two are related, in that IS_JACKED_IN uses a decomposed version of IS_RIGGING. #define IS_RIGGING(ch) (AFF_FLAGGED(ch, AFF_RIG) || PLR_FLAGGED(ch, PLR_REMOTE)) -#define IS_JACKED_IN(ch) (IS_RIGGING(ch) || PLR_FLAGGED(ch, PLR_MATRIX) || PLR_FLAGGED(ch, PLR_REMOTE)) +#define IS_JACKED_IN(ch) (!IS_NPC(ch) && (AFF_FLAGGED(ch, AFF_RIG) || PLR_FLAGS((ch)).AreAnySet(PLR_MATRIX, PLR_REMOTE, ENDBIT))) + #define CAN_SEE_IN_DARK(ch) (SEES_ASTRAL(ch) || CURRENT_VISION(ch) == THERMOGRAPHIC || PRF_FLAGGED((ch), PRF_HOLYLIGHT)) #define GET_BUILDING(ch) ((ch)->char_specials.programming) #define IS_WORKING(ch) ((AFF_FLAGS(ch).AreAnySet(BR_TASK_AFF_FLAGS, AFF_PILOT, AFF_RIG, AFF_BONDING, AFF_CONJURE, AFF_PACKING, ENDBIT))) From d2ffbd8ee2abb9ef58ef7f16f310bd00c53ba367 Mon Sep 17 00:00:00 2001 From: Lucien Date: Sun, 15 Mar 2026 09:50:26 -0700 Subject: [PATCH 03/13] Checkpointing changes --- src/newfight.cpp | 72 +++++++++++++++++++----------------------------- 1 file changed, 29 insertions(+), 43 deletions(-) diff --git a/src/newfight.cpp b/src/newfight.cpp index f58023d1b..82991d601 100644 --- a/src/newfight.cpp +++ b/src/newfight.cpp @@ -420,10 +420,10 @@ bool hit_with_multiweapon_toggle(struct char_data *attacker, struct char_data *v if (!att->ranged->using_mounted_gun) { int maximum_recoil_comp_from_gyros = att->ranged->modifiers[COMBAT_MOD_MOVEMENT] + att->ranged->modifiers[COMBAT_MOD_RECOIL]; if (att->ranged->gyro) { - att->ranged->modifiers[COMBAT_MOD_GYRO] -= MIN(maximum_recoil_comp_from_gyros, GET_OBJ_VAL(att->ranged->gyro, 0)); + att->ranged->modifiers[COMBAT_MOD_GYRO] -= MIN(maximum_recoil_comp_from_gyros, GET_GYRO_RECOIL_COMP(att->ranged->gyro)); } else if (att->cyber->cyberarm_gyromount) { if (!GUN_IS_CYBER_GYRO_MOUNTABLE(att->weapon)) { - send_to_char(att->ch, "Your cyberarm gyro locks up-- %s is too heavy for it to compensate recoil for!\r\n", decapitalize_a_an(GET_OBJ_NAME(att->weapon))); + send_to_char(att->ch, "Your cyberarm gyro is locked up-- %s is too heavy for it to compensate recoil for!\r\n", decapitalize_a_an(GET_OBJ_NAME(att->weapon))); snprintf(rbuf, sizeof(rbuf), "^Y%s's cyberarm gyro not activating-- weapon too heavy.^n", GET_CHAR_NAME( att->ch )); SEND_RBUF_TO_ROLLS_FOR_BOTH_ATTACKER_AND_DEFENDER; } else { @@ -432,29 +432,16 @@ bool hit_with_multiweapon_toggle(struct char_data *attacker, struct char_data *v } } - // Deduct ammo. THIS MUST BE THE LAST PRECONDITION CHECK. - if (!MOB_FLAGGED(att->ch, MOB_EMPLACED)) { + // Deduct ammo. THIS MUST BE AFTER ALL PRECONDITIONS so we don't deduct ammo from someone who's not firing. + if (!MOB_FLAGGED(att->ch, MOB_EMPLACED) || (weap_ammo && !IS_NPC(att->ch))) { // Subtract the full ammo count. NPCs are the exception: Their manned weapons have unlimited ammo. - if (att->ranged->burst_count) { - if (weap_ammo) { - if (!IS_NPC(att->ch)) { - update_ammobox_ammo_quantity(weap_ammo, -(att->ranged->burst_count), "newfight burst deduction"); - AMMOTRACK(att->ch, GET_AMMOBOX_WEAPON(weap_ammo), GET_AMMOBOX_TYPE(weap_ammo), AMMOTRACK_COMBAT, -(att->ranged->burst_count)); - } - } else if (att->ranged->magazine) { - GET_MAGAZINE_AMMO_COUNT(att->ranged->magazine) -= (att->ranged->burst_count); - AMMOTRACK(att->ch, GET_MAGAZINE_BONDED_ATTACKTYPE(att->ranged->magazine), GET_MAGAZINE_AMMO_TYPE(att->ranged->magazine), AMMOTRACK_COMBAT, -(att->ranged->burst_count)); - } - } - // Just deduct one round from their total. - else { - if (weap_ammo) { - GET_AMMOBOX_QUANTITY(weap_ammo)--; - AMMOTRACK(att->ch, GET_AMMOBOX_WEAPON(weap_ammo), GET_AMMOBOX_TYPE(weap_ammo), AMMOTRACK_COMBAT, -1); - } else if (att->ranged->magazine) { - GET_MAGAZINE_AMMO_COUNT(att->ranged->magazine)--; - AMMOTRACK(att->ch, GET_MAGAZINE_BONDED_ATTACKTYPE(att->ranged->magazine), GET_MAGAZINE_AMMO_TYPE(att->ranged->magazine), AMMOTRACK_COMBAT, -1); - } + int quantity_to_deduct = (att->ranged->burst_count ? att->ranged->burst_count : 1); + if (weap_ammo) { + update_ammobox_ammo_quantity(weap_ammo, -quantity_to_deduct, "newfight ammobox deduction"); + AMMOTRACK(att->ch, GET_AMMOBOX_WEAPON(weap_ammo), GET_AMMOBOX_TYPE(weap_ammo), AMMOTRACK_COMBAT, -quantity_to_deduct); + } else if (att->ranged->magazine) { + GET_MAGAZINE_AMMO_COUNT(att->ranged->magazine) -= quantity_to_deduct; + AMMOTRACK(att->ch, GET_MAGAZINE_BONDED_ATTACKTYPE(att->ranged->magazine), GET_MAGAZINE_AMMO_TYPE(att->ranged->magazine), AMMOTRACK_COMBAT, -quantity_to_deduct); } } @@ -491,20 +478,17 @@ bool hit_with_multiweapon_toggle(struct char_data *attacker, struct char_data *v GET_CHAR_NAME( att->ch ), att->ranged->burst_count, MOB_FLAGGED(att->ch, MOB_EMPLACED) ? 10 : att->ranged->recoil_comp); - - { - att->ranged->tn += modify_target_rbuf_raw(att->ch, rbuf, sizeof(rbuf), att->ranged->modifiers[COMBAT_MOD_VISIBILITY], FALSE); - for (int mod_index = 0; mod_index < NUM_COMBAT_MODIFIERS; mod_index++) { - // Ranged-specific modifiers. - buf_mod(rbuf, sizeof(rbuf), combat_modifiers[mod_index], att->ranged->modifiers[mod_index]); - att->ranged->tn += att->ranged->modifiers[mod_index]; - } - snprintf(ENDOF(rbuf), sizeof(rbuf) - strlen(rbuf), "\r\n^jTotal ranged attack roll TN after modifiers: %d.^n", att->ranged->tn); - SEND_RBUF_TO_ROLLS_FOR_BOTH_ATTACKER_AND_DEFENDER; + att->ranged->tn += modify_target_rbuf_raw(att->ch, rbuf, sizeof(rbuf), att->ranged->modifiers[COMBAT_MOD_VISIBILITY], FALSE); + for (int mod_index = 0; mod_index < NUM_COMBAT_MODIFIERS; mod_index++) { + // Ranged-specific modifiers. + buf_mod(rbuf, sizeof(rbuf), combat_modifiers[mod_index], att->ranged->modifiers[mod_index]); + att->ranged->tn += att->ranged->modifiers[mod_index]; } + snprintf(ENDOF(rbuf), sizeof(rbuf) - strlen(rbuf), "\r\n^jTotal ranged attack roll TN after modifiers: %d.^n", att->ranged->tn); + SEND_RBUF_TO_ROLLS_FOR_BOTH_ATTACKER_AND_DEFENDER; - // Calculate the attacker's total skill (this modifies TN) + // Calculate the attacker's total skill (this modifies TN). In a code block for var scoping. { int prior_tn = att->ranged->tn; att->ranged->dice = get_skill(att->ch, att->ranged->skill, att->ranged->tn); @@ -526,17 +510,19 @@ bool hit_with_multiweapon_toggle(struct char_data *attacker, struct char_data *v snprintf(rbuf, sizeof(rbuf), "^jAfter get_skill(), attacker's ranged attack roll TN is ^c%d^n.", att->ranged->tn); SEND_RBUF_TO_ROLLS_FOR_BOTH_ATTACKER_AND_DEFENDER; - int bonus_from_offense_pool = MIN(GET_SKILL(att->ch, att->ranged->skill), GET_OFFENSE(att->ch)); - snprintf(rbuf, sizeof(rbuf), "^JAttack: Rolling %d + %d dice VS TN %d... ", att->ranged->dice, bonus_from_offense_pool, att->ranged->tn); - att->ranged->dice += bonus_from_offense_pool; + { + int bonus_from_offense_pool = MIN(GET_SKILL(att->ch, att->ranged->skill), GET_OFFENSE(att->ch)); + snprintf(rbuf, sizeof(rbuf), "^JAttack: Rolling %d + %d dice VS TN %d... ", att->ranged->dice, bonus_from_offense_pool, att->ranged->tn); + att->ranged->dice += bonus_from_offense_pool; - att->ranged->successes = success_test(att->ranged->dice, att->ranged->tn); - snprintf(ENDOF(rbuf), sizeof(rbuf) - strlen(rbuf), "^c%d^J successes.^n", att->ranged->successes); - SEND_RBUF_TO_ROLLS_FOR_BOTH_ATTACKER_AND_DEFENDER; + att->ranged->successes = success_test(att->ranged->dice, att->ranged->tn); + snprintf(ENDOF(rbuf), sizeof(rbuf) - strlen(rbuf), "^c%d^J success%s.^n", att->ranged->successes, att->ranged->successes == 1 ? "" : "s"); + SEND_RBUF_TO_ROLLS_FOR_BOTH_ATTACKER_AND_DEFENDER; + } // If you can't dodge, you're presumably not using your dice on your dodge pool, so shift those this round. if (def->dodge_pool > 0) { - if (def->is_paralyzed_or_insensate || AFF_FLAGGED(def->ch, AFF_PRONE) || def->is_surprised) { + if (def->is_paralyzed || def->is_insensate || AFF_FLAGGED(def->ch, AFF_PRONE) || def->is_surprised) { def->body_pool += def->dodge_pool; def->dodge_pool = 0; act("Temporarily shifting $n's dodge pool to body pool: Can't dodge.", TRUE, def->ch, 0, 0, TO_ROLLS); @@ -544,7 +530,7 @@ bool hit_with_multiweapon_toggle(struct char_data *attacker, struct char_data *v // Surprised, oversized, unconscious, or prone? No dodge test for you. att->ranged->successes = MAX(att->ranged->successes, 0); snprintf(rbuf, sizeof(rbuf), "^eOpponent unable to dodge, attacker's successes will remain at ^c%d^e.^n", att->ranged->successes); - if (AFF_FLAGGED(def->ch, AFF_PRONE) && !IS_JACKED_IN(def->ch)) + if (AFF_FLAGGED(def->ch, AFF_PRONE) && !def->is_insensate) send_to_char(def->ch, "^yYou're unable to dodge while prone!^n\r\n"); } else { // You can move, perform the dodge test. From 8f7d242885882e1e6c1bb8dd145f94ee7881720a Mon Sep 17 00:00:00 2001 From: Lucien Date: Sun, 15 Mar 2026 09:56:05 -0700 Subject: [PATCH 04/13] Fixed missing taser check --- src/newfight.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/newfight.cpp b/src/newfight.cpp index 82991d601..599578d58 100644 --- a/src/newfight.cpp +++ b/src/newfight.cpp @@ -309,8 +309,6 @@ bool hit_with_multiweapon_toggle(struct char_data *attacker, struct char_data *v } } - // TODO: Review the rolls act code and figure out how to split things up between meatspace and decking rolls. A new TO_MATRIX_ROLLS maybe? - // Setup: Compute modifiers to the TN based on the def->ch's current state. if (def->is_paralyzed || def->is_insensate) att->ranged->modifiers[COMBAT_MOD_POSITION] -= 6; From 9e9fc858dabc4020d138b07fedccf0a06000dc91 Mon Sep 17 00:00:00 2001 From: Lucien Date: Sun, 15 Mar 2026 10:06:32 -0700 Subject: [PATCH 05/13] Fixed missing taser check --- src/newfight.cpp | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src/newfight.cpp b/src/newfight.cpp index 599578d58..b10d01b75 100644 --- a/src/newfight.cpp +++ b/src/newfight.cpp @@ -605,7 +605,8 @@ bool hit_with_multiweapon_toggle(struct char_data *attacker, struct char_data *v // SR3 p124. att->ranged->power = att->ranged->power_before_armor - (int)(def->standard_impact_rating / 2); } else { - switch (weap_ammo ? GET_AMMOBOX_TYPE(weap_ammo) : GET_MAGAZINE_AMMO_TYPE(att->ranged->magazine)) { + int ammo_type = weap_ammo ? GET_AMMOBOX_TYPE(weap_ammo) : GET_MAGAZINE_AMMO_TYPE(att->ranged->magazine); + switch (ammo_type) { case AMMO_AV: case AMMO_APDS: if (IS_SPIRIT(def->ch) || IS_ANY_ELEMENTAL(def->ch)) { @@ -645,8 +646,15 @@ bool hit_with_multiweapon_toggle(struct char_data *attacker, struct char_data *v att->ranged->is_gel = TRUE; // Affects knockdown tests att->ranged->is_physical = FALSE; break; - default: + default: // this is probably terrible coding style, but I'm having default fall through to ammo_normal since that's the assumption. + mudlog_vfprintf(att->ch, LOG_SYSLOG, "SYSERR: Encountered unknown ammo type %d (mag: %s, weap: %s) in h_w_m_t ammo switch", + ammo_type, + GET_OBJ_NAME((weap_ammo ? weap_ammo : att->ranged->magazine)), + GET_OBJ_NAME(att->weapon)); + // fall through + case AMMO_NORMAL: att->ranged->power = att->ranged->power_before_armor - def->standard_ballistic_rating; + break; } } } @@ -659,12 +667,19 @@ bool hit_with_multiweapon_toggle(struct char_data *attacker, struct char_data *v if (GET_SKILL(att->ch, att->ranged->skill) >= 8 && SHOTS_FIRED(att->ch) < 10000) SHOTS_FIRED(att->ch)++; - // Check for hardened armor per CC p51. + // Check for hardened armor per CC p51. Raw weapon power is correct here, we've already reduced hardened rating by ammo type above. if (def->hardened_armor_ballistic_rating) { if (def->hardened_armor_ballistic_rating >= GET_WEAPON_POWER(att->weapon)) { - act("Your rounds ricochet off of $S hardened armor!", FALSE, att->ch, 0, def->ch, TO_CHAR); - act("$n's rounds ricochet off of your hardened armor!", FALSE, att->ch, 0, def->ch, TO_VICT); - act("$n's rounds ricochet off of $N's hardened armor!", FALSE, att->ch, 0, def->ch, TO_NOTVICT); + if (att->ranged->burst_count > 1) { + act("Your rounds ricochet off of $S hardened armor!", FALSE, att->ch, 0, def->ch, TO_CHAR); + act("$n's rounds ricochet off of your hardened armor!", FALSE, att->ch, 0, def->ch, TO_VICT); + act("$n's rounds ricochet off of $N's hardened armor!", FALSE, att->ch, 0, def->ch, TO_NOTVICT); + } else { + act("Your round ricochets off of $S hardened armor!", FALSE, att->ch, 0, def->ch, TO_CHAR); + act("$n's round ricochets off of your hardened armor!", FALSE, att->ch, 0, def->ch, TO_VICT); + act("$n's round ricochets off of $N's hardened armor!", FALSE, att->ch, 0, def->ch, TO_NOTVICT); + } + send_to_char(att->ch, "^o(OOC: %s has hardened armor! You need at least ^O%d^o weapon power to damage %s with your current ammo type, and you only have %d.)^n\r\n", decapitalize_a_an(GET_CHAR_NAME(def->ch)), def->hardened_armor_ballistic_rating + 1, From c1155fb10e8e1de734f7e90f1b858a00b14045da Mon Sep 17 00:00:00 2001 From: Lucien Date: Sun, 15 Mar 2026 12:21:15 -0700 Subject: [PATCH 06/13] saving changes --- src/newfight.cpp | 323 +++++++++++++++++++++++------------------------ src/newfight.hpp | 4 +- 2 files changed, 158 insertions(+), 169 deletions(-) diff --git a/src/newfight.cpp b/src/newfight.cpp index b10d01b75..13086da59 100644 --- a/src/newfight.cpp +++ b/src/newfight.cpp @@ -74,10 +74,6 @@ bool hit_with_multiweapon_toggle(struct char_data *attacker, struct char_data *v struct combat_data attacker_data(attacker, weap); struct combat_data defender_data(victim, vict_weap); - // Since we use this code for riggers attacking others, rigging only counts against the victim. - if (IS_RIGGING(victim)) - defender_data.is_paralyzed_or_insensate = TRUE; - // Allows for switching roles, which can happen during melee counterattacks. struct combat_data *att = &attacker_data; struct combat_data *def = &defender_data; @@ -91,22 +87,31 @@ bool hit_with_multiweapon_toggle(struct char_data *attacker, struct char_data *v if (def->is_surprised) AFF_FLAGS(def->ch).RemoveBit(AFF_SURPRISE); + // Setup: If you're somehow attacking the meatspace form of someone whose mind is elsewhere (remote riggers, deckers, projectors, etc), they are treated as insensate. + if (IS_JACKED_IN(victim)) + defender_data.is_insensate = TRUE; - // Precondition: Prevent astral state mismatch (non-manifested projection or dematerialized spirit etc fighting a meatspace body or v/v) - for (auto &c : {AttDef{att, def}, AttDef{def, att}}) { - if (IS_ASTRAL(c.victim->ch)) { - if (SEES_ASTRAL(c.fighter->ch)) { - return astral_fight(c.fighter->ch, c.victim->ch); + // Precondition: If you're asleep or paralyzed, you don't get to fight, and also your opponent sets combat to their desired range. + for (auto &c : {AttDef{att, def, "fight"}, AttDef{def, att, "react"}}) { + if (c.fighter->is_paralyzed || c.fighter->is_insensate) { + // Fighter having AFF_APPROACH set is always disadvantageous for them. + AFF_FLAGS(c.fighter->ch).SetBit(AFF_APPROACH); + + // If the victim wants ranged combat, set their approach flag (distant). Otherwise, remove it (close). + if (c.victim->ranged_combat_mode) { + AFF_FLAGS(c.victim->ch).SetBit(AFF_APPROACH); } else { - mudlog_vfprintf(c.fighter->ch, LOG_SYSLOG, "SYSERR: Entered hit() with %sastral (%s) attacking %sastral (%s).", - IS_ASTRAL(c.fighter->ch) ? "" : "non-", - GET_CHAR_NAME(c.fighter->ch), - IS_ASTRAL(c.victim->ch) ? "" : "non-", - GET_CHAR_NAME(c.victim->ch)); - act("Unable to hit $N- astral state mismatch with $n.", FALSE, c.fighter->ch, 0, c.victim->ch, TO_ROLLS); - stop_fighting(att->ch); // <- not a mistake, we always want to stop att here since it's their combat loop. + AFF_FLAGS(c.victim->ch).RemoveBit(AFF_APPROACH); } - return FALSE; + + // Only message if you're not insensate. + if (!c.fighter->is_insensate) { + send_to_char(c.fighter->ch, "You can't %s-- you're paralyzed!\r\n", c.msg); + } + + // Only bail out if we're evaluating the attacker. + if (c.fighter == att) + return FALSE; } } @@ -118,6 +123,21 @@ bool hit_with_multiweapon_toggle(struct char_data *attacker, struct char_data *v return FALSE; } + // Precondition: Prevent astral state mismatch (non-manifested projection or dematerialized spirit etc fighting a meatspace body or v/v) + if ((IS_ASTRAL(att->ch) && !SEES_ASTRAL(def->ch)) || (IS_ASTRAL(def->ch) && !SEES_ASTRAL(att->ch))) { + // Astral beings can't fight non-astral characters and v/v. + act("$n is unable to hit $N- astral state mismatch.", FALSE, att->ch, 0, def->ch, TO_ROLLS); + mudlog_vfprintf(att->ch, LOG_SYSLOG, "SYSERR: Entered hit() with %sastral (%s) attacking %sastral (%s).", + IS_ASTRAL(att->ch) ? "" : "non-", + GET_CHAR_NAME(att->ch), + IS_ASTRAL(def->ch) ? "" : "non-", + GET_CHAR_NAME(def->ch)); + stop_fighting(att->ch); + return false; + } else if (IS_ASTRAL(att->ch) || IS_ASTRAL(def->ch)) { + return astral_fight(att->ch, def->ch); + } + // Short-circuit: If you're wielding an activated Dominator, you don't care about all these pesky rules. if (att->weapon && GET_OBJ_SPEC(att->weapon) == weapon_dominator) { if (GET_LEVEL(def->ch) > GET_LEVEL(att->ch)) { @@ -173,30 +193,6 @@ bool hit_with_multiweapon_toggle(struct char_data *attacker, struct char_data *v } } - // Precondition: If you're asleep or paralyzed, you don't get to fight, and also your opponent sets combat to their desired range. - for (auto &c : {AttDef{att, def, "fight"}, AttDef{def, att, "react"}}) { - if (c.fighter->is_paralyzed || c.fighter->is_insensate) { - // Fighter having AFF_APPROACH set is always disadvantageous for them. - AFF_FLAGS(c.fighter->ch).SetBit(AFF_APPROACH); - - // If the victim wants ranged combat, set their approach flag (distant). Otherwise, remove it (close). - if (c.victim->ranged_combat_mode) { - AFF_FLAGS(c.victim->ch).SetBit(AFF_APPROACH); - } else { - AFF_FLAGS(c.victim->ch).RemoveBit(AFF_APPROACH); - } - - // Only message if you're not insensate. - if (!c.fighter->is_insensate) { - send_to_char(c.fighter->ch, "You can't %s-- you're paralyzed!\r\n", c.msg); - } - - // Only bail out if we're evaluating the attacker. - if (c.fighter == att) - return FALSE; - } - } - // Precondition: If you're out of ammo, you don't get to fight. // We specifically don't do melee combat instead since the gun-users tend to get wrecked when switched to that unexpectedly. if (att->weapon && !has_ammo_no_deduct(att->ch, att->weapon)) { @@ -228,11 +224,6 @@ bool hit_with_multiweapon_toggle(struct char_data *attacker, struct char_data *v att->ranged->modifiers[COMBAT_MOD_SMARTLINK] = 0; } - // Setup: If you're somehow attacking the meatspace form of someone whose mind is elsewhere (remote riggers, deckers, projectors, etc), they are treated as insensate. - if (IS_JACKED_IN(def->ch)) { - def->is_insensate = true; - } - if (att->veh && !att->weapon) { mudlog("SYSERR: Somehow, we ended up in a vehicle attacking someone with no weapon!", att->ch, LOG_SYSLOG, TRUE); send_to_char("You'll have to leave your vehicle for that.\r\n", att->ch); @@ -716,7 +707,10 @@ bool hit_with_multiweapon_toggle(struct char_data *attacker, struct char_data *v } // Melee combat. If you're here, we don't care about your ranged combat setup-- it's face-beating time. All calculations here are done for both attacker and defender (in case of defender counterstriking) else { - // Ensure that neither combatant has the closing flag set. + // TODO: Review the rolls act code and figure out how to split things up between meatspace and decking rolls. A new TO_MATRIX_ROLLS maybe? + int dont_compile = "msg"; + + // Ensure that neither combatant has the closing flag set (if we've gotten here, the closing test was already cleared in perform-violence()) AFF_FLAGS(att->ch).RemoveBit(AFF_APPROACH); AFF_FLAGS(def->ch).RemoveBit(AFF_APPROACH); @@ -728,7 +722,7 @@ bool hit_with_multiweapon_toggle(struct char_data *attacker, struct char_data *v } // Treat unconscious/paralyzed/etc as being a position mod of -6 (reflects ease of coup de grace) - if (def->is_paralyzed_or_insensate) + if (def->is_paralyzed || def->is_insensate) att->melee->modifiers[COMBAT_MOD_POSITION] -= 6; else if (AFF_FLAGGED(def->ch, AFF_PRONE)) att->melee->modifiers[COMBAT_MOD_POSITION] -= 2; @@ -752,52 +746,32 @@ bool hit_with_multiweapon_toggle(struct char_data *attacker, struct char_data *v } else { */ - { - int prior_tn = att->melee->tn; - int skill_dice = att->melee->skill_bonus + get_skill(att->ch, att->melee->skill, att->melee->tn); - int cpool_dice = MIN(skill_dice, GET_OFFENSE(att->ch)); + // Set up dice for attacker and defender. + for (auto &c : {AttDef{att, def}, AttDef{def, att}}) { + int prior_tn = c.fighter->melee->tn; + int skill_dice = c.fighter->melee->skill_bonus + get_skill(c.fighter->ch, c.fighter->melee->skill, c.fighter->melee->tn); + int cpool_dice = MIN(skill_dice, GET_OFFENSE(c.fighter->ch)); - if (GET_CHIPJACKED_SKILL(att->ch, att->melee->skill)) { + if (GET_CHIPJACKED_SKILL(c.fighter->ch, c.fighter->melee->skill)) { cpool_dice = 0; } - att->melee->dice = skill_dice + cpool_dice; - snprintf(rbuf, sizeof(rbuf), "Attacker has %d skill (incl %d weap focus), %d pool%s: rolls %d dice.", + c.fighter->melee->dice = skill_dice + cpool_dice; + snprintf(rbuf, sizeof(rbuf), "%s has %d skill (incl %d weap focus), %d pool%s: rolls %d dice.", + c.fighter == att ? "Attacker" : "Defender", skill_dice, - att->melee->skill_bonus, + c.fighter->melee->skill_bonus, cpool_dice, - GET_CHIPJACKED_SKILL(att->ch, att->melee->skill) ? " (zeroed due to using activesoft)" : "", - att->melee->dice); - if (att->melee->tn != prior_tn) { - snprintf(ENDOF(rbuf), sizeof(rbuf) - strlen(rbuf), "\r\nAttacker TN modified in get_skill() from %d to %d.", prior_tn, att->melee->tn); - } - } - SEND_RBUF_TO_ROLLS_FOR_BOTH_ATTACKER_AND_DEFENDER; - - { - int prior_tn = def->melee->tn; - int skill_dice = def->melee->skill_bonus + get_skill(def->ch, def->melee->skill, def->melee->tn); - int cpool_dice = MIN(skill_dice, GET_OFFENSE(def->ch)); - - if (GET_CHIPJACKED_SKILL(def->ch, def->melee->skill)) { - cpool_dice = 0; - } - - def->melee->dice = skill_dice + cpool_dice; - snprintf(rbuf, sizeof(rbuf), "Defender has %d skill (incl %d weap focus), %d pool%s: rolls %d dice.", - skill_dice, - def->melee->skill_bonus, - cpool_dice, - GET_CHIPJACKED_SKILL(def->ch, def->melee->skill) ? " (zeroed due to using activesoft)" : "", - def->melee->dice); - if (def->melee->tn != prior_tn) { - snprintf(ENDOF(rbuf), sizeof(rbuf) - strlen(rbuf), "\r\nDefender TN modified in get_skill() from %d to %d.", prior_tn, def->melee->tn); + GET_CHIPJACKED_SKILL(c.fighter->ch, c.fighter->melee->skill) ? " (zeroed due to using activesoft)" : "", + c.fighter->melee->dice); + if (c.fighter->melee->tn != prior_tn) { + snprintf(ENDOF(rbuf), sizeof(rbuf) - strlen(rbuf), "\r\n%s TN modified in get_skill() from %d to %d.", + c.fighter == att ? "Attacker" : "Defender", + prior_tn, + att->melee->tn); } + SEND_RBUF_TO_ROLLS_FOR_BOTH_ATTACKER_AND_DEFENDER; } - SEND_RBUF_TO_ROLLS_FOR_BOTH_ATTACKER_AND_DEFENDER; - - - // } // Adepts get bonus dice when counterattacking. if (GET_POWER(def->ch, ADEPT_COUNTERSTRIKE) > 0) { @@ -806,10 +780,12 @@ bool hit_with_multiweapon_toggle(struct char_data *attacker, struct char_data *v SEND_RBUF_TO_ROLLS_FOR_BOTH_ATTACKER_AND_DEFENDER; } - // Bugfix: If you're unconscious or mortally wounded, you don't get to counterattack. - if (GET_PHYSICAL(def->ch) <= 0 || GET_MENTAL(def->ch) <= 0 || IS_JACKED_IN(def->ch)) { + // Bugfix: If you're unable to fight, you don't get to counterattack. + if (def->is_insensate || def->is_paralyzed) { + // Shift your offense dice to your body pool. + def->body_pool += GET_OFFENSE(def->ch); def->melee->dice = 0; - strlcpy(rbuf, "^yDefender incapped, dice capped to zero.^n", sizeof(rbuf)); + strlcpy(rbuf, "^yDefender insensate/paralyzed, dice capped to zero. Any offense dice shifted to body pool for this round.^n", sizeof(rbuf)); SEND_RBUF_TO_ROLLS_FOR_BOTH_ATTACKER_AND_DEFENDER; } @@ -819,25 +795,25 @@ bool hit_with_multiweapon_toggle(struct char_data *attacker, struct char_data *v // Calculate the net reach. int net_reach = (GET_REACH(att->ch) + att->melee->reach_modifier) - (GET_REACH(def->ch) + def->melee->reach_modifier); - // Skilled NPCs get to switch to close combat mode at this time (those cheating bastards.) - engage_close_combat_if_appropriate(att, def, net_reach); - engage_close_combat_if_appropriate(def, att, -net_reach); - + // MitS 149: Ignore reach modifiers. This should happen _before_ NPCs decide their close combat status. if (GET_POWER(att->ch, ADEPT_DISTANCE_STRIKE)) { - // MitS 149: Ignore reach modifiers. net_reach = 0; } - bool is_close_combat = AFF_FLAGGED(att->ch, AFF_CLOSECOMBAT) || AFF_FLAGGED(def->ch, AFF_CLOSECOMBAT); - if (is_close_combat) { - // CC p99: Ignore reach modifiers, decrease user's power by one. - net_reach = 0; + // Skilled NPCs get to switch their close combat mode at this time (those cheating bastards.) + engage_close_combat_if_appropriate(att, def, net_reach); + engage_close_combat_if_appropriate(def, att, -net_reach); - if (AFF_FLAGGED(att->ch, AFF_CLOSECOMBAT)) { - att->melee->power -= 1; - act("Decreased melee power by 1 and negated net reach due to attacker's close combat toggle.", TRUE, att->ch, NULL, NULL, TO_ROLLS); - } else { - act("Negated net reach due to defender's close combat toggle.", TRUE, att->ch, NULL, NULL, TO_ROLLS); + // Apply close-combat power penalties to attacker and defender as appropriate (CC p99) + for (auto &c : {AttDef{att, def}, AttDef{def, att}}) { + if (AFF_FLAGGED(c.fighter->ch, AFF_CLOSECOMBAT)) { + c.fighter->melee->power -= 1; + if (net_reach != 0) { + net_reach = 0; + act("Decreased $n's melee power by 1 and negated net reach due to $s close combat toggle.", TRUE, c.fighter->ch, NULL, NULL, TO_ROLLS); + } else { + act("Decreased $n's melee power by 1 due to $s close combat toggle.", TRUE, c.fighter->ch, NULL, NULL, TO_ROLLS); + } } } @@ -848,7 +824,7 @@ bool hit_with_multiweapon_toggle(struct char_data *attacker, struct char_data *v def->melee->modifiers[COMBAT_MOD_REACH] -= -net_reach; #ifdef USE_RIOT_SHIELD_CODE - // Add in riot shield TNs. + // Add in riot shield TNs. Did not review this in the 15-03-2026 pass since it's disabled atm. if (att->melee->riot_shield || def->melee->riot_shield) { int shield_count = (att->melee->riot_shield ? 1 : 0) + (def->melee->riot_shield ? 1 : 0); int shield_tn_penalty = shield_count * 2; @@ -878,65 +854,56 @@ bool hit_with_multiweapon_toggle(struct char_data *attacker, struct char_data *v // ------------------------------------------------------------------------------------------------------- // Calculate and display pre-success-test information. - snprintf(rbuf, sizeof(rbuf), "^cCalculating melee combat modifiers. %s's TN modifiers: ", GET_CHAR_NAME(att->ch) ); - // This feels a little shitty, but we know that if we're in melee mode, the TN has not been touched yet, and if we're not then it's already been calculated. - att->melee->tn += modify_target_rbuf_raw(att->ch, rbuf, sizeof(rbuf), att->melee->modifiers[COMBAT_MOD_VISIBILITY], FALSE); - for (int mod_index = 0; mod_index < NUM_COMBAT_MODIFIERS; mod_index++) { - buf_mod(rbuf, sizeof(rbuf), combat_modifiers[mod_index], att->melee->modifiers[mod_index]); - att->melee->tn += att->melee->modifiers[mod_index]; - } + strlcpy(rbuf, "^cCalculating melee combat modifiers.", sizeof(rbuf)); SEND_RBUF_TO_ROLLS_FOR_BOTH_ATTACKER_AND_DEFENDER; - snprintf(rbuf, sizeof(rbuf), "^c%s%s's TN modifiers: ", GET_CHAR_NAME( def->ch ), - (GET_PHYSICAL(def->ch) <= 0 || GET_MENTAL(def->ch) <= 0) ? " (incap)" : "" ); - def->melee->tn += modify_target_rbuf_raw(def->ch, rbuf, sizeof(rbuf), def->melee->modifiers[COMBAT_MOD_VISIBILITY], FALSE); - for (int mod_index = 0; mod_index < NUM_COMBAT_MODIFIERS; mod_index++) { - buf_mod(rbuf, sizeof(rbuf), combat_modifiers[mod_index], def->melee->modifiers[mod_index]); - def->melee->tn += def->melee->modifiers[mod_index]; - } - SEND_RBUF_TO_ROLLS_FOR_BOTH_ATTACKER_AND_DEFENDER; + for (auto &c : {AttDef{att, def}, AttDef{def, att}}) { + snprintf(rbuf, sizeof(rbuf), "^c%s%s's TN modifiers: ", GET_CHAR_NAME(c.fighter->ch), + c.fighter->is_insensate || c.fighter->is_paralyzed ? " (incap)" : ""); + // TN should be 4 + any defaulting penalty already, so we just modify_target it to get the rest of the penalties applied. + c.fighter->melee->tn += modify_target_rbuf_raw(c.fighter->ch, rbuf, sizeof(rbuf), c.fighter->melee->modifiers[COMBAT_MOD_VISIBILITY], FALSE); + for (int mod_index = 0; mod_index < NUM_COMBAT_MODIFIERS; mod_index++) { + buf_mod(rbuf, sizeof(rbuf), combat_modifiers[mod_index], c.fighter->melee->modifiers[mod_index]); + c.fighter->melee->tn += c.fighter->melee->modifiers[mod_index]; + } - // Minimum TN is 2. - att->melee->tn = MAX(att->melee->tn, 2); - def->melee->tn = MAX(def->melee->tn, 2); + if (c.fighter->melee->tn < 2) { + strlcat(rbuf, " (hit minimum 2 TN)", sizeof(rbuf)); + c.fighter->melee->tn = 2; + } - // Canary check-- this value is set to zero during initialization and should not have been touched yet. - if (def->melee->successes != 0) { - mudlog("FIGHT MEMORY ERROR: def->melee->successes was not zero!", def->ch, LOG_SYSLOG, TRUE); - } - if (att->melee->successes != 0) { - mudlog("FIGHT MEMORY ERROR: att->melee->successes was not zero!", def->ch, LOG_SYSLOG, TRUE); + // Canary check-- this value is set to zero during initialization and should not have been touched yet. + if (c.fighter->melee->successes != 0) { + mudlog_vfprintf(c.fighter->ch, LOG_SYSLOG, "FIGHT MEMORY ERROR: %s->melee->successes was not zero!", c.fighter == att ? "att" : "def"); + } + + SEND_RBUF_TO_ROLLS_FOR_BOTH_ATTACKER_AND_DEFENDER; } // Calculate the clash, unless there's some surprise involved (hitting someone unconscious is technically surprising for them) - if (!def->is_paralyzed_or_insensate && !def->is_surprised) { + if (!(def->is_paralyzed || def->is_insensate || def->is_surprised)) { att->melee->successes = success_test(att->melee->dice, att->melee->tn); def->melee->successes = success_test(def->melee->dice, def->melee->tn); + net_successes = att->melee->successes - def->melee->successes; + + if (def->weapon && GET_OBJ_TYPE(def->weapon) != ITEM_WEAPON) { + // Defender's wielding a non-weapon? Whoops, net successes will never be less than 0. + strlcpy(rbuf, "Defender wielding non-weapon-- cannot win clash. Net will go no lower than 0.", sizeof(rbuf)); + SEND_RBUF_TO_ROLLS_FOR_BOTH_ATTACKER_AND_DEFENDER; + net_successes = MAX(0, net_successes); + } } else { - strlcpy(rbuf, "Surprised-- defender gets no roll.", sizeof(rbuf)); + strlcpy(rbuf, "Surprised, paralyzed, insensate etc-- defender gets no clash roll.", sizeof(rbuf)); SEND_RBUF_TO_ROLLS_FOR_BOTH_ATTACKER_AND_DEFENDER; - // No, you CANNOT collapse these two lines into MAX(1, success_test()), because it calls s_t() twice. - att->melee->successes = success_test(att->melee->dice, att->melee->tn); - att->melee->successes = MAX(1, att->melee->successes); + int test_result = success_test(att->melee->dice, att->melee->tn); // don't put this in MAX, it calls it twice then tests vs first and actually uses second. + att->melee->successes = MAX(1, test_result); def->melee->successes = 0; + net_successes = att->melee->successes; } - net_successes = att->melee->successes - def->melee->successes; // Store our successes for the monowhip test, since there's a chance it'll be flipped in counterattack. successes_for_use_in_monowhip_test_check = att->melee->successes; - if (def->weapon && GET_OBJ_TYPE(def->weapon) != ITEM_WEAPON) { - // Defender's wielding a non-weapon? Whoops, net successes will never be less than 0. - strlcpy(rbuf, "Defender wielding non-weapon-- cannot win clash. Net will go no lower than 0.", sizeof(rbuf)); - SEND_RBUF_TO_ROLLS_FOR_BOTH_ATTACKER_AND_DEFENDER; - net_successes = MAX(0, net_successes); - } - if (GET_POS(def->ch) <= POS_STUNNED) { - strlcpy(rbuf, "Defender stunned/morted-- cannot win clash. Net will go no lower than 0.", sizeof(rbuf)); - SEND_RBUF_TO_ROLLS_FOR_BOTH_ATTACKER_AND_DEFENDER; - net_successes = MAX(0, net_successes); - } - // Compose and send various messages. snprintf(rbuf, sizeof(rbuf), "^g%s got ^W%d^g success%s from ^W%d^g dice at TN ^W%d^g.^n\r\n", GET_CHAR_NAME(att->ch), @@ -1322,28 +1289,48 @@ bool does_weapon_have_bayonet(struct obj_data *weapon) { } void engage_close_combat_if_appropriate(struct combat_data *att, struct combat_data *def, int net_reach) { - if (IS_NPC(att->ch) && net_reach != 0 && AFF_FLAGGED(att->ch, AFF_SMART_ENOUGH_TO_TOGGLE_CLOSECOMBAT)) { + if (net_reach != 0 && AFF_FLAGGED(att->ch, AFF_SMART_ENOUGH_TO_TOGGLE_CLOSECOMBAT) && IS_NPC(att->ch)) { // If the net reach does not favor the NPC, switch on close combat. if (net_reach < 0 && !AFF_FLAGGED(att->ch, AFF_CLOSECOMBAT)) { AFF_FLAGS(att->ch).SetBit(AFF_CLOSECOMBAT); - if (att->weapon) { - act("$n shifts $s grip on $p, trying to get inside $N's guard!", TRUE, att->ch, att->weapon, def->ch, TO_NOTVICT); - act("$n shifts $s grip on $p, trying to get inside your guard!", TRUE, att->ch, att->weapon, def->ch, TO_VICT); + if (MOB_FLAGGED(att->ch, MOB_INANIMATE)) { + if (att->weapon) { + act("$n realigns $p, trying to get inside $N's guard!", TRUE, att->ch, att->weapon, def->ch, TO_NOTVICT); + act("$n realigns $p, trying to get inside your guard!", TRUE, att->ch, att->weapon, def->ch, TO_VICT); + } else { + act("$n maneuvers in close, trying to get inside $N's guard!", TRUE, att->ch, NULL, def->ch, TO_NOTVICT); + act("$n maneuvers in close, trying to get inside your guard!", TRUE, att->ch, NULL, def->ch, TO_VICT); + } } else { - act("$n ducks in close, trying to get inside $N's guard!", TRUE, att->ch, NULL, def->ch, TO_NOTVICT); - act("$n ducks in close, trying to get inside your guard!", TRUE, att->ch, NULL, def->ch, TO_VICT); + if (att->weapon) { + act("$n shifts $s grip on $p, trying to get inside $N's guard!", TRUE, att->ch, att->weapon, def->ch, TO_NOTVICT); + act("$n shifts $s grip on $p, trying to get inside your guard!", TRUE, att->ch, att->weapon, def->ch, TO_VICT); + } else { + act("$n ducks in close, trying to get inside $N's guard!", TRUE, att->ch, NULL, def->ch, TO_NOTVICT); + act("$n ducks in close, trying to get inside your guard!", TRUE, att->ch, NULL, def->ch, TO_VICT); + } } } // Otherwise, switch it off. else if (net_reach > 0 && AFF_FLAGGED(att->ch, AFF_CLOSECOMBAT)) { AFF_FLAGS(att->ch).RemoveBit(AFF_CLOSECOMBAT); - if (att->weapon) { - act("$n shifts $s grip on $p, trying to keep $N outside $s guard!", TRUE, att->ch, att->weapon, def->ch, TO_NOTVICT); - act("$n shifts $s grip on $p, trying to keep you outside $s guard!", TRUE, att->ch, att->weapon, def->ch, TO_VICT); + if (MOB_FLAGGED(att->ch, MOB_INANIMATE)) { + if (att->weapon) { + act("$n realigns $p, trying to keep $N outside $s guard!", TRUE, att->ch, att->weapon, def->ch, TO_NOTVICT); + act("$n realigns $p, trying to keep you outside $s guard!", TRUE, att->ch, att->weapon, def->ch, TO_VICT); + } else { + act("$n maneuvers away, trying to keep $N outside $s guard!", TRUE, att->ch, NULL, def->ch, TO_NOTVICT); + act("$n maneuvers away, trying to keep you outside $s guard!", TRUE, att->ch, NULL, def->ch, TO_VICT); + } } else { - act("$n backs up, trying to keep $N outside $s guard!", TRUE, att->ch, NULL, def->ch, TO_NOTVICT); - act("$n backs up, trying to keep you outside $s guard!", TRUE, att->ch, NULL, def->ch, TO_VICT); + if (att->weapon) { + act("$n shifts $s grip on $p, trying to keep $N outside $s guard!", TRUE, att->ch, att->weapon, def->ch, TO_NOTVICT); + act("$n shifts $s grip on $p, trying to keep you outside $s guard!", TRUE, att->ch, att->weapon, def->ch, TO_VICT); + } else { + act("$n backs up, trying to keep $N outside $s guard!", TRUE, att->ch, NULL, def->ch, TO_NOTVICT); + act("$n backs up, trying to keep you outside $s guard!", TRUE, att->ch, NULL, def->ch, TO_VICT); + } } } } @@ -1521,12 +1508,15 @@ bool perform_nerve_strike(struct combat_data *att, struct combat_data *def, char } // Calculate and display pre-success-test information. - snprintf(rbuf, rbuf_len, "%s VS %s: Nerve Strike target is 4 + impact %d (PS: -%d) + modifiers: ", + snprintf(rbuf, rbuf_len, "%s VS %s: Nerve Strike target is 4 + impact %d + modifiers: ", GET_CHAR_NAME(att->ch), GET_CHAR_NAME(def->ch), - def->standard_impact_rating, - GET_POWER(att->ch, ADEPT_PENETRATINGSTRIKE) - ); + def->standard_impact_rating + ); + + if (impact_armor != def->standard_impact_rating) { + snprintf(ENDOF(rbuf), sizeof(rbuf) - strlen(rbuf), "-%d armor (penetrating strike), ", def->standard_impact_rating - impact_armor); + } att->melee->tn += impact_armor + modify_target_rbuf_raw(att->ch, rbuf, rbuf_len, att->melee->modifiers[COMBAT_MOD_VISIBILITY], FALSE); @@ -1553,23 +1543,22 @@ bool perform_nerve_strike(struct combat_data *att, struct combat_data *def, char att->melee->dice += bonus; att->melee->successes = success_test(att->melee->dice, att->melee->tn); - int temp_qui_loss = (int) (att->melee->successes / 2); + int temp_qui_loss = MAX(0, (int) (att->melee->successes / 2)); snprintf(ENDOF(rbuf), rbuf_len - strlen(rbuf), ", and got %d successes, which translates to %d qui loss.", att->melee->successes, temp_qui_loss); SEND_RBUF_TO_ROLLS_FOR_BOTH_ATTACKER_AND_DEFENDER; - if (att->melee->successes > 1) { + if (temp_qui_loss > 0) { GET_TEMP_QUI_LOSS(def->ch) += temp_qui_loss * TEMP_QUI_LOSS_DIVISOR; affect_total(def->ch); if (access_level(att->ch, LVL_ADMIN) && PRF_FLAGGED(att->ch, PRF_ROLLS)) { - send_to_char(att->ch, "[Remaining qui: %d (TQL %d).]\r\n", - GET_QUI(def->ch), - GET_TEMP_QUI_LOSS(def->ch)); + snprintf(rbuf, sizeof(rbuf), "[Remaining qui: %d (TQL %d).]\r\n", GET_QUI(def->ch), GET_TEMP_QUI_LOSS(def->ch)); + act(rbuf, FALSE, att->ch, 0, def->ch, TO_STAFF_ROLLS); } - char msg_buf[500]; + char msg_buf[500] = {0}; if (GET_QUI(def->ch) <= 0) { snprintf(msg_buf, sizeof(msg_buf), "You hit $N's pressure points successfully, %s %s paralyzed!", HSSH(def->ch), HSSH_SHOULD_PLURAL(def->ch) ? "is" : "are"); act(msg_buf, FALSE, att->ch, 0, def->ch, TO_CHAR); diff --git a/src/newfight.hpp b/src/newfight.hpp index 6ff6c76c1..c44b8142f 100644 --- a/src/newfight.hpp +++ b/src/newfight.hpp @@ -444,8 +444,8 @@ struct combat_data // Figure out if they're unable to move at all (paralyzed, asleep/stunned/morted, projecting, jacked in, etc) is_paralyzed = GET_QUI(ch) <= 0; is_insensate = (GET_POS(ch) <= POS_SLEEPING // We don't use AWAKE() here since that evaluates to true when qui <= 0. - || PLR_FLAGS(ch).AreAnySet(PLR_MATRIX, PLR_PROJECT, ENDBIT) // We don't use IS_JACKED_IN here since that would be true for all our riggers. - ); + || PLR_FLAGS(ch).AreAnySet(PLR_MATRIX, PLR_PROJECT, ENDBIT)); /* We don't use IS_JACKED_IN here since that would be true for all our riggers. + Rigger insensate is instead set for the defender in the actual combat code. */ // Check if they're using a gyromount. using_gyro = (ranged->gyro || cyber->cyberarm_gyromount); From 12bd9bb7294218afcae2d84f27685a83091b5227 Mon Sep 17 00:00:00 2001 From: Lucien Date: Sun, 15 Mar 2026 14:17:20 -0700 Subject: [PATCH 07/13] Made combat_message distance-strike aware --- src/fight.cpp | 93 +++++++++++++++++++++++++++------------------------ 1 file changed, 49 insertions(+), 44 deletions(-) diff --git a/src/fight.cpp b/src/fight.cpp index 342aca603..14ad041d1 100644 --- a/src/fight.cpp +++ b/src/fight.cpp @@ -4362,23 +4362,30 @@ void combat_message_process_ranged_response(struct char_data *ch, rnum_t rnum) { //#define COMBAT_MESSAGE_DEBUG_LOG(str) ; void combat_message(struct char_data *ch, struct char_data *victim, struct obj_data *weapon, int damage, int burst, int vision_penalty_for_messaging) { - char buf[MAX_MESSAGE_LENGTH], buf1[MAX_MESSAGE_LENGTH], buf2[MAX_MESSAGE_LENGTH], buf3[MAX_MESSAGE_LENGTH], buf4[MAX_MESSAGE_LENGTH], - been_heard[MAX_STRING_LENGTH], temp[20]; + char buf[MAX_MESSAGE_LENGTH], buf1[MAX_MESSAGE_LENGTH], buf2[MAX_MESSAGE_LENGTH], buf3[MAX_MESSAGE_LENGTH], buf4[MAX_MESSAGE_LENGTH]; + char been_heard[MAX_STRING_LENGTH], temp[20], blindfire_buf[100], action_verb[20]; struct obj_data *obj = NULL; struct room_data *ch_room = NULL, *vict_room = NULL; rnum_t room1 = 0, room2 = 0, rnum = 0; struct veh_data *veh = NULL; - if (weapon == NULL) { + if (weapon == NULL && !GET_POWER(ch, ADEPT_DISTANCE_STRIKE)) { mudlog("SYSERR: Null weapon in combat_message()!", ch, LOG_SYSLOG, TRUE); return; } + strlcpy(action_verb, "fire", sizeof(buf)); + if (burst <= 1) { - if (GET_OBJ_VAL(weapon, 4) == SKILL_SHOTGUNS || GET_OBJ_VAL(weapon, 4) == SKILL_ASSAULT_CANNON) - strlcpy(buf, "single shell from $p", sizeof(buf)); - else - strlcpy(buf, "single round from $p", sizeof(buf)); + if (weapon) { + if (GET_WEAPON_SKILL(weapon) == SKILL_SHOTGUNS || GET_WEAPON_SKILL(weapon) == SKILL_ASSAULT_CANNON) + strlcpy(buf, "single shell from $p", sizeof(buf)); + else + strlcpy(buf, "single round from $p", sizeof(buf)); + } else { + strlcpy(buf, "distance strike", sizeof(buf)); + strlcpy(action_verb, "throw", sizeof(buf)); + } } else if (burst == 2) { strlcpy(buf, "short burst from $p", sizeof(buf)); } else if (burst == 3) { @@ -4387,8 +4394,6 @@ void combat_message(struct char_data *ch, struct char_data *victim, struct obj_d strlcpy(buf, "long burst from $p", sizeof(buf)); } - char blindfire_buf[100]; - if (vision_penalty_for_messaging >= MAX_VISIBILITY_PENALTY) { strlcpy(blindfire_buf, "blindly ", sizeof(blindfire_buf)); } else { @@ -4413,32 +4418,32 @@ void combat_message(struct char_data *ch, struct char_data *victim, struct obj_d } switch (switch_num) { case 1: - snprintf(buf1, sizeof(buf1), "^r%s$n^r %sfires a %s^r at you, but the shot goes wide.^n", vehicle_message, blindfire_buf, buf); - snprintf(buf2, sizeof(buf2), "^yYou %sfire a %s^y at $N^y, but the shot goes wide.^n", blindfire_buf, buf); - snprintf(buf3, sizeof(buf3), "%s$n %sfires a %s^n at $N, but the shot goes wide.", vehicle_message, blindfire_buf, buf); + snprintf(buf1, sizeof(buf1), "^r%s$n^r %s%ss a %s^r at you, but the shot goes wide.^n", vehicle_message, blindfire_buf, action_verb, buf); + snprintf(buf2, sizeof(buf2), "^yYou %s%s a %s^y at $N^y, but the shot goes wide.^n", blindfire_buf, action_verb, buf); + snprintf(buf3, sizeof(buf3), "%s$n %s%ss a %s^n at $N, but the shot goes wide.", vehicle_message, blindfire_buf, action_verb, buf); break; case 2: - snprintf(buf1, sizeof(buf1), "^r%s$n^r %sfires a %s^r at you, but you easily dodge.^n", vehicle_message, blindfire_buf, buf); - snprintf(buf2, sizeof(buf2), "^yYou %sfire a %s^y at $N^y, but $E easily dodge%s.^n", blindfire_buf, buf, HSSH_SHOULD_PLURAL(victim) ? "s" : ""); - snprintf(buf3, sizeof(buf3), "%s$n %sfires a %s^n at $N, but $E easily dodge%s.", vehicle_message, blindfire_buf, buf, HSSH_SHOULD_PLURAL(victim) ? "s" : ""); + snprintf(buf1, sizeof(buf1), "^r%s$n^r %s%ss a %s^r at you, but you easily dodge.^n", vehicle_message, blindfire_buf, action_verb, buf); + snprintf(buf2, sizeof(buf2), "^yYou %s%s a %s^y at $N^y, but $E easily dodge%s.^n", blindfire_buf, action_verb, buf, HSSH_SHOULD_PLURAL(victim) ? "s" : ""); + snprintf(buf3, sizeof(buf3), "%s$n %s%ss a %s^n at $N, but $E easily dodge%s.", vehicle_message, blindfire_buf, action_verb, buf, HSSH_SHOULD_PLURAL(victim) ? "s" : ""); break; case 3: - snprintf(buf1, sizeof(buf1), "^r%s$n^r %sfires a %s^r at you, but you move out of the way.^n", vehicle_message, blindfire_buf, buf); - snprintf(buf2, sizeof(buf2), "^yYou %sfire a %s^y at $N^y, but $E move%s out of the way.^n", blindfire_buf, buf, HSSH_SHOULD_PLURAL(victim) ? "s" : ""); - snprintf(buf3, sizeof(buf3), "%s$n %sfires a %s^n at $N, but $E move%s out of the way.", vehicle_message, blindfire_buf, buf, HSSH_SHOULD_PLURAL(victim) ? "s" : ""); + snprintf(buf1, sizeof(buf1), "^r%s$n^r %s%ss a %s^r at you, but you move out of the way.^n", vehicle_message, blindfire_buf, action_verb, buf); + snprintf(buf2, sizeof(buf2), "^yYou %s%s a %s^y at $N^y, but $E move%s out of the way.^n", blindfire_buf, action_verb, buf, HSSH_SHOULD_PLURAL(victim) ? "s" : ""); + snprintf(buf3, sizeof(buf3), "%s$n %s%ss a %s^n at $N, but $E move%s out of the way.", vehicle_message, blindfire_buf, action_verb, buf, HSSH_SHOULD_PLURAL(victim) ? "s" : ""); break; } } else if (damage == 0) { switch (number(1, 2)) { case 1: - snprintf(buf1, sizeof(buf1), "^r%s$n^r %sfires a %s^r at you, but your armor holds.^n", vehicle_message, blindfire_buf, buf); - snprintf(buf2, sizeof(buf2), "^yYou %sfire a %s^y at $N^y, but it doesn't seem to hurt $M.^n", blindfire_buf, buf); - snprintf(buf3, sizeof(buf3), "%s$n %sfires a %s^n at $N, but it doesn't seem to hurt $M.", vehicle_message, blindfire_buf, buf); + snprintf(buf1, sizeof(buf1), "^r%s$n^r %s%ss a %s^r at you, but your armor holds.^n", vehicle_message, blindfire_buf, action_verb, buf); + snprintf(buf2, sizeof(buf2), "^yYou %s%s a %s^y at $N^y, but it doesn't seem to hurt $M.^n", blindfire_buf, action_verb, buf); + snprintf(buf3, sizeof(buf3), "%s$n %s%ss a %s^n at $N, but it doesn't seem to hurt $M.", vehicle_message, blindfire_buf, action_verb, buf); break; case 2: - snprintf(buf1, sizeof(buf1), "^r%s$n^r %sfires a %s^r at you, but you roll with the impact.^n", vehicle_message, blindfire_buf, buf); - snprintf(buf2, sizeof(buf2), "^yYou %sfire a %s^y at $N^y, but $E roll%s with the impact.^n", blindfire_buf, buf, HSSH_SHOULD_PLURAL(victim) ? "s" : ""); - snprintf(buf3, sizeof(buf3), "%s$n %sfires a %s^n at $N, but $E roll%s with the impact.", vehicle_message, blindfire_buf, buf, HSSH_SHOULD_PLURAL(victim) ? "s" : ""); + snprintf(buf1, sizeof(buf1), "^r%s$n^r %s%ss a %s^r at you, but you roll with the impact.^n", vehicle_message, blindfire_buf, action_verb, buf); + snprintf(buf2, sizeof(buf2), "^yYou %s%s a %s^y at $N^y, but $E roll%s with the impact.^n", blindfire_buf, action_verb, buf, HSSH_SHOULD_PLURAL(victim) ? "s" : ""); + snprintf(buf3, sizeof(buf3), "%s$n %s%ss a %s^n at $N, but $E roll%s with the impact.", vehicle_message, blindfire_buf, action_verb, buf, HSSH_SHOULD_PLURAL(victim) ? "s" : ""); break; } } else if (damage == LIGHT) { @@ -4476,37 +4481,37 @@ void combat_message(struct char_data *ch, struct char_data *victim, struct obj_d if (damage < 0) { switch (number(1, 3)) { case 1: - snprintf(buf1, sizeof(buf1), "^r$n^r %sfires a %s^r at you, but you manage to dodge.^n", blindfire_buf, buf); - snprintf(buf2, sizeof(buf2), "^yYou %sfire a %s^y at $N^y, but $E manage%s to dodge.^n", blindfire_buf, buf, HSSH_SHOULD_PLURAL(victim) ? "s" : ""); - snprintf(buf3, sizeof(buf3), "$n %sfires a %s^n at $N, but $E manage%s to dodge.", blindfire_buf, buf, HSSH_SHOULD_PLURAL(victim) ? "s" : ""); - snprintf(buf4, sizeof(buf4), "$N %sfires a %s^n at $n, but $e manage%s to dodge.", blindfire_buf, buf, HSSH_SHOULD_PLURAL(victim) ? "s" : ""); + snprintf(buf1, sizeof(buf1), "^r$n^r %s%ss a %s^r at you, but you manage to dodge.^n", blindfire_buf, action_verb, buf); + snprintf(buf2, sizeof(buf2), "^yYou %s%s a %s^y at $N^y, but $E manage%s to dodge.^n", blindfire_buf, action_verb, buf, HSSH_SHOULD_PLURAL(victim) ? "s" : ""); + snprintf(buf3, sizeof(buf3), "$n %s%ss a %s^n at $N, but $E manage%s to dodge.", blindfire_buf, action_verb, buf, HSSH_SHOULD_PLURAL(victim) ? "s" : ""); + snprintf(buf4, sizeof(buf4), "$N %s%ss a %s^n at $n, but $e manage%s to dodge.", blindfire_buf, action_verb, buf, HSSH_SHOULD_PLURAL(victim) ? "s" : ""); break; case 2: - snprintf(buf1, sizeof(buf1), "^r$n^r %sfires a %s^r at you, but you easily dodge.^n", blindfire_buf, buf); - snprintf(buf2, sizeof(buf2), "^yYou %sfire a %s^y at $N^y, but $E easily dodge%s.^n", blindfire_buf, buf, HSSH_SHOULD_PLURAL(victim) ? "s" : ""); - snprintf(buf3, sizeof(buf3), "$n %sfires a %s^n at $N, but $E easily dodge%s.", blindfire_buf, buf, HSSH_SHOULD_PLURAL(victim) ? "s" : ""); - snprintf(buf4, sizeof(buf4), "$N %sfires a %s^n at $n, but $e easily dodge%s.", blindfire_buf, buf, HSSH_SHOULD_PLURAL(victim) ? "s" : ""); + snprintf(buf1, sizeof(buf1), "^r$n^r %s%ss a %s^r at you, but you easily dodge.^n", blindfire_buf, action_verb, buf); + snprintf(buf2, sizeof(buf2), "^yYou %s%s a %s^y at $N^y, but $E easily dodge%s.^n", blindfire_buf, action_verb, buf, HSSH_SHOULD_PLURAL(victim) ? "s" : ""); + snprintf(buf3, sizeof(buf3), "$n %s%ss a %s^n at $N, but $E easily dodge%s.", blindfire_buf, action_verb, buf, HSSH_SHOULD_PLURAL(victim) ? "s" : ""); + snprintf(buf4, sizeof(buf4), "$N %s%ss a %s^n at $n, but $e easily dodge%s.", blindfire_buf, action_verb, buf, HSSH_SHOULD_PLURAL(victim) ? "s" : ""); break; case 3: - snprintf(buf1, sizeof(buf1), "^r$n^r %sfires a %s^r at you, but you move out of the way.^n", blindfire_buf, buf); - snprintf(buf2, sizeof(buf2), "^yYou %sfire a %s^y at $N^y, but $E move%s out of the way.^n", blindfire_buf, buf, HSSH_SHOULD_PLURAL(victim) ? "s" : ""); - snprintf(buf3, sizeof(buf3), "$n %sfires a %s^n at $N, but $E move%s out of the way.", blindfire_buf, buf, HSSH_SHOULD_PLURAL(victim) ? "s" : ""); - snprintf(buf4, sizeof(buf4), "$N %sfires a %s^n at $n, but $e move%s out of the way.", blindfire_buf, buf, HSSH_SHOULD_PLURAL(victim) ? "s" : ""); + snprintf(buf1, sizeof(buf1), "^r$n^r %s%ss a %s^r at you, but you move out of the way.^n", blindfire_buf, action_verb, buf); + snprintf(buf2, sizeof(buf2), "^yYou %s%s a %s^y at $N^y, but $E move%s out of the way.^n", blindfire_buf, action_verb, buf, HSSH_SHOULD_PLURAL(victim) ? "s" : ""); + snprintf(buf3, sizeof(buf3), "$n %s%ss a %s^n at $N, but $E move%s out of the way.", blindfire_buf, action_verb, buf, HSSH_SHOULD_PLURAL(victim) ? "s" : ""); + snprintf(buf4, sizeof(buf4), "$N %s%ss a %s^n at $n, but $e move%s out of the way.", blindfire_buf, action_verb, buf, HSSH_SHOULD_PLURAL(victim) ? "s" : ""); break; } } else if (damage == 0) { switch (number(1, 2)) { case 1: - snprintf(buf1, sizeof(buf1), "^r$n^r %sfires a %s^r at you, but your armor holds.^n", blindfire_buf, buf); - snprintf(buf2, sizeof(buf2), "^yYou %sfire a %s^y at $N^y, but it doesn't seem to hurt $M.^n", blindfire_buf, buf); - snprintf(buf3, sizeof(buf3), "$n %sfires a %s^n at $N, but it doesn't seem to hurt $M.", blindfire_buf, buf); - snprintf(buf4, sizeof(buf4), "$N %sfires a %s^n at $n, but it doesn't seem to hurt $m.", blindfire_buf, buf); + snprintf(buf1, sizeof(buf1), "^r$n^r %s%ss a %s^r at you, but your armor holds.^n", blindfire_buf, action_verb, buf); + snprintf(buf2, sizeof(buf2), "^yYou %s%s a %s^y at $N^y, but it doesn't seem to hurt $M.^n", blindfire_buf, action_verb, buf); + snprintf(buf3, sizeof(buf3), "$n %s%ss a %s^n at $N, but it doesn't seem to hurt $M.", blindfire_buf, action_verb, buf); + snprintf(buf4, sizeof(buf4), "$N %s%ss a %s^n at $n, but it doesn't seem to hurt $m.", blindfire_buf, action_verb, buf); break; case 2: - snprintf(buf1, sizeof(buf1), "^r$n^r %sfires a %s^r at you, but you roll with the impact.^n", blindfire_buf, buf); - snprintf(buf2, sizeof(buf2), "^yYou %sfire a %s^y at $N^y, but $E roll%s with the impact.^n", blindfire_buf, buf, HSSH_SHOULD_PLURAL(victim) ? "s" : ""); - snprintf(buf3, sizeof(buf3), "$n %sfires a %s^n at $N, but $E roll%s with the impact.", blindfire_buf, buf, HSSH_SHOULD_PLURAL(victim) ? "s" : ""); - snprintf(buf4, sizeof(buf4), "$N %sfires a %s^n at $n, but $e roll%s with the impact.", blindfire_buf, buf, HSSH_SHOULD_PLURAL(victim) ? "s" : ""); + snprintf(buf1, sizeof(buf1), "^r$n^r %s%ss a %s^r at you, but you roll with the impact.^n", blindfire_buf, action_verb, buf); + snprintf(buf2, sizeof(buf2), "^yYou %s%s a %s^y at $N^y, but $E roll%s with the impact.^n", blindfire_buf, action_verb, buf, HSSH_SHOULD_PLURAL(victim) ? "s" : ""); + snprintf(buf3, sizeof(buf3), "$n %s%ss a %s^n at $N, but $E roll%s with the impact.", blindfire_buf, action_verb, buf, HSSH_SHOULD_PLURAL(victim) ? "s" : ""); + snprintf(buf4, sizeof(buf4), "$N %s%ss a %s^n at $n, but $e roll%s with the impact.", blindfire_buf, action_verb, buf, HSSH_SHOULD_PLURAL(victim) ? "s" : ""); break; } } else if (damage == LIGHT) { From 269f05fd838367929715644234f163476ac3157a Mon Sep 17 00:00:00 2001 From: Lucien Date: Sun, 15 Mar 2026 14:17:48 -0700 Subject: [PATCH 08/13] Made get_skill() more reliable about when it writes out --- src/utils.cpp | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/utils.cpp b/src/utils.cpp index 052adf04c..0e4e57ccf 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -1922,7 +1922,11 @@ int get_skill(struct char_data *ch, int skill, int &target, char *writeout_buffe if (target >= 8) { strlcat(gskbuf, "failed to default (TN 8+), returning 0 dice. ", sizeof(gskbuf)); - act(gskbuf, 1, ch, NULL, NULL, TO_ROLLS); + if (writeout_buffer) { + strlcpy(writeout_buffer, gskbuf, writeout_buffer_size); + } else { + act(gskbuf, 1, ch, NULL, NULL, TO_ROLLS); + } send_to_char("You can't even begin to figure out such a complex task...\r\n", ch); return 0; } @@ -1931,7 +1935,11 @@ int get_skill(struct char_data *ch, int skill, int &target, char *writeout_buffe if (skills[skill].no_defaulting_allowed) { strlcat(gskbuf, "failed to default (skill prohibits default), returning 0 dice. ", sizeof(gskbuf)); - act(gskbuf, 1, ch, NULL, NULL, TO_ROLLS); + if (writeout_buffer) { + strlcpy(writeout_buffer, gskbuf, writeout_buffer_size); + } else { + act(gskbuf, 1, ch, NULL, NULL, TO_ROLLS); + } send_to_char("You can't even begin to figure out how to do that...\r\n", ch); return 0; } @@ -2289,7 +2297,11 @@ int get_skill(struct char_data *ch, int skill, int &target, char *writeout_buffe snprintf(ENDOF(gskbuf), sizeof(gskbuf) - strlen(gskbuf), "Final total skill: %d dice.", skill_dice); - act(gskbuf, 1, ch, NULL, NULL, TO_ROLLS); + if (writeout_buffer) { + strlcpy(writeout_buffer, gskbuf, writeout_buffer_size); + } else { + act(gskbuf, 1, ch, NULL, NULL, TO_ROLLS); + } if (skill_dice <= 0) { From 86c12e46291f79b926dfff6a49096e236b373536 Mon Sep 17 00:00:00 2001 From: Lucien Date: Sun, 15 Mar 2026 15:31:54 -0700 Subject: [PATCH 09/13] Fixed inconsistent application of chipjack cpool limits --- src/newfight.cpp | 148 +++++++++++++++++++++++------------------------ src/newfight.hpp | 37 ++++++++++-- 2 files changed, 104 insertions(+), 81 deletions(-) diff --git a/src/newfight.cpp b/src/newfight.cpp index 13086da59..32093bc13 100644 --- a/src/newfight.cpp +++ b/src/newfight.cpp @@ -480,11 +480,19 @@ bool hit_with_multiweapon_toggle(struct char_data *attacker, struct char_data *v // Calculate the attacker's total skill (this modifies TN). In a code block for var scoping. { int prior_tn = att->ranged->tn; - att->ranged->dice = get_skill(att->ch, att->ranged->skill, att->ranged->tn); + int skill_dice = get_skill(att->ch, att->ranged->skill, att->ranged->tn, rbuf, sizeof(rbuf)); + att->ranged->dice = skill_dice + att->ranged->cpool_offense_dice; + // Print info. + snprintf(rbuf, sizeof(rbuf), "Attacker has %d skill + %d pool%s: will roll %d dice.", + skill_dice, + att->ranged->cpool_offense_dice, + GET_CHIPJACKED_SKILL(att->ch, att->ranged->skill) ? " (zeroed due to using activesoft)" : "", + att->ranged->dice); + if (att->ranged->tn != prior_tn) { - snprintf(rbuf, sizeof(rbuf), "^jRanged attack roll TN modified in get_skill() to ^c%d^j.^n", att->ranged->tn); - } else { - strlcpy(rbuf, "^jRanged attack roll TN not modified in get_skill().^n", sizeof(rbuf)); + snprintf(ENDOF(rbuf), sizeof(rbuf) - strlen(rbuf), "\r\n^jTN modified in get_skill() from %d to ^c%d^j.^n", + prior_tn, + att->ranged->tn); } SEND_RBUF_TO_ROLLS_FOR_BOTH_ATTACKER_AND_DEFENDER; } @@ -496,19 +504,14 @@ bool hit_with_multiweapon_toggle(struct char_data *attacker, struct char_data *v SEND_RBUF_TO_ROLLS_FOR_BOTH_ATTACKER_AND_DEFENDER; } - snprintf(rbuf, sizeof(rbuf), "^jAfter get_skill(), attacker's ranged attack roll TN is ^c%d^n.", att->ranged->tn); + att->ranged->successes = success_test(att->ranged->dice, att->ranged->tn); + snprintf(rbuf, sizeof(rbuf), "^JAttack: Rolling %d dice VS TN %d yielded ^c%d^J success%s.^n", + att->ranged->dice, + att->ranged->tn, + att->ranged->successes, + att->ranged->successes == 1 ? "" : "s"); SEND_RBUF_TO_ROLLS_FOR_BOTH_ATTACKER_AND_DEFENDER; - { - int bonus_from_offense_pool = MIN(GET_SKILL(att->ch, att->ranged->skill), GET_OFFENSE(att->ch)); - snprintf(rbuf, sizeof(rbuf), "^JAttack: Rolling %d + %d dice VS TN %d... ", att->ranged->dice, bonus_from_offense_pool, att->ranged->tn); - att->ranged->dice += bonus_from_offense_pool; - - att->ranged->successes = success_test(att->ranged->dice, att->ranged->tn); - snprintf(ENDOF(rbuf), sizeof(rbuf) - strlen(rbuf), "^c%d^J success%s.^n", att->ranged->successes, att->ranged->successes == 1 ? "" : "s"); - SEND_RBUF_TO_ROLLS_FOR_BOTH_ATTACKER_AND_DEFENDER; - } - // If you can't dodge, you're presumably not using your dice on your dodge pool, so shift those this round. if (def->dodge_pool > 0) { if (def->is_paralyzed || def->is_insensate || AFF_FLAGGED(def->ch, AFF_PRONE) || def->is_surprised) { @@ -550,8 +553,8 @@ bool hit_with_multiweapon_toggle(struct char_data *attacker, struct char_data *v def->ranged->tn = MAX(def->ranged->tn, 2); // No, you CANNOT collapse these two lines into MAX(0, s_t()), because it calls s_t() twice. - def->ranged->successes = success_test(def->ranged->dice, def->ranged->tn); - def->ranged->successes = MAX(0, def->ranged->successes); + int test_result = success_test(def->ranged->dice, def->ranged->tn); + def->ranged->successes = MAX(0, test_result); att->ranged->successes -= def->ranged->successes; snprintf(rbuf, sizeof(rbuf), "^eDodge: Dice %d (%d pool + %d sidestep), TN %d, Successes ^c%d^e. This means attacker's net successes = ^c%d^e.^n", @@ -707,9 +710,6 @@ bool hit_with_multiweapon_toggle(struct char_data *attacker, struct char_data *v } // Melee combat. If you're here, we don't care about your ranged combat setup-- it's face-beating time. All calculations here are done for both attacker and defender (in case of defender counterstriking) else { - // TODO: Review the rolls act code and figure out how to split things up between meatspace and decking rolls. A new TO_MATRIX_ROLLS maybe? - int dont_compile = "msg"; - // Ensure that neither combatant has the closing flag set (if we've gotten here, the closing test was already cleared in perform-violence()) AFF_FLAGS(att->ch).RemoveBit(AFF_APPROACH); AFF_FLAGS(def->ch).RemoveBit(AFF_APPROACH); @@ -746,33 +746,6 @@ bool hit_with_multiweapon_toggle(struct char_data *attacker, struct char_data *v } else { */ - // Set up dice for attacker and defender. - for (auto &c : {AttDef{att, def}, AttDef{def, att}}) { - int prior_tn = c.fighter->melee->tn; - int skill_dice = c.fighter->melee->skill_bonus + get_skill(c.fighter->ch, c.fighter->melee->skill, c.fighter->melee->tn); - int cpool_dice = MIN(skill_dice, GET_OFFENSE(c.fighter->ch)); - - if (GET_CHIPJACKED_SKILL(c.fighter->ch, c.fighter->melee->skill)) { - cpool_dice = 0; - } - - c.fighter->melee->dice = skill_dice + cpool_dice; - snprintf(rbuf, sizeof(rbuf), "%s has %d skill (incl %d weap focus), %d pool%s: rolls %d dice.", - c.fighter == att ? "Attacker" : "Defender", - skill_dice, - c.fighter->melee->skill_bonus, - cpool_dice, - GET_CHIPJACKED_SKILL(c.fighter->ch, c.fighter->melee->skill) ? " (zeroed due to using activesoft)" : "", - c.fighter->melee->dice); - if (c.fighter->melee->tn != prior_tn) { - snprintf(ENDOF(rbuf), sizeof(rbuf) - strlen(rbuf), "\r\n%s TN modified in get_skill() from %d to %d.", - c.fighter == att ? "Attacker" : "Defender", - prior_tn, - att->melee->tn); - } - SEND_RBUF_TO_ROLLS_FOR_BOTH_ATTACKER_AND_DEFENDER; - } - // Adepts get bonus dice when counterattacking. if (GET_POWER(def->ch, ADEPT_COUNTERSTRIKE) > 0) { def->melee->dice += GET_POWER(def->ch, ADEPT_COUNTERSTRIKE); @@ -780,18 +753,41 @@ bool hit_with_multiweapon_toggle(struct char_data *attacker, struct char_data *v SEND_RBUF_TO_ROLLS_FOR_BOTH_ATTACKER_AND_DEFENDER; } - // Bugfix: If you're unable to fight, you don't get to counterattack. - if (def->is_insensate || def->is_paralyzed) { - // Shift your offense dice to your body pool. - def->body_pool += GET_OFFENSE(def->ch); - def->melee->dice = 0; - strlcpy(rbuf, "^yDefender insensate/paralyzed, dice capped to zero. Any offense dice shifted to body pool for this round.^n", sizeof(rbuf)); - SEND_RBUF_TO_ROLLS_FOR_BOTH_ATTACKER_AND_DEFENDER; + // Set up dice for attacker and defender. This was mostly handled previously, so we just check for inability to fight then print. + for (auto &c : {AttDef{att, def}, AttDef{def, att}}) { + // If you're unable to fight, you don't get to counterattack. + if (c.fighter->is_insensate || c.fighter->is_paralyzed) { + // Shift your offense dice to your body pool. + c.fighter->body_pool += GET_OFFENSE(c.fighter->ch); + c.fighter->melee->dice = 0; + snprintf(rbuf, sizeof(rbuf), "^y%s insensate/paralyzed, dice capped to zero. Any offense dice shifted to body pool for this round.^n", + c.fighter == att ? "Attacker (BUG, REPORT TO STAFF)" : "Defender"); + SEND_RBUF_TO_ROLLS_FOR_BOTH_ATTACKER_AND_DEFENDER; + } else { + // Dice for melee are your skill, your weapon focus bonus, plus your cpool offense dice. + int prior_tn = c.fighter->melee->tn; + int skill_dice = get_skill(c.fighter->ch, c.fighter->melee->skill, c.fighter->melee->tn); + c.fighter->melee->dice = skill_dice + + c.fighter->melee->skill_bonus + + c.fighter->melee->cpool_offense_dice; + // Print info. + snprintf(rbuf, sizeof(rbuf), "%s has %d skill + %d weap focus + %d pool%s: will roll %d dice.", + c.fighter == att ? "Attacker" : "Defender", + skill_dice, + c.fighter->melee->skill_bonus, + c.fighter->melee->cpool_offense_dice, + GET_CHIPJACKED_SKILL(c.fighter->ch, c.fighter->melee->skill) ? " (zeroed due to using activesoft)" : "", + c.fighter->melee->dice); + if (c.fighter->melee->tn != prior_tn) { + snprintf(ENDOF(rbuf), sizeof(rbuf) - strlen(rbuf), "\r\n%s TN modified in get_skill() will roll %d to %d.", + c.fighter == att ? "Attacker" : "Defender", + prior_tn, + att->melee->tn); + } + SEND_RBUF_TO_ROLLS_FOR_BOTH_ATTACKER_AND_DEFENDER; + } } - snprintf(rbuf, sizeof(rbuf), "^g%s's dice: ^W%d^g, %s's dice: ^W%d^g.^n", GET_CHAR_NAME(att->ch), att->melee->dice, GET_CHAR_NAME(def->ch), def->melee->dice); - SEND_RBUF_TO_ROLLS_FOR_BOTH_ATTACKER_AND_DEFENDER; - // Calculate the net reach. int net_reach = (GET_REACH(att->ch) + att->melee->reach_modifier) - (GET_REACH(def->ch) + def->melee->reach_modifier); @@ -893,7 +889,7 @@ bool hit_with_multiweapon_toggle(struct char_data *attacker, struct char_data *v net_successes = MAX(0, net_successes); } } else { - strlcpy(rbuf, "Surprised, paralyzed, insensate etc-- defender gets no clash roll.", sizeof(rbuf)); + strlcpy(rbuf, "Surprised, paralyzed, insensate etc-- defender gets no clash roll (default to 0 successes).", sizeof(rbuf)); SEND_RBUF_TO_ROLLS_FOR_BOTH_ATTACKER_AND_DEFENDER; int test_result = success_test(att->melee->dice, att->melee->tn); // don't put this in MAX, it calls it twice then tests vs first and actually uses second. att->melee->successes = MAX(1, test_result); @@ -1081,8 +1077,8 @@ bool hit_with_multiweapon_toggle(struct char_data *attacker, struct char_data *v int bod_success = 0; int bod_dice = def->body_pool; - // Unconscious? No pool dice for you. - if (def->is_paralyzed_or_insensate) + // Unconscious? No pool dice for you. (houserule) + if (def->is_paralyzed || def->is_insensate) bod_dice = 0; // Add your attribute. @@ -1507,7 +1503,21 @@ bool perform_nerve_strike(struct combat_data *att, struct combat_data *def, char impact_armor = MAX(0, def->standard_impact_rating - GET_POWER(att->ch, ADEPT_PENETRATINGSTRIKE)); } - // Calculate and display pre-success-test information. + // Calculate the attacker's total skill and print it. + { + int prior_tn = att->melee->tn; + att->melee->skill_dice = get_skill(att->ch, SKILL_UNARMED_COMBAT, att->melee->tn); + att->melee->dice = att->melee->skill_dice + + att->melee->cpool_offense_dice; + // Print info. + snprintf(rbuf, sizeof(rbuf), "Nerve strike: Attacker has %d skill + %d pool%s: will roll %d dice.", + att->melee->skill_dice, + att->melee->cpool_offense_dice, + GET_CHIPJACKED_SKILL(att->ch, SKILL_UNARMED_COMBAT) ? " (zeroed due to using activesoft)" : "", + att->melee->dice); + SEND_RBUF_TO_ROLLS_FOR_BOTH_ATTACKER_AND_DEFENDER; + } + snprintf(rbuf, rbuf_len, "%s VS %s: Nerve Strike target is 4 + impact %d + modifiers: ", GET_CHAR_NAME(att->ch), GET_CHAR_NAME(def->ch), @@ -1518,7 +1528,7 @@ bool perform_nerve_strike(struct combat_data *att, struct combat_data *def, char snprintf(ENDOF(rbuf), sizeof(rbuf) - strlen(rbuf), "-%d armor (penetrating strike), ", def->standard_impact_rating - impact_armor); } - att->melee->tn += impact_armor + modify_target_rbuf_raw(att->ch, rbuf, rbuf_len, att->melee->modifiers[COMBAT_MOD_VISIBILITY], FALSE); + att->melee->tn += impact_armor + modify_target_rbuf_raw(att->ch, ENDOF(rbuf), rbuf_len + strlen(rbuf), att->melee->modifiers[COMBAT_MOD_VISIBILITY], FALSE); for (int mod_index = 0; mod_index < NUM_COMBAT_MODIFIERS; mod_index++) { buf_mod(rbuf, rbuf_len, combat_modifiers[mod_index], att->melee->modifiers[mod_index]); @@ -1528,20 +1538,6 @@ bool perform_nerve_strike(struct combat_data *att, struct combat_data *def, char snprintf(ENDOF(rbuf), rbuf_len - strlen(rbuf), ". Total TN is %d.", att->melee->tn); SEND_RBUF_TO_ROLLS_FOR_BOTH_ATTACKER_AND_DEFENDER; - // Calculate the attacker's total skill and execute a success test. - { - int prior_tn = att->melee->tn; - att->melee->dice = get_skill(att->ch, SKILL_UNARMED_COMBAT, att->melee->tn); - if (att->melee->tn != prior_tn) { - snprintf(rbuf, rbuf_len, "TN modified in get_skill() to %d.", att->melee->tn); - SEND_RBUF_TO_ROLLS_FOR_BOTH_ATTACKER_AND_DEFENDER; - } - } - - int bonus = MIN(GET_SKILL(att->ch, SKILL_UNARMED_COMBAT), GET_OFFENSE(att->ch)); - snprintf(rbuf, rbuf_len, "Attacker is rolling %d + %d dice", att->melee->dice, bonus); - att->melee->dice += bonus; - att->melee->successes = success_test(att->melee->dice, att->melee->tn); int temp_qui_loss = MAX(0, (int) (att->melee->successes / 2)); snprintf(ENDOF(rbuf), rbuf_len - strlen(rbuf), ", and got %d successes, which translates to %d qui loss.", diff --git a/src/newfight.hpp b/src/newfight.hpp index c44b8142f..88bdc0cf1 100644 --- a/src/newfight.hpp +++ b/src/newfight.hpp @@ -87,11 +87,11 @@ struct cyberware_data { struct ranged_combat_data { int skill; + int cpool_offense_dice; int power; int power_before_armor; int dam_type; int damage_level; - int unaugmented_damage_level; bool is_physical; int tn; int dice; @@ -109,7 +109,7 @@ struct ranged_combat_data { struct obj_data *gyro; ranged_combat_data(struct char_data *ch, struct obj_data *weapon, bool ranged_combat_mode) : - skill(0), power(0), power_before_armor(0), dam_type(0), damage_level(0), unaugmented_damage_level(0), + skill(0), cpool_offense_dice(0), power(0), power_before_armor(0), dam_type(0), damage_level(0), is_physical(FALSE), tn(4), dice(0), successes(0), is_gel(FALSE), burst_count(0), recoil_comp(0), using_mounted_gun(FALSE), magazine(NULL), gyro(NULL) { @@ -163,7 +163,7 @@ struct ranged_combat_data { if (GET_EQ(ch, WEAR_WIELD) && GET_EQ(ch, WEAR_HOLD)) modifiers[COMBAT_MOD_DUAL_WIELDING] = 2; else - modifiers[COMBAT_MOD_SMARTLINK] -= check_smartlink(ch, weapon); + modifiers[COMBAT_MOD_SMARTLINK] -= check_smartlink(ch, weapon, false); // Setup: Apply handedness changes here. For firearms, we only care about wielding a 2h weapon with 1 hand. if (IS_OBJ_STAT(weapon, ITEM_EXTRA_INVERT_TWOHANDED) @@ -176,12 +176,20 @@ struct ranged_combat_data { // TODO: Cannon Companion p99 racial modifiers for weapons held } + + // Calculate the max offensive dice they can have in cpool. + if (GET_CHIPJACKED_SKILL(ch, skill)) { + cpool_offense_dice = 0; + } else { + cpool_offense_dice = MIN(GET_SKILL(ch, skill), GET_OFFENSE(ch)); + } } }; struct melee_combat_data { int skill; int skill_bonus; + int cpool_offense_dice; int power; int power_before_armor; int dam_type; @@ -194,11 +202,14 @@ struct melee_combat_data { bool is_monowhip; struct obj_data *riot_shield; + bool is_distance_strike; + int modifiers[NUM_COMBAT_MODIFIERS]; melee_combat_data(struct char_data *ch, struct obj_data *weapon, bool ranged_combat_mode, struct cyberware_data *cyber) : - skill(0), skill_bonus(0), power(0), power_before_armor(0), dam_type(0), damage_level(0), is_physical(FALSE), tn(4), - dice(0), successes(0), reach_modifier(0), is_monowhip(FALSE), riot_shield(NULL) + skill(0), skill_bonus(0), cpool_offense_dice(0), power(0), power_before_armor(0), dam_type(0), damage_level(0), + is_physical(FALSE), tn(4), dice(0), successes(0), reach_modifier(0), is_monowhip(FALSE), + is_distance_strike(FALSE), riot_shield(NULL) { assert(ch != NULL); @@ -364,6 +375,8 @@ struct melee_combat_data { damage_level = GET_POWER(ch, ADEPT_KILLING_HANDS); is_physical = TRUE; } + + is_distance_strike = GET_POWER(ch, ADEPT_DISTANCE_STRIKE) > 0; } is_physical = is_physical || IS_DAMTYPE_PHYSICAL(dam_type); @@ -373,6 +386,13 @@ struct melee_combat_data { if (!riot_shield || GET_OBJ_TYPE(riot_shield) != ITEM_WORN) { riot_shield = NULL; } + + // Calculate the max offensive dice they can have in cpool. Add weapon focus skill bonus to the cap. + if (GET_CHIPJACKED_SKILL(ch, skill)) { + cpool_offense_dice = 0; + } else { + cpool_offense_dice = MIN(GET_SKILL(ch, skill) + skill_bonus, GET_OFFENSE(ch)); + } } }; @@ -425,11 +445,18 @@ struct combat_data weapon = weap; + // If we're using a weapon, set ranged_combat_mode to true. ranged_combat_mode = (weap && GET_OBJ_TYPE(weap) == ITEM_WEAPON && WEAPON_IS_GUN(weapon)); cyber = new struct cyberware_data(ch); + + // If we're using Distance Strike with no cyberweapons etc, override ranged to true. + if (cyber->num_cyberweapons <= 0 && GET_POWER(ch, ADEPT_DISTANCE_STRIKE)) { + ranged_combat_mode = true; + } + ranged = new struct ranged_combat_data(ch, weapon, ranged_combat_mode); melee = new struct melee_combat_data(ch, weapon, ranged_combat_mode, cyber); From 4ec8944cde7111e828223bdd58a98d91857a3691 Mon Sep 17 00:00:00 2001 From: Lucien Date: Sun, 15 Mar 2026 15:34:30 -0700 Subject: [PATCH 10/13] Swapped direct lookup of distance strike to struct version --- src/newfight.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/newfight.cpp b/src/newfight.cpp index 32093bc13..e080240e5 100644 --- a/src/newfight.cpp +++ b/src/newfight.cpp @@ -792,7 +792,7 @@ bool hit_with_multiweapon_toggle(struct char_data *attacker, struct char_data *v int net_reach = (GET_REACH(att->ch) + att->melee->reach_modifier) - (GET_REACH(def->ch) + def->melee->reach_modifier); // MitS 149: Ignore reach modifiers. This should happen _before_ NPCs decide their close combat status. - if (GET_POWER(att->ch, ADEPT_DISTANCE_STRIKE)) { + if (att->melee->is_distance_strike) { net_reach = 0; } @@ -926,7 +926,7 @@ bool hit_with_multiweapon_toggle(struct char_data *attacker, struct char_data *v // If your enemy got more successes than you, guess what? You're the one who gets their face caved in. if (net_successes < 0) { - if (GET_POWER(att->ch, ADEPT_DISTANCE_STRIKE)) { + if (att->melee->is_distance_strike) { // MitS 149: You cannot be counterstriked while using distance strike. return FALSE; } @@ -988,7 +988,7 @@ bool hit_with_multiweapon_toggle(struct char_data *attacker, struct char_data *v // Most of our melee combat fields were set during setup, so we're only here for the effects of armor. if (att->cyber->num_cyberweapons <= 0 && GET_POWER(att->ch, ADEPT_PENETRATINGSTRIKE) - && !GET_POWER(att->ch, ADEPT_DISTANCE_STRIKE)) + && !att->melee->is_distance_strike) { strlcpy(rbuf, "Using penetrating strike.", sizeof(rbuf)); att->melee->power = att->melee->power_before_armor - MAX(0, def->standard_impact_rating - GET_POWER(att->ch, ADEPT_PENETRATINGSTRIKE)); @@ -1342,7 +1342,7 @@ bool handle_flame_aura(struct combat_data *att, struct combat_data *def) { return FALSE; // no-op: adept distance strike - if (GET_POWER(att->ch, ADEPT_DISTANCE_STRIKE)) + if (att->melee->is_distance_strike) return FALSE; int force = -1; @@ -1499,19 +1499,19 @@ bool perform_nerve_strike(struct combat_data *att, struct combat_data *def, char int impact_armor = def->standard_impact_rating; // Apply Penetrating Strike to the nerve strike. - if (GET_POWER(att->ch, ADEPT_PENETRATINGSTRIKE) && !GET_POWER(att->ch, ADEPT_DISTANCE_STRIKE)) { + if (GET_POWER(att->ch, ADEPT_PENETRATINGSTRIKE) && !att->melee->is_distance_strike) { impact_armor = MAX(0, def->standard_impact_rating - GET_POWER(att->ch, ADEPT_PENETRATINGSTRIKE)); } // Calculate the attacker's total skill and print it. { int prior_tn = att->melee->tn; - att->melee->skill_dice = get_skill(att->ch, SKILL_UNARMED_COMBAT, att->melee->tn); - att->melee->dice = att->melee->skill_dice + int skill_dice = get_skill(att->ch, SKILL_UNARMED_COMBAT, att->melee->tn); + att->melee->dice = skill_dice + att->melee->cpool_offense_dice; // Print info. snprintf(rbuf, sizeof(rbuf), "Nerve strike: Attacker has %d skill + %d pool%s: will roll %d dice.", - att->melee->skill_dice, + skill_dice, att->melee->cpool_offense_dice, GET_CHIPJACKED_SKILL(att->ch, SKILL_UNARMED_COMBAT) ? " (zeroed due to using activesoft)" : "", att->melee->dice); From 2d2eb9da99e8203e4f774007b7ab2e39136879b3 Mon Sep 17 00:00:00 2001 From: Lucien Date: Sun, 15 Mar 2026 15:50:56 -0700 Subject: [PATCH 11/13] Fixed irregularities around penetrating strike --- src/newfight.cpp | 27 +++++++++++---------------- src/newfight.hpp | 4 +++- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/src/newfight.cpp b/src/newfight.cpp index e080240e5..1d51de255 100644 --- a/src/newfight.cpp +++ b/src/newfight.cpp @@ -986,12 +986,10 @@ bool hit_with_multiweapon_toggle(struct char_data *attacker, struct char_data *v // Cyber and unarmed combat. else { // Most of our melee combat fields were set during setup, so we're only here for the effects of armor. - if (att->cyber->num_cyberweapons <= 0 - && GET_POWER(att->ch, ADEPT_PENETRATINGSTRIKE) - && !att->melee->is_distance_strike) - { - strlcpy(rbuf, "Using penetrating strike.", sizeof(rbuf)); - att->melee->power = att->melee->power_before_armor - MAX(0, def->standard_impact_rating - GET_POWER(att->ch, ADEPT_PENETRATINGSTRIKE)); + if (att->melee->penetrating_strike) { + int penetrating_strike_delta = MIN(att->melee->penetrating_strike, def->standard_impact_rating); + snprintf(ENDOF(rbuf), sizeof(rbuf) - strlen(rbuf), "-%d armor (penetrating strike), ", penetrating_strike_delta); + att->melee->power = att->melee->power_before_armor - def->standard_impact_rating - penetrating_strike_delta; } else { strlcpy(rbuf, "Using unarmed / cyberweapon.", sizeof(rbuf)); att->melee->power = att->melee->power_before_armor - def->standard_impact_rating; @@ -1497,12 +1495,6 @@ bool perform_nerve_strike(struct combat_data *att, struct combat_data *def, char return TRUE; } - int impact_armor = def->standard_impact_rating; - // Apply Penetrating Strike to the nerve strike. - if (GET_POWER(att->ch, ADEPT_PENETRATINGSTRIKE) && !att->melee->is_distance_strike) { - impact_armor = MAX(0, def->standard_impact_rating - GET_POWER(att->ch, ADEPT_PENETRATINGSTRIKE)); - } - // Calculate the attacker's total skill and print it. { int prior_tn = att->melee->tn; @@ -1518,17 +1510,20 @@ bool perform_nerve_strike(struct combat_data *att, struct combat_data *def, char SEND_RBUF_TO_ROLLS_FOR_BOTH_ATTACKER_AND_DEFENDER; } - snprintf(rbuf, rbuf_len, "%s VS %s: Nerve Strike target is 4 + impact %d + modifiers: ", + snprintf(rbuf, rbuf_len, "%s VS %s: Nerve Strike target is 4 + %d impact armor + modifiers: ", GET_CHAR_NAME(att->ch), GET_CHAR_NAME(def->ch), def->standard_impact_rating ); + att->melee->tn += def->standard_impact_rating; - if (impact_armor != def->standard_impact_rating) { - snprintf(ENDOF(rbuf), sizeof(rbuf) - strlen(rbuf), "-%d armor (penetrating strike), ", def->standard_impact_rating - impact_armor); + if (att->melee->penetrating_strike) { + int penetrating_strike_delta = MIN(att->melee->penetrating_strike, def->standard_impact_rating); + snprintf(ENDOF(rbuf), sizeof(rbuf) - strlen(rbuf), "-%d armor (penetrating strike), ", penetrating_strike_delta); + att->melee->tn -= penetrating_strike_delta; } - att->melee->tn += impact_armor + modify_target_rbuf_raw(att->ch, ENDOF(rbuf), rbuf_len + strlen(rbuf), att->melee->modifiers[COMBAT_MOD_VISIBILITY], FALSE); + att->melee->tn += modify_target_rbuf_raw(att->ch, rbuf, rbuf_len, att->melee->modifiers[COMBAT_MOD_VISIBILITY], FALSE); for (int mod_index = 0; mod_index < NUM_COMBAT_MODIFIERS; mod_index++) { buf_mod(rbuf, rbuf_len, combat_modifiers[mod_index], att->melee->modifiers[mod_index]); diff --git a/src/newfight.hpp b/src/newfight.hpp index 88bdc0cf1..a4da8419f 100644 --- a/src/newfight.hpp +++ b/src/newfight.hpp @@ -203,13 +203,14 @@ struct melee_combat_data { struct obj_data *riot_shield; bool is_distance_strike; + int penetrating_strike; int modifiers[NUM_COMBAT_MODIFIERS]; melee_combat_data(struct char_data *ch, struct obj_data *weapon, bool ranged_combat_mode, struct cyberware_data *cyber) : skill(0), skill_bonus(0), cpool_offense_dice(0), power(0), power_before_armor(0), dam_type(0), damage_level(0), is_physical(FALSE), tn(4), dice(0), successes(0), reach_modifier(0), is_monowhip(FALSE), - is_distance_strike(FALSE), riot_shield(NULL) + is_distance_strike(FALSE), penetrating_strike(0), riot_shield(NULL) { assert(ch != NULL); @@ -377,6 +378,7 @@ struct melee_combat_data { } is_distance_strike = GET_POWER(ch, ADEPT_DISTANCE_STRIKE) > 0; + penetrating_strike = (cyber->num_cyberweapons <= 0 && !is_distance_strike) ? GET_POWER(ch, ADEPT_PENETRATINGSTRIKE) : 0; } is_physical = is_physical || IS_DAMTYPE_PHYSICAL(dam_type); From 1d6c8db851419c642d691f429bace4996a443956 Mon Sep 17 00:00:00 2001 From: Lucien Date: Sun, 15 Mar 2026 16:19:33 -0700 Subject: [PATCH 12/13] Cleaned up some messaging and an inversion of penetrating strike --- src/newfight.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/newfight.cpp b/src/newfight.cpp index 1d51de255..6bea68937 100644 --- a/src/newfight.cpp +++ b/src/newfight.cpp @@ -927,7 +927,8 @@ bool hit_with_multiweapon_toggle(struct char_data *attacker, struct char_data *v // If your enemy got more successes than you, guess what? You're the one who gets their face caved in. if (net_successes < 0) { if (att->melee->is_distance_strike) { - // MitS 149: You cannot be counterstriked while using distance strike. + // MitS 149: You cannot be counterstriked while using distance strike. Send a message and bail. + combat_message(att->ch, def->ch, att->weapon, 0, 0, att->melee->modifiers[COMBAT_MOD_VISIBILITY]); return FALSE; } // This messaging gets a little annoying. @@ -988,15 +989,15 @@ bool hit_with_multiweapon_toggle(struct char_data *attacker, struct char_data *v // Most of our melee combat fields were set during setup, so we're only here for the effects of armor. if (att->melee->penetrating_strike) { int penetrating_strike_delta = MIN(att->melee->penetrating_strike, def->standard_impact_rating); - snprintf(ENDOF(rbuf), sizeof(rbuf) - strlen(rbuf), "-%d armor (penetrating strike), ", penetrating_strike_delta); - att->melee->power = att->melee->power_before_armor - def->standard_impact_rating - penetrating_strike_delta; + snprintf(ENDOF(rbuf), sizeof(rbuf) - strlen(rbuf), "Using penetrating strike (-%d armor).", penetrating_strike_delta); + att->melee->power = att->melee->power_before_armor - (def->standard_impact_rating - penetrating_strike_delta); } else { strlcpy(rbuf, "Using unarmed / cyberweapon.", sizeof(rbuf)); att->melee->power = att->melee->power_before_armor - def->standard_impact_rating; } } - snprintf(ENDOF(rbuf), sizeof(rbuf) - strlen(rbuf), " After armor: ^c%d%s^n.", + snprintf(ENDOF(rbuf), sizeof(rbuf) - strlen(rbuf), " After armor: ^c%d %s^n.", att->melee->power, GET_SHORT_WOUND_NAME(att->melee->damage_level)); @@ -1008,6 +1009,7 @@ bool hit_with_multiweapon_toggle(struct char_data *attacker, struct char_data *v if (att->weapon) { has_magic_weapon = GET_WEAPON_FOCUS_RATING(att->weapon) > 0 && (IS_NPNPC(att->ch) || is_weapon_focus_usable_by(att->weapon, att->ch)); } else { + // SR3 p170: Killing Hands explicitly beats Immunity to Normal Weapons. has_magic_weapon = GET_POWER(att->ch, ADEPT_KILLING_HANDS); } @@ -1075,7 +1077,7 @@ bool hit_with_multiweapon_toggle(struct char_data *attacker, struct char_data *v int bod_success = 0; int bod_dice = def->body_pool; - // Unconscious? No pool dice for you. (houserule) + // Unconscious? No pool dice for you. (houserule to reduce the time people spend beating on unconscious/morted mobs) if (def->is_paralyzed || def->is_insensate) bod_dice = 0; @@ -1211,7 +1213,7 @@ bool hit_with_multiweapon_toggle(struct char_data *attacker, struct char_data *v } //Handle suprise attack/alertness here -- defender didn't die. if (IS_NPC(def->ch)) { - set_mob_alert(def->ch, 20); + set_mob_alert(def->ch, 30); } } } From 30c92b815112179c7fce2956edb2d7a371a03158 Mon Sep 17 00:00:00 2001 From: Lucien Date: Sun, 15 Mar 2026 17:59:36 -0700 Subject: [PATCH 13/13] Messaging oversight fix --- src/newfight.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/newfight.cpp b/src/newfight.cpp index 6bea68937..3a0a6b2b2 100644 --- a/src/newfight.cpp +++ b/src/newfight.cpp @@ -115,6 +115,9 @@ bool hit_with_multiweapon_toggle(struct char_data *attacker, struct char_data *v } } + // Below this line, we know that the attacker is neither paralyzed nor insensate-- no need to filter message visibility based on that. + // The _defender_ might still be, so keep an eye out for any send_to_char that override that. + // Precondition: If you're in melee combat and your foe isn't present, stop fighting. if (!att->ranged_combat_mode && att->ch->in_room != def->ch->in_room) { send_to_char(att->ch, "You relax with the knowledge that your opponent is no longer present.\r\n"); @@ -779,10 +782,10 @@ bool hit_with_multiweapon_toggle(struct char_data *attacker, struct char_data *v GET_CHIPJACKED_SKILL(c.fighter->ch, c.fighter->melee->skill) ? " (zeroed due to using activesoft)" : "", c.fighter->melee->dice); if (c.fighter->melee->tn != prior_tn) { - snprintf(ENDOF(rbuf), sizeof(rbuf) - strlen(rbuf), "\r\n%s TN modified in get_skill() will roll %d to %d.", + snprintf(ENDOF(rbuf), sizeof(rbuf) - strlen(rbuf), "\r\n%s TN modified in get_skill() will roll from %d to %d.", c.fighter == att ? "Attacker" : "Defender", prior_tn, - att->melee->tn); + c.fighter->melee->tn); } SEND_RBUF_TO_ROLLS_FOR_BOTH_ATTACKER_AND_DEFENDER; }