#include #include #include #include #include #include #include #include #include namespace { struct DetectionState : public NiRefObject { ~DetectionState() override; uint32_t level; uint8_t unk14; uint8_t unk15; uint8_t unk16; uint8_t pad17; float unk18; NiPoint3 unk1C; float unk28; NiPoint3 unk2C; float unk38; NiPoint3 unk3C; }; static_assert(sizeof(DetectionState) == 0x48); BGSEncounterZone* GetLocationEncounterZone(Actor* apActor) { TP_THIS_FUNCTION(TGetLocationEncounterZone, BGSEncounterZone*, Actor); POINTER_SKYRIMSE(TGetLocationEncounterZone, getLocationEncounterZone, 20203); return TiltedPhoques::ThisCall(getLocationEncounterZone, apActor); } bool IsValidTarget(CombatTargetSelector* apThis, Actor* apAttacker, Actor* apTarget, BGSEncounterZone* apEncounterZone) { TP_THIS_FUNCTION(TIsValidTarget, bool, CombatTargetSelector, Actor* apAttacker, Actor* apTarget, BGSEncounterZone* apEncounterZone); POINTER_SKYRIMSE(TIsValidTarget, isValidTarget, 47196); return TiltedPhoques::ThisCall(isValidTarget, apThis, apAttacker, apTarget, apEncounterZone); } DetectionState* GetDetectionState(Actor* apAttacker, Actor* apTarget) { TP_THIS_FUNCTION(TGetDetectionState, DetectionState*, Actor, Actor* apTarget); POINTER_SKYRIMSE(TGetDetectionState, getDetectionState, 37757); return TiltedPhoques::ThisCall(getDetectionState, apAttacker, apTarget); } float GetCombatTargetSelectorDetectionTimeLimit() { POINTER_SKYRIMSE(float, s_value, 382393); return *s_value; } float GetCombatTargetSelectorRecentLOSTimeLimit() { POINTER_SKYRIMSE(float, s_value, 382400); return *s_value; } bool ActorHasEquippedRangedWeapon_m(Actor* apActor) { TP_THIS_FUNCTION(TActorHasEquippedRangedWeapon_m, bool, Actor); POINTER_SKYRIMSE(TActorHasEquippedRangedWeapon_m, actorHasEquippedRangedWeapon_m, 47303); return TiltedPhoques::ThisCall(actorHasEquippedRangedWeapon_m, apActor); } BGSWorldLocation* RefrGetWorldLocation(TESObjectREFR* apThis, BGSWorldLocation* apResult) { TP_THIS_FUNCTION(TRefrGetWorldLocation, BGSWorldLocation*, TESObjectREFR, BGSWorldLocation*); POINTER_SKYRIMSE(TRefrGetWorldLocation, refrGetWorldLocation, 19784); return TiltedPhoques::ThisCall(refrGetWorldLocation, apThis, apResult); } float GetModifiedDistance(BGSWorldLocation* apThis, BGSWorldLocation* apWorldLocation) { TP_THIS_FUNCTION(TGetModifiedDistance, float, BGSWorldLocation, BGSWorldLocation*); POINTER_SKYRIMSE(TGetModifiedDistance, getModifiedDistance, 18518); return TiltedPhoques::ThisCall(getModifiedDistance, apThis, apWorldLocation); } float GetDword_142FE5B78() { POINTER_SKYRIMSE(float, s_value, 405282); return *s_value; } bool CheckMovement(CombatController* apThis, BGSWorldLocation* apWorldLocation) { TP_THIS_FUNCTION(TCheckMovement, bool, CombatController, BGSWorldLocation*); POINTER_SKYRIMSE(TCheckMovement, checkMovement, 33261); return TiltedPhoques::ThisCall(checkMovement, apThis, apWorldLocation); } bool sub_1407E7A40(Actor* apThis, BGSWorldLocation* apWorldLocation) { TP_THIS_FUNCTION(TFunc, bool, Actor, BGSWorldLocation*); POINTER_SKYRIMSE(TFunc, func, 47307); return TiltedPhoques::ThisCall(func, apThis, apWorldLocation); } bool IsFleeing(Actor* apThis) { TP_THIS_FUNCTION(TFunc, bool, Actor); POINTER_SKYRIMSE(TFunc, func, 37577); return TiltedPhoques::ThisCall(func, apThis); } float CalculateTargetScore(CombatTargetSelector* apThis, CombatTarget* apCombatTarget, Actor* apAttacker, Actor* apTarget, BGSEncounterZone* apEncounterZone) { float score = 0.f; CombatController* pCombatController = apThis->pCombatController; Actor* pCachedAttacker = pCombatController->pCachedAttacker.object; BGSWorldLocation pAttackerWorldLocation{}; RefrGetWorldLocation(apAttacker, &pAttackerWorldLocation); BGSWorldLocation pTargetWorldLocation{}; RefrGetWorldLocation(apTarget, &pTargetWorldLocation); float aiTime = AITimer::GetAITime(); auto* pDetectionState = GetDetectionState(apAttacker, apTarget); if (pDetectionState && (aiTime - pDetectionState->unk38) <= GetCombatTargetSelectorDetectionTimeLimit()) { if (pDetectionState->unk15) score = 1000.f; else if (pCachedAttacker == apTarget && (aiTime - pDetectionState->unk18) < GetCombatTargetSelectorRecentLOSTimeLimit()) score = 1000.f; if (pDetectionState->unk14) score += 100.f; } if (pCachedAttacker == apTarget) score += 100.f; if (ActorHasEquippedRangedWeapon_m(apAttacker) == ActorHasEquippedRangedWeapon_m(apTarget)) score += 100.f; bool isSameWorldLocation = pTargetWorldLocation.pSpace == pAttackerWorldLocation.pSpace; float distanceFactor = 0.f; if (isSameWorldLocation) distanceFactor = GetModifiedDistance(&pAttackerWorldLocation, &pTargetWorldLocation); else if (apTarget != pCachedAttacker) distanceFactor = 2048.f; score += (float)((float)(1.0 - (float)(fminf(distanceFactor, 2048.f) * 0.00048828125f)) * 1000.f); if (apCombatTarget->attackerCount > (pCachedAttacker == apTarget)) score += GetDword_142FE5B78(); if (pCombatController->pInventory->maximumRange < 1024.f || !isSameWorldLocation) { if (!CheckMovement(pCombatController, &pTargetWorldLocation)) score -= 2000.f; if (!isSameWorldLocation && !sub_1407E7A40(apAttacker, &pTargetWorldLocation)) score -= 2500.f; } if (apTarget->VirtIsDead(false)) score -= 500.f; if (IsFleeing(apTarget)) score -= 500.f; return score; } } // namespace void DebugService::DrawCombatView() { if (!m_formId) return; ImGui::Begin("Combat"); ImGui::InputScalar("Form ID", ImGuiDataType_U32, (void*)&m_formId, nullptr, nullptr, "%" PRIx32, ImGuiInputTextFlags_ReadOnly | ImGuiInputTextFlags_CharsHexadecimal); Actor* pActor = Cast(TESForm::GetById(m_formId)); if (pActor && pActor->pCombatController && pActor->pCombatController->pCombatGroup && pActor->pCombatController->pActiveTargetSelector) { Actor* pTarget = Cast(TESObjectREFR::GetByHandle(pActor->pCombatController->targetHandle)); if (pTarget) { uint32_t targetFormID = pTarget->formID; ImGui::InputScalar("Current target ID", ImGuiDataType_U32, (void*)&targetFormID, nullptr, nullptr, "%" PRIx32, ImGuiInputTextFlags_ReadOnly | ImGuiInputTextFlags_CharsHexadecimal); } BGSEncounterZone* pZone = GetLocationEncounterZone(pActor); ImGui::BeginChild("Potential targets", ImVec2(0, 200), true); auto& targets = pActor->pCombatController->pCombatGroup->targets; for (int i = 0; i < targets.length; i++) { auto& target = targets[i]; Actor* pTarget = Cast(TESObjectREFR::GetByHandle(target.targetHandle)); if (pTarget) { const uint32_t formID = pTarget->formID; ImGui::InputScalar("Form ID", ImGuiDataType_U32, (void*)&formID, nullptr, nullptr, "%" PRIx32, ImGuiInputTextFlags_ReadOnly | ImGuiInputTextFlags_CharsHexadecimal); float score = CalculateTargetScore(pActor->pCombatController->pActiveTargetSelector, &target, pActor, pTarget, pZone); ImGui::InputFloat("Score", &score, 0.f, 0.f, "%.3f", ImGuiInputTextFlags_ReadOnly); } ImGui::Separator(); } ImGui::EndChild(); } ImGui::End(); }