Skip to content
2 changes: 1 addition & 1 deletion Client/game_sa/CAnimBlendAssociationSA.h
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ class CAnimBlendAssociationSAInterface
unsigned short m_bLockLastX : 1;
unsigned short m_bLockLastY : 1; // only applies if m_bLockLastX is set

unsigned short m_bf9 : 1; // doesn't seem to be used
unsigned short m_walkAnim : 1; // CPed::PlayFootSteps
unsigned short m_bf10 : 1; // doesn't seem to be used

// If set to TRUE, then result:
Expand Down
36 changes: 36 additions & 0 deletions Client/game_sa/CEventSoundQuietSA.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*****************************************************************************
*
* PROJECT: Multi Theft Auto
* LICENSE: See LICENSE in the top level directory
* FILE: game_sa/CEventSoundQuietSA.h
* PURPOSE: Header file for damage event class
*
* Multi Theft Auto is available from https://www.multitheftauto.com/
*
*****************************************************************************/

#pragma once
#include <cstdint>
#include "CVector.h"

class CEntitySAInterface;

class CEventSoundQuietSAInterface
{
public:
std::uint8_t parent[0x14]; // CEventEditableResponse (inherited class)
CEntitySAInterface* entity{nullptr};
float localSoundLevel{};
std::uint32_t startTimeInMs{};
CVector position{};

CEventSoundQuietSAInterface(CEntitySAInterface* ped, float volume, std::uint32_t startTimeInMs = 0, CVector& pos = CVector())
{
((void*(__thiscall*)(CEventSoundQuietSAInterface*, CEntitySAInterface*, float, int, CVector*))0x5E05B0)(this, ped, volume, startTimeInMs, &pos);
}

~CEventSoundQuietSAInterface()
{
((void(__thiscall*)(void*))0x5DEA00)(this);
}
};
2 changes: 1 addition & 1 deletion Client/game_sa/CPedIntelligenceSA.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class CPedIntelligenceSAInterface
public:
// CEventHandlerHistory @ + 56
CPedSAInterface* pPed;
DWORD taskManager; // +4 (really CTaskManagerSAInterface)
std::uint8_t taskManager[0x30]; // +4 (really CTaskManagerSAInterface)
BYTE bPad[16];
CFightManagerInterface* fightInterface; // +24
BYTE bPad2[184];
Expand Down
266 changes: 266 additions & 0 deletions Client/game_sa/CPedSA.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@
#include "CProjectileInfoSA.h"
#include "CWeaponStatManagerSA.h"
#include "CFireManagerSA.h"
#include "CAnimManagerSA.h"
#include "CAnimBlendAssociationSA.h"
#include "TaskSA.h"
#include "CAnimBlendHierarchySA.h"

#include "enums/AdhesionGroup.h"

#include "CEventSoundQuietSA.h"

extern CGameSA* pGame;

Expand Down Expand Up @@ -576,6 +584,262 @@ void CPedSA::GetAttachedSatchels(std::vector<SSatchelsData>& satchelsList) const
}
}

