diff --git a/src/Hooks.cpp b/src/Hooks.cpp index a135a9a..ad060de 100644 --- a/src/Hooks.cpp +++ b/src/Hooks.cpp @@ -948,6 +948,9 @@ namespace Hooks case RE::FormType::ProjectileMissile: aimType = Settings::uTargetLockMissileAimType; break; + case RE::FormType::ProjectileCone: + aimType = Settings::uTargetLockMissileAimType; + break; default: aimType = TargetLockProjectileAimType::kFreeAim; } @@ -1070,6 +1073,13 @@ namespace Hooks ProjectileAimSupport(a_this); } + void ProjectileHook::GetLinearVelocityCone(RE::Projectile* a_this, RE::NiPoint3& a_outVelocity) + { + _GetLinearVelocityCone(a_this, a_outVelocity); + + ProjectileAimSupport(a_this); + } + bool ProjectileHook::Func183(RE::Projectile* a_this) { // player only, 0x100000 == player @@ -1214,6 +1224,93 @@ namespace Hooks RE::NiPoint3 rightVector = direction.Cross(upVector); direction = RotateAngleAxis(direction, AngleToRadian(*g_f3PArrowTiltUpAngle), rightVector); + linearVelocity = direction * velocityScalar; + } + } else if (a_this->formType.get() == RE::FormType::ProjectileCone && directionalMovementHandler->IsFreeCamera() && + (!directionalMovementHandler->HasTargetLocked() || Settings::uTargetLockMissileAimType == TargetLockProjectileAimType::kFreeAim)) { + // The game launches cone projectiles with the shooter's body pitch instead of the camera aim pitch, + // so they miss the crosshair while the third person free camera is active (the character's pitch is + // not synced to the camera). Re-aim them along the camera ray. Only relevant for long range cone + // projectiles used by some spell mods, e.g. Astral Magic 2. + auto playerCamera = RE::PlayerCamera::GetSingleton(); + if (playerCamera->currentState && playerCamera->currentState->id == RE::CameraState::kThirdPerson) { + constexpr RE::NiPoint3 forwardVector{ 0.f, 1.f, 0.f }; + auto thirdPersonState = static_cast(playerCamera->currentState.get()); + RE::NiQuaternion cameraRotation; + thirdPersonState->GetRotation(cameraRotation); + + auto cameraForwardVector = RotateVector(forwardVector, cameraRotation); + + cameraForwardVector.Unitize(); + + auto cameraPos = playerCamera->cameraRoot->world.translate; + + RE::NiPoint3 rayStart = cameraPos; + RE::NiPoint3 rayEnd = cameraPos + cameraForwardVector * 5000.f; + RE::NiPoint3 hitPos = rayEnd; + + RE::NiPoint3 cameraToPlayer = playerCamera->cameraTarget.get()->GetPosition() - cameraPos; + RE::NiPoint3 cameraToTarget = rayEnd - cameraPos; + RE::NiPoint3 projected = Project(cameraToPlayer, cameraToTarget); + RE::NiPoint3 projectedPos = RE::NiPoint3(projected.x + cameraPos.x, projected.y + cameraPos.y, projected.z + cameraPos.z); + rayStart = projectedPos; + + uint16_t playerCollisionGroup = 0; + + auto playerCharacter = RE::PlayerCharacter::GetSingleton(); + if (auto playerBody = playerCharacter->Get3D()) { + if (auto collisionObject = playerBody->GetCollisionObject()) { + if (auto rigidBody = collisionObject->GetRigidBody()) { + playerCollisionGroup = static_cast(rigidBody->referencedObject.get())->collidable.broadPhaseHandle.collisionFilterInfo >> 16; + } + } + } + + float bhkWorldScale = *g_worldScale; + + collector.Reset(); + RE::hkpWorldRayCastInput raycastInput; + raycastInput.filterInfo = ((uint32_t)playerCollisionGroup << 16) | 0x28; + raycastInput.from.quad = _mm_setr_ps(rayStart.x * bhkWorldScale, rayStart.y * bhkWorldScale, rayStart.z * bhkWorldScale, 0.f); + raycastInput.to.quad = _mm_setr_ps(rayEnd.x * bhkWorldScale, rayEnd.y * bhkWorldScale, rayEnd.z * bhkWorldScale, 0.f); + auto world = playerCharacter->parentCell->GetbhkWorld(); + world->worldLock.LockForRead(); + CastRay(world->GetWorld2(), raycastInput, collector); + world->worldLock.UnlockForRead(); + if (collector.doesHitExist) { + auto distance = rayEnd - rayStart; + hitPos = rayStart + (distance * collector.closestHitInfo.hitFraction); + } + + RE::NiPoint3 direction = hitPos - a_this->data.location; + direction.Unitize(); + + float rotationX, rotationZ; + + rotationX = atan2(-direction.z, std::sqrtf(direction.x * direction.x + direction.y * direction.y)); + rotationZ = atan2(direction.x, direction.y); + + if (rotationZ < 0.0) { + rotationZ += PI; + } + + if (direction.x < 0.0) { + rotationZ += PI; + } + + a_this->data.angle.x = rotationX; + a_this->data.angle.z = rotationZ; + + auto projectileNode = a_this->Get3D(); + if (projectileNode) { + SetRotationMatrix(projectileNode->local.rotate, direction.x, direction.y, direction.z); + } + + // no f3PArrowTiltUpAngle tilt here: unlike arrows, cone projectiles get no engine side + // crosshair correction that would compensate for it, so the exact ray direction is what we want + auto& linearVelocity = a_this->GetProjectileRuntimeData().linearVelocity; + float velocityScalar = linearVelocity.Length(); + linearVelocity = direction * velocityScalar; } } diff --git a/src/Hooks.h b/src/Hooks.h index f394ad0..7101ccf 100644 --- a/src/Hooks.h +++ b/src/Hooks.h @@ -243,9 +243,11 @@ namespace Hooks REL::Relocation ArrowProjectileVtbl{ RE::VTABLE_ArrowProjectile[0] }; // 1676318 REL::Relocation MissileProjectileVtbl{ RE::VTABLE_MissileProjectile[0] }; // 167AE78 REL::Relocation BeamProjectileVtbl{ RE::VTABLE_BeamProjectile[0] }; // 1677660 + REL::Relocation ConeProjectileVtbl{ RE::VTABLE_ConeProjectile[0] }; _GetLinearVelocityProjectile = ProjectileVtbl.write_vfunc(0x86, GetLinearVelocityProjectile); _GetLinearVelocityArrow = ArrowProjectileVtbl.write_vfunc(0x86, GetLinearVelocityArrow); _GetLinearVelocityMissile = MissileProjectileVtbl.write_vfunc(0x86, GetLinearVelocityMissile); + _GetLinearVelocityCone = ConeProjectileVtbl.write_vfunc(0x86, GetLinearVelocityCone); auto& trampoline = SKSE::GetTrampoline(); REL::Relocation hook{ RELOCATION_ID(43030, 44222) }; // 754820, 7821A0 @@ -258,12 +260,14 @@ namespace Hooks static void GetLinearVelocityProjectile(RE::Projectile* a_this, RE::NiPoint3& a_outVelocity); static void GetLinearVelocityArrow(RE::Projectile* a_this, RE::NiPoint3& a_outVelocity); static void GetLinearVelocityMissile(RE::Projectile* a_this, RE::NiPoint3& a_outVelocity); + static void GetLinearVelocityCone(RE::Projectile* a_this, RE::NiPoint3& a_outVelocity); static bool Func183(RE::Projectile* a_this); static void InitProjectile(RE::Projectile* a_this); static inline REL::Relocation _GetLinearVelocityProjectile; static inline REL::Relocation _GetLinearVelocityArrow; static inline REL::Relocation _GetLinearVelocityMissile; + static inline REL::Relocation _GetLinearVelocityCone; static inline REL::Relocation _InitProjectile; };