#include "common.h"
#include "patcher.h"
#include "Building.h"
#include "Treadable.h"
#include "Train.h"
#include "Pools.h"
#include "Timer.h"
#include "Camera.h"
#include "World.h"
#include "FileMgr.h"
#include "ZoneCull.h"
#include "Zones.h"
int32 &CCullZones::NumCullZones = *(int*)0x8F2564;
CCullZone(&CCullZones::aZones)[NUMCULLZONES] = *(CCullZone(*)[NUMCULLZONES])*(uintptr*)0x864750;
int32 &CCullZones::NumAttributeZones = *(int*)0x8E29D0;
CAttributeZone (&CCullZones::aAttributeZones)[NUMATTRIBZONES] = *(CAttributeZone(*)[NUMATTRIBZONES])*(uintptr*)0x709C60;
uint16 (&CCullZones::aIndices)[NUMZONEINDICES] = *(uint16(*)[NUMZONEINDICES])*(uintptr*)0x847330;
int16 (&CCullZones::aPointersToBigBuildingsForBuildings)[NUMBUILDINGS] = *(int16(*)[NUMBUILDINGS])*(uintptr*)0x86C9D0;
int16 (&CCullZones::aPointersToBigBuildingsForTreadables)[NUMTREADABLES] = *(int16(*)[NUMTREADABLES])*(uintptr*)0x8F1B8C;
int32 &CCullZones::CurrentWantedLevelDrop_Player = *(int32*)0x880DA8;
int32 &CCullZones::CurrentFlags_Camera = *(int32*)0x940718;
int32 &CCullZones::CurrentFlags_Player = *(int32*)0x9415F0;
int32 &CCullZones::OldCullZone = *(int32*)0x8E2C90;
int32 &CCullZones::EntityIndicesUsed = *(int32*)0x8F2508;
bool &CCullZones::bCurrentSubwayIsInvisible = *(bool*)0x95CDA5;
bool &CCullZones::bCullZonesDisabled = *(bool*)0x95CD4A;
void
CCullZones::Init(void)
{
int i;
NumAttributeZones = 0;
NumCullZones = 0;
CurrentWantedLevelDrop_Player = 0;
CurrentFlags_Camera = 0;
CurrentFlags_Player = 0;
OldCullZone = -1;
EntityIndicesUsed = 0;
bCurrentSubwayIsInvisible = false;
for(i = 0; i < NUMBUILDINGS; i++)
aPointersToBigBuildingsForBuildings[i] = -1;
for(i = 0; i < NUMTREADABLES; i++)
aPointersToBigBuildingsForTreadables[i] = -1;
}
bool CCullZone::TestLine(CVector vec1, CVector vec2)
{
CColPoint colPoint;
CEntity *entity;
if (CWorld::ProcessLineOfSight(vec1, vec2, colPoint, entity, true, false, false, false, false, true, false))
return true;
if (CWorld::ProcessLineOfSight(CVector(vec1.x + 0.05f, vec1.y, vec1.z), CVector(vec2.x + 0.05f, vec2.y, vec2.z), colPoint, entity, true, false, false, false, false, true, false))
return true;
if (CWorld::ProcessLineOfSight(CVector(vec1.x - 0.05f, vec1.y, vec1.z), CVector(vec2.x - 0.05f, vec2.y, vec2.z), colPoint, entity, true, false, false, false, false, true, false))
return true;
if (CWorld::ProcessLineOfSight(CVector(vec1.x, vec1.y + 0.05f, vec1.z), CVector(vec2.x, vec2.y + 0.05f, vec2.z), colPoint, entity, true, false, false, false, false, true, false))
return true;
if (CWorld::ProcessLineOfSight(CVector(vec1.x, vec1.y - 0.05f, vec1.z), CVector(vec2.x, vec2.y - 0.05f, vec2.z), colPoint, entity, true, false, false, false, false, true, false))
return true;
if (CWorld::ProcessLineOfSight(CVector(vec1.x, vec1.y, vec1.z + 0.05f), CVector(vec2.x, vec2.y, vec2.z + 0.05f), colPoint, entity, true, false, false, false, false, true, false))
return true;
return CWorld::ProcessLineOfSight(CVector(vec1.x, vec1.y, vec1.z - 0.05f), CVector(vec2.x, vec2.y, vec2.z - 0.05f), colPoint, entity, true, false, false, false, false, true, false);
}
uint16* pTempArrayIndices;
int TempEntityIndicesUsed;
void
CCullZones::ResolveVisibilities(void)
{
int fd;
CFileMgr::SetDir("");
fd = CFileMgr::OpenFile("DATA\\cullzone.dat", "rb");
if(fd > 0){
CFileMgr::Read(fd, (char*)&NumCullZones, sizeof(NumCullZones));
CFileMgr::Read(fd, (char*)aZones, sizeof(aZones));
CFileMgr::Read(fd, (char*)&NumAttributeZones, sizeof(NumAttributeZones));
CFileMgr::Read(fd, (char*)aAttributeZones, sizeof(aAttributeZones));
CFileMgr::Read(fd, (char*)aIndices, sizeof(aIndices));
CFileMgr::Read(fd, (char*)aPointersToBigBuildingsForBuildings, sizeof(aPointersToBigBuildingsForBuildings));
CFileMgr::Read(fd, (char*)aPointersToBigBuildingsForTreadables, sizeof(aPointersToBigBuildingsForTreadables));
CFileMgr::CloseFile(fd);
}else{
#if 0
// TODO: implement code from mobile to generate data here
EntityIndicesUsed = 0;
BuildListForBigBuildings();
pTempArrayIndices = new uint16[140000];
TempEntityIndicesUsed = 0;
for (int i = 0; i < NumCullZones; i++) {
DoVisibilityTestCullZone(i, true);
}
CompressIndicesArray();
delete[] pTempArrayIndices;
fd = CFileMgr::OpenFileForWriting("data\\cullzone.dat");
if (fd != 0) {
CFileMgr::Write(fd, (char*)&NumCullZones, sizeof(NumCullZones));
CFileMgr::Write(fd, (char*)aZones, sizeof(aZones));
CFileMgr::Write(fd, (char*)&NumAttributeZones, sizeof(NumAttributeZones));
CFileMgr::Write(fd, (char*)&aAttributeZones, sizeof(aAttributeZones));
CFileMgr::Write(fd, (char*)&aIndices, sizeof(aIndices));
CFileMgr::Write(fd, (char*)&aPointersToBigBuildingsForBuildings, sizeof(aPointersToBigBuildingsForBuildings));
CFileMgr::Write(fd, (char*)&aPointersToBigBuildingsForTreadables, sizeof(aPointersToBigBuildingsForTreadables));
CFileMgr::CloseFile(fd);
}
#endif
}
}
void
CCullZones::BuildListForBigBuildings()
{
for (int i = CPools::GetBuildingPool()->GetSize()-1; i >= 0; i--) {
CBuilding *building = CPools::GetBuildingPool()->GetSlot(i);
if (building == nil || !building->bIsBIGBuilding) continue;
CSimpleModelInfo *nonlod = (CSimpleModelInfo*)((CSimpleModelInfo *)CModelInfo::GetModelInfo(building->GetModelIndex()))->m_atomics[2];
if (nonlod == nil) continue;
for (int j = i; j >= 0; j--) {
CBuilding *building2 = CPools::GetBuildingPool()->GetSlot(j);
if (building2 == nil || building2->bIsBIGBuilding) continue;
if (CModelInfo::GetModelInfo(building2->GetModelIndex()) == nonlod) {
if ((building2->GetPosition() - building->GetPosition()).Magnitude() < 5.0f) {
aPointersToBigBuildingsForBuildings[j] = i;
}
}
}
for (int j = CPools::GetTreadablePool()->GetSize()-1; j >= 0; j--) {
CTreadable *treadable = CPools::GetTreadablePool()->GetSlot(j);
if (treadable == nil || treadable->bIsBIGBuilding) continue;
if (CModelInfo::GetModelInfo(treadable->GetModelIndex()) == nonlod) {
if ((treadable->GetPosition() - building->GetPosition()).Magnitude() < 5.0f) {
aPointersToBigBuildingsForTreadables[j] = i;
}
}
}
}
}
void
CCullZones::DoVisibilityTestCullZone(int zoneId, bool doIt)
{
aZones[zoneId].m_groupIndexCount[0] = 0;
aZones[zoneId].m_groupIndexCount[1] = 0;
aZones[zoneId].m_groupIndexCount[2] = 0;
aZones[zoneId].m_indexStart = TempEntityIndicesUsed;
aZones[zoneId].FindTestPoints();
if (!doIt) return;
for (int i = CPools::GetBuildingPool()->GetSize() - 1; i >= 0; i--) {
CBuilding *building = CPools::GetBuildingPool()->GetSlot(i);
if (building != nil && !building->bIsBIGBuilding && aZones[zoneId].IsEntityCloseEnoughToZone(building, aPointersToBigBuildingsForBuildings[i] != -1)) {
CBuilding *building2 = nil;
if (aPointersToBigBuildingsForBuildings[i] != -1)
building2 = CPools::GetBuildingPool()->GetSlot(aPointersToBigBuildingsForBuildings[i]);
if (!aZones[zoneId].TestEntityVisibilityFromCullZone(building, 0.0f, building2)) {
pTempArrayIndices[TempEntityIndicesUsed++] = i;
aZones[zoneId].m_groupIndexCount[0]++;
}
}
}
for (int i = CPools::GetTreadablePool()->GetSize() - 1; i >= 0; i--) {
CTreadable* building = CPools::GetTreadablePool()->GetSlot(i);
if (building != nil && aZones[zoneId].IsEntityCloseEnoughToZone(building, aPointersToBigBuildingsForTreadables[i] != -1)) {
CTreadable* building2 = nil;
if (aPointersToBigBuildingsForTreadables[i] != -1)
building2 = CPools::GetTreadablePool()->GetSlot(aPointersToBigBuildingsForTreadables[i]);
if (!aZones[zoneId].TestEntityVisibilityFromCullZone(building, 10.0f, building2)) {
pTempArrayIndices[TempEntityIndicesUsed++] = i;
aZones[zoneId].m_groupIndexCount[1]++;
}
}
}
for (int i = CPools::GetTreadablePool()->GetSize() - 1; i >= 0; i--) {
CTreadable *building = CPools::GetTreadablePool()->GetSlot(i);
if (building != nil && aZones[zoneId].CalcDistToCullZoneSquared(building->GetPosition().x, building->GetPosition().y) < 40000.0f) {
int start = aZones[zoneId].m_groupIndexCount[0] + aZones[zoneId].m_indexStart;
int end = aZones[zoneId].m_groupIndexCount[1] + start;
bool alreadyAdded = false;
for (int k = start; k < end; k++) {
if (aIndices[k] == i)
alreadyAdded = true;
}
if (!alreadyAdded) {
CBuilding *building2 = nil;
if (aPointersToBigBuildingsForTreadables[i] != -1)
building2 = CPools::GetBuildingPool()->GetSlot(aPointersToBigBuildingsForTreadables[i]);
if (!aZones[zoneId].TestEntityVisibilityFromCullZone(building, 0.0f, building2)) {
pTempArrayIndices[TempEntityIndicesUsed++] = i;
aZones[zoneId].m_groupIndexCount[2]++;
}
}
}
}
}
void
CCullZones::Update(void)
{
bool invisible;
if(bCullZonesDisabled)
return;
switch(CTimer::GetFrameCounter() & 7){
case 0:
case 4:
/* Update Cull zone */
ForceCullZoneCoors(TheCamera.GetGameCamPosition());
break;
case 2:
/* Update camera attributes */
CurrentFlags_Camera = FindAttributesForCoors(TheCamera.GetGameCamPosition(), nil);
invisible = (CurrentFlags_Camera & ATTRZONE_SUBWAYVISIBLE) == 0;
if(invisible != bCurrentSubwayIsInvisible){
MarkSubwayAsInvisible(!invisible);
bCurrentSubwayIsInvisible = invisible;
}
break;
case 6:
/* Update player attributes */
CurrentFlags_Player = FindAttributesForCoors(FindPlayerCoors(),
&CurrentWantedLevelDrop_Player);
break;
}
}
void
CCullZones::ForceCullZoneCoors(CVector coors)
{
int32 z;
z = FindCullZoneForCoors(coors);
if(z != OldCullZone){
if(OldCullZone >= 0)
aZones[OldCullZone].DoStuffLeavingZone();
if(z >= 0)
aZones[z].DoStuffEnteringZone();
OldCullZone = z;
}
}
int32
CCullZones::FindCullZoneForCoors(CVector coors)
{
int i;
for(i = 0; i < NumCullZones; i++)
if(coors.x >= aZones[i].minx && coors.x <= aZones[i].maxx &&
coors.y >= aZones[i].miny && coors.y <= aZones[i].maxy &&
coors.z >= aZones[i].minz && coors.z <= aZones[i].maxz)
return i;
return -1;
}
int32
CCullZones::FindAttributesForCoors(CVector coors, int32 *wantedLevel)
{
int i;
int32 attribs;
if (wantedLevel)
*wantedLevel = 0;
attribs = 0;
for(i = 0; i < NumAttributeZones; i++)
if(coors.x >= aAttributeZones[i].minx && coors.x <= aAttributeZones[i].maxx &&
coors.y >= aAttributeZones[i].miny && coors.y <= aAttributeZones[i].maxy &&
coors.z >= aAttributeZones[i].minz && coors.z <= aAttributeZones[i].maxz){
attribs |= aAttributeZones[i].attributes;
if(wantedLevel)
*wantedLevel = max(*wantedLevel, aAttributeZones[i].wantedLevel);
}
return attribs;
}
CAttributeZone*
CCullZones::FindZoneWithStairsAttributeForPlayer(void)
{
int i;
CVector coors;
coors = FindPlayerCoors();
for(i = 0; i < NumAttributeZones; i++)
if(aAttributeZones[i].attributes & ATTRZONE_STAIRS &&
coors.x >= aAttributeZones[i].minx && coors.x <= aAttributeZones[i].maxx &&
coors.y >= aAttributeZones[i].miny && coors.y <= aAttributeZones[i].maxy &&
coors.z >= aAttributeZones[i].minz && coors.z <= aAttributeZones[i].maxz)
return &aAttributeZones[i];
return nil;
}
void
CCullZones::MarkSubwayAsInvisible(bool visible)
{
int i, n;
CEntity *e;
CVehicle *v;
n = CPools::GetBuildingPool()->GetSize()-1;
for(i = n; i >= 0; i--){
e = CPools::GetBuildingPool()->GetSlot(i);
if(e && e->bIsSubway)
e->bIsVisible = visible;
}
n = CPools::GetTreadablePool()->GetSize()-1;
for(i = n; i >= 0; i--){
e = CPools::GetTreadablePool()->GetSlot(i);
if(e && e->bIsSubway)
e->bIsVisible = visible;
}
n = CPools::GetVehiclePool()->GetSize()-1;
for(i = n; i >= 0; i--){
v = CPools::GetVehiclePool()->GetSlot(i);
if(v && v->IsTrain() && ((CTrain*)v)->m_nTrackId != TRACK_ELTRAIN)
v->bIsVisible = visible;
}
}
void
CCullZones::AddCullZone(CVector const &position,
float minx, float maxx,
float miny, float maxy,
float minz, float maxz,
uint16 flag, int16 wantedLevel)
{
CCullZone *cull;
CAttributeZone *attrib;
CVector v;
if((flag & ATTRZONE_NOTCULLZONE) == 0){
cull = &aZones[NumCullZones++];
v = position;
// WTF is this?
if((v-CVector(1032.14f, -624.255f, 24.93f)).Magnitude() < 1.0f)
v = CVector(1061.7f, -613.0f, 19.0f);
if((v-CVector(1029.48f, -495.757f, 21.98f)).Magnitude() < 1.0f)
v = CVector(1061.4f, -506.0f, 18.5f);
cull->position.x = clamp(v.x, minx, maxx);
cull->position.y = clamp(v.y, miny, maxy);
cull->position.z = clamp(v.z, minz, maxz);
cull->minx = minx;
cull->maxx = maxx;
cull->miny = miny;
cull->maxy = maxy;
cull->minz = minz;
cull->maxz = maxz;
cull->m_groupIndexCount[0] = 0;
cull->m_groupIndexCount[1] = 0;
cull->m_groupIndexCount[2] = 0;
cull->m_indexStart = 0;
}
if(flag & ~ATTRZONE_NOTCULLZONE){
attrib = &aAttributeZones[NumAttributeZones++];
attrib->minx = minx;
attrib->maxx = maxx;
attrib->miny = miny;
attrib->maxy = maxy;
attrib->minz = minz;
attrib->maxz = maxz;
attrib->attributes = flag;
attrib->wantedLevel = wantedLevel;
}
}
void
CCullZone::DoStuffLeavingZone(void)
{
int i;
for(i = 0; i < m_numBuildings; i++)
DoStuffLeavingZone_OneBuilding(CCullZones::aIndices[m_indexStart + i]);
for(; i < m_numBuildings + m_numTreadablesPlus10m + m_numTreadables ; i++)
DoStuffLeavingZone_OneTreadableBoth(CCullZones::aIndices[m_indexStart + i]);
}
void
CCullZone::DoStuffLeavingZone_OneBuilding(uint16 i)
{
int16 bb;
int j;
if(i < 6000){
CPools::GetBuildingPool()->GetSlot(i)->bZoneCulled = false;
bb = CCullZones::aPointersToBigBuildingsForBuildings[i];
if(bb != -1)
CPools::GetBuildingPool()->GetSlot(bb)->bZoneCulled = false;
}else{
i -= 6000;
for(j = 0; j < 3; j++)
DoStuffLeavingZone_OneBuilding(CCullZones::aIndices[i+j]);
}
}
void
CCullZone::DoStuffLeavingZone_OneTreadableBoth(uint16 i)
{
int16 bb;
int j;
if(i < 6000){
CPools::GetTreadablePool()->GetSlot(i)->bZoneCulled = false;
CPools::GetTreadablePool()->GetSlot(i)->bZoneCulled2 = false;
bb = CCullZones::aPointersToBigBuildingsForTreadables[i];
if(bb != -1)
CPools::GetBuildingPool()->GetSlot(bb)->bZoneCulled = false;
}else{
i -= 6000;
for(j = 0; j < 3; j++)
DoStuffLeavingZone_OneTreadableBoth(CCullZones::aIndices[i+j]);
}
}
void
CCullZone::DoStuffEnteringZone(void)
{
int i;
for(i = 0; i < m_numBuildings; i++)
DoStuffEnteringZone_OneBuilding(CCullZones::aIndices[m_indexStart + i]);
for(; i < m_numBuildings + m_numTreadablesPlus10m; i++)
DoStuffEnteringZone_OneTreadablePlus10m(CCullZones::aIndices[m_indexStart + i]);
for(; i < m_numBuildings + m_numTreadablesPlus10m + m_numTreadables; i++)
DoStuffEnteringZone_OneTreadable(CCullZones::aIndices[m_indexStart + i]);
}
void
CCullZone::DoStuffEnteringZone_OneBuilding(uint16 i)
{
int16 bb;
int j;
if(i < 6000){
CPools::GetBuildingPool()->GetSlot(i)->bZoneCulled = true;
bb = CCullZones::aPointersToBigBuildingsForBuildings[i];
if(bb != -1)
CPools::GetBuildingPool()->GetSlot(bb)->bZoneCulled = true;
}else{
i -= 6000;
for(j = 0; j < 3; j++)
DoStuffLeavingZone_OneBuilding(CCullZones::aIndices[i+j]);
}
}
void
CCullZone::DoStuffEnteringZone_OneTreadablePlus10m(uint16 i)
{
int16 bb;
int j;
if(i < 6000){
CPools::GetTreadablePool()->GetSlot(i)->bZoneCulled = true;;
CPools::GetTreadablePool()->GetSlot(i)->bZoneCulled2 = true;;
bb = CCullZones::aPointersToBigBuildingsForTreadables[i];
if(bb != -1)
CPools::GetBuildingPool()->GetSlot(bb)->bZoneCulled = true;
}else{
i -= 6000;
for(j = 0; j < 3; j++)
DoStuffLeavingZone_OneBuilding(CCullZones::aIndices[i+j]);
}
}
void
CCullZone::DoStuffEnteringZone_OneTreadable(uint16 i)
{
int16 bb;
int j;
if(i < 6000){
CPools::GetTreadablePool()->GetSlot(i)->bZoneCulled = true;;
bb = CCullZones::aPointersToBigBuildingsForTreadables[i];
if(bb != -1)
CPools::GetBuildingPool()->GetSlot(bb)->bZoneCulled = true;
}else{
i -= 6000;
for(j = 0; j < 3; j++)
DoStuffLeavingZone_OneBuilding(CCullZones::aIndices[i+j]);
}
}
float
CCullZone::CalcDistToCullZoneSquared(float x, float y)
{
float rx, ry;
float temp;
temp = minx;
if (temp <= x) {
temp = maxx;
if (x <= temp)
rx = 0.0f;
else
rx = sq(x - temp);
} else
rx = sq(x - temp);
temp = miny;
if (temp <= y) {
temp = maxy;
if (y <= temp)
ry = 0.0f;
else
ry = sq(y - temp);
} else
ry = sq(y - temp);
return rx + ry;
}
bool
CCullZone::IsEntityCloseEnoughToZone(CEntity *entity, bool checkLevel)
{
CVector &pos = entity->GetPosition();
CSimpleModelInfo *minfo = (CSimpleModelInfo*)CModelInfo::GetModelInfo(entity->GetModelIndex());
float distToZone = CalcDistToCullZone(pos.x, pos.y);
float lodDist;
if (minfo->m_isSubway)
lodDist = minfo->GetLargestLodDistance() + 30.0f;
else
lodDist = minfo->GetLargestLodDistance() + 50.0f;
if (lodDist > distToZone) return true;
if (!checkLevel) return false;
return CTheZones::GetLevelFromPosition(pos) == CTheZones::GetLevelFromPosition(CVector(minx, miny, minz));
}
bool
CCullZones::DoWeHaveMoreThanXOccurencesOfSet(int32 count, uint16 *set)
{
int32 curCount;
int32 start;
int32 size;
for (int i = 0; i < NumCullZones; i++) {
curCount = 0;
for (int group = 0; group < 3; group++) {
aZones[i].GetGroupStartAndSize(group, start, size);
int unk = 0; // TODO: figure out
for (int j = 0; j < size; j++) {
for (int k = 0; k < 3; k++) {
if (set[k] == pTempArrayIndices[start+j])
unk++;
}
}
if (unk == 3 && ++curCount >= count)
return true;
}
}
return false;
}
STARTPATCHES
InjectHook(0x524BC0, &CCullZones::Init, PATCH_JUMP);
InjectHook(0x524EC0, &CCullZones::ResolveVisibilities, PATCH_JUMP);
InjectHook(0x524F80, &CCullZones::Update, PATCH_JUMP);
InjectHook(0x525370, &CCullZones::AddCullZone, PATCH_JUMP);
InjectHook(0x5250D0, &CCullZones::ForceCullZoneCoors, PATCH_JUMP);
InjectHook(0x525130, &CCullZones::FindCullZoneForCoors, PATCH_JUMP);
InjectHook(0x5251C0, &CCullZones::FindAttributesForCoors, PATCH_JUMP);
InjectHook(0x525290, &CCullZones::FindZoneWithStairsAttributeForPlayer, PATCH_JUMP);
InjectHook(0x525610, &CCullZone::DoStuffLeavingZone, PATCH_JUMP);
InjectHook(0x525810, &CCullZone::DoStuffEnteringZone, PATCH_JUMP);
ENDPATCHES