void __fastcall CPedSA::PlayFootSteps(CPedSAInterface* ped)
{
if (!ped->pedFlags.bIsStanding)
return;

bool doWooble = false;

auto ComputeVolumeAndFreq = [](CPedSAInterface* ped, float& volumeOffset, float& relativeFreq, bool forAnim = false)
{
volumeOffset = 0.0f;
relativeFreq = 1.0f;

if (ped->pedFlags.bIsDucking)
{
volumeOffset = -18.0f;
relativeFreq = 0.8f;
}
else
{
switch (ped->moveState)
{
case PedMoveState::PEDMOVE_RUN:
volumeOffset = -6.0f;
relativeFreq = 1.1f;
break;
case PedMoveState::PEDMOVE_SPRINT:
relativeFreq = 1.2f;
break;
default:
// A custom option so that footstep sounds for animations aren’t quieter than normal steps
volumeOffset = forAnim ? -6.0f : -12.0f;
relativeFreq = forAnim ? 1.0f : 0.9f;
break;
}

if (ped->iMoveAnimGroup == 69) // ANIM_PLAYER_SNEAK_PED
{
volumeOffset -= 6.0f;
relativeFreq -= 0.1f;
}
}
};

auto DoFootstep = [doWooble, &ComputeVolumeAndFreq](CPedSAInterface* ped, bool isLeftFoot, bool forAnim = false)
{
if (ped->pedAudio.canAddEvent) // CPedAudio::AddEvent
{
float volumeOffset, relativeFreq;
ComputeVolumeAndFreq(ped, volumeOffset, relativeFreq, forAnim);

ped->pedAudio.AddAudioEvent(isLeftFoot ? AudioEvents::AE_PED_FOOTSTEP_LEFT : AudioEvents::AE_PED_FOOTSTEP_RIGHT, volumeOffset, relativeFreq, nullptr, 0, 0, 0);
}

// CPed::DoFootLanded
((void(__thiscall*)(CPedSAInterface*, int, bool))0x5E5380)(ped, isLeftFoot ? 1 : 0, doWooble);
};

auto DoFootstepForAnim = [DoFootstep, ComputeVolumeAndFreq](CAnimBlendAssociationSAInterface* anim, CPedSAInterface* ped, float stepInterval)
{
static bool leftStep = true;
static float lastStepTime = 0.0f;

if (anim->fCurrentTime - lastStepTime >= stepInterval)
{
DoFootstep(ped, leftStep, true);

leftStep = !leftStep;
lastStepTime += stepInterval;
}

if (anim->fCurrentTime < lastStepTime)
{
lastStepTime = 0.0f;
leftStep = true;
}
};

auto anim = pGame->GetAnimManager()->RpAnimBlendClumpGetFirstAssociation(ped->m_pRwObject);
if (!anim)
return;

CAnimBlendAssociationSAInterface* walkAnim = nullptr;
float partialBlend = 0.0f, walkcycleBlend = 0.0f;

auto UpdateAnimBlend = [&](CAnimBlendAssociationSAInterface* assoc)
{
if (assoc->m_walkAnim)
{
walkAnim = assoc;
walkcycleBlend += assoc->fBlendAmount;
}
else if (!assoc->m_bAddAnimBlendToTotalBlend && static_cast<eAnimID>(assoc->sAnimID) != eAnimID::ANIM_ID_FIGHT_IDLE && (assoc->m_bPartial || !ped->pedFlags.bIsDucking))
{
partialBlend += assoc->fBlendAmount;
}
};

for (auto& assoc = anim; assoc != nullptr; assoc = pGame->GetAnimManager()->RpAnimBlendGetNextAssociation(assoc))
UpdateAnimBlend(assoc->GetInterface());

if (!walkAnim || walkcycleBlend <= 0.5f || partialBlend >= 1.0f)
{
// Custom bugfix GitHub Issue #4359
// Checking for the TASK_SIMPLE_NAMED_ANIM task is not sufficient, because it usually doesn’t exist in the following cases.
// The ped has moveState set to STANDING_STILL, and the walkAnim flag on animations is not set,
auto checkWalkingAnim = [&](const char* animName, float stepInterval) -> bool
{
auto assoc = pGame->GetAnimManager()->RpAnimBlendClumpGetAssociation(ped->m_pRwObject, animName);
if (assoc)
{
DoFootstepForAnim(assoc->GetInterface(), ped, stepInterval);
return true;
}
return false;
};

if (checkWalkingAnim("cs_wuzi_pt1", 0.6f) || checkWalkingAnim("wuzi_walk", 0.5f))
return;

static constexpr const char* partialAnimsWithWalking[] = {"crry_prtial", "silence_reload"};
bool hasPartialAnimWithWalking = std::any_of(std::begin(partialAnimsWithWalking), std::end(partialAnimsWithWalking), [&](const char* name)
{
return pGame->GetAnimManager()->RpAnimBlendClumpGetAssociation(ped->m_pRwObject, name);
});

if (!hasPartialAnimWithWalking)
return;

auto walkAssoc = pGame->GetAnimManager()->RpAnimBlendClumpGetFirstAssociation(ped->m_pRwObject);
UpdateAnimBlend(walkAssoc->GetInterface());

if (!walkAnim)
return;
}

static constexpr float INITIAL_FOOT_LANDED_RATIO = 1.0f / 15.0f;
static constexpr float DUCK_FOOTSTEP_MOVE_TIME = 6.0f / 30.0f;

eAnimID animID = static_cast<eAnimID>(walkAnim->sAnimID);
doWooble = (animID == eAnimID::ANIM_ID_WALK || animID == eAnimID::ANIM_ID_RUN || animID == eAnimID::ANIM_ID_SPRINT);

float leftFootLanded = walkAnim->pAnimHierarchy->fTotalTime * INITIAL_FOOT_LANDED_RATIO;
float rightFootLanded = walkAnim->pAnimHierarchy->fTotalTime / 2.0f + leftFootLanded;
if (ped->pedFlags.bIsDucking)
{
leftFootLanded += DUCK_FOOTSTEP_MOVE_TIME;
rightFootLanded += DUCK_FOOTSTEP_MOVE_TIME;
}

auto HandleFoot = [&](bool isLeftFoot)
{
float footTime = isLeftFoot ? leftFootLanded : rightFootLanded;

if (ped->pPedStats->id == 40) // STAT_SKATER
{
float leftDown = 0.0f;
float rightDown = (animID == eAnimID::ANIM_ID_WALK) ? 16.0f / 30.0f : 10.0f / 30.0f;
float volume = 1.0f;

// SurfaceInfos_c::GetAdhesionGroup
AdhesionGroups::Enum surface = ((AdhesionGroups::Enum(__thiscall*)(void*, int))0x55E5C0)((void*)0xB79538, ped->m_ucCollisionContactSurfaceType);

switch (surface)
{
case AdhesionGroups::LOOSE:
if (rand() % 127 > 0)
ped->vecAnimMovingShiftLocal *= 0.5f;

volume = 0.5f;
break;
case AdhesionGroups::SAND:
if (rand() % 63 > 0)
ped->vecAnimMovingShiftLocal *= 0.2f;

volume = 0.2f;
break;
case AdhesionGroups::WET:
ped->vecAnimMovingShiftLocal *= 0.3f;
volume = 0.2f;
break;
}

float downTime = isLeftFoot ? leftDown : rightDown;
if (volume > 0.2f && walkAnim->fCurrentTime > downTime && walkAnim->fCurrentTime - walkAnim->fTimeStep <= downTime)
ped->pedAudio.AddAudioEvent(isLeftFoot ? AudioEvents::AE_PED_SKATE_LEFT : AudioEvents::AE_PED_SKATE_RIGHT, std::log10(volume) * 20.0f, animID == eAnimID::ANIM_ID_WALK ? 0.75f : 1.0f, nullptr, 0, 0, 0);

return;
}

if (walkAnim->fCurrentTime >= footTime && walkAnim->fCurrentTime - walkAnim->fTimeStep < footTime)
{
if (ped->bPedType < 2 && ped->pPlayerData)
{
CPlayerPedDataSAInterface* playerData = ped->pPlayerData;

// CPedClothesDesc::GetIsWearingBalaclava
bool doingRobbery = ((bool(__fastcall*)(CPedClothesDesc*, int))0x5A7950)(playerData->m_pClothes, 0);
float volume = 0.0f;

// 30 = SOUNDVOL_BARELY_AUDIBLE
// 45 = SOUNDVOL_NORM_FOOTSTEPS,
// 55 = SOUNDVOL_HEAVY_FOOTSTEPS,
// 65 = SOUNDVOL_HEAVY_FOOTSTEPS + 10
switch (ped->moveState)
{
case PedMoveState::PEDMOVE_SPRINT:
volume = doingRobbery ? 65.0f : 55.0f;
break;
case PedMoveState::PEDMOVE_RUN:
case PedMoveState::PEDMOVE_JOG:
if (playerData->m_moveBlendRatio < 2.0f)
{
if (doingRobbery && playerData->m_moveBlendRatio > 1.1f)
volume = 30.0f + 20.0f * (playerData->m_moveBlendRatio - 1.0f);
else if (playerData->m_moveBlendRatio > 1.5f)
volume = 30.0f + 15.0f * (playerData->m_moveBlendRatio - 1.0f);
}
else
volume = doingRobbery ? 55.0f : 45.0f;
break;
}

if (volume > 0.0f)
{
CEventSoundQuietSAInterface eventFootstepNoise(ped, volume);

// GetEventGlobalGroup
void* eventGlobalGroup = ((void*(__cdecl*)())0x4ABA50)();

// CEventGroup::Add
((void(__thiscall*)(void*, void*, int))0x4AB420)(eventGlobalGroup, &eventFootstepNoise, 0);
}
}

DoFootstep(ped, isLeftFoot);
}
};

HandleFoot(true);
HandleFoot(false);

// CTaskManager::GetSimplestActiveTask
auto* task = ((CTaskSAInterface * (__thiscall*)(void*))0x6819D0)(&ped->pPedIntelligence->taskManager);
if (!task)
return;

eTaskType taskType = reinterpret_cast<eTaskType(__thiscall*)(void*)>(task->VTBL->GetTaskType)(task);
if (taskType != TASK_SIMPLE_LAND)
return;

if (((bool(__thiscall*)(void*))0x678FE0)((void*)task)) // CTaskSimpleLand::RightFootLanded
((void(__thiscall*)(CPedSAInterface*, int, bool))0x5E5380)(ped, 0, true); // CPed::DoFootLanded
else if (((bool(__thiscall*)(void*))0x679010)((void*)task)) // CTaskSimpleLand::LeftFootLanded
((void(__thiscall*)(CPedSAInterface*, int, bool))0x5E5380)(ped, 1, true); // CPed::DoFootLanded
}

