Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 93 additions & 55 deletions src/LocationalDamage.cpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,25 @@
#include "LocationalDamage.h"

#include <cmath>

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;
}
Expand All @@ -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;
}
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -95,7 +110,6 @@ namespace LocDamageNamespace {
double legZone;

double zHeight;
double zHeightMe;

if (s.isSneaking) {
net_height = net_height * .75;
Expand All @@ -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<float> d, std::vector<float> a, std::vector<float> p, std::vector<uint32_t> 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];
Expand Down Expand Up @@ -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);
}

Expand All @@ -229,4 +267,4 @@ namespace LocDamageNamespace {

return true;
}
}
}
4 changes: 3 additions & 1 deletion src/LocationalDamage.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
#ifndef LOCATIONALDAMAGE_H
#define LOCATIONALDAMAGE_H

#include <cstdint>
#include <vector>

#include <RE/T/TypeTraits.h>
#include <SKSE/SKSE.h>
#include <spdlog/sinks/basic_file_sink.h>

#define PI 3.14159265
Expand Down Expand Up @@ -101,7 +103,7 @@ namespace LocDamageNamespace

uint32_t GetHitZone(RE::StaticFunctionTag*, std::vector<float> d, std::vector<float> a, std::vector<float> p, std::vector<uint32_t> 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);

Expand Down