TombEngine/TombEngine/Specific/level.cpp
2024-12-05 10:20:32 +01:00

1750 lines
43 KiB
C++

#include "framework.h"
#include "Specific/level.h"
#include <process.h>
#include <zlib.h>
#include "Game/animation.h"
#include "Game/animation.h"
#include "Game/control/box.h"
#include "Game/control/control.h"
#include "Game/control/volume.h"
#include "Game/control/lot.h"
#include "Game/items.h"
#include "Game/Lara/lara.h"
#include "Game/Lara/lara_initialise.h"
#include "Game/misc.h"
#include "Game/pickup/pickup.h"
#include "Game/savegame.h"
#include "Game/Setup.h"
#include "Game/spotcam.h"
#include "Objects/Generic/Doors/generic_doors.h"
#include "Objects/Sink.h"
#include "Renderer/Renderer.h"
#include "Scripting/Include/Flow/ScriptInterfaceFlowHandler.h"
#include "Scripting/Include/Objects/ScriptInterfaceObjectsHandler.h"
#include "Scripting/Include/ScriptInterfaceGame.h"
#include "Scripting/Include/ScriptInterfaceLevel.h"
#include "Sound/sound.h"
#include "Specific/Input/Input.h"
#include "Specific/trutils.h"
#include "Specific/winmain.h"
using TEN::Renderer::g_Renderer;
using namespace TEN::Entities::Doors;
using namespace TEN::Input;
using namespace TEN::Utils;
const std::vector<GAME_OBJECT_ID> BRIDGE_OBJECT_IDS =
{
ID_EXPANDING_PLATFORM,
ID_FALLING_BLOCK,
ID_FALLING_BLOCK2,
ID_CRUMBLING_FLOOR,
ID_TRAPDOOR1,
ID_TRAPDOOR2,
ID_TRAPDOOR3,
ID_FLOOR_TRAPDOOR1,
ID_FLOOR_TRAPDOOR2,
ID_CEILING_TRAPDOOR1,
ID_CEILING_TRAPDOOR2,
ID_SCALING_TRAPDOOR,
ID_ONEBLOCK_PLATFORM,
ID_TWOBLOCK_PLATFORM,
ID_RAISING_BLOCK1,
ID_RAISING_BLOCK2,
ID_RAISING_BLOCK3,
ID_RAISING_BLOCK4,
ID_PUSHABLE_OBJECT_CLIMBABLE1,
ID_PUSHABLE_OBJECT_CLIMBABLE2,
ID_PUSHABLE_OBJECT_CLIMBABLE3,
ID_PUSHABLE_OBJECT_CLIMBABLE4,
ID_PUSHABLE_OBJECT_CLIMBABLE5,
ID_PUSHABLE_OBJECT_CLIMBABLE6,
ID_PUSHABLE_OBJECT_CLIMBABLE7,
ID_PUSHABLE_OBJECT_CLIMBABLE8,
ID_PUSHABLE_OBJECT_CLIMBABLE9,
ID_PUSHABLE_OBJECT_CLIMBABLE10,
ID_BRIDGE_FLAT,
ID_BRIDGE_TILT1,
ID_BRIDGE_TILT2,
ID_BRIDGE_TILT3,
ID_BRIDGE_TILT4,
ID_BRIDGE_CUSTOM
};
LEVEL g_Level;
std::vector<int> MoveablesIds;
int* StaticObjectsLUT = nullptr;
int StaticObjectsLUTSize = 0;
std::vector<int> SpriteSequencesIds;
char* DataPtr;
char* CurrentDataPtr;
bool FirstLevel = true;
int SystemNameHash = 0;
int LastLevelHash = 0;
std::filesystem::file_time_type LastLevelTimestamp;
std::string LastLevelFilePath;
unsigned char ReadUInt8()
{
unsigned char value = *(unsigned char*)CurrentDataPtr;
CurrentDataPtr += 1;
return value;
}
short ReadInt16()
{
short value = *(short*)CurrentDataPtr;
CurrentDataPtr += 2;
return value;
}
unsigned short ReadUInt16()
{
unsigned short value = *(unsigned short*)CurrentDataPtr;
CurrentDataPtr += 2;
return value;
}
int ReadInt32()
{
int value = *(int*)CurrentDataPtr;
CurrentDataPtr += 4;
return value;
}
float ReadFloat()
{
float value = *(float*)CurrentDataPtr;
CurrentDataPtr += 4;
return value;
}
Vector2 ReadVector2()
{
Vector2 value;
value.x = ReadFloat();
value.y = ReadFloat();
return value;
}
Vector3 ReadVector3()
{
Vector3 value;
value.x = ReadFloat();
value.y = ReadFloat();
value.z = ReadFloat();
return value;
}
Vector4 ReadVector4()
{
Vector4 value;
value.x = ReadFloat();
value.y = ReadFloat();
value.z = ReadFloat();
value.w = ReadFloat();
return value;
}
bool ReadBool()
{
return bool(ReadUInt8());
}
void ReadBytes(void* dest, int count)
{
memcpy(dest, CurrentDataPtr, count);
CurrentDataPtr += count;
}
long long ReadLEB128(bool sign)
{
long long result = 0;
int currentShift = 0;
unsigned char currentByte;
do
{
currentByte = ReadUInt8();
result |= (long long)(currentByte & 0x7F) << currentShift;
currentShift += 7;
} while ((currentByte & 0x80) != 0);
if (sign) // Sign extend
{
int shift = 64 - currentShift;
if (shift > 0)
result = (long long)(result << shift) >> shift;
}
return result;
}
std::string ReadString()
{
auto byteCount = ReadLEB128(false);
if (byteCount <= 0)
{
return std::string();
}
else
{
auto newPtr = CurrentDataPtr + byteCount;
auto result = std::string(CurrentDataPtr, newPtr);
CurrentDataPtr = newPtr;
return result;
}
}
void LoadItems()
{
g_Level.NumItems = ReadInt32();
TENLog("Moveables: " + std::to_string(g_Level.NumItems), LogLevel::Info);
if (g_Level.NumItems == 0)
return;
InitializeItemArray(g_Level.NumItems + MAX_SPAWNED_ITEM_COUNT);
for (int i = 0; i < g_Level.NumItems; i++)
{
auto* item = &g_Level.Items[i];
item->Data = ItemData{};
item->ObjectNumber = from_underlying(ReadInt16());
item->RoomNumber = ReadInt16();
item->Pose.Position.x = ReadInt32();
item->Pose.Position.y = ReadInt32();
item->Pose.Position.z = ReadInt32();
item->Pose.Orientation.y = ReadInt16();
item->Pose.Orientation.x = ReadInt16();
item->Pose.Orientation.z = ReadInt16();
item->Model.Color = ReadVector4();
item->TriggerFlags = ReadInt16();
item->Flags = ReadInt16();
item->Name = ReadString();
g_GameScriptEntities->AddName(item->Name, (short)i);
g_GameScriptEntities->TryAddColliding((short)i);
memcpy(&item->StartPose, &item->Pose, sizeof(Pose));
}
// Initialize items.
for (int i = 0; i <= 1; i++)
{
// HACK: Initialize bridges first. Required because other items need final floordata to init properly.
if (i == 0)
{
for (int j = 0; j < g_Level.NumItems; j++)
{
const auto& item = g_Level.Items[j];
if (Contains(BRIDGE_OBJECT_IDS, item.ObjectNumber))
InitializeItem(j);
}
}
// Initialize non-bridge items second.
else if (i == 1)
{
for (int j = 0; j < g_Level.NumItems; j++)
{
const auto& item = g_Level.Items[j];
if (!item.IsBridge())
InitializeItem(j);
}
}
}
}
void LoadObjects()
{
Objects.Initialize();
StaticObjects.clear();
if (StaticObjectsLUT != nullptr)
{
delete StaticObjectsLUT;
}
StaticObjectsLUTSize = 1000;
StaticObjectsLUT = (int*)malloc(StaticObjectsLUTSize * sizeof(int));
int meshCount = ReadInt32();
TENLog("Mesh count: " + std::to_string(meshCount), LogLevel::Info);
g_Level.Meshes.reserve(meshCount);
for (int i = 0; i < meshCount; i++)
{
auto mesh = MESH{};
mesh.lightMode = (LightMode)ReadUInt8();
mesh.sphere.Center.x = ReadFloat();
mesh.sphere.Center.y = ReadFloat();
mesh.sphere.Center.z = ReadFloat();
mesh.sphere.Radius = ReadFloat();
int vertexCount = ReadInt32();
mesh.positions.resize(vertexCount);
ReadBytes(mesh.positions.data(), 12 * vertexCount);
mesh.colors.resize(vertexCount);
ReadBytes(mesh.colors.data(), 12 * vertexCount);
mesh.effects.resize(vertexCount);
ReadBytes(mesh.effects.data(), 12 * vertexCount);
mesh.bones.resize(vertexCount);
ReadBytes(mesh.bones.data(), 4 * vertexCount);
int bucketCount = ReadInt32();
mesh.buckets.reserve(bucketCount);
for (int j = 0; j < bucketCount; j++)
{
auto bucket = BUCKET{};
bucket.texture = ReadInt32();
bucket.blendMode = (BlendMode)ReadUInt8();
bucket.animated = ReadBool();
bucket.numQuads = 0;
bucket.numTriangles = 0;
int polyCount = ReadInt32();
bucket.polygons.reserve(polyCount);
for (int k = 0; k < polyCount; k++)
{
auto poly = POLYGON{};
poly.shape = ReadInt32();
poly.animatedSequence = ReadInt32();
poly.animatedFrame = ReadInt32();
poly.shineStrength = ReadFloat();
int count = (poly.shape == 0 ? 4 : 3);
poly.indices.resize(count);
poly.textureCoordinates.resize(count);
poly.normals.resize(count);
poly.tangents.resize(count);
poly.binormals.resize(count);
for (int n = 0; n < count; n++)
poly.indices[n] = ReadInt32();
for (int n = 0; n < count; n++)
poly.textureCoordinates[n] = ReadVector2();
for (int n = 0; n < count; n++)
poly.normals[n] = ReadVector3();
for (int n = 0; n < count; n++)
poly.tangents[n] = ReadVector3();
for (int n = 0; n < count; n++)
poly.binormals[n] = ReadVector3();
bucket.polygons.push_back(poly);
if (poly.shape == 0)
bucket.numQuads++;
else
bucket.numTriangles++;
}
mesh.buckets.push_back(bucket);
}
g_Level.Meshes.push_back(mesh);
}
int animCount = ReadInt32();
TENLog("Animation count: " + std::to_string(animCount), LogLevel::Info);
g_Level.Anims.resize(animCount);
for (int i = 0; i < animCount; i++)
{
auto* anim = &g_Level.Anims[i];
anim->FramePtr = ReadInt32();
anim->Interpolation = ReadInt32();
anim->ActiveState = ReadInt32();
anim->VelocityStart = ReadVector3();
anim->VelocityEnd = ReadVector3();
anim->frameBase = ReadInt32();
anim->frameEnd = ReadInt32();
anim->JumpAnimNum = ReadInt32();
anim->JumpFrameNum = ReadInt32();
anim->NumStateDispatches = ReadInt32();
anim->StateDispatchIndex = ReadInt32();
anim->NumCommands = ReadInt32();
anim->CommandIndex = ReadInt32();
}
int changeCount = ReadInt32();
g_Level.Changes.resize(changeCount);
ReadBytes(g_Level.Changes.data(), sizeof(StateDispatchData) * changeCount);
int rangeCount = ReadInt32();
g_Level.Ranges.resize(rangeCount);
ReadBytes(g_Level.Ranges.data(), sizeof(StateDispatchRangeData) * rangeCount);
int commandCount = ReadInt32();
g_Level.Commands.resize(commandCount);
ReadBytes(g_Level.Commands.data(), sizeof(short) * commandCount);
int boneCount = ReadInt32();
g_Level.Bones.resize(boneCount);
ReadBytes(g_Level.Bones.data(), 4 * boneCount);
int frameCount = ReadInt32();
g_Level.Frames.resize(frameCount);
for (int i = 0; i < frameCount; i++)
{
auto* frame = &g_Level.Frames[i];
frame->BoundingBox.X1 = ReadInt16();
frame->BoundingBox.X2 = ReadInt16();
frame->BoundingBox.Y1 = ReadInt16();
frame->BoundingBox.Y2 = ReadInt16();
frame->BoundingBox.Z1 = ReadInt16();
frame->BoundingBox.Z2 = ReadInt16();
// NOTE: Braces are necessary to ensure correct value init order.
frame->Offset = Vector3{ (float)ReadInt16(), (float)ReadInt16(), (float)ReadInt16() };
int angleCount = ReadInt16();
frame->BoneOrientations.resize(angleCount);
for (int j = 0; j < angleCount; j++)
{
auto* q = &frame->BoneOrientations[j];
q->x = ReadFloat();
q->y = ReadFloat();
q->z = ReadFloat();
q->w = ReadFloat();
}
}
int modelCount = ReadInt32();
TENLog("Model count: " + std::to_string(modelCount), LogLevel::Info);
for (int i = 0; i < modelCount; i++)
{
int objNum = ReadInt32();
MoveablesIds.push_back(objNum);
Objects[objNum].loaded = true;
Objects[objNum].nmeshes = ReadInt32();
Objects[objNum].meshIndex = ReadInt32();
Objects[objNum].boneIndex = ReadInt32();
Objects[objNum].frameBase = ReadInt32();
Objects[objNum].animIndex = ReadInt32();
}
TENLog("Initializing objects...", LogLevel::Info);
InitializeObjects();
int staticCount = ReadInt32();
TENLog("Statics: " + std::to_string(staticCount), LogLevel::Info);
for (int i = 0; i < staticCount; i++)
{
auto staticObj = StaticInfo{};
staticObj.ObjectNumber = ReadInt32();
staticObj.meshNumber = ReadInt32();
staticObj.visibilityBox.X1 = ReadInt16();
staticObj.visibilityBox.X2 = ReadInt16();
staticObj.visibilityBox.Y1 = ReadInt16();
staticObj.visibilityBox.Y2 = ReadInt16();
staticObj.visibilityBox.Z1 = ReadInt16();
staticObj.visibilityBox.Z2 = ReadInt16();
staticObj.collisionBox.X1 = ReadInt16();
staticObj.collisionBox.X2 = ReadInt16();
staticObj.collisionBox.Y1 = ReadInt16();
staticObj.collisionBox.Y2 = ReadInt16();
staticObj.collisionBox.Z1 = ReadInt16();
staticObj.collisionBox.Z2 = ReadInt16();
staticObj.flags = ReadInt16();
staticObj.shatterType = (ShatterType)ReadInt16();
staticObj.shatterSound = ReadInt16();
if (staticObj.ObjectNumber >= StaticObjectsLUTSize)
{
int* LUT = (int*)malloc(staticObj.ObjectNumber * sizeof(int));
memcpy(LUT, StaticObjectsLUT, StaticObjectsLUTSize * sizeof(int));
delete StaticObjectsLUT;
StaticObjectsLUT = LUT;
StaticObjectsLUTSize = staticObj.ObjectNumber;
}
StaticObjectsLUT[staticObj.ObjectNumber] = (int)StaticObjects.size();
StaticObjects.push_back(staticObj);
}
}
void LoadCameras()
{
int cameraCount = ReadInt32();
TENLog("Camera count: " + std::to_string(cameraCount), LogLevel::Info);
g_Level.Cameras.reserve(cameraCount);
for (int i = 0; i < cameraCount; i++)
{
auto& camera = g_Level.Cameras.emplace_back();
camera.Index = i;
camera.Position.x = ReadInt32();
camera.Position.y = ReadInt32();
camera.Position.z = ReadInt32();
camera.RoomNumber = ReadInt32();
camera.Flags = ReadInt32();
camera.Speed = ReadInt32();
camera.Name = ReadString();
g_GameScriptEntities->AddName(camera.Name, camera);
}
NumberSpotcams = ReadInt32();
// TODO: Read properly!
if (NumberSpotcams != 0)
ReadBytes(SpotCam, NumberSpotcams * sizeof(SPOTCAM));
int sinkCount = ReadInt32();
TENLog("Sink count: " + std::to_string(sinkCount), LogLevel::Info);
g_Level.Sinks.reserve(sinkCount);
for (int i = 0; i < sinkCount; i++)
{
auto& sink = g_Level.Sinks.emplace_back();
sink.Position.x = ReadInt32();
sink.Position.y = ReadInt32();
sink.Position.z = ReadInt32();
sink.Strength = ReadInt32();
sink.BoxIndex = ReadInt32();
sink.Name = ReadString();
g_GameScriptEntities->AddName(sink.Name, sink);
}
}
void LoadTextures()
{
TENLog("Loading textures... ", LogLevel::Info);
int size;
int textureCount = ReadInt32();
TENLog("Room texture count: " + std::to_string(textureCount), LogLevel::Info);
g_Level.RoomTextures.reserve(textureCount);
for (int i = 0; i < textureCount; i++)
{
auto texture = TEXTURE{};
texture.width = ReadInt32();
texture.height = ReadInt32();
size = ReadInt32();
texture.colorMapData.resize(size);
ReadBytes(texture.colorMapData.data(), size);
bool hasNormalMap = ReadBool();
if (hasNormalMap)
{
size = ReadInt32();
texture.normalMapData.resize(size);
ReadBytes(texture.normalMapData.data(), size);
}
g_Level.RoomTextures.push_back(texture);
}
textureCount = ReadInt32();
TENLog("Object texture count: " + std::to_string(textureCount), LogLevel::Info);
g_Level.MoveablesTextures.reserve(textureCount);
for (int i = 0; i < textureCount; i++)
{
auto texture = TEXTURE{};
texture.width = ReadInt32();
texture.height = ReadInt32();
size = ReadInt32();
texture.colorMapData.resize(size);
ReadBytes(texture.colorMapData.data(), size);
bool hasNormalMap = ReadBool();
if (hasNormalMap)
{
size = ReadInt32();
texture.normalMapData.resize(size);
ReadBytes(texture.normalMapData.data(), size);
}
g_Level.MoveablesTextures.push_back(texture);
}
textureCount = ReadInt32();
TENLog("Static texture count: " + std::to_string(textureCount), LogLevel::Info);
g_Level.StaticsTextures.reserve(textureCount);
for (int i = 0; i < textureCount; i++)
{
auto texture = TEXTURE{};
texture.width = ReadInt32();
texture.height = ReadInt32();
size = ReadInt32();
texture.colorMapData.resize(size);
ReadBytes(texture.colorMapData.data(), size);
bool hasNormalMap = ReadBool();
if (hasNormalMap)
{
size = ReadInt32();
texture.normalMapData.resize(size);
ReadBytes(texture.normalMapData.data(), size);
}
g_Level.StaticsTextures.push_back(texture);
}
textureCount = ReadInt32();
TENLog("Anim texture count: " + std::to_string(textureCount), LogLevel::Info);
g_Level.AnimatedTextures.reserve(textureCount);
for (int i = 0; i < textureCount; i++)
{
auto texture = TEXTURE{};
texture.width = ReadInt32();
texture.height = ReadInt32();
size = ReadInt32();
texture.colorMapData.resize(size);
ReadBytes(texture.colorMapData.data(), size);
bool hasNormalMap = ReadBool();
if (hasNormalMap)
{
size = ReadInt32();
texture.normalMapData.resize(size);
ReadBytes(texture.normalMapData.data(), size);
}
g_Level.AnimatedTextures.push_back(texture);
}
textureCount = ReadInt32();
TENLog("Sprite texture count: " + std::to_string(textureCount), LogLevel::Info);
g_Level.SpritesTextures.reserve(textureCount);
for (int i = 0; i < textureCount; i++)
{
auto texture = TEXTURE{};
texture.width = ReadInt32();
texture.height = ReadInt32();
size = ReadInt32();
texture.colorMapData.resize(size);
ReadBytes(texture.colorMapData.data(), size);
g_Level.SpritesTextures.push_back(texture);
}
g_Level.SkyTexture.width = ReadInt32();
g_Level.SkyTexture.height = ReadInt32();
size = ReadInt32();
g_Level.SkyTexture.colorMapData.resize(size);
ReadBytes(g_Level.SkyTexture.colorMapData.data(), size);
}
// The way floordata "planes" were previously stored was non-standard.
// Instead of a Plane object with a normal + distance,
// they used a Vector3 object with data laid out as follows:
// x: X tilt grade (0.25f = 1/4 block).
// y: Z tilt grade (0.25f = 1/4 block).
// z: Plane's absolute height at the sector's center (i.e. distance in regular plane terms).
static Plane ConvertFakePlaneToPlane(const Vector3& fakePlane, bool isFloor)
{
// Calculate normal from tilt grades.
int sign = isFloor ? -1 : 1;
auto normal = Vector3(-fakePlane.x, 1.0f, -fakePlane.y) * sign;
normal.Normalize();
// Determine distance.
float dist = fakePlane.z;
// Return plane.
return Plane(normal, dist);
}
void LoadDynamicRoomData()
{
int roomCount = ReadInt32();
if (g_Level.Rooms.size() != roomCount)
throw std::exception("Dynamic room data count is inconsistent with room count.");
for (int i = 0; i < roomCount; i++)
{
auto& room = g_Level.Rooms[i];
room.Name = ReadString();
int tagCount = ReadInt32();
room.Tags.resize(0);
room.Tags.reserve(tagCount);
for (int j = 0; j < tagCount; j++)
room.Tags.push_back(ReadString());
room.ambient = ReadVector3();
room.flippedRoom = ReadInt32();
room.flags = ReadInt32();
room.meshEffect = ReadInt32();
room.reverbType = (ReverbType)ReadInt32();
room.flipNumber = ReadInt32();
int staticCount = ReadInt32();
room.mesh.resize(0);
room.mesh.reserve(staticCount);
for (int j = 0; j < staticCount; j++)
{
auto& mesh = room.mesh.emplace_back();
mesh.roomNumber = i;
mesh.pos.Position.x = ReadInt32();
mesh.pos.Position.y = ReadInt32();
mesh.pos.Position.z = ReadInt32();
mesh.pos.Orientation.y = ReadUInt16();
mesh.pos.Orientation.x = ReadUInt16();
mesh.pos.Orientation.z = ReadUInt16();
mesh.scale = ReadFloat();
mesh.flags = ReadUInt16();
mesh.color = ReadVector4();
mesh.staticNumber = ReadUInt16();
mesh.HitPoints = ReadInt16();
mesh.Name = ReadString();
g_GameScriptEntities->AddName(mesh.Name, mesh);
}
int triggerVolumeCount = ReadInt32();
room.TriggerVolumes.resize(0);
room.TriggerVolumes.reserve(triggerVolumeCount);
for (int j = 0; j < triggerVolumeCount; j++)
{
auto& volume = room.TriggerVolumes.emplace_back();
volume.Type = (VolumeType)ReadInt32();
auto pos = ReadVector3();
auto orient = ReadVector4();
auto scale = ReadVector3();
volume.Enabled = ReadBool();
volume.DetectInAdjacentRooms = ReadBool();
volume.Name = ReadString();
volume.EventSetIndex = ReadInt32();
volume.Box = BoundingOrientedBox(pos, scale, orient);
volume.Sphere = BoundingSphere(pos, scale.x);
volume.StateQueue.reserve(VOLUME_STATE_QUEUE_SIZE);
g_GameScriptEntities->AddName(volume.Name, volume);
}
g_GameScriptEntities->AddName(room.Name, room);
room.itemNumber = NO_VALUE;
room.fxNumber = NO_VALUE;
}
}
void LoadStaticRoomData()
{
constexpr auto ILLEGAL_FLOOR_SLOPE_ANGLE = ANGLE(36.0f);
constexpr auto ILLEGAL_CEILING_SLOPE_ANGLE = ANGLE(45.0f);
int roomCount = ReadInt32();
TENLog("Room count: " + std::to_string(roomCount), LogLevel::Info);
g_Level.Rooms.reserve(roomCount);
for (int i = 0; i < roomCount; i++)
{
auto& room = g_Level.Rooms.emplace_back();
room.Position.x = ReadInt32();
room.Position.y = 0;
room.Position.z = ReadInt32();
room.BottomHeight = ReadInt32();
room.TopHeight = ReadInt32();
int vertexCount = ReadInt32();
room.positions.reserve(vertexCount);
for (int j = 0; j < vertexCount; j++)
room.positions.push_back(ReadVector3());
room.colors.reserve(vertexCount);
for (int j = 0; j < vertexCount; j++)
room.colors.push_back(ReadVector3());
room.effects.reserve(vertexCount);
for (int j = 0; j < vertexCount; j++)
room.effects.push_back(ReadVector3());
int bucketCount = ReadInt32();
room.buckets.reserve(bucketCount);
for (int j = 0; j < bucketCount; j++)
{
auto bucket = BUCKET{};
bucket.texture = ReadInt32();
bucket.blendMode = (BlendMode)ReadUInt8();
bucket.animated = ReadBool();
bucket.numQuads = 0;
bucket.numTriangles = 0;
int polyCount = ReadInt32();
bucket.polygons.reserve(polyCount);
for (int k = 0; k < polyCount; k++)
{
auto poly = POLYGON{};
poly.shape = ReadInt32();
poly.animatedSequence = ReadInt32();
poly.animatedFrame = ReadInt32();
int count = (poly.shape == 0 ? 4 : 3);
poly.indices.resize(count);
poly.textureCoordinates.resize(count);
poly.normals.resize(count);
poly.tangents.resize(count);
poly.binormals.resize(count);
for (int l = 0; l < count; l++)
poly.indices[l] = ReadInt32();
for (int n = 0; n < count; n++)
poly.textureCoordinates[n] = ReadVector2();
for (int n = 0; n < count; n++)
poly.normals[n] = ReadVector3();
for (int n = 0; n < count; n++)
poly.tangents[n] = ReadVector3();
for (int n = 0; n < count; n++)
poly.binormals[n] = ReadVector3();
bucket.polygons.push_back(poly);
(poly.shape == 0) ? bucket.numQuads++ : bucket.numTriangles++;
}
room.buckets.push_back(bucket);
}
int portalCount = ReadInt32();
for (int j = 0; j < portalCount; j++)
LoadPortal(room);
room.ZSize = ReadInt32();
room.XSize = ReadInt32();
auto roomPos = Vector2i(room.Position.x, room.Position.z);
room.Sectors.reserve(room.XSize * room.ZSize);
for (int x = 0; x < room.XSize; x++)
{
for (int z = 0; z < room.ZSize; z++)
{
auto sector = FloorInfo{};
sector.Position = roomPos + Vector2i(BLOCK(x), BLOCK(z));
sector.RoomNumber = i;
sector.TriggerIndex = ReadInt32();
sector.PathfindingBoxID = ReadInt32();
sector.FloorSurface.Triangles[0].Material =
sector.FloorSurface.Triangles[1].Material =
sector.CeilingSurface.Triangles[0].Material =
sector.CeilingSurface.Triangles[1].Material = (MaterialType)ReadInt32();
sector.Stopper = (bool)ReadInt32();
sector.FloorSurface.SplitAngle = FROM_RAD(ReadFloat());
sector.FloorSurface.Triangles[0].SteepSlopeAngle = ILLEGAL_FLOOR_SLOPE_ANGLE;
sector.FloorSurface.Triangles[1].SteepSlopeAngle = ILLEGAL_FLOOR_SLOPE_ANGLE;
sector.FloorSurface.Triangles[0].PortalRoomNumber = ReadInt32();
sector.FloorSurface.Triangles[1].PortalRoomNumber = ReadInt32();
sector.FloorSurface.Triangles[0].Plane = ConvertFakePlaneToPlane(ReadVector3(), true);
sector.FloorSurface.Triangles[1].Plane = ConvertFakePlaneToPlane(ReadVector3(), true);
sector.CeilingSurface.SplitAngle = FROM_RAD(ReadFloat());
sector.CeilingSurface.Triangles[0].SteepSlopeAngle = ILLEGAL_CEILING_SLOPE_ANGLE;
sector.CeilingSurface.Triangles[1].SteepSlopeAngle = ILLEGAL_CEILING_SLOPE_ANGLE;
sector.CeilingSurface.Triangles[0].PortalRoomNumber = ReadInt32();
sector.CeilingSurface.Triangles[1].PortalRoomNumber = ReadInt32();
sector.CeilingSurface.Triangles[0].Plane = ConvertFakePlaneToPlane(ReadVector3(), false);
sector.CeilingSurface.Triangles[1].Plane = ConvertFakePlaneToPlane(ReadVector3(), false);
sector.SidePortalRoomNumber = ReadInt32();
sector.Flags.Death = ReadBool();
sector.Flags.Monkeyswing = ReadBool();
sector.Flags.ClimbNorth = ReadBool();
sector.Flags.ClimbSouth = ReadBool();
sector.Flags.ClimbEast = ReadBool();
sector.Flags.ClimbWest = ReadBool();
sector.Flags.MarkTriggerer = ReadBool();
sector.Flags.MarkTriggererActive = 0; // TODO: Needs to be written to and read from savegames.
sector.Flags.MarkBeetle = ReadBool();
room.Sectors.push_back(sector);
}
}
int lightCount = ReadInt32();
room.lights.reserve(lightCount);
for (int j = 0; j < lightCount; j++)
{
auto light = ROOM_LIGHT{};
light.x = ReadInt32();
light.y = ReadInt32();
light.z = ReadInt32();
light.dx = ReadFloat();
light.dy = ReadFloat();
light.dz = ReadFloat();
light.r = ReadFloat();
light.g = ReadFloat();
light.b = ReadFloat();
light.intensity = ReadFloat();
light.in = ReadFloat();
light.out = ReadFloat();
light.length = ReadFloat();
light.cutoff = ReadFloat();
light.type = ReadUInt8();
light.castShadows = ReadBool();
room.lights.push_back(light);
}
room.RoomNumber = i;
}
}
void LoadRooms()
{
TENLog("Loading rooms... ", LogLevel::Info);
Wibble = 0;
LoadStaticRoomData();
BuildOutsideRoomsTable();
int floordataCount = ReadInt32();
g_Level.FloorData.resize(floordataCount);
ReadBytes(g_Level.FloorData.data(), floordataCount * sizeof(short));
}
void FreeLevel(bool partial)
{
if (FirstLevel)
{
FirstLevel = false;
return;
}
// Should happen before resetting items.
if (partial)
ResetRoomData();
g_Level.Items.resize(0);
g_Level.AIObjects.resize(0);
g_Level.Cameras.resize(0);
g_Level.Sinks.resize(0);
g_Level.SoundSources.resize(0);
g_Level.VolumeEventSets.resize(0);
g_Level.GlobalEventSets.resize(0);
g_Level.LoopedEventSetIndices.resize(0);
g_GameScript->FreeLevelScripts();
g_GameScriptEntities->FreeEntities();
if (partial)
return;
g_Renderer.FreeRendererData();
MoveablesIds.resize(0);
SpriteSequencesIds.resize(0);
g_Level.RoomTextures.resize(0);
g_Level.MoveablesTextures.resize(0);
g_Level.StaticsTextures.resize(0);
g_Level.AnimatedTextures.resize(0);
g_Level.SpritesTextures.resize(0);
g_Level.AnimatedTexturesSequences.resize(0);
g_Level.Rooms.resize(0);
g_Level.Bones.resize(0);
g_Level.Meshes.resize(0);
g_Level.PathfindingBoxes.resize(0);
g_Level.Overlaps.resize(0);
g_Level.Anims.resize(0);
g_Level.Changes.resize(0);
g_Level.Ranges.resize(0);
g_Level.Commands.resize(0);
g_Level.Frames.resize(0);
g_Level.Sprites.resize(0);
g_Level.SoundDetails.resize(0);
g_Level.SoundMap.resize(0);
g_Level.FloorData.resize(0);
for (int i = 0; i < 2; i++)
{
for (int j = 0; j < (int)ZoneType::MaxZone; j++)
g_Level.Zones[j][i].clear();
}
FreeSamples();
}
size_t ReadFileEx(void* ptr, size_t size, size_t count, FILE* stream)
{
_lock_file(stream);
size_t result = fread(ptr, size, count, stream);
_unlock_file(stream);
return result;
}
void LoadSoundSources()
{
int soundSourceCount = ReadInt32();
TENLog("Sound source count: " + std::to_string(soundSourceCount), LogLevel::Info);
g_Level.SoundSources.reserve(soundSourceCount);
for (int i = 0; i < soundSourceCount; i++)
{
auto& source = g_Level.SoundSources.emplace_back(SoundSourceInfo{});
source.Position.x = ReadInt32();
source.Position.y = ReadInt32();
source.Position.z = ReadInt32();
source.SoundID = ReadInt32();
source.Flags = ReadInt32();
source.Name = ReadString();
g_GameScriptEntities->AddName(source.Name, source);
}
}
void LoadAnimatedTextures()
{
int animatedTextureCount = ReadInt32();
TENLog("Anim texture count: " + std::to_string(animatedTextureCount), LogLevel::Info);
for (int i = 0; i < animatedTextureCount; i++)
{
auto sequence = ANIMATED_TEXTURES_SEQUENCE{};
sequence.atlas = ReadInt32();
sequence.Fps = ReadInt32();
sequence.numFrames = ReadInt32();
for (int j = 0; j < sequence.numFrames; j++)
{
auto frame = ANIMATED_TEXTURES_FRAME{};
frame.x1 = ReadFloat();
frame.y1 = ReadFloat();
frame.x2 = ReadFloat();
frame.y2 = ReadFloat();
frame.x3 = ReadFloat();
frame.y3 = ReadFloat();
frame.x4 = ReadFloat();
frame.y4 = ReadFloat();
sequence.frames.push_back(frame);
}
g_Level.AnimatedTexturesSequences.push_back(sequence);
}
}
void LoadAIObjects()
{
int aiObjectCount = ReadInt32();
TENLog("AI object count: " + std::to_string(aiObjectCount), LogLevel::Info);
g_Level.AIObjects.reserve(aiObjectCount);
for (int i = 0; i < aiObjectCount; i++)
{
auto& obj = g_Level.AIObjects.emplace_back();
obj.objectNumber = (GAME_OBJECT_ID)ReadInt16();
obj.roomNumber = ReadInt16();
obj.pos.Position.x = ReadInt32();
obj.pos.Position.y = ReadInt32();
obj.pos.Position.z = ReadInt32();
obj.pos.Orientation.y = ReadInt16();
obj.pos.Orientation.x = ReadInt16();
obj.pos.Orientation.z = ReadInt16();
obj.triggerFlags = ReadInt16();
obj.flags = ReadInt16();
obj.boxNumber = ReadInt32();
obj.Name = ReadString();
g_GameScriptEntities->AddName(obj.Name, obj);
}
}
void LoadEvent(EventSet& eventSet)
{
int eventType = ReadInt32();
if (eventType >= (int)EventType::Count)
{
TENLog("Unknown event type detected for event set " + eventSet.Name + ". Fall back to default.", LogLevel::Warning);
eventType = (int)EventType::Enter;
}
auto& evt = eventSet.Events[eventType];
evt.Mode = (EventMode)ReadInt32();
evt.Function = ReadString();
evt.Data = ReadString();
evt.CallCounter = ReadInt32();
evt.Enabled = ReadBool();
}
void LoadEventSets()
{
int eventSetCount = ReadInt32();
if (eventSetCount == 0)
return;
int globalEventSetCount = ReadInt32();
TENLog("Global event set count: " + std::to_string(globalEventSetCount), LogLevel::Info);
for (int i = 0; i < globalEventSetCount; i++)
{
auto eventSet = EventSet();
eventSet.Name = ReadString();
int eventCount = ReadInt32();
for (int j = 0; j < eventCount; j++)
LoadEvent(eventSet);
g_Level.GlobalEventSets.push_back(eventSet);
if (!eventSet.Events[(int)EventType::Loop].Function.empty())
g_Level.LoopedEventSetIndices.push_back(i);
}
int volumeEventSetCount = ReadInt32();
TENLog("Volume event set count: " + std::to_string(volumeEventSetCount), LogLevel::Info);
for (int i = 0; i < volumeEventSetCount; i++)
{
auto eventSet = EventSet();
eventSet.Name = ReadString();
eventSet.Activators = (ActivatorFlags)ReadInt32();
int eventCount = ReadInt32();
for (int j = 0; j < eventCount; j++)
LoadEvent(eventSet);
g_Level.VolumeEventSets.push_back(eventSet);
}
}
FILE* FileOpen(const char* fileName)
{
FILE* ptr = fopen(fileName, "rb");
return ptr;
}
void FileClose(FILE* ptr)
{
fclose(ptr);
}
bool Decompress(byte* dest, byte* src, unsigned long compressedSize, unsigned long uncompressedSize)
{
z_stream strm;
ZeroMemory(&strm, sizeof(z_stream));
strm.avail_in = compressedSize;
strm.avail_out = uncompressedSize;
strm.next_out = (BYTE*)dest;
strm.next_in = (BYTE*)src;
inflateInit(&strm);
inflate(&strm, Z_FULL_FLUSH);
if (strm.total_out == uncompressedSize)
{
inflateEnd(&strm);
return true;
}
return false;
}
long GetRemainingSize(FILE* filePtr)
{
long current_position = ftell(filePtr);
if (fseek(filePtr, 0, SEEK_END) != 0)
return NO_VALUE;
long size = ftell(filePtr);
if (fseek(filePtr, current_position, SEEK_SET) != 0)
return NO_VALUE;
return size;
}
bool ReadCompressedBlock(FILE* filePtr, bool skip)
{
int compressedSize = 0;
int uncompressedSize = 0;
ReadFileEx(&uncompressedSize, 1, 4, filePtr);
ReadFileEx(&compressedSize, 1, 4, filePtr);
// Safeguard against changed file format.
long remainingSize = GetRemainingSize(filePtr);
if (uncompressedSize <= 0 || compressedSize <= 0 || compressedSize > remainingSize)
throw std::exception{ "Data block size is incorrect. Probably old level version?" };
if (skip)
{
fseek(filePtr, compressedSize, SEEK_CUR);
return false;
}
auto compressedBuffer = (char*)malloc(compressedSize);
ReadFileEx(compressedBuffer, compressedSize, 1, filePtr);
DataPtr = (char*)malloc(uncompressedSize);
Decompress((byte*)DataPtr, (byte*)compressedBuffer, compressedSize, uncompressedSize);
free(compressedBuffer);
CurrentDataPtr = DataPtr;
return true;
}
void FinalizeBlock()
{
if (DataPtr == nullptr)
return;
free(DataPtr);
DataPtr = nullptr;
CurrentDataPtr = nullptr;
}
void UpdateProgress(float progress, bool skip = false)
{
if (skip)
return;
g_Renderer.UpdateProgress(progress);
}
bool LoadLevel(const std::string& path, bool partial)
{
FILE* filePtr = nullptr;
bool loadedSuccessfully = false;
try
{
filePtr = FileOpen(path.c_str());
if (!filePtr)
throw std::exception{ (std::string{ "Unable to read level file: " } + path).c_str() };
char header[4];
unsigned char version[4];
int systemHash = 0;
int levelHash = 0;
// Read file header
ReadFileEx(&header, 1, 4, filePtr);
ReadFileEx(&version, 1, 4, filePtr);
ReadFileEx(&systemHash, 1, 4, filePtr);
ReadFileEx(&levelHash, 1, 4, filePtr);
// Check file header.
if (std::string(header) != "TEN")
throw std::invalid_argument("Level file header is not valid! Must be TEN. Probably old level version?");
// Check level file integrity to allow or disallow fast reload.
if (partial && levelHash != LastLevelHash)
{
TENLog("Level file has changed since the last load; fast reload is not possible.", LogLevel::Warning);
partial = false;
FreeLevel(false); // Erase all precached data.
}
// Store information about last loaded level file.
LastLevelFilePath = path;
LastLevelHash = levelHash;
LastLevelTimestamp = std::filesystem::last_write_time(path);
TENLog("Level compiler version: " + std::to_string(version[0]) + "." + std::to_string(version[1]) + "." + std::to_string(version[2]), LogLevel::Info);
// Check if level version is higher than engine version
auto assemblyVersion = TEN::Utils::GetProductOrFileVersion(true);
for (int i = 0; i < assemblyVersion.size(); i++)
{
if (assemblyVersion[i] < version[i])
{
TENLog("Level version is different from TEN version.", LogLevel::Warning);
break;
}
}
// Check system name hash and reset it if it's valid (because we use build & play feature only once)
if (SystemNameHash != 0)
{
if (SystemNameHash != systemHash)
throw std::exception("An attempt was made to use level debug feature on a different system.");
InitializeGame = true;
SystemNameHash = 0;
}
if (partial)
{
TENLog("Loading same level. Skipping media and geometry data.", LogLevel::Info);
SetScreenFadeOut(FADE_SCREEN_SPEED * 2, true);
}
else
{
SetScreenFadeIn(FADE_SCREEN_SPEED, true);
}
UpdateProgress(0);
// Media block
if (ReadCompressedBlock(filePtr, partial))
{
LoadTextures();
UpdateProgress(30);
LoadSamples();
UpdateProgress(40);
FinalizeBlock();
}
// Geometry block
if (ReadCompressedBlock(filePtr, partial))
{
LoadRooms();
UpdateProgress(50);
LoadObjects();
UpdateProgress(60);
LoadSprites();
LoadBoxes();
LoadAnimatedTextures();
UpdateProgress(70);
FinalizeBlock();
}
// Dynamic data block
if (ReadCompressedBlock(filePtr, false))
{
LoadDynamicRoomData();
LoadItems();
LoadAIObjects();
LoadCameras();
LoadSoundSources();
LoadEventSets();
UpdateProgress(80, partial);
FinalizeBlock();
}
TENLog("Initializing level...", LogLevel::Info);
// Initialize game.
InitializeGameFlags();
InitializeLara(!InitializeGame && CurrentLevel > 0);
InitializeNeighborRoomList();
GetCarriedItems();
GetAIPickups();
g_GameScriptEntities->AssignLara();
UpdateProgress(90, partial);
if (!partial)
{
g_Renderer.PrepareDataForTheRenderer();
SetScreenFadeOut(FADE_SCREEN_SPEED, true);
StopSoundTracks(SOUND_XFADETIME_BGM_START);
}
else
{
SetScreenFadeIn(FADE_SCREEN_SPEED, true);
StopSoundTracks(SOUND_XFADETIME_LEVELJUMP);
}
UpdateProgress(100, partial);
// MIRROR: remove after TE changes
g_Level.Mirrors.clear();
MirrorInfo mirror;
mirror.RoomNumber = 7;
mirror.MirrorPlane = Plane(Vector3(0, 0, 30000), Vector3(0, 0, -1));
g_Level.Mirrors.push_back(mirror);
mirror.MirrorPlane = Plane(Vector3(0, -2048, 0), Vector3(0,-1, 0));
g_Level.Mirrors.push_back(mirror);
TENLog("Level loading complete.", LogLevel::Info);
loadedSuccessfully = true;
}
catch (std::exception& ex)
{
FinalizeBlock();
StopSoundTracks(SOUND_XFADETIME_LEVELJUMP);
TENLog("Error while loading level: " + std::string(ex.what()), LogLevel::Error);
loadedSuccessfully = false;
SystemNameHash = 0;
}
// Now the entire level is decompressed, we can close it
FileClose(filePtr);
filePtr = nullptr;
return loadedSuccessfully;
}
void LoadSamples()
{
TENLog("Loading samples... ", LogLevel::Info);
int soundMapSize = ReadInt16();
TENLog("Sound map size: " + std::to_string(soundMapSize), LogLevel::Info);
g_Level.SoundMap.resize(soundMapSize);
ReadBytes(g_Level.SoundMap.data(), soundMapSize * sizeof(short));
int sampleInfoCount = ReadInt32();
if (!sampleInfoCount)
{
TENLog("No samples were found or loaded.", LogLevel::Warning);
return;
}
TENLog("Sample info count: " + std::to_string(sampleInfoCount), LogLevel::Info);
g_Level.SoundDetails.resize(sampleInfoCount);
ReadBytes(g_Level.SoundDetails.data(), sampleInfoCount * sizeof(SampleInfo));
int sampleCount = ReadInt32();
if (sampleCount <= 0)
return;
TENLog("Sample count: " + std::to_string(sampleCount), LogLevel::Info);
int uncompressedSize = 0;
int compressedSize = 0;
char* buffer = (char*)malloc(2 * 1024 * 1024);
for (int i = 0; i < sampleCount; i++)
{
uncompressedSize = ReadInt32();
compressedSize = ReadInt32();
ReadBytes(buffer, compressedSize);
LoadSample(buffer, compressedSize, uncompressedSize, i);
}
free(buffer);
}
void LoadBoxes()
{
// Read boxes
int boxCount = ReadInt32();
TENLog("Box count: " + std::to_string(boxCount), LogLevel::Info);
g_Level.PathfindingBoxes.resize(boxCount);
ReadBytes(g_Level.PathfindingBoxes.data(), boxCount * sizeof(BOX_INFO));
// Read overlaps
int overlapCount = ReadInt32();
TENLog("Overlap count: " + std::to_string(overlapCount), LogLevel::Info);
g_Level.Overlaps.resize(overlapCount);
ReadBytes(g_Level.Overlaps.data(), overlapCount * sizeof(OVERLAP));
// Read zones
int zoneGroupCount = ReadInt32();
TENLog("Zone group count: " + std::to_string(zoneGroupCount), LogLevel::Info);
for (int i = 0; i < 2; i++)
{
for (int j = 0; j < zoneGroupCount; j++)
{
if (j >= (int)ZoneType::MaxZone)
{
int excessiveZoneGroups = zoneGroupCount - j + 1;
TENLog("Level file contains extra pathfinding data, number of excessive zone groups is " +
std::to_string(excessiveZoneGroups) + ". These zone groups will be ignored.", LogLevel::Warning);
CurrentDataPtr += boxCount * sizeof(int);
}
else
{
g_Level.Zones[j][i].resize(boxCount);
ReadBytes(g_Level.Zones[j][i].data(), boxCount * sizeof(int));
}
}
}
// By default all blockable boxes are blocked
for (int i = 0; i < boxCount; i++)
{
if (g_Level.PathfindingBoxes[i].flags & BLOCKABLE)
g_Level.PathfindingBoxes[i].flags |= BLOCKED;
}
}
bool LoadLevelFile(int levelIndex)
{
const auto& level = *g_GameFlow->GetLevel(levelIndex);
auto assetDir = g_GameFlow->GetGameDir();
auto levelPath = assetDir + level.FileName;
bool isDummyLevel = false;
if (!std::filesystem::is_regular_file(levelPath))
{
if (levelIndex == 0)
{
levelPath = assetDir + "dummy.ten";
GenerateDummyLevel(levelPath);
TENLog("Title level file not found, using dummy level.", LogLevel::Info);
isDummyLevel = true;
}
else
{
TENLog("Level file not found: " + levelPath, LogLevel::Error);
return false;
}
}
if (!isDummyLevel)
TENLog("Loading level file: " + levelPath, LogLevel::Info);
auto timestamp = std::filesystem::last_write_time(levelPath);
bool fastReload = (g_GameFlow->GetSettings()->FastReload && levelIndex == CurrentLevel && timestamp == LastLevelTimestamp && levelPath == LastLevelFilePath);
// If fast reload is in action, draw last game frame instead of loading screen.
auto loadingScreenPath = TEN::Utils::ToWString(assetDir + level.LoadScreenFileName);
g_Renderer.SetLoadingScreen(fastReload ? std::wstring{} : loadingScreenPath);
BackupLara();
StopAllSounds();
CleanUp();
FreeLevel(fastReload);
LevelLoadTask = std::async(std::launch::async, LoadLevel, levelPath, fastReload);
bool loadSuccess = LevelLoadTask.get();
if (loadSuccess && isDummyLevel)
std::filesystem::remove(levelPath);
return loadSuccess;
}
void LoadSprites()
{
int spriteCount = ReadInt32();
g_Level.Sprites.resize(spriteCount);
TENLog("Sprite count: " + std::to_string(spriteCount), LogLevel::Info);
for (int i = 0; i < spriteCount; i++)
{
auto* spr = &g_Level.Sprites[i];
spr->tile = ReadInt32();
spr->x1 = ReadFloat();
spr->y1 = ReadFloat();
spr->x2 = ReadFloat();
spr->y2 = ReadFloat();
spr->x3 = ReadFloat();
spr->y3 = ReadFloat();
spr->x4 = ReadFloat();
spr->y4 = ReadFloat();
}
int spriteSeqCount = ReadInt32();
TENLog("Sprite sequence count: " + std::to_string(spriteSeqCount), LogLevel::Info);
for (int i = 0; i < spriteSeqCount; i++)
{
int spriteID = ReadInt32();
short negLength = ReadInt16();
short offset = ReadInt16();
if (spriteID >= ID_NUMBER_OBJECTS)
{
GetStaticObject(spriteID - ID_NUMBER_OBJECTS).meshNumber = offset;
}
else
{
Objects[spriteID].nmeshes = negLength;
Objects[spriteID].meshIndex = offset;
Objects[spriteID].loaded = true;
SpriteSequencesIds.push_back(spriteID);
}
}
}
void GetCarriedItems()
{
for (int i = 0; i < g_Level.NumItems; ++i)
g_Level.Items[i].CarriedItem = NO_VALUE;
for (int i = 0; i < g_Level.NumItems; ++i)
{
auto& item = g_Level.Items[i];
const auto& object = Objects[item.ObjectNumber];
if (object.intelligent ||
(item.ObjectNumber >= ID_SEARCH_OBJECT1 && item.ObjectNumber <= ID_SEARCH_OBJECT3) ||
(item.ObjectNumber == ID_SARCOPHAGUS))
{
for (short linkNumber = g_Level.Rooms[item.RoomNumber].itemNumber; linkNumber != NO_VALUE; linkNumber = g_Level.Items[linkNumber].NextItem)
{
auto& item2 = g_Level.Items[linkNumber];
if (abs(item2.Pose.Position.x - item.Pose.Position.x) < CLICK(2) &&
abs(item2.Pose.Position.z - item.Pose.Position.z) < CLICK(2) &&
abs(item2.Pose.Position.y - item.Pose.Position.y) < CLICK(1) &&
Objects[item2.ObjectNumber].isPickup)
{
item2.CarriedItem = item.CarriedItem;
item.CarriedItem = linkNumber;
RemoveDrawnItem(linkNumber);
item2.RoomNumber = NO_VALUE;
}
}
}
}
}
void GetAIPickups()
{
for (int i = 0; i < g_Level.NumItems; ++i)
{
auto* item = &g_Level.Items[i];
if (Objects[item->ObjectNumber].intelligent)
{
item->AIBits = 0;
for (int number = 0; number < g_Level.AIObjects.size(); ++number)
{
auto* object = &g_Level.AIObjects[number];
if (abs(object->pos.Position.x - item->Pose.Position.x) < CLICK(2) &&
abs(object->pos.Position.z - item->Pose.Position.z) < CLICK(2) &&
object->roomNumber == item->RoomNumber &&
object->objectNumber < ID_AI_PATROL2)
{
item->AIBits = (1 << (object->objectNumber - ID_AI_GUARD)) & 0x1F;
item->ItemFlags[3] = object->triggerFlags;
if (object->objectNumber != ID_AI_GUARD)
object->roomNumber = NO_VALUE;
}
}
if (item->Data.is<CreatureInfo>())
{
auto* creature = GetCreatureInfo(item);
creature->Tosspad |= item->AIBits << 8 | (char)item->ItemFlags[3];
}
}
}
}
void BuildOutsideRoomsTable()
{
for (int x = 0; x < OUTSIDE_SIZE; x++)
{
for (int z = 0; z < OUTSIDE_SIZE; z++)
OutsideRoomTable[x][z].clear();
}
for (int i = 0; i < g_Level.Rooms.size(); i++)
{
auto* room = &g_Level.Rooms[i];
int rx = (room->Position.x / BLOCK(1));
int rz = (room->Position.z / BLOCK(1));
for (int x = 0; x < OUTSIDE_SIZE; x++)
{
if (x < (rx + 1) || x > (rx + room->XSize - 2))
continue;
for (int z = 0; z < OUTSIDE_SIZE; z++)
{
if (z < (rz + 1) || z > (rz + room->ZSize - 2))
continue;
OutsideRoomTable[x][z].push_back(i);
}
}
}
}
void LoadPortal(ROOM_INFO& room)
{
auto door = ROOM_DOOR{};
door.room = ReadInt16();
door.normal.x = ReadInt32();
door.normal.y = ReadInt32();
door.normal.z = ReadInt32();
for (int k = 0; k < 4; k++)
{
door.vertices[k].x = ReadInt32();
door.vertices[k].y = ReadInt32();
door.vertices[k].z = ReadInt32();
}
room.doors.push_back(door);
}