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
134 changes: 134 additions & 0 deletions Client/game_sa/CCameraSA.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "StdInc.h"
#include "CCameraSA.h"
#include "CGameSA.h"
#include "CPedSA.h"
#include <atomic>
#include <cmath>
#include <cstdint>
Expand All @@ -36,6 +37,79 @@ namespace
{
return std::isfinite(vec.fX) && std::isfinite(vec.fY) && std::isfinite(vec.fZ);
}

// Per-weapon aiming camera state. A zero offset or zero zoom means disabled.
CVector s_aimOffset[WEAPONTYPE_LAST_WEAPONTYPE]{};
float s_aimZoom[WEAPONTYPE_LAST_WEAPONTYPE]{};

// Blend-in state: ramps from 0 to 1 over ~0.15s when entering aim mode.
float s_aimBlend = 0.0f;
int s_lastAimFrame = -999;
constexpr float kAimBlendSpeed = 1.0f / (0.15f * 30.0f);

constexpr DWORD HOOKSITE_AimWeapon = 0x527A95; // CALL CCam::Process_AimWeapon
constexpr DWORD FUNC_AimWeapon_Orig = 0x521500;

static void __fastcall Hook_Process_AimWeapon(CCamSAInterface* cam, int /*edx*/, const CVector& vec, float arg3, float arg4, float arg5)
{
using Orig_t = void(__thiscall*)(CCamSAInterface*, const CVector&, float, float, float);
static const auto s_orig = reinterpret_cast<Orig_t>(FUNC_AimWeapon_Orig);

// Let GTA compute Source/Front/Up first, then layer overrides on top.
s_orig(cam, vec, arg3, arg4, arg5);

using FindPed_t = CPedSAInterface*(__cdecl*)(int);
static const auto s_findPed = reinterpret_cast<FindPed_t>(0x56E210);

CPedSAInterface* pPed = s_findPed(-1);
if (!pPed || pPed->pedFlags.bInVehicle)
return;

const std::uint8_t slot = pPed->bCurrentWeaponSlot;
if (slot >= WEAPONSLOT_MAX)
return;

const eWeaponType wtype = pPed->Weapons[slot].m_eWeaponType;
if (wtype <= WEAPONTYPE_UNARMED || wtype >= WEAPONTYPE_LAST_WEAPONTYPE)
return;

const CVector& off = s_aimOffset[wtype];
const float zoom = s_aimZoom[wtype];
const bool hasOffset = (off.fX != 0.0f || off.fY != 0.0f || off.fZ != 0.0f);
const bool hasZoom = (zoom > 0.0f);

if (!hasOffset && !hasZoom)
return;

// CTimer::m_FrameCounter at 0xB7CB4C, ms_fTimeStep at 0xB7CB5C.
const int curFrame = *(const int*)0xB7CB4C;
const float timeStep = *(const float*)0xB7CB5C;

// Reset the blend if the hook went idle for more than one frame.
if (curFrame > s_lastAimFrame + 2)
s_aimBlend = 0.0f;
s_lastAimFrame = curFrame;

s_aimBlend = std::min(1.0f, s_aimBlend + kAimBlendSpeed * timeStep);

if (hasOffset)
{
// Right = Front cross Up
CVector right;
right.fX = cam->Front.fY * cam->Up.fZ - cam->Front.fZ * cam->Up.fY;
right.fY = cam->Front.fZ * cam->Up.fX - cam->Front.fX * cam->Up.fZ;
right.fZ = cam->Front.fX * cam->Up.fY - cam->Front.fY * cam->Up.fX;

const float b = s_aimBlend;
cam->Source.fX += (right.fX * off.fX + cam->Up.fX * off.fY + cam->Front.fX * off.fZ) * b;
cam->Source.fY += (right.fY * off.fX + cam->Up.fY * off.fY + cam->Front.fY * off.fZ) * b;
cam->Source.fZ += (right.fZ * off.fX + cam->Up.fZ * off.fY + cam->Front.fZ * off.fZ) * b;
cam->SourceBeforeLookBehind = cam->Source;
}

if (hasZoom)
cam->FOV += (zoom - cam->FOV) * s_aimBlend;
}
}

