Skip to content
Draft
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
676 changes: 676 additions & 0 deletions Client/game_sa/CWorldSA.cpp

Large diffs are not rendered by default.

116 changes: 106 additions & 10 deletions Client/game_sa/CWorldSA.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,48 @@
#pragma once

#include <game/CWorld.h>
#include <cstdint>
#include <vector>

#define FUNC_Add 0x563220
#define FUNC_Remove 0x563280
#define FUNC_ProcessLineOfSight 0x56BA00
#define FUNC_FindGroundZFor3DCoord 0x5696C0
#define FUNC_IsLineOfSightClear 0x56A490
#define FUNC_HasCollisionBeenLoaded 0x410CE0
#define FUNC_RemoveBuildingsNotInArea 0x4094B0
#define FUNC_RemoveReferencesToDeletedObject 0x565510
#define FUNC_COcclusion_ProcessBeforeRendering 0x7201C0
#define VAR_COcclusion_NumActiveOccluders 0xC73CC0
#define FUNC_Add 0x563220
#define FUNC_Remove 0x563280
#define FUNC_ProcessLineOfSight 0x56BA00
#define FUNC_FindGroundZFor3DCoord 0x5696C0
#define FUNC_IsLineOfSightClear 0x56A490
#define FUNC_HasCollisionBeenLoaded 0x410CE0
#define FUNC_RemoveBuildingsNotInArea 0x4094B0
#define FUNC_RemoveReferencesToDeletedObject 0x565510
#define FUNC_COcclusion_ProcessBeforeRendering 0x7201C0
#define VAR_COcclusion_NumActiveOccluders 0xC73CC0

// COcclusion::AddOne is __cdecl with 11 stack arguments: centre xyz, three full extents, three euler
// angles in degrees, an unused flag and the interior selector as a byte. It appends an 18 byte entry
// to one of two fixed arrays and returns nothing, dropping the call silently when the array is full.
// The centre is stored as int16 in quarter units, the extents are truncated to whole units first and
// the angles are byte angles of 360/256 degrees; all of them truncate toward zero.
#define FUNC_COcclusion_Init 0x71DCA0
#define FUNC_COcclusion_AddOne 0x71DCD0
#define VAR_COcclusion_NumOccluders 0xC73F98
#define ARRAY_COcclusion_Occluders 0xC73FA0
#define VAR_COcclusion_NumInteriorOccluders 0xC73CC4
#define ARRAY_COcclusion_InteriorOccluders 0xC73CC8

// The exterior count is only a bump allocator that the renderer never reads: membership of the
// exterior array is carried by singly linked lists threaded through the 16 bit word at entry+0x10,
// whose heads and cursor live here. The interior array has no lists and is scanned up to its count.
// Editing the lists by hand can cross link them, which hangs the render loop, so every removal
// rebuilds the array from a snapshot instead.
// Four 16 bit words four bytes apart: the far list head, the near list head, the walk cursor and the
// entry before it. COcclusion::Init writes 0xFFFF to all four.
#define VAR_COcclusion_ListHeads 0x8D5D68
#define COCCLUSION_LIST_HEAD_COUNT 4

// An index into one of the two arrays, or OCCLUDER_SLOT_NONE when the box did not get a slot
#define OCCLUDER_SLOT_NONE 0xFFFFFFFF

#define COCCLUSION_MAX_OCCLUDERS 1000
#define COCCLUSION_MAX_INTERIOR_OCCLUDERS 40
#define COCCLUSION_ENTRY_SIZE 18
#define CALL_CCullZones_FindTunnelAttributesForCoors 0x55570D
#define FUNC_CWorld_FindPositionForTrackPosition 0x6F59E0
#define FUNC_CWorld_TestSphereAgainstWorld 0x569E20
Expand Down Expand Up @@ -70,6 +101,15 @@ class CWorldSA : public CWorld
float GetAircraftMaxVelocity();
void SetOcclusionsEnabled(bool bEnabled);
bool GetOcclusionsEnabled();
bool AddOccluder(const CVector& vecPosition, const CVector& vecSize, const CVector& vecRotation, bool bInterior, void* pChangeSource, uint& uiOutId);
bool SetOccluder(uint uiId, const CVector& vecPosition, const CVector& vecSize, const CVector& vecRotation, void* pChangeSource);
bool RemoveOccluder(uint uiId, void* pChangeSource);
bool RestoreOccluder(uint uiId, void* pChangeSource);
uint RemoveOccludersInRadius(const CVector& vecPosition, float fRadius, bool bInterior, void* pChangeSource);
uint RestoreOccludersInRadius(const CVector& vecPosition, float fRadius, bool bInterior, void* pChangeSource);
void GetOccluders(bool bInterior, std::vector<SOccluderInfo>& outList);
void GetOccluderCapacity(bool bInterior, uint& uiOutUsed, uint& uiOutFree);
void UndoOccluderChanges(void* pChangeSource = nullptr);
void FindWorldPositionForRailTrackPosition(float fRailTrackPosition, int iTrackId, CVector* pOutVecPosition);
int FindClosestRailTrackNode(const CVector& vecPosition, uchar& ucOutTrackId, float& fOutRailDistance);
bool CalculateImpactPosition(const CVector& vecInputStart, CVector& vecInputEnd);
Expand All @@ -82,6 +122,62 @@ class CWorldSA : public CWorld
bool checkObjects, bool checkDummies, bool cameraIgnore, STestSphereAgainstWorldResult& result) override;

private:
// Every scripted occluder is kept here and replayed through COcclusion::AddOne after the vanilla
// array is restored from the snapshot, so the engine always builds the lists itself.
struct SScriptedOccluder
{
uint uiId;
uint uiSlot = OCCLUDER_SLOT_NONE;
CVector vecPosition;
CVector vecSize;
CVector vecRotation;
bool bInterior;
void* pChangeSource;
};

// The map's own occluders are kept exactly as the loader left them. Disabling one is a set
// membership on the MTA side, so a rebuild can leave it out and hand its slot to a script.
struct SVanillaOccluder
{
uint uiId;
uint uiSlot = OCCLUDER_SLOT_NONE;
bool bInterior;
std::uint8_t aBytes[COCCLUSION_ENTRY_SIZE];
std::set<void*> setDisabledBy;
bool bActive{true};

// An edit replaces the loaded geometry until the resource that made it restores the occluder
// or stops. The loaded bytes are kept either way, so a restore is still byte exact.
bool bEdited = false;
CVector vecEditPosition;
CVector vecEditSize;
CVector vecEditRotation;
void* pEditSource = nullptr;
};

bool WriteOccluderInPlace(uint uiSlot, bool bInterior, const CVector& vecHeldPosition, const CVector& vecPosition, const CVector& vecSize,
const CVector& vecRotation);
bool SetVanillaOccluder(uint uiId, const CVector& vecPosition, const CVector& vecSize, const CVector& vecRotation, void* pChangeSource);
static CVector GetVanillaOccluderCentre(const SVanillaOccluder& occluder);
void CaptureOccluderBaseline();
void RebuildOccluders();
void RestartOccluderListWalk();
uint CountEnabledVanillaOccluders(bool bInterior) const;
uint CountScriptedOccluders(bool bInterior) const;
bool IsOccluderPoolFull(bool bInterior) const;
static uint GetOccluderPoolMax(bool bInterior);

// True when lifting this source's mark is the one that puts the occluder back in the array
static bool WouldReEnable(const SVanillaOccluder& occluder, void* pChangeSource)
{
return occluder.setDisabledBy.size() == 1 && occluder.setDisabledBy.count(pChangeSource) == 1;
}

bool m_bOccluderBaselineTaken = false;
uint m_uiNextOccluderId = 1;
std::vector<SScriptedOccluder> m_ScriptedOccluders;
std::vector<SVanillaOccluder> m_VanillaOccluders;

float m_fAircraftMaxHeight;
CSurfaceType* m_pSurfaceInfo;
};
1 change: 1 addition & 0 deletions Client/mods/deathmatch/logic/CClientGame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3474,6 +3474,7 @@ void CClientGame::Event_OnIngame()

g_pGame->GetBuildingRemoval()->ClearRemovedBuildingLists();
g_pGame->GetWorld()->SetOcclusionsEnabled(true);
g_pGame->GetWorld()->UndoOccluderChanges();

g_pGame->ResetModelLodDistances();
g_pGame->ResetModelFlags();
Expand Down
3 changes: 3 additions & 0 deletions Client/mods/deathmatch/logic/CResource.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,9 @@ CResource::~CResource()
// Undo all changes to water
g_pGame->GetWaterManager()->UndoChanges(this);

// Undo all changes to occluders
g_pGame->GetWorld()->UndoOccluderChanges(this);

// Cancel all downloads started by this resource
if (g_pClientGame->GetSingularFileDownloadManager())
g_pClientGame->GetSingularFileDownloadManager()->CancelResourceDownloads(this);
Expand Down
171 changes: 171 additions & 0 deletions Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
*****************************************************************************/

#include "StdInc.h"
#include <cmath>
#include <game/CColPoint.h>
#include <game/CObjectGroupPhysicalProperties.h>
#include <game/CStreaming.h>
Expand Down Expand Up @@ -93,6 +94,14 @@ void CLuaEngineDefs::LoadFunctions()
{"engineLoadIFP", EngineLoadIFP},
{"engineImportTXD", EngineImportTXD},
{"engineAddClothingTXD", ArgumentParser<EngineAddClothingTXD>},
{"engineAddOccluder", ArgumentParser<EngineAddOccluder>},
{"engineSetOccluder", ArgumentParser<EngineSetOccluder>},
{"engineRemoveOccluder", ArgumentParser<EngineRemoveOccluder>},
{"engineRestoreOccluder", ArgumentParser<EngineRestoreOccluder>},
{"engineRemoveOccluders", ArgumentParser<EngineRemoveOccluders>},
{"engineRestoreOccluders", ArgumentParser<EngineRestoreOccluders>},
{"engineGetOccluders", EngineGetOccluders},
{"engineGetOccluderCapacity", ArgumentParser<EngineGetOccluderCapacity>},
{"engineReplaceCOL", EngineReplaceCOL},
{"engineRestoreCOL", EngineRestoreCOL},
{"engineReplaceModel", EngineReplaceModel},
Expand Down Expand Up @@ -678,6 +687,168 @@ bool CLuaEngineDefs::EngineAddClothingTXD(CClientTXD* pTXD, std::string strModel
return true;
}

static void ValidateOccluderGeometry(float fX, float fY, float fZ, float fSizeX, float fSizeY, float fSizeZ, int iFirstArgument)
{
const float fCoords[3] = {fX, fY, fZ};
for (int i = 0; i < 3; ++i)
if (std::fabs(fCoords[i]) > 8000.0f)
throw std::invalid_argument(SString("Expected position in range -8000 - 8000 at argument %d, got %g", iFirstArgument + i, fCoords[i]));

const float fSizes[3] = {fSizeX, fSizeY, fSizeZ};
int iFlatSides = 0;
for (int i = 0; i < 3; ++i)
{
if (fSizes[i] < 0.0f || fSizes[i] > 8000.0f)
throw std::invalid_argument(SString("Expected size in range 0 - 8000 at argument %d, got %g", iFirstArgument + 3 + i, fSizes[i]));
if (fSizes[i] < 1.0f)
++iFlatSides;
}
if (iFlatSides > 1)
throw std::invalid_argument(SString("Expected at most one size below 1 at arguments %d - %d", iFirstArgument + 3, iFirstArgument + 5));
}

std::variant<std::uint32_t, bool> CLuaEngineDefs::EngineAddOccluder(lua_State* const luaVM, float fX, float fY, float fZ, float fSizeX, float fSizeY,
float fSizeZ, std::optional<float> fRotX, std::optional<float> fRotY,
std::optional<float> fRotZ, std::optional<bool> bInterior)
{
CLuaMain* pLuaMain = m_pLuaManager->GetVirtualMachine(luaVM);
if (!pLuaMain)
return false;

ValidateOccluderGeometry(fX, fY, fZ, fSizeX, fSizeY, fSizeZ, 1);

unsigned int uiId = 0;
if (!g_pGame->GetWorld()->AddOccluder(CVector(fX, fY, fZ), CVector(fSizeX, fSizeY, fSizeZ),
CVector(fRotX.value_or(0.0f), fRotY.value_or(0.0f), fRotZ.value_or(0.0f)), bInterior.value_or(false),
pLuaMain->GetResource(), uiId))
return false;

return uiId;
}

bool CLuaEngineDefs::EngineSetOccluder(lua_State* const luaVM, std::uint32_t uiId, float fX, float fY, float fZ, float fSizeX, float fSizeY, float fSizeZ,
std::optional<float> fRotX, std::optional<float> fRotY, std::optional<float> fRotZ)
{
CLuaMain* pLuaMain = m_pLuaManager->GetVirtualMachine(luaVM);
if (!pLuaMain)
return false;

ValidateOccluderGeometry(fX, fY, fZ, fSizeX, fSizeY, fSizeZ, 2);

return g_pGame->GetWorld()->SetOccluder(uiId, CVector(fX, fY, fZ), CVector(fSizeX, fSizeY, fSizeZ),
CVector(fRotX.value_or(0.0f), fRotY.value_or(0.0f), fRotZ.value_or(0.0f)), pLuaMain->GetResource());
}

bool CLuaEngineDefs::EngineRemoveOccluder(lua_State* const luaVM, std::uint32_t uiId)
{
CLuaMain* pLuaMain = m_pLuaManager->GetVirtualMachine(luaVM);
if (!pLuaMain)
return false;

return g_pGame->GetWorld()->RemoveOccluder(uiId, pLuaMain->GetResource());
}

bool CLuaEngineDefs::EngineRestoreOccluder(lua_State* const luaVM, std::uint32_t uiId)
{
CLuaMain* pLuaMain = m_pLuaManager->GetVirtualMachine(luaVM);
if (!pLuaMain)
return false;

return g_pGame->GetWorld()->RestoreOccluder(uiId, pLuaMain->GetResource());
}

std::uint32_t CLuaEngineDefs::EngineRemoveOccluders(lua_State* const luaVM, float fX, float fY, float fZ, float fRadius, std::optional<bool> bInterior)
{
CLuaMain* pLuaMain = m_pLuaManager->GetVirtualMachine(luaVM);
if (!pLuaMain)
return 0;

if (!std::isfinite(fRadius) || fRadius <= 0.0f)
throw std::invalid_argument(SString("Expected a positive radius at argument 4, got %g", fRadius));

return g_pGame->GetWorld()->RemoveOccludersInRadius(CVector(fX, fY, fZ), fRadius, bInterior.value_or(false), pLuaMain->GetResource());
}

std::uint32_t CLuaEngineDefs::EngineRestoreOccluders(lua_State* const luaVM, float fX, float fY, float fZ, float fRadius, std::optional<bool> bInterior)
{
CLuaMain* pLuaMain = m_pLuaManager->GetVirtualMachine(luaVM);
if (!pLuaMain)
return 0;

if (!std::isfinite(fRadius) || fRadius <= 0.0f)
throw std::invalid_argument(SString("Expected a positive radius at argument 4, got %g", fRadius));

return g_pGame->GetWorld()->RestoreOccludersInRadius(CVector(fX, fY, fZ), fRadius, bInterior.value_or(false), pLuaMain->GetResource());
}

int CLuaEngineDefs::EngineGetOccluders(lua_State* luaVM)
{
bool bInterior = false;

CScriptArgReader argStream(luaVM);
argStream.ReadBool(bInterior, false);

if (argStream.HasErrors())
{
m_pScriptDebugging->LogCustom(luaVM, argStream.GetFullErrorMessage());
lua_pushboolean(luaVM, false);
return 1;
}

std::vector<SOccluderInfo> occluders;
g_pGame->GetWorld()->GetOccluders(bInterior, occluders);

lua_createtable(luaVM, static_cast<int>(occluders.size()), 0);

int iIndex = 1;
for (const SOccluderInfo& info : occluders)
{
lua_createtable(luaVM, 0, 14);

lua_pushstring(luaVM, "id");
lua_pushnumber(luaVM, info.uiId);
lua_settable(luaVM, -3);

const char* const szKeys[9] = {"x", "y", "z", "sizeX", "sizeY", "sizeZ", "rotX", "rotY", "rotZ"};
const float fValues[9] = {info.vecPosition.fX, info.vecPosition.fY, info.vecPosition.fZ, info.vecSize.fX, info.vecSize.fY,
info.vecSize.fZ, info.vecRotation.fX, info.vecRotation.fY, info.vecRotation.fZ};
for (int i = 0; i < 9; i++)
{
lua_pushstring(luaVM, szKeys[i]);
lua_pushnumber(luaVM, fValues[i]);
lua_settable(luaVM, -3);
}

lua_pushstring(luaVM, "interior");
lua_pushboolean(luaVM, info.bInterior);
lua_settable(luaVM, -3);

lua_pushstring(luaVM, "enabled");
lua_pushboolean(luaVM, info.bEnabled);
lua_settable(luaVM, -3);

lua_pushstring(luaVM, "active");
lua_pushboolean(luaVM, info.bActive);
lua_settable(luaVM, -3);

lua_pushstring(luaVM, "scripted");
lua_pushboolean(luaVM, info.bScripted);
lua_settable(luaVM, -3);

lua_rawseti(luaVM, -2, iIndex++);
}

return 1;
}

CLuaMultiReturn<std::uint32_t, std::uint32_t> CLuaEngineDefs::EngineGetOccluderCapacity(std::optional<bool> bInterior)
{
unsigned int uiUsed = 0;
unsigned int uiFree = 0;
g_pGame->GetWorld()->GetOccluderCapacity(bInterior.value_or(false), uiUsed, uiFree);
return {uiUsed, uiFree};
}

CClientIMG* CLuaEngineDefs::EngineLoadIMG(lua_State* const luaVM, std::string strRelativeFilePath)
{
CLuaMain* pLuaMain = m_pLuaManager->GetVirtualMachine(luaVM);
Expand Down
15 changes: 13 additions & 2 deletions Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,19 @@ class CLuaEngineDefs : public CLuaDefs
LUA_DECLARE(EngineGetObjectGroupPhysicalProperty)
LUA_DECLARE(EngineRestoreObjectGroupPhysicalProperties)

static bool EngineAddClothingModel(CClientDFF* pDff, std::string strModelName);
static bool EngineAddClothingTXD(CClientTXD* pTxd, std::string strModelName);
static bool EngineAddClothingModel(CClientDFF* pDff, std::string strModelName);
static bool EngineAddClothingTXD(CClientTXD* pTxd, std::string strModelName);
static std::variant<std::uint32_t, bool> EngineAddOccluder(lua_State* const luaVM, float fX, float fY, float fZ, float fSizeX, float fSizeY, float fSizeZ,
std::optional<float> fRotX, std::optional<float> fRotY, std::optional<float> fRotZ,
std::optional<bool> bInterior);
static bool EngineSetOccluder(lua_State* const luaVM, std::uint32_t uiId, float fX, float fY, float fZ, float fSizeX, float fSizeY, float fSizeZ,
std::optional<float> fRotX, std::optional<float> fRotY, std::optional<float> fRotZ);
static bool EngineRemoveOccluder(lua_State* const luaVM, std::uint32_t uiId);
static bool EngineRestoreOccluder(lua_State* const luaVM, std::uint32_t uiId);
static std::uint32_t EngineRemoveOccluders(lua_State* const luaVM, float fX, float fY, float fZ, float fRadius, std::optional<bool> bInterior);
static std::uint32_t EngineRestoreOccluders(lua_State* const luaVM, float fX, float fY, float fZ, float fRadius, std::optional<bool> bInterior);
LUA_DECLARE(EngineGetOccluders);
static CLuaMultiReturn<std::uint32_t, std::uint32_t> EngineGetOccluderCapacity(std::optional<bool> bInterior);
static uint EngineGetModelFlags(uint uiModelID);
static bool EngineSetModelFlags(uint uiModelID, uint uiFlags, std::optional<bool> bIdeFlags);
static bool EngineGetModelFlag(uint uiModelID, eModelIdeFlag eFlag);
Expand Down
Loading
Loading