diff --git a/src/LocationalDamage.cpp b/src/LocationalDamage.cpp index fecf89a..d6d3b54 100644 --- a/src/LocationalDamage.cpp +++ b/src/LocationalDamage.cpp @@ -1,10 +1,25 @@ #include "LocationalDamage.h" +#include + namespace LocDamageNamespace { constexpr std::string_view PapyrusClass = "LDHitZones"; + namespace { + // Papyrus arrays arrive through CommonLibSSE as std::vector copies. A None array, or an + // array the script built with too few elements, becomes an empty/short vector here, and + // indexing past its end is undefined behaviour (silent garbage or a crash). Validate first. + bool checkArray(std::size_t actual, std::size_t expected, const char* name) { + if (actual < expected) { + SKSE::log::error("GetHitZone: array '{}' has {} element(s), expected at least {}", name, actual, expected); + return false; + } + return true; + } + } + int armHitCalc(float facing, float headingAngle, double armRange) { - if (abs(facing) < 45) { + if (std::fabs(facing) < 45) { if (headingAngle > armRange) { return rArm; } @@ -21,7 +36,7 @@ namespace LocDamageNamespace { else if (facing > 45 && facing < 135) { return rArm; } - else if (abs(facing) >= 135) { + else if (std::fabs(facing) >= 135) { if (headingAngle > armRange) { return lArm; } @@ -37,7 +52,7 @@ namespace LocDamageNamespace { } int shoulderhitCalc(bool powerAttack, float facing, float headingAngle, float shoulderRange, int weaponType) { - if (abs(facing) < 45) { + if (std::fabs(facing) < 45) { // In front of the target if (headingAngle > shoulderRange) { return rShoulder; @@ -64,7 +79,7 @@ namespace LocDamageNamespace { else if (facing > 45 && facing < 135) { return rShoulder; } - else if (abs(facing) >= 135) { + else if (std::fabs(facing) >= 135) { // Behind the target if (headingAngle > shoulderRange) { return lShoulder; @@ -95,7 +110,6 @@ namespace LocDamageNamespace { double legZone; double zHeight; - double zHeightMe; if (s.isSneaking) { net_height = net_height * .75; @@ -109,79 +123,103 @@ namespace LocDamageNamespace { } zHeight = p.zPos + net_height; - zHeightMe = p.zPosMe + net_height_me; - // Y-AXIS ZONES + // Y-AXIS ZONES (pitch thresholds, degrees, positive = aggressor looking down). + // None of these depend on one another, so they are all computed up front; this lets the + // trace line below report the full picture for every hit without changing any result. dispHeight = zHeight - (p.zPosMe + net_height_me * 0.900); headZone = atan(dispHeight / p.dist) * 180 / PI; - // Head strike - if (a.ax < headZone) { - return head; - } - dispHeight = zHeight - (p.zPosMe + net_height_me * 0.850); neckZone = atan(dispHeight / p.dist) * 180 / PI; - // Neck strike - if (a.ax < neckZone) { - if (a.facing <= -135 || a.facing >= 135) { - return nape; - } - else { - return neck; - } - } - - // X-AXIS ZONES - dispWidth = d.width - d.widthMe * .900; - shoulderRange = abs(asin(dispWidth / p.dx) * 180 / PI); - dispWidth = d.width - d.widthMe * .800; - armRange = abs(asin(dispWidth / p.dx) * 180 / PI); - dispHeight = zHeight - (p.zPosMe + net_height_me * 0.750); shoulderZone = atan(dispHeight / p.dist) * 180 / PI; - // Shoulder strike - if (a.ax < shoulderZone) { - return shoulderhitCalc(s.isPowerAttack, a.facing, a.headingAngle, shoulderRange, s.weaponType); - } - dispHeight = zHeight - (p.zPosMe + net_height_me * 0.500); chestZone = atan(dispHeight / p.dist) * 180 / PI; - // Chest strike - if (a.ax < chestZone) { - return armHitCalc(a.facing, a.headingAngle, armRange); - } - dispHeight = zHeight - (p.zPosMe + net_height_me * 0.400); groinZone = atan(dispHeight / p.dist) * 180 / PI; - // Groin strike - if (a.ax < groinZone) { - if (abs(a.facing) < 45) { - return groin; + dispHeight = zHeight - (p.zPosMe + net_height_me * 0.200); + legZone = atan(dispHeight / p.dist) * 180 / PI; + + // X-AXIS ZONES (lateral aim tolerance, degrees) + dispWidth = d.width - d.widthMe * .900; + shoulderRange = std::fabs(asin(dispWidth / p.dx) * 180 / PI); + dispWidth = d.width - d.widthMe * .800; + armRange = std::fabs(asin(dispWidth / p.dx) * 180 / PI); + + if (!(p.dist > 0)) { + SKSE::log::warn("hitDetection: dist={} is not positive; pitch thresholds are degenerate", p.dist); + } + if (std::isnan(armRange) || std::isnan(shoulderRange)) { + // asin() of |x| > 1 (or dx == 0). NaN compares false against everything, so every + // lateral test fails and the hit falls through to chest/back. + SKSE::log::warn("hitDetection: lateral range is NaN (dispWidth/dx out of [-1,1]); width={} widthMe={} dx={}", + d.width, d.widthMe, p.dx); + } + + uint32_t zone; + + if (a.ax < headZone) { + // Head strike + zone = head; + } + else if (a.ax < neckZone) { + // Neck strike + if (a.facing <= -135 || a.facing >= 135) { + zone = nape; } else { - return legs; + zone = neck; } } - - dispHeight = zHeight - (p.zPosMe + net_height_me * 0.200); - legZone = atan(dispHeight / p.dist) * 180 / PI; - - // Leg strike - if (a.ax < legZone) { - return legs; + else if (a.ax < shoulderZone) { + // Shoulder strike + zone = shoulderhitCalc(s.isPowerAttack, a.facing, a.headingAngle, shoulderRange, s.weaponType); + } + else if (a.ax < chestZone) { + // Chest strike + zone = armHitCalc(a.facing, a.headingAngle, armRange); + } + else if (a.ax < groinZone) { + // Groin strike + if (std::fabs(a.facing) < 45) { + zone = groin; + } + else { + zone = legs; + } + } + else if (a.ax < legZone) { + // Leg strike + zone = legs; + } + else { + // Feet strike + zone = feet; } - // Feet strike - return feet; + SKSE::log::trace( + "hit zone={} | in: facing={} heading={} ax={} dist={} dx={} dy={} z={} zMe={} w={} wMe={} h={} hMe={} scale={} scaleMe={} " + "sneak={} sneakMe={} sprintMe={} recoilMe={} power={} weapon={} | " + "thresholds: head<{:.2f} neck<{:.2f} shoulder<{:.2f} chest<{:.2f} groin<{:.2f} legs<{:.2f} shoulderRange={:.2f} armRange={:.2f}", + zone, a.facing, a.headingAngle, a.ax, p.dist, p.dx, p.dy, p.zPos, p.zPosMe, d.width, d.widthMe, d.height, d.heightMe, + d.scale, d.scaleMe, s.isSneaking, s.isSneakingMe, s.isSprintingMe, s.isRecoilingMe, s.isPowerAttack, s.weaponType, + headZone, neckZone, shoulderZone, chestZone, groinZone, legZone, shoulderRange, armRange); + + return zone; } uint32_t GetHitZone(RE::StaticFunctionTag*, std::vector d, std::vector a, std::vector p, std::vector s) { - //gLog.SetLogLevel(IDebugLog::kLevel_Message); + if (!checkArray(d.size(), 6, "d (dimensions)") || + !checkArray(a.size(), 3, "a (angles)") || + !checkArray(p.size(), 5, "p (positions)") || + !checkArray(s.size(), 6, "s (states)")) { + return 0; + } // Dimensions const auto scale = d[0]; @@ -219,7 +257,7 @@ namespace LocDamageNamespace { return hitDetection(dimensions, angles, pos, actions); } - uint32_t ArmHitCalcSKSE(RE::StaticFunctionTag*, float facing, float headingAngle, double armRange) { + uint32_t ArmHitCalcSKSE(RE::StaticFunctionTag*, float facing, float headingAngle, float armRange) { return (uint32_t)armHitCalc(facing, headingAngle, armRange); } @@ -229,4 +267,4 @@ namespace LocDamageNamespace { return true; } -} \ No newline at end of file +} diff --git a/src/LocationalDamage.h b/src/LocationalDamage.h index 317b8e4..43d9fa3 100644 --- a/src/LocationalDamage.h +++ b/src/LocationalDamage.h @@ -5,9 +5,11 @@ #ifndef LOCATIONALDAMAGE_H #define LOCATIONALDAMAGE_H +#include #include #include +#include #include #define PI 3.14159265 @@ -101,7 +103,7 @@ namespace LocDamageNamespace uint32_t GetHitZone(RE::StaticFunctionTag*, std::vector d, std::vector a, std::vector p, std::vector s); - uint32_t ArmHitCalcSKSE(RE::StaticFunctionTag*, float facing, float headingAngle, double armRange); + uint32_t ArmHitCalcSKSE(RE::StaticFunctionTag*, float facing, float headingAngle, float armRange); bool RegisterFuncs(RE::BSScript::IVirtualMachine* vm);