extern CGameSA* pGame;
Expand Down Expand Up @@ -92,6 +166,9 @@ CCameraSA::CCameraSA(CCameraSAInterface* cameraInterface)
s_cameraClipMask.store(static_cast<uint8_t>(CameraClipFlags::Objects) | static_cast<uint8_t>(CameraClipFlags::Vehicles), std::memory_order_relaxed);

HookInstall(HOOKPOS_Camera_CollisionDetection, (DWORD)HOOK_Camera_CollisionDetection, 5);

// Redirect the CALL at HOOKSITE_AimWeapon (E8 opcode + 4-byte relative offset).
MemPut<DWORD>(HOOKSITE_AimWeapon + 1, reinterpret_cast<DWORD>(&Hook_Process_AimWeapon) - (HOOKSITE_AimWeapon + 5));
}

CCameraSA::~CCameraSA()
Expand Down Expand Up @@ -783,3 +860,60 @@ bool CCameraSA::IsSphereVisible(CVector* center, float radius) const

return ((bool(__thiscall*)(CCameraSAInterface*, CVector*, float))0x420D40)(cameraInterface, center, radius);
}

void CCameraSA::SetWeaponAimCameraOffset(eWeaponType weaponType, float fX, float fY, float fZ)
{
if (weaponType > WEAPONTYPE_UNARMED && weaponType < WEAPONTYPE_LAST_WEAPONTYPE)
s_aimOffset[weaponType] = CVector(fX, fY, fZ);
}

void CCameraSA::GetWeaponAimCameraOffset(eWeaponType weaponType, float& fX, float& fY, float& fZ)
{
if (weaponType > WEAPONTYPE_UNARMED && weaponType < WEAPONTYPE_LAST_WEAPONTYPE)
{
fX = s_aimOffset[weaponType].fX;
fY = s_aimOffset[weaponType].fY;
fZ = s_aimOffset[weaponType].fZ;
}
else
{
fX = fY = fZ = 0.0f;
}
}

void CCameraSA::ResetWeaponAimCameraOffset(eWeaponType weaponType)
{
if (weaponType > WEAPONTYPE_UNARMED && weaponType < WEAPONTYPE_LAST_WEAPONTYPE)
s_aimOffset[weaponType] = CVector(0.0f, 0.0f, 0.0f);
}

void CCameraSA::SetWeaponAimCameraZoom(eWeaponType weaponType, float fFOV)
{
// FOV outside (0, 180) would produce a degenerate projection matrix.
if (weaponType > WEAPONTYPE_UNARMED && weaponType < WEAPONTYPE_LAST_WEAPONTYPE && fFOV > 0.0f && fFOV < 180.0f)
s_aimZoom[weaponType] = fFOV;
}

float CCameraSA::GetWeaponAimCameraZoom(eWeaponType weaponType)
{
if (weaponType > WEAPONTYPE_UNARMED && weaponType < WEAPONTYPE_LAST_WEAPONTYPE)
return s_aimZoom[weaponType];
return 0.0f;
}

void CCameraSA::ResetWeaponAimCameraZoom(eWeaponType weaponType)
{
if (weaponType > WEAPONTYPE_UNARMED && weaponType < WEAPONTYPE_LAST_WEAPONTYPE)
s_aimZoom[weaponType] = 0.0f;
}

void CCameraSA::ResetAllWeaponAimCameraOverrides()
{
for (int i = 0; i < WEAPONTYPE_LAST_WEAPONTYPE; ++i)
{
s_aimOffset[i] = CVector(0.0f, 0.0f, 0.0f);
s_aimZoom[i] = 0.0f;
}
s_aimBlend = 0.0f;
s_lastAimFrame = -999;
}
8 changes: 8 additions & 0 deletions Client/game_sa/CCameraSA.h
Original file line number Diff line number Diff line change
Expand Up @@ -425,4 +425,12 @@ class CCameraSA : public CCamera