////////////////////////////////////////////////////////////////
//
// CPed_PreRenderAfterTest
Expand Down Expand Up @@ -656,4 +920,6 @@ void CPedSA::StaticSetHooks()
{
EZHookInstall(CPed_PreRenderAfterTest);
EZHookInstall(CPed_PreRenderAfterTest_Mid);

HookInstallCall(0x5E92C8, (DWORD)CPedSA::PlayFootSteps);
}
3 changes: 3 additions & 0 deletions Client/game_sa/CPedSA.h
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ static_assert(sizeof(CPedAcquaintanceSAInterface) == 0x14, "Invalid size for CPe

class CPedStatSAInterface
{
public:
std::uint32_t id;
char name[24];
float fleedDistance;
Expand Down Expand Up @@ -475,6 +476,8 @@ class CPedSA : public virtual CPed, public virtual CPhysicalSA

void GetAttachedSatchels(std::vector<SSatchelsData> &satchelsList) const override;

static void __fastcall PlayFootSteps(CPedSAInterface* ped);

static void StaticSetHooks();

private:
Expand Down
6 changes: 6 additions & 0 deletions Client/game_sa/CPedSoundSA.h
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,12 @@ class CPedSoundEntitySAInterface : public CAEAudioEntity

std::uint8_t field_154[4];
std::uint8_t field_158[4];

void AddAudioEvent(AudioEvents::Enum audioEventID, float volume, float freq, CEntitySAInterface* targetEntity, std::uint32_t surfaceId, int somethingWithFightTask, int maxVolumeDuringFightTask)
{
if (canAddEvent)
((void(__thiscall*)(CPedSoundEntitySAInterface*, int, float, float, CEntitySAInterface*, int, int, int))0x4E2BB0)(this, audioEventID, volume, freq, targetEntity, surfaceId, somethingWithFightTask, maxVolumeDuringFightTask);
}
};
static_assert(sizeof(CPedSoundEntitySAInterface) == 0x15C, "Invalid size for CPedSoundEntitySAInterface");

Expand Down
1 change: 1 addition & 0 deletions Client/sdk/game/CAudioEngine.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include <CVector.h>
#include "Common.h"
#include "CWeaponInfo.h"
#include "enums/AudioEvents.h"

class CEntity;
class CEntitySAInterface;
Expand Down
Loading
Loading