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
589 changes: 589 additions & 0 deletions Client/game_sa/CCullZonesSA.cpp

Large diffs are not rendered by default.

49 changes: 49 additions & 0 deletions Client/game_sa/CCullZonesSA.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*****************************************************************************
*
* PROJECT: Multi Theft Auto
* LICENSE: See LICENSE in the top level directory
* FILE: game_sa/CCullZonesSA.h
* PURPOSE: GTA SA CULL zone relocation and editable catalog
*
* Multi Theft Auto is available from https://www.multitheftauto.com/
*
*****************************************************************************/

#pragma once

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

class CCullZonesSA
{
public:
CCullZonesSA();
~CCullZonesSA();

std::size_t GetCount();
bool GetByIndex(std::size_t index, SCullZoneInfo& outInfo);
std::uint32_t Create(const SCullZoneDefinition& definition, const void* owner);
bool Set(std::uint32_t id, const SCullZoneDefinition& definition, const void* owner);
bool SetEnabled(std::uint32_t id, bool enabled, const void* owner);
bool Remove(std::uint32_t id, const void* owner);
bool Restore(std::uint32_t id, const void* owner);
void RemoveChangesByOwner(const void* owner);
void SetMirrorsEnabled(bool enabled);
void UpdateCurrentFlags(const CVector& playerPosition, const CVector& cameraPosition);

private:
struct SZoneEntry;

void EnsureCatalog();
void RebuildNativeArrays();
SZoneEntry* Find(std::uint32_t id);
bool CanClaim(SZoneEntry& entry, const void* owner);
bool HasCapacityFor(ECullZoneType type, const SZoneEntry* replacedEntry = nullptr) const;

bool m_catalogReady{false};
bool m_mirrorsEnabled{true};
int m_savedMirrorCount{-1};
std::uint32_t m_nextId{1};
std::vector<SZoneEntry> m_entries;
};
57 changes: 54 additions & 3 deletions Client/game_sa/CWorldSA.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
*****************************************************************************/

#include "StdInc.h"
#include "CCullZonesSA.h"
#include <numbers>
#include <multiplayer/CMultiplayer.h>
#include <core/CCoreInterface.h>
Expand All @@ -30,13 +31,63 @@ namespace
extern const unsigned char aOriginalSurfaceInfo[2292];
}

CWorldSA::CWorldSA()
CWorldSA::CWorldSA() : m_pSurfaceInfo(reinterpret_cast<CSurfaceType*>(ARRAY_SurfaceInfos)), m_cullZones(std::make_unique<CCullZonesSA>())
{
m_pSurfaceInfo = reinterpret_cast<CSurfaceType*>(ARRAY_SurfaceInfos);

InstallHooks();
}

CWorldSA::~CWorldSA() = default;

std::size_t CWorldSA::GetCullZoneCount()
{
return m_cullZones->GetCount();
}

bool CWorldSA::GetCullZoneByIndex(std::size_t index, SCullZoneInfo& outInfo)
{
return m_cullZones->GetByIndex(index, outInfo);
}

std::uint32_t CWorldSA::CreateCullZone(const SCullZoneDefinition& definition, const void* owner)
{
return m_cullZones->Create(definition, owner);
}

bool CWorldSA::SetCullZone(std::uint32_t id, const SCullZoneDefinition& definition, const void* owner)
{
return m_cullZones->Set(id, definition, owner);
}

bool CWorldSA::SetCullZoneEnabled(std::uint32_t id, bool enabled, const void* owner)
{
return m_cullZones->SetEnabled(id, enabled, owner);
}

bool CWorldSA::RemoveCullZone(std::uint32_t id, const void* owner)
{
return m_cullZones->Remove(id, owner);
}

bool CWorldSA::RestoreCullZone(std::uint32_t id, const void* owner)
{
return m_cullZones->Restore(id, owner);
}

void CWorldSA::RemoveCullZoneChangesByOwner(const void* owner)
{
m_cullZones->RemoveChangesByOwner(owner);
}

void CWorldSA::SetCullMirrorZonesEnabled(bool enabled)
{
m_cullZones->SetMirrorsEnabled(enabled);
}

void CWorldSA::UpdateCullZoneFlags(const CVector& playerPosition, const CVector& cameraPosition)
{
m_cullZones->UpdateCurrentFlags(playerPosition, cameraPosition);
}