// Additional methods
void RestoreLastGoodState();

void SetWeaponAimCameraOffset(eWeaponType weaponType, float fX, float fY, float fZ) override;
void GetWeaponAimCameraOffset(eWeaponType weaponType, float& fX, float& fY, float& fZ) override;
void ResetWeaponAimCameraOffset(eWeaponType weaponType) override;
void SetWeaponAimCameraZoom(eWeaponType weaponType, float fFOV) override;
float GetWeaponAimCameraZoom(eWeaponType weaponType) override;
void ResetWeaponAimCameraZoom(eWeaponType weaponType) override;
void ResetAllWeaponAimCameraOverrides() override;
};
1 change: 1 addition & 0 deletions Client/mods/deathmatch/logic/CClientGame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3492,6 +3492,7 @@ void CClientGame::Event_OnIngame()
g_pGame->GetWaterManager()->Reset(); // Deletes all custom water elements, ResetMapInfo only reverts changes to water level
g_pGame->GetWaterManager()->SetWaterDrawnLast(true);
m_pCamera->ResetCameraClip();
g_pGame->GetCamera()->ResetAllWeaponAimCameraOverrides();

// Deallocate all custom models
m_pManager->GetModelManager()->RemoveAll();
Expand Down
65 changes: 65 additions & 0 deletions Client/mods/deathmatch/logic/luadefs/CLuaCameraDefs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include <game/CPlayerInfo.h>
#include <game/CSettings.h>
#include <game/CCam.h>
#include <game/CWeaponInfo.h>
#include <lua/CLuaFunctionParser.h>

#define MIN_CLIENT_REQ_SETCAMERATARGET_USE_ANY_ELEMENTS "1.5.8-9.20979"
Expand Down Expand Up @@ -45,6 +46,14 @@ void CLuaCameraDefs::LoadFunctions()

{"shakeCamera", ArgumentParser<ShakeCamera>},
{"resetShakeCamera", ArgumentParser<ResetShakeCamera>},

// Weapon aim camera overrides
{"setWeaponAimCameraOffset", ArgumentParserWarn<false, SetWeaponAimCameraOffset>},
{"getWeaponAimCameraOffset", ArgumentParserWarn<false, GetWeaponAimCameraOffset>},
{"resetWeaponAimCameraOffset", ArgumentParserWarn<false, ResetWeaponAimCameraOffset>},
{"setWeaponAimCameraZoom", ArgumentParserWarn<false, SetWeaponAimCameraZoom>},
{"getWeaponAimCameraZoom", ArgumentParserWarn<false, GetWeaponAimCameraZoom>},
{"resetWeaponAimCameraZoom", ArgumentParserWarn<false, ResetWeaponAimCameraZoom>},
};

// Add functions
Expand Down Expand Up @@ -576,3 +585,59 @@ bool CLuaCameraDefs::ResetShakeCamera() noexcept
m_pManager->GetCamera()->ResetShakeCamera();
return true;
}

bool CLuaCameraDefs::SetWeaponAimCameraOffset(int weaponType, float fX, float fY, float fZ)
{
if (weaponType <= WEAPONTYPE_UNARMED || weaponType >= WEAPONTYPE_LAST_WEAPONTYPE)
throw std::invalid_argument("Invalid weapon type");

g_pGame->GetCamera()->SetWeaponAimCameraOffset(static_cast<eWeaponType>(weaponType), fX, fY, fZ);
return true;
}

CLuaMultiReturn<float, float, float> CLuaCameraDefs::GetWeaponAimCameraOffset(int weaponType)
{
if (weaponType <= WEAPONTYPE_UNARMED || weaponType >= WEAPONTYPE_LAST_WEAPONTYPE)
throw std::invalid_argument("Invalid weapon type");

float fX, fY, fZ;
g_pGame->GetCamera()->GetWeaponAimCameraOffset(static_cast<eWeaponType>(weaponType), fX, fY, fZ);
return {fX, fY, fZ};
}

bool CLuaCameraDefs::SetWeaponAimCameraZoom(int weaponType, float fFOV)
{
if (weaponType <= WEAPONTYPE_UNARMED || weaponType >= WEAPONTYPE_LAST_WEAPONTYPE)
throw std::invalid_argument("Invalid weapon type");
if (fFOV < 0.0f || fFOV > 179.0f)
throw std::invalid_argument("Invalid FOV range (0-179)");

g_pGame->GetCamera()->SetWeaponAimCameraZoom(static_cast<eWeaponType>(weaponType), fFOV);
return true;
}

float CLuaCameraDefs::GetWeaponAimCameraZoom(int weaponType)
{
if (weaponType <= WEAPONTYPE_UNARMED || weaponType >= WEAPONTYPE_LAST_WEAPONTYPE)
throw std::invalid_argument("Invalid weapon type");

return g_pGame->GetCamera()->GetWeaponAimCameraZoom(static_cast<eWeaponType>(weaponType));
}

bool CLuaCameraDefs::ResetWeaponAimCameraOffset(int weaponType)
{
if (weaponType <= WEAPONTYPE_UNARMED || weaponType >= WEAPONTYPE_LAST_WEAPONTYPE)
throw std::invalid_argument("Invalid weapon type");

g_pGame->GetCamera()->ResetWeaponAimCameraOffset(static_cast<eWeaponType>(weaponType));
return true;
}

bool CLuaCameraDefs::ResetWeaponAimCameraZoom(int weaponType)
{
if (weaponType <= WEAPONTYPE_UNARMED || weaponType >= WEAPONTYPE_LAST_WEAPONTYPE)
throw std::invalid_argument("Invalid weapon type");

g_pGame->GetCamera()->ResetWeaponAimCameraZoom(static_cast<eWeaponType>(weaponType));
return true;
}
8 changes: 8 additions & 0 deletions Client/mods/deathmatch/logic/luadefs/CLuaCameraDefs.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,14 @@ class CLuaCameraDefs : public CLuaDefs
static bool ShakeCamera(float radius, std::optional<float> x, std::optional<float> y, std::optional<float> z) noexcept;
static bool ResetShakeCamera() noexcept;

// Weapon aim camera overrides
static bool SetWeaponAimCameraOffset(int weaponType, float fX, float fY, float fZ);
static CLuaMultiReturn<float, float, float> GetWeaponAimCameraOffset(int weaponType);
static bool ResetWeaponAimCameraOffset(int weaponType);
static bool SetWeaponAimCameraZoom(int weaponType, float fFOV);
static float GetWeaponAimCameraZoom(int weaponType);
static bool ResetWeaponAimCameraZoom(int weaponType);

// For OOP only
LUA_DECLARE(OOP_GetCameraPosition);
LUA_DECLARE(OOP_SetCameraPosition);
Expand Down
10 changes: 10 additions & 0 deletions Client/sdk/game/CCamera.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#pragma once

#include "CEntity.h"
#include "CWeaponInfo.h"

class CMatrix;
class CCam;
Expand Down Expand Up @@ -157,4 +158,13 @@ class CCamera
virtual bool GetTransitionMatrix(CMatrix& matrix) const = 0;

virtual bool IsSphereVisible(CVector* center, float radius) const = 0;

// Per-weapon aiming camera. Offset is in camera space (x=right, y=up, z=forward).
virtual void SetWeaponAimCameraOffset(eWeaponType weaponType, float fX, float fY, float fZ) = 0;
virtual void GetWeaponAimCameraOffset(eWeaponType weaponType, float& fX, float& fY, float& fZ) = 0;
virtual void ResetWeaponAimCameraOffset(eWeaponType weaponType) = 0;
virtual void SetWeaponAimCameraZoom(eWeaponType weaponType, float fFOV) = 0;
virtual float GetWeaponAimCameraZoom(eWeaponType weaponType) = 0;
virtual void ResetWeaponAimCameraZoom(eWeaponType weaponType) = 0;
virtual void ResetAllWeaponAimCameraOverrides() = 0;
};