CSurfaceType* CWorldSA::GetSurfaceInfo()
{
return m_pSurfaceInfo;
Expand Down
20 changes: 18 additions & 2 deletions Client/game_sa/CWorldSA.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
#pragma once

#include <game/CWorld.h>
#include <memory>

class CCullZonesSA;

#define FUNC_Add 0x563220
#define FUNC_Remove 0x563280
Expand Down Expand Up @@ -43,6 +46,7 @@ class CWorldSA : public CWorld
{
public:
CWorldSA();
~CWorldSA();
void InstallHooks();
void Add(CEntity* entity, eDebugCaller CallerId);
void Add(CEntitySAInterface* entityInterface, eDebugCaller CallerId);
Expand Down Expand Up @@ -78,7 +82,19 @@ class CWorldSA : public CWorld
CEntity* TestSphereAgainstWorld(const CVector& sphereCenter, float radius, CEntity* ignoredEntity, bool checkBuildings, bool checkVehicles, bool checkPeds,
bool checkObjects, bool checkDummies, bool cameraIgnore, STestSphereAgainstWorldResult& result) override;

std::size_t GetCullZoneCount() override;
bool GetCullZoneByIndex(std::size_t index, SCullZoneInfo& outInfo) override;
std::uint32_t CreateCullZone(const SCullZoneDefinition& definition, const void* owner) override;
bool SetCullZone(std::uint32_t id, const SCullZoneDefinition& definition, const void* owner) override;
bool SetCullZoneEnabled(std::uint32_t id, bool enabled, const void* owner) override;
bool RemoveCullZone(std::uint32_t id, const void* owner) override;
bool RestoreCullZone(std::uint32_t id, const void* owner) override;
void RemoveCullZoneChangesByOwner(const void* owner) override;
void SetCullMirrorZonesEnabled(bool enabled) override;
void UpdateCullZoneFlags(const CVector& playerPosition, const CVector& cameraPosition) override;

private:
float m_fAircraftMaxHeight;
CSurfaceType* m_pSurfaceInfo;
float m_fAircraftMaxHeight;
CSurfaceType* m_pSurfaceInfo;
std::unique_ptr<CCullZonesSA> m_cullZones;
};
11 changes: 11 additions & 0 deletions Client/mods/deathmatch/logic/CClientGame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3922,6 +3922,17 @@ void CClientGame::PreRenderSkyHandler()

void CClientGame::PreWeatherUpdateHandler()
{
// CGame::Process does not reach GTA's later CCullZones::Update call on the
// multiplayer path. Refresh both attribute masks before weather consumes them.
if (m_pManager->IsGameLoaded() && m_pLocalPlayer && m_pCamera)
{
CVector playerPosition;
CVector cameraPosition;
m_pLocalPlayer->GetPosition(playerPosition);
m_pCamera->GetPosition(cameraPosition);
g_pGame->GetWorld()->UpdateCullZoneFlags(playerPosition, cameraPosition);
}

// Fix #4803: Set MTA's weather types and zero InterpolationValue BEFORE
// CWeather::Update runs. Zeroing InterpolationValue prevents the wrap branch
// from ever firing (engine_interp >= 0 is always true), so the engine computes
Expand Down
10 changes: 10 additions & 0 deletions Client/mods/deathmatch/logic/CResource.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,11 @@ CResource::CResource(unsigned short usNetID, const char* szResourceName, CClient

CResource::~CResource()
{
// CULL zones are native state rather than client elements. Restore vanilla
// definitions and remove this resource's custom entries before its identity dies.
if (g_pGame && g_pGame->GetWorld())
g_pGame->GetWorld()->RemoveCullZoneChangesByOwner(this);

// Remove refrences from requested models
m_modelStreamer.ReleaseAll();

Expand Down Expand Up @@ -425,6 +430,11 @@ void CResource::Stop()
Arguments.PushResource(this);
m_pResourceEntity->CallEvent("onClientResourceStop", Arguments, true);

// Stop does not immediately destroy the resource object. Release its native
// ownership now so a same-session restart begins from the vanilla state.
if (g_pGame && g_pGame->GetWorld())
g_pGame->GetWorld()->RemoveCullZoneChangesByOwner(this);

// When a custom application is used - reset discord stuff
const auto discord = g_pCore->GetDiscord();
if (discord && !discord->IsDiscordCustomDetailsDisallowed() && discord->GetDiscordResourceName() == m_strResourceName)
Expand Down
6 changes: 6 additions & 0 deletions Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ ADD_ENUM(static_cast<eLuaType>(LUA_TUSERDATA), "userdata")
ADD_ENUM(static_cast<eLuaType>(LUA_TTHREAD), "thread")
IMPLEMENT_ENUM_END("lua-type")

IMPLEMENT_ENUM_CLASS_BEGIN(ECullZoneType)
ADD_ENUM(ECullZoneType::ATTRIBUTE, "attribute")
ADD_ENUM(ECullZoneType::TUNNEL, "tunnel")
ADD_ENUM(ECullZoneType::MIRROR, "mirror")
IMPLEMENT_ENUM_CLASS_END("cull-zone-type")

IMPLEMENT_ENUM_BEGIN(CGUIVerticalAlign)
ADD_ENUM(CGUI_ALIGN_TOP, "top")
ADD_ENUM(CGUI_ALIGN_BOTTOM, "bottom")
Expand Down
2 changes: 2 additions & 0 deletions Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include <game/CRenderWare.h>
#include <game/CHud.h>
#include <game/CStreaming.h>
#include <game/CWorld.h>
#include <type_traits>

#include "enums/VehicleComponent.h"
Expand Down Expand Up @@ -97,6 +98,7 @@ DECLARE_ENUM_CLASS(eRenderStage);
DECLARE_ENUM_CLASS(FxParticleSystems);
DECLARE_ENUM(ePools);
DECLARE_ENUM_CLASS(WorldProperty);
DECLARE_ENUM_CLASS(ECullZoneType);
DECLARE_ENUM_CLASS(eModelLoadState);
DECLARE_ENUM_CLASS(PreloadAreaOption);
DECLARE_ENUM_CLASS(RestreamOption);
Expand Down
126 changes: 126 additions & 0 deletions Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,126 @@
#include <lua/CLuaFunctionParser.h>
#include "CLuaEngineDefs.h"
#include <enums/VehicleType.h>
#include <cmath>
#include <cstdint>
#include <limits>
#include <optional>
#include <stdexcept>
#include <string>
#include <unordered_map>
#include <variant>
#include <vector>

namespace
{
using CullZoneValue = std::variant<std::uint32_t, float, bool, ECullZoneType>;
using CullZoneTable = std::unordered_map<std::string, CullZoneValue>;

std::uint16_t GetCullZoneFlags(ECullZoneType type, double flags)
{
const double maximumFlags = type == ECullZoneType::MIRROR ? std::numeric_limits<std::uint8_t>::max() : std::numeric_limits<std::uint16_t>::max();
if (flags < 0.0 || flags > maximumFlags || std::trunc(flags) != flags)
throw std::invalid_argument{"Flags must be a whole number in range for the selected CULL zone type"};

return static_cast<std::uint16_t>(flags);
}

std::uint32_t GetCullZoneId(double id)
{
if (id < 1.0 || id > std::numeric_limits<std::uint32_t>::max() || std::trunc(id) != id)
throw std::invalid_argument{"CULL zone ID must be a positive whole number"};

return static_cast<std::uint32_t>(id);
}

SCullZoneDefinition MakeCullZoneDefinition(ECullZoneType type, float centerX, float centerY, float centerZ, float width, float depth, float height,
double flags, std::optional<float> rotationDegrees, std::optional<float> mirrorV,
std::optional<float> mirrorNormalX, std::optional<float> mirrorNormalY, std::optional<float> mirrorNormalZ)
{
SCullZoneDefinition definition;
definition.type = type;
definition.centerX = centerX;
definition.centerY = centerY;
definition.centerZ = centerZ;
definition.width = width;
definition.depth = depth;
definition.height = height;
definition.flags = GetCullZoneFlags(type, flags);
definition.rotationDegrees = rotationDegrees.value_or(0.0f);
definition.mirrorV = mirrorV.value_or(0.0f);
definition.mirrorNormalX = mirrorNormalX.value_or(0.0f);
definition.mirrorNormalY = mirrorNormalY.value_or(0.0f);
definition.mirrorNormalZ = mirrorNormalZ.value_or(1.0f);
return definition;
}

std::vector<CullZoneTable> EngineGetCullZones(std::optional<ECullZoneType> type)
{
CWorld* world = g_pGame->GetWorld();
std::vector<CullZoneTable> result;
result.reserve(world->GetCullZoneCount());

for (std::size_t i = 0; i < world->GetCullZoneCount(); ++i)
{
SCullZoneInfo info;
if (!world->GetCullZoneByIndex(i, info) || (type && info.type != *type))
continue;

result.emplace_back(CullZoneTable{{"id", info.id},
{"type", info.type},
{"x", info.centerX},
{"y", info.centerY},
{"z", info.centerZ},
{"width", info.width},
{"depth", info.depth},
{"height", info.height},
{"rotation", info.rotationDegrees},
{"flags", static_cast<std::uint32_t>(info.flags)},
{"mirrorV", info.mirrorV},
{"normalX", info.mirrorNormalX},
{"normalY", info.mirrorNormalY},
{"normalZ", info.mirrorNormalZ},
{"enabled", info.enabled},
{"original", info.original}});
}
return result;
}

std::variant<bool, std::uint32_t> EngineCreateCullZone(lua_State* luaVM, ECullZoneType type, float centerX, float centerY, float centerZ, float width,
float depth, float height, double flags, std::optional<float> rotationDegrees,
std::optional<float> mirrorV, std::optional<float> mirrorNormalX, std::optional<float> mirrorNormalY,
std::optional<float> mirrorNormalZ)
{
const SCullZoneDefinition definition = MakeCullZoneDefinition(type, centerX, centerY, centerZ, width, depth, height, flags, rotationDegrees, mirrorV,
mirrorNormalX, mirrorNormalY, mirrorNormalZ);
const std::uint32_t id = g_pGame->GetWorld()->CreateCullZone(definition, &lua_getownerresource(luaVM));
return id ? std::variant<bool, std::uint32_t>{id} : std::variant<bool, std::uint32_t>{false};
}

bool EngineSetCullZone(lua_State* luaVM, double id, ECullZoneType type, float centerX, float centerY, float centerZ, float width, float depth, float height,
double flags, std::optional<float> rotationDegrees, std::optional<float> mirrorV, std::optional<float> mirrorNormalX,
std::optional<float> mirrorNormalY, std::optional<float> mirrorNormalZ)
{
const SCullZoneDefinition definition = MakeCullZoneDefinition(type, centerX, centerY, centerZ, width, depth, height, flags, rotationDegrees, mirrorV,
mirrorNormalX, mirrorNormalY, mirrorNormalZ);
return g_pGame->GetWorld()->SetCullZone(GetCullZoneId(id), definition, &lua_getownerresource(luaVM));
}

bool EngineSetCullZoneEnabled(lua_State* luaVM, double id, bool enabled)
{
return g_pGame->GetWorld()->SetCullZoneEnabled(GetCullZoneId(id), enabled, &lua_getownerresource(luaVM));
}

bool EngineRemoveCullZone(lua_State* luaVM, double id)
{
return g_pGame->GetWorld()->RemoveCullZone(GetCullZoneId(id), &lua_getownerresource(luaVM));
}

bool EngineRestoreCullZone(lua_State* luaVM, double id)
{
return g_pGame->GetWorld()->RestoreCullZone(GetCullZoneId(id), &lua_getownerresource(luaVM));
}
} // namespace

//! Set the CModelCacheManager limits
//! By passing `nil`/no value the original values are restored
Expand Down Expand Up @@ -104,6 +224,12 @@ void CLuaEngineDefs::LoadFunctions()
{"engineGetModelLODDistance", EngineGetModelLODDistance},
{"engineSetModelLODDistance", EngineSetModelLODDistance},
{"engineResetModelLODDistance", EngineResetModelLODDistance},
{"engineGetCullZones", ArgumentParser<EngineGetCullZones>},
{"engineCreateCullZone", ArgumentParser<EngineCreateCullZone>},
{"engineSetCullZone", ArgumentParser<EngineSetCullZone>},
{"engineSetCullZoneEnabled", ArgumentParser<EngineSetCullZoneEnabled>},
{"engineRemoveCullZone", ArgumentParser<EngineRemoveCullZone>},
{"engineRestoreCullZone", ArgumentParser<EngineRestoreCullZone>},
{"engineSetAsynchronousLoading", EngineSetAsynchronousLoading},
{"engineApplyShaderToWorldTexture", EngineApplyShaderToWorldTexture},
{"engineRemoveShaderFromWorldTexture", EngineRemoveShaderFromWorldTexture},
Expand Down
Loading
Loading