From eccdf2e25e1175a45cfeec21b3e9fefa0326d885 Mon Sep 17 00:00:00 2001 From: Louve <33836535+Rackover@users.noreply.github.com> Date: Sat, 4 Feb 2023 18:29:32 +0100 Subject: [PATCH] Zonebuilder update for iw5xport compat (#750) Co-authored-by: Louvenarde Co-authored-by: Roxanne Co-authored-by: FutureRave --- .../Modules/AssetInterfaces/IFxEffectDef.cpp | 213 +-- .../Modules/AssetInterfaces/IFxEffectDef.hpp | 8 +- .../Modules/AssetInterfaces/IFxWorld.cpp | 162 +- .../Modules/AssetInterfaces/IFxWorld.hpp | 2 + .../Modules/AssetInterfaces/IGameWorldMp.cpp | 6 +- .../Modules/AssetInterfaces/IGfxWorld.cpp | 611 ++++---- .../Modules/AssetInterfaces/ILoadedSound.cpp | 20 +- .../Modules/AssetInterfaces/IMaterial.cpp | 13 +- .../AssetInterfaces/IMaterialTechniqueSet.cpp | 3 + .../Modules/AssetInterfaces/IPhysPreset.cpp | 58 +- .../Modules/AssetInterfaces/IPhysPreset.hpp | 2 + .../Modules/AssetInterfaces/ISndCurve.cpp | 52 +- .../Modules/AssetInterfaces/ISndCurve.hpp | 1 + .../Modules/AssetInterfaces/IXAnimParts.cpp | 194 ++- .../Modules/AssetInterfaces/IXModel.cpp | 14 +- .../Modules/AssetInterfaces/IclipMap_t.cpp | 1310 ++++++++++++++++- .../Modules/AssetInterfaces/IclipMap_t.hpp | 5 + .../AssetInterfaces/Isnd_alias_list_t.cpp | 168 ++- .../AssetInterfaces/Isnd_alias_list_t.hpp | 1 + src/Components/Modules/Debug.cpp | 5 + src/Components/Modules/Logger.cpp | 3 +- src/Components/Modules/Renderer.cpp | 52 +- src/Components/Modules/Renderer.hpp | 4 +- src/Game/Structs.hpp | 385 ++++- src/Utils/Json.cpp | 8 + src/Utils/Json.hpp | 18 +- src/Utils/Maths.cpp | 4 +- src/Utils/Maths.hpp | 4 +- src/Utils/Stream.cpp | 58 +- src/Utils/Stream.hpp | 66 +- 30 files changed, 2803 insertions(+), 647 deletions(-) diff --git a/src/Components/Modules/AssetInterfaces/IFxEffectDef.cpp b/src/Components/Modules/AssetInterfaces/IFxEffectDef.cpp index 994a6349..74942a3b 100644 --- a/src/Components/Modules/AssetInterfaces/IFxEffectDef.cpp +++ b/src/Components/Modules/AssetInterfaces/IFxEffectDef.cpp @@ -1,7 +1,7 @@ #include #include "IFxEffectDef.hpp" -#define IW4X_FX_VERSION 1 +#define IW4X_FX_VERSION 2 namespace Assets { @@ -65,132 +65,139 @@ namespace Assets void IFxEffectDef::loadBinary(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) { Components::FileSystem::File fxFile(std::format("fx/{}.iw4xFx", name)); - - if (fxFile.exists()) + if (!fxFile.exists()) { - Utils::Stream::Reader buffer(builder->getAllocator(), fxFile.getBuffer()); + return; + } - __int64 magic = buffer.read<__int64>(); - if (std::memcmp(&magic, "IW4xFx ", 8)) + Utils::Stream::Reader buffer(builder->getAllocator(), fxFile.getBuffer()); + + auto magic = buffer.read(); + if (std::memcmp(&magic, "IW4xFx ", 8) != 0) + { + Components::Logger::Error(Game::ERR_FATAL, "Reading fx '{}' failed, header is invalid!", name); + } + + int version = buffer.read(); + if (version > IW4X_FX_VERSION) + { + Components::Logger::Error(Game::ERR_FATAL, "Reading fx '{}' failed, expected version is {}, but it was {}!", name, IW4X_FX_VERSION, version); + } + + auto* asset = buffer.readObject(); + header->fx = asset; + + if (asset->name) + { + asset->name = buffer.readCString(); + } + + if (asset->elemDefs) + { + asset->elemDefs = buffer.readArray(asset->elemDefCountEmission + asset->elemDefCountLooping + asset->elemDefCountOneShot); + + for (auto i = 0; i < (asset->elemDefCountEmission + asset->elemDefCountLooping + asset->elemDefCountOneShot); ++i) { - Components::Logger::Error(Game::ERR_FATAL, "Reading fx '{}' failed, header is invalid!", name); - } + auto* elemDef = &asset->elemDefs[i]; - int version = buffer.read(); - if (version != IW4X_FX_VERSION) - { - Components::Logger::Error(Game::ERR_FATAL, "Reading fx '{}' failed, expected version is {}, but it was {}!", name, IW4X_FX_VERSION, version); - } - - Game::FxEffectDef* asset = buffer.readObject(); - header->fx = asset; - - if (asset->name) - { - asset->name = buffer.readCString(); - } - - if (asset->elemDefs) - { - asset->elemDefs = buffer.readArray(asset->elemDefCountEmission + asset->elemDefCountLooping + asset->elemDefCountOneShot); - - for (int i = 0; i < (asset->elemDefCountEmission + asset->elemDefCountLooping + asset->elemDefCountOneShot); ++i) + if (elemDef->velSamples) { - Game::FxElemDef* elemDef = &asset->elemDefs[i]; + elemDef->velSamples = buffer.readArray(elemDef->velIntervalCount + 1); + } - if (elemDef->velSamples) - { - elemDef->velSamples = buffer.readArray(elemDef->velIntervalCount + 1); - } + if (elemDef->visSamples) + { + elemDef->visSamples = buffer.readArray(elemDef->visStateIntervalCount + 1); + } - if (elemDef->visSamples) + // Save_FxElemDefVisuals + { + if (elemDef->elemType == Game::FX_ELEM_TYPE_DECAL) { - elemDef->visSamples = buffer.readArray(elemDef->visStateIntervalCount + 1); - } - - // Save_FxElemDefVisuals - { - if (elemDef->elemType == Game::FX_ELEM_TYPE_DECAL) + if (elemDef->visuals.markArray) { - if (elemDef->visuals.markArray) + elemDef->visuals.markArray = buffer.readArray(elemDef->visualCount); + + for (char j = 0; j < elemDef->visualCount; ++j) { - elemDef->visuals.markArray = buffer.readArray(elemDef->visualCount); - - for (char j = 0; j < elemDef->visualCount; ++j) + if (elemDef->visuals.markArray[j].materials[0]) { - if (elemDef->visuals.markArray[j].materials[0]) - { - elemDef->visuals.markArray[j].materials[0] = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_MATERIAL, buffer.readString().data(), builder).material; - } + elemDef->visuals.markArray[j].materials[0] = Components::AssetHandler::FindAssetForZone(Game::ASSET_TYPE_MATERIAL, buffer.readString(), builder).material; + } - if (elemDef->visuals.markArray[j].materials[1]) - { - elemDef->visuals.markArray[j].materials[1] = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_MATERIAL, buffer.readString().data(), builder).material; - } + if (elemDef->visuals.markArray[j].materials[1]) + { + elemDef->visuals.markArray[j].materials[1] = Components::AssetHandler::FindAssetForZone(Game::ASSET_TYPE_MATERIAL, buffer.readString(), builder).material; } } } - else if (elemDef->visualCount > 1) + } + else if (elemDef->visualCount > 1) + { + if (elemDef->visuals.array) { - if (elemDef->visuals.array) - { - elemDef->visuals.array = buffer.readArray(elemDef->visualCount); + elemDef->visuals.array = buffer.readArray(elemDef->visualCount); - for (char j = 0; j < elemDef->visualCount; ++j) + for (char j = 0; j < elemDef->visualCount; ++j) + { + this->loadFxElemVisuals(&elemDef->visuals.array[j], elemDef->elemType, builder, &buffer); + } + } + } + else if (elemDef->visualCount == 1) + { + this->loadFxElemVisuals(&elemDef->visuals.instance, elemDef->elemType, builder, &buffer); + } + } + + if (elemDef->effectOnImpact.handle) + { + elemDef->effectOnImpact.handle = Components::AssetHandler::FindAssetForZone(Game::ASSET_TYPE_FX, buffer.readString(), builder).fx; + } + + if (elemDef->effectOnDeath.handle) + { + elemDef->effectOnDeath.handle = Components::AssetHandler::FindAssetForZone(Game::ASSET_TYPE_FX, buffer.readString(), builder).fx; + } + + if (elemDef->effectEmitted.handle) + { + elemDef->effectEmitted.handle = Components::AssetHandler::FindAssetForZone(Game::ASSET_TYPE_FX, buffer.readString(), builder).fx; + } + + // Save_FxElemExtendedDefPtr + { + + if (elemDef->elemType == Game::FX_ELEM_TYPE_TRAIL) + { + // Save_FxTrailDef + { + if (elemDef->extended.trailDef) + { + auto* trailDef = buffer.readObject(); + elemDef->extended.trailDef = trailDef; + + if (trailDef->verts) { - this->loadFxElemVisuals(&elemDef->visuals.array[j], elemDef->elemType, builder, &buffer); + trailDef->verts = buffer.readArray(trailDef->vertCount); + } + + if (trailDef->inds) + { + trailDef->inds = buffer.readArray(trailDef->indCount); } } } - else + } + else if (version >= 2) + { + if (elemDef->elemType == Game::FX_ELEM_TYPE_SPARK_FOUNTAIN) { - this->loadFxElemVisuals(&elemDef->visuals.instance, elemDef->elemType, builder, &buffer); - } - } - - if (elemDef->effectOnImpact.handle) - { - elemDef->effectOnImpact.handle = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_FX, buffer.readString().data(), builder).fx; - } - - if (elemDef->effectOnDeath.handle) - { - elemDef->effectOnDeath.handle = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_FX, buffer.readString().data(), builder).fx; - } - - if (elemDef->effectEmitted.handle) - { - elemDef->effectEmitted.handle = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_FX, buffer.readString().data(), builder).fx; - } - - // Save_FxElemExtendedDefPtr - { - - if (elemDef->elemType == Game::FX_ELEM_TYPE_TRAIL) - { - // Save_FxTrailDef + if (elemDef->extended.sparkFountainDef) { - if (elemDef->extended.trailDef) - { - Game::FxTrailDef* trailDef = buffer.readObject(); - elemDef->extended.trailDef = trailDef; - - if (trailDef->verts) - { - trailDef->verts = buffer.readArray(trailDef->vertCount); - } - - if (trailDef->inds) - { - trailDef->inds = buffer.readArray(trailDef->indCount); - } - } + elemDef->extended.sparkFountainDef = buffer.readObject(); } } - else if (elemDef->extended.trailDef) - { - Components::Logger::Error(Game::ERR_FATAL, "Fx element of type {} has traildef, that's impossible?\n", elemDef->elemType); - } } } } @@ -270,7 +277,7 @@ namespace Assets // TODO: Convert editor fx to real fx } #else - (name); + (void)name; #endif } diff --git a/src/Components/Modules/AssetInterfaces/IFxEffectDef.hpp b/src/Components/Modules/AssetInterfaces/IFxEffectDef.hpp index 9a877050..defa408e 100644 --- a/src/Components/Modules/AssetInterfaces/IFxEffectDef.hpp +++ b/src/Components/Modules/AssetInterfaces/IFxEffectDef.hpp @@ -12,13 +12,13 @@ namespace Assets void load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) override; private: - void markFxElemVisuals(Game::FxElemVisuals* visuals, char elemType, Components::ZoneBuilder::Zone* builder); - void saveFxElemVisuals(Game::FxElemVisuals* visuals, Game::FxElemVisuals* destVisuals, char elemType, Components::ZoneBuilder::Zone* builder); + static void markFxElemVisuals(Game::FxElemVisuals* visuals, char elemType, Components::ZoneBuilder::Zone* builder); + static void saveFxElemVisuals(Game::FxElemVisuals* visuals, Game::FxElemVisuals* destVisuals, char elemType, Components::ZoneBuilder::Zone* builder); - void loadEfx(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder); + static void loadEfx(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder); void loadNative(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder); void loadBinary(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder); - void loadFxElemVisuals(Game::FxElemVisuals* visuals, char elemType, Components::ZoneBuilder::Zone* builder, Utils::Stream::Reader* reader); + static void loadFxElemVisuals(Game::FxElemVisuals* visuals, char elemType, Components::ZoneBuilder::Zone* builder, Utils::Stream::Reader* reader); }; } diff --git a/src/Components/Modules/AssetInterfaces/IFxWorld.cpp b/src/Components/Modules/AssetInterfaces/IFxWorld.cpp index 6988543f..98d99d78 100644 --- a/src/Components/Modules/AssetInterfaces/IFxWorld.cpp +++ b/src/Components/Modules/AssetInterfaces/IFxWorld.cpp @@ -1,6 +1,11 @@ #include + +#include + #include "IFxWorld.hpp" +#define IW4X_FXWORLD_VERSION 1 + namespace Assets { void IFxWorld::save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) @@ -187,6 +192,161 @@ namespace Assets } void IFxWorld::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) + { + if (!header->fxWorld) loadFromDisk(header, name, builder); + if (!header->fxWorld) generate(header, name, builder); + + assert(header->fxWorld); + } + + void IFxWorld::loadFromDisk(Game::XAssetHeader* header, const std::string& _name, Components::ZoneBuilder::Zone* builder) + { + std::string name = _name; + Utils::String::Replace(name, "maps/mp/", ""); + Utils::String::Replace(name, ".d3dbsp", ""); + + Components::FileSystem::File fxWorldFile(std::format("fxworld/{}.iw4x.json", name)); + if (!fxWorldFile.exists()) + { + return; + } + + nlohmann::json fxWorldJson; + try + { + fxWorldJson = nlohmann::json::parse(fxWorldFile.getBuffer()); + } + catch (const std::exception& e) + { + Components::Logger::PrintError(Game::CON_CHANNEL_ERROR, "Invalid JSON for gameworld {}! Error message: {}", name, e.what()); + return; + } + + if (!fxWorldJson.is_object()) + { + Components::Logger::PrintError(Game::CON_CHANNEL_ERROR, "Invalid FXWORLD JSON for {}\n", name); + return; + } + + auto version = fxWorldJson["version"].is_number() ? fxWorldJson["version"].get() : 0; + if (version != IW4X_FXWORLD_VERSION) + { + Components::Logger::PrintError(Game::CON_CHANNEL_ERROR, "Invalid FXWORLD json version for {}, expected {} and got {}\n", name, IW4X_FXWORLD_VERSION, version); + return; + } + + auto map = builder->getAllocator()->allocate(); + map->name = builder->getAllocator()->duplicateString(_name); + + try + { + auto glassSys = &map->glassSys; + auto glassSysJson = fxWorldJson["glassSys"]; + + glassSys->time = glassSysJson["time"].get(); + glassSys->prevTime = glassSysJson["prevTime"].get(); + glassSys->defCount = glassSysJson["defCount"].get(); + glassSys->pieceLimit = glassSysJson["pieceLimit"].get(); + glassSys->pieceWordCount = glassSysJson["pieceWordCount"].get(); + glassSys->initPieceCount = glassSysJson["initPieceCount"].get(); + glassSys->cellCount = glassSysJson["cellCount"].get(); + glassSys->activePieceCount = glassSysJson["activePieceCount"].get(); + glassSys->firstFreePiece = glassSysJson["firstFreePiece"].get(); + glassSys->geoDataLimit = glassSysJson["geoDataLimit"].get(); + glassSys->geoDataCount = glassSysJson["geoDataCount"].get(); + glassSys->initGeoDataCount = glassSysJson["initGeoDataCount"].get(); + + auto i = 0; + glassSys->defs = builder->getAllocator()->allocateArray(glassSys->defCount); + for (auto member : glassSysJson["defs"]) + { + auto def = &glassSys->defs[i]; + + def->halfThickness = member["halfThickness"].get(); + + auto xy = 0; + for (auto x = 0; x < 2; x++) + { + for (auto y = 0; y < 2; y++) + { + def->texVecs[x][y] = member["texVecs"][xy].get(); + xy++; + } + } + + def->color.packed = member["color"].get(); + + auto matShateredName = member["materialShattered"].get(); + auto matName = member["material"].get(); + auto physPresetName = member["physPreset"].get(); + def->material = Components::AssetHandler::FindAssetForZone(Game::ASSET_TYPE_MATERIAL, matName, builder).material; + def->materialShattered = Components::AssetHandler::FindAssetForZone(Game::ASSET_TYPE_MATERIAL, matShateredName, builder).material; + def->physPreset = Components::AssetHandler::FindAssetForZone(Game::ASSET_TYPE_PHYSPRESET, physPresetName, builder ).physPreset; + + assert(def->material); + assert(def->materialShattered); + assert(def->physPreset); + ++i; + } + + i = 0; + glassSys->initPieceStates = builder->getAllocator()->allocateArray(glassSys->initPieceCount); + for (auto member : glassSysJson["initPieceStates"]) + { + auto initial = &glassSys->initPieceStates[i]; + + for (int j = 0; j < ARRAYSIZE(initial->frame.quat); j++) + { + initial->frame.quat[j] = member["frame"]["quat"][j].get(); + } + + for (int j = 0; j < ARRAYSIZE(initial->frame.origin); j++) + { + initial->frame.origin[j] = member["frame"]["origin"][j].get(); + } + + initial->radius = member["radius"].get(); + initial->texCoordOrigin[0] = member["texCoordOrigin"][0].get(); + initial->texCoordOrigin[1] = member["texCoordOrigin"][1].get(); + initial->supportMask = member["supportMask"].get(); + initial->areaX2 = member["areaX2"].get(); + initial->defIndex = member["defIndex"].get(); + initial->vertCount = member["vertCount"].get(); + initial->fanDataCount = member["fanDataCount"].get(); + ++i; + } + + i = 0; + glassSys->initGeoData = builder->getAllocator()->allocateArray(glassSys->initGeoDataCount); + for (auto member : glassSysJson["initGeoData"]) + { + auto data = &glassSys->initGeoData[i]; + data->anonymous[0] = member[0]; + data->anonymous[1] = member[1]; + ++i; + } + } + catch (const nlohmann::json::exception& e) + { + Components::Logger::PrintError(Game::CON_CHANNEL_ERROR, "Malformed FXWORLD JSON for {}! Error message: {}\n", name, e.what()); + return; + } + + map->glassSys.piecePlaces = builder->getAllocator()->allocateArray(map->glassSys.pieceLimit); + map->glassSys.pieceStates = builder->getAllocator()->allocateArray(map->glassSys.pieceLimit); + map->glassSys.pieceDynamics = builder->getAllocator()->allocateArray(map->glassSys.pieceLimit); + map->glassSys.geoData = builder->getAllocator()->allocateArray(map->glassSys.geoDataLimit); + map->glassSys.isInUse = builder->getAllocator()->allocateArray(map->glassSys.pieceWordCount); + map->glassSys.cellBits = builder->getAllocator()->allocateArray(map->glassSys.pieceWordCount * map->glassSys.cellCount); + map->glassSys.visData = builder->getAllocator()->allocateArray((map->glassSys.pieceLimit + 15) & 0xFFFFFFF0); // ugh + map->glassSys.linkOrg = reinterpret_cast(builder->getAllocator()->allocateArray(map->glassSys.pieceLimit)); + map->glassSys.halfThickness = builder->getAllocator()->allocateArray(map->glassSys.pieceLimit * 3); + map->glassSys.lightingHandles = builder->getAllocator()->allocateArray(map->glassSys.initPieceCount); + + header->fxWorld = map; + } + + void IFxWorld::generate(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) { Game::FxWorld* map = Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_FXWORLD, name.data()).fxWorld; if (map) return; @@ -194,7 +354,7 @@ namespace Assets // Generate map = builder->getAllocator()->allocate(); map->name = builder->getAllocator()->duplicateString(name); - + // No glass for you! ZeroMemory(&map->glassSys, sizeof(map->glassSys)); diff --git a/src/Components/Modules/AssetInterfaces/IFxWorld.hpp b/src/Components/Modules/AssetInterfaces/IFxWorld.hpp index a19ddf56..4f1590c1 100644 --- a/src/Components/Modules/AssetInterfaces/IFxWorld.hpp +++ b/src/Components/Modules/AssetInterfaces/IFxWorld.hpp @@ -10,5 +10,7 @@ namespace Assets void save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override; void mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override; void load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) override; + void loadFromDisk(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder); + void generate(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder); }; } diff --git a/src/Components/Modules/AssetInterfaces/IGameWorldMp.cpp b/src/Components/Modules/AssetInterfaces/IGameWorldMp.cpp index 2bbbb004..b3f5e5f0 100644 --- a/src/Components/Modules/AssetInterfaces/IGameWorldMp.cpp +++ b/src/Components/Modules/AssetInterfaces/IGameWorldMp.cpp @@ -153,7 +153,7 @@ namespace Assets { nlohmann::json::array_t jsonPiecesIndices = jsonGlassName["piecesIndices"]; glassData->glassNames[i].pieceCount = static_cast(jsonPiecesIndices.size()); - + glassData->glassNames[i].pieceIndices = builder->getAllocator()->allocateArray(glassData->glassNames[i].pieceCount); for (size_t j = 0; j < glassData->glassNames[i].pieceCount; j++) { glassData->glassNames[i].pieceIndices[j] = jsonPiecesIndices[j].get(); @@ -162,9 +162,9 @@ namespace Assets } } - if (gameWorldJson["glassPieces"].is_array()) + if (jsonGlassData["glassPieces"].is_array()) { - nlohmann::json::array_t glassPieces = gameWorldJson["glassPieces"]; + nlohmann::json::array_t glassPieces = jsonGlassData["glassPieces"]; glassData->pieceCount = glassPieces.size(); glassData->glassPieces = builder->getAllocator()->allocateArray(glassData->pieceCount); diff --git a/src/Components/Modules/AssetInterfaces/IGfxWorld.cpp b/src/Components/Modules/AssetInterfaces/IGfxWorld.cpp index 8548c309..5a9e2bd7 100644 --- a/src/Components/Modules/AssetInterfaces/IGfxWorld.cpp +++ b/src/Components/Modules/AssetInterfaces/IGfxWorld.cpp @@ -3,27 +3,6 @@ #define IW4X_GFXMAP_VERSION 1 -// The xmodel vehicle_small_hatch_green_destructible_mp causes EXTREME lag -// when placed in the world, for reasons unknown. -// -// Something happens with the SModelSurfIterator which makes it load garbage -// as an XSurface in the middle of otherwise valid surfaces. This bug is very -// easy to reproduce with an empty map and just this car in the middle -// -// As of know we do not know why the iterator corruption occurs or what causes -// it. It doesn't seem linked to the SModel, nor to the materials or techsets, -// nor to the sortkeys, nor to the tilemode, boneinfo, and so on. So for now -// and to make it work for majority of users, we just swap the car. (no, using -// the identical car from iw4's favela_escape doesn't work either!) -// -// Two other models have this problem: ch_apartment_9story_noentry_02 and -// ch_apartment_5story_noentry_01 -// But these exist in mp_vacant in slightly different versions, and can be -// swapped safely by deleting the two .iw4XModel files and requiring mp_vacant -// or a minimal zone containing just these two models. -// -#define SWAP_GREEN_VEHICLE_XMODEL 1 - namespace Assets { void IGfxWorld::loadGfxWorldDpvsStatic(Game::GfxWorld* world, Game::GfxWorldDpvsStatic* asset, Components::ZoneBuilder::Zone* builder, Utils::Stream::Reader* reader) @@ -44,11 +23,12 @@ namespace Assets for (unsigned int i = 0; i < world->surfaceCount; ++i) { - Game::GfxSurface* surface = &asset->surfaces[i]; - + auto* surface = &asset->surfaces[i]; if (surface->material) { - world->dpvs.surfaces[i].material = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_MATERIAL, reader->readString().data(), builder).material; + auto materialName = reader->readString(); + world->dpvs.surfaces[i].material = Components::AssetHandler::FindAssetForZone(Game::ASSET_TYPE_MATERIAL, materialName, builder).material; + assert(world->dpvs.surfaces[i].material); } } } @@ -69,8 +49,19 @@ namespace Assets if (model->model) { auto name = reader->readString(); + while (name.ends_with(".")) + { + // Happens with some flowers in mp_paris + // I'm not confident this will work on every map + // But regardless Game FS does not support having a file terminated with "." + // Probably an artist made a typo in MW3... + // Example: "foliage_gardenflowers_red_bright..iw4xModel" + name = name.substr(0, name.size() - 1); + } + + assert(!name.empty()); - model->model = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_XMODEL, name.data(), builder).model; + model->model = Components::AssetHandler::FindAssetForZone(Game::ASSET_TYPE_XMODEL, name, builder).model; assert(model->model); } @@ -86,7 +77,7 @@ namespace Assets for (unsigned int i = 0; i < asset->reflectionProbeCount; ++i) { - asset->reflectionProbes[i] = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_IMAGE, reader->readString().data(), builder).image; + asset->reflectionProbes[i] = Components::AssetHandler::FindAssetForZone(Game::ASSET_TYPE_IMAGE, reader->readString(), builder).image; } } @@ -101,28 +92,27 @@ namespace Assets for (int i = 0; i < asset->lightmapCount; ++i) { - Game::GfxLightmapArray* lightmapArray = &asset->lightmaps[i]; - + auto* lightmapArray = &asset->lightmaps[i]; if (lightmapArray->primary) { - lightmapArray->primary = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_IMAGE, reader->readString().data(), builder).image; + lightmapArray->primary = Components::AssetHandler::FindAssetForZone(Game::ASSET_TYPE_IMAGE, reader->readString(), builder).image; } if (lightmapArray->secondary) { - lightmapArray->secondary = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_IMAGE, reader->readString().data(), builder).image; + lightmapArray->secondary = Components::AssetHandler::FindAssetForZone(Game::ASSET_TYPE_IMAGE, reader->readString(), builder).image; } } } if (asset->lightmapOverridePrimary) { - asset->lightmapOverridePrimary = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_IMAGE, reader->readString().data(), builder).image; + asset->lightmapOverridePrimary = Components::AssetHandler::FindAssetForZone(Game::ASSET_TYPE_IMAGE, reader->readString(), builder).image; } if (asset->lightmapOverrideSecondary) { - asset->lightmapOverrideSecondary = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_IMAGE, reader->readString().data(), builder).image; + asset->lightmapOverrideSecondary = Components::AssetHandler::FindAssetForZone(Game::ASSET_TYPE_IMAGE, reader->readString(), builder).image; } // saveGfxWorldVertexData @@ -156,295 +146,305 @@ namespace Assets Components::FileSystem::File mapFile(std::format("gfxworld/{}.iw4xGfxWorld", name)); - if (mapFile.exists()) + if (!mapFile.exists()) { - Utils::Stream::Reader reader(builder->getAllocator(), mapFile.getBuffer()); + return; + } - __int64 magic = reader.read<__int64>(); - if (std::memcmp(&magic, "IW4xGfxW", 8)) + Utils::Stream::Reader reader(builder->getAllocator(), mapFile.getBuffer()); + + auto magic = reader.read(); + if (std::memcmp(&magic, "IW4xGfxW", 8) != 0) + { + Components::Logger::Error(Game::ERR_FATAL, "Reading gfxworld '{}' failed, header is invalid!", name); + } + + int version = reader.read(); + if (version != IW4X_GFXMAP_VERSION) + { + Components::Logger::Error(Game::ERR_FATAL, "Reading gfxworld '{}' failed, expected version is {}, but it was {}!", name, IW4X_GFXMAP_VERSION, version); + } + + auto* asset = reader.readObject(); + header->gfxWorld = asset; + + if (asset->name) + { + asset->name = reader.readCString(); + } + + if (asset->baseName) + { + asset->baseName = reader.readCString(); + } + + if (asset->skies) + { + asset->skies = reader.readArray(asset->skyCount); + + for (int i = 0; i < asset->skyCount; ++i) { - Components::Logger::Error(Game::ERR_FATAL, "Reading gfxworld '{}' failed, header is invalid!", name); - } + auto* sky = &asset->skies[i]; - int version = reader.read(); - if (version != IW4X_GFXMAP_VERSION) - { - Components::Logger::Error(Game::ERR_FATAL, "Reading gfxworld '{}' failed, expected version is {}, but it was {}!", name, IW4X_GFXMAP_VERSION, version); - } - - Game::GfxWorld* asset = reader.readObject(); - header->gfxWorld = asset; - - if (asset->name) - { - asset->name = reader.readCString(); - } - - if (asset->baseName) - { - asset->baseName = reader.readCString(); - } - - if (asset->skies) - { - asset->skies = reader.readArray(asset->skyCount); - - for (int i = 0; i < asset->skyCount; ++i) + if (sky->skyStartSurfs) { - Game::GfxSky* sky = &asset->skies[i]; + sky->skyStartSurfs = reader.readArray(sky->skySurfCount); + } - if (sky->skyStartSurfs) + if (sky->skyImage) + { + sky->skyImage = Components::AssetHandler::FindAssetForZone(Game::ASSET_TYPE_IMAGE, reader.readString(), builder).image; + } + } + } + + // GfxWorldDpvsPlanes + { + if (asset->dpvsPlanes.planes) + { + asset->dpvsPlanes.planes = reader.readArray(asset->planeCount); + + auto clip = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_CLIPMAP_MP, asset->name, builder).clipMap; + if (clip) + { + assert(clip->planeCount == static_cast(asset->planeCount)); + for (size_t i = 0; i < clip->planeCount; i++) { - sky->skyStartSurfs = reader.readArray(sky->skySurfCount); + assert(!std::memcmp(&clip->planes[i], &asset->dpvsPlanes.planes[i], sizeof Game::cplane_s)); } - if (sky->skyImage) - { - sky->skyImage = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_IMAGE, reader.readString().data(), builder).image; - } + asset->dpvsPlanes.planes = clip->planes; + } + else + { + Components::Logger::Error(Game::ERR_FATAL, "GfxWorld dpvs planes not mapped. This shouldn't happen. Make sure to load the ClipMap first!\n"); } } - // GfxWorldDpvsPlanes + if (asset->dpvsPlanes.nodes) { - if (asset->dpvsPlanes.planes) - { - void* oldPtr = asset->dpvsPlanes.planes; - asset->dpvsPlanes.planes = reader.readArray(asset->planeCount); - - if (builder->getAllocator()->isPointerMapped(oldPtr)) - { - asset->dpvsPlanes.planes = builder->getAllocator()->getPointer(oldPtr); - } - else - { - builder->getAllocator()->mapPointer(oldPtr, asset->dpvsPlanes.planes); - Components::Logger::Print("GfxWorld dpvs planes not mapped. This shouldn't happen. Make sure to load the ClipMap first!\n"); - } - } - - if (asset->dpvsPlanes.nodes) - { - asset->dpvsPlanes.nodes = reader.readArray(asset->nodeCount); - } + asset->dpvsPlanes.nodes = reader.readArray(asset->nodeCount); } + } + auto cellCount = asset->dpvsPlanes.cellCount; - int cellCount = asset->dpvsPlanes.cellCount; + if (asset->aabbTreeCounts) + { + asset->aabbTreeCounts = reader.readArray(cellCount); + } - if (asset->aabbTreeCounts) + if (asset->aabbTrees) + { + asset->aabbTrees = reader.readArray(cellCount); + + for (auto i = 0; i < cellCount; ++i) { - asset->aabbTreeCounts = reader.readArray(cellCount); - } + auto* cellTree = &asset->aabbTrees[i]; - if (asset->aabbTrees) - { - asset->aabbTrees = reader.readArray(cellCount); - - for (int i = 0; i < cellCount; ++i) + if (cellTree->aabbTree) { - Game::GfxCellTree* cellTree = &asset->aabbTrees[i]; + cellTree->aabbTree = reader.readArray(asset->aabbTreeCounts[i].aabbTreeCount); - if (cellTree->aabbTree) + for (int j = 0; j < asset->aabbTreeCounts[i].aabbTreeCount; ++j) { - cellTree->aabbTree = reader.readArray(asset->aabbTreeCounts[i].aabbTreeCount); + auto* aabbTree = &cellTree->aabbTree[j]; - for (int j = 0; j < asset->aabbTreeCounts[i].aabbTreeCount; ++j) + if (aabbTree->smodelIndexes) { - Game::GfxAabbTree* aabbTree = &cellTree->aabbTree[j]; - - if (aabbTree->smodelIndexes) + auto* oldPointer = aabbTree->smodelIndexes; + if (builder->getAllocator()->isPointerMapped(oldPointer)) { - unsigned short* oldPointer = aabbTree->smodelIndexes; - if(builder->getAllocator()->isPointerMapped(oldPointer)) - { - // We still have to read it - reader.readArray(aabbTree->smodelIndexCount); + // We still have to read it + reader.readArray(aabbTree->smodelIndexCount); - aabbTree->smodelIndexes = builder->getAllocator()->getPointer(oldPointer); - } - else - { - aabbTree->smodelIndexes = reader.readArray(aabbTree->smodelIndexCount); + aabbTree->smodelIndexes = builder->getAllocator()->getPointer(oldPointer); + } + else + { + aabbTree->smodelIndexes = reader.readArray(aabbTree->smodelIndexCount); - for (unsigned short k = 0; k < aabbTree->smodelIndexCount; ++k) - { - builder->getAllocator()->mapPointer(&oldPointer[k], &aabbTree->smodelIndexes[k]); - } + for (unsigned short k = 0; k < aabbTree->smodelIndexCount; ++k) + { + builder->getAllocator()->mapPointer(&oldPointer[k], &aabbTree->smodelIndexes[k]); } } } } } } + } - if (asset->cells) + if (asset->cells) + { + asset->cells = reader.readArray(cellCount); + + for (auto i = 0; i < cellCount; ++i) { - asset->cells = reader.readArray(cellCount); + auto* cell = &asset->cells[i]; - for (int i = 0; i < cellCount; ++i) + if (cell->portals) { - Game::GfxCell* cell = &asset->cells[i]; + cell->portals = reader.readArray(cell->portalCount); - if (cell->portals) + for (auto j = 0; j < cell->portalCount; ++j) { - cell->portals = reader.readArray(cell->portalCount); - - for (int j = 0; j < cell->portalCount; ++j) + auto* portal = &cell->portals[j]; + if (portal->vertices) { - Game::GfxPortal* portal = &cell->portals[j]; - - if (portal->vertices) - { - portal->vertices = reader.readArray(portal->vertexCount); - } + portal->vertices = reader.readArray(portal->vertexCount); } } + } - if (cell->reflectionProbes) + if (cell->reflectionProbes) + { + cell->reflectionProbes = reader.readArray(cell->reflectionProbeCount); + } + } + } + + this->loadGfxWorldDraw(&asset->draw, builder, &reader); + + // GfxLightGrid + { + if (asset->lightGrid.rowDataStart) + { + asset->lightGrid.rowDataStart = reader.readArray((asset->lightGrid.maxs[asset->lightGrid.rowAxis] - asset->lightGrid.mins[asset->lightGrid.rowAxis]) + 1); + } + + if (asset->lightGrid.rawRowData) + { + asset->lightGrid.rawRowData = reader.readArray(asset->lightGrid.rawRowDataSize); + } + + if (asset->lightGrid.entries) + { + asset->lightGrid.entries = reader.readArray(asset->lightGrid.entryCount); + } + + if (asset->lightGrid.colors) + { + asset->lightGrid.colors = reader.readArray(asset->lightGrid.colorCount); + } + } + + if (asset->models) + { + asset->models = reader.readArray(asset->modelCount); + } + + if (asset->materialMemory) + { + asset->materialMemory = reader.readArray(asset->materialMemoryCount); + + for (auto i = 0; i < asset->materialMemoryCount; ++i) + { + auto* materialMemory = &asset->materialMemory[i]; + if (materialMemory->material) + { + auto materialName = reader.readString(); + materialMemory->material = Components::AssetHandler::FindAssetForZone(Game::ASSET_TYPE_MATERIAL, materialName, builder).material; + assert(materialMemory->material); + } + } + } + + if (asset->sun.spriteMaterial) + { + auto materialName = reader.readString(); + asset->sun.spriteMaterial = Components::AssetHandler::FindAssetForZone(Game::ASSET_TYPE_MATERIAL, materialName, builder).material; + assert(asset->sun.spriteMaterial); + } + + if (asset->sun.flareMaterial) + { + auto materialName = reader.readString(); + asset->sun.flareMaterial = Components::AssetHandler::FindAssetForZone(Game::ASSET_TYPE_MATERIAL, materialName, builder).material; + assert(asset->sun.flareMaterial); + } + + if (asset->outdoorImage) + { + auto materialName = reader.readString(); + asset->outdoorImage = Components::AssetHandler::FindAssetForZone(Game::ASSET_TYPE_IMAGE, materialName, builder).image; + assert(asset->outdoorImage); + } + + if (asset->primaryLightCount > 0) + { + Utils::Stream::ClearPointer(&asset->primaryLightEntityShadowVis); + } + + if (asset->dpvsDyn.dynEntClientCount[0] > 0) + { + Utils::Stream::ClearPointer(&asset->sceneDynModel); + Utils::Stream::ClearPointer(&asset->primaryLightDynEntShadowVis[0]); + Utils::Stream::ClearPointer(&asset->nonSunPrimaryLightForModelDynEnt); + } + + if (asset->dpvsDyn.dynEntClientCount[1] > 0) + { + Utils::Stream::ClearPointer(&asset->sceneDynBrush); + Utils::Stream::ClearPointer(&asset->primaryLightDynEntShadowVis[1]); + } + + if (asset->shadowGeom) + { + asset->shadowGeom = reader.readArray(asset->primaryLightCount); + + for (unsigned int i = 0; i < asset->primaryLightCount; ++i) + { + auto* shadowGeometry = &asset->shadowGeom[i]; + + if (shadowGeometry->sortedSurfIndex) + { + shadowGeometry->sortedSurfIndex = reader.readArray(shadowGeometry->surfaceCount); + } + + if (shadowGeometry->smodelIndex) + { + shadowGeometry->smodelIndex = reader.readArray(shadowGeometry->smodelCount); + } + } + } + + if (asset->lightRegion) + { + asset->lightRegion = reader.readArray(asset->primaryLightCount); + + for (unsigned int i = 0; i < asset->primaryLightCount; ++i) + { + auto* lightRegion = &asset->lightRegion[i]; + + if (lightRegion->hulls) + { + lightRegion->hulls = reader.readArray(lightRegion->hullCount); + + for (unsigned int j = 0; j < lightRegion->hullCount; ++j) { - cell->reflectionProbes = reader.readArray(cell->reflectionProbeCount); - } - } - } - - this->loadGfxWorldDraw(&asset->draw, builder, &reader); - - // GfxLightGrid - { - if (asset->lightGrid.rowDataStart) - { - asset->lightGrid.rowDataStart = reader.readArray((asset->lightGrid.maxs[asset->lightGrid.rowAxis] - asset->lightGrid.mins[asset->lightGrid.rowAxis]) + 1); - } - - if (asset->lightGrid.rawRowData) - { - asset->lightGrid.rawRowData = reader.readArray(asset->lightGrid.rawRowDataSize); - } - - if (asset->lightGrid.entries) - { - asset->lightGrid.entries = reader.readArray(asset->lightGrid.entryCount); - } - - if (asset->lightGrid.colors) - { - asset->lightGrid.colors = reader.readArray(asset->lightGrid.colorCount); - } - } - - if (asset->models) - { - asset->models = reader.readArray(asset->modelCount); - } - - if (asset->materialMemory) - { - asset->materialMemory = reader.readArray(asset->materialMemoryCount); - - for (int i = 0; i < asset->materialMemoryCount; ++i) - { - Game::MaterialMemory* materialMemory = &asset->materialMemory[i]; - - if (materialMemory->material) - { - materialMemory->material = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_MATERIAL, reader.readString().data(), builder).material; - } - } - } - - if (asset->sun.spriteMaterial) - { - asset->sun.spriteMaterial = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_MATERIAL, reader.readString().data(), builder).material; - } - - if (asset->sun.flareMaterial) - { - asset->sun.flareMaterial = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_MATERIAL, reader.readString().data(), builder).material; - } - - if (asset->outdoorImage) - { - asset->outdoorImage = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_IMAGE, reader.readString().data(), builder).image; - } - - if (asset->primaryLightCount > 0) - { - Utils::Stream::ClearPointer(&asset->primaryLightEntityShadowVis); - } - - if (asset->dpvsDyn.dynEntClientCount[0] > 0) - { - Utils::Stream::ClearPointer(&asset->sceneDynModel); - Utils::Stream::ClearPointer(&asset->primaryLightDynEntShadowVis[0]); - Utils::Stream::ClearPointer(&asset->nonSunPrimaryLightForModelDynEnt); - } - - if (asset->dpvsDyn.dynEntClientCount[1] > 0) - { - Utils::Stream::ClearPointer(&asset->sceneDynBrush); - Utils::Stream::ClearPointer(&asset->primaryLightDynEntShadowVis[1]); - } - - if (asset->shadowGeom) - { - asset->shadowGeom = reader.readArray(asset->primaryLightCount); - - for (unsigned int i = 0; i < asset->primaryLightCount; ++i) - { - Game::GfxShadowGeometry* shadowGeometry = &asset->shadowGeom[i]; - - if (shadowGeometry->sortedSurfIndex) - { - shadowGeometry->sortedSurfIndex = reader.readArray(shadowGeometry->surfaceCount); - } - - if (shadowGeometry->smodelIndex) - { - shadowGeometry->smodelIndex = reader.readArray(shadowGeometry->smodelCount); - } - } - } - - if (asset->lightRegion) - { - asset->lightRegion = reader.readArray(asset->primaryLightCount); - - for (unsigned int i = 0; i < asset->primaryLightCount; ++i) - { - Game::GfxLightRegion* lightRegion = &asset->lightRegion[i]; - - if (lightRegion->hulls) - { - lightRegion->hulls = reader.readArray(lightRegion->hullCount); - - for (unsigned int j = 0; j < lightRegion->hullCount; ++j) + auto* lightRegionHull = &lightRegion->hulls[j]; + if (lightRegionHull->axis) { - Game::GfxLightRegionHull* lightRegionHull = &lightRegion->hulls[j]; - - if (lightRegionHull->axis) - { - lightRegionHull->axis = reader.readArray(lightRegionHull->axisCount); - } + lightRegionHull->axis = reader.readArray(lightRegionHull->axisCount); } } } } + } - this->loadGfxWorldDpvsStatic(asset, &asset->dpvs, builder, &reader); + this->loadGfxWorldDpvsStatic(asset, &asset->dpvs, builder, &reader); - // Obsolete, IW3 has no support for that - if (asset->heroOnlyLights) - { - asset->heroOnlyLights = reader.readArray(asset->heroOnlyLightCount); - } + // Obsolete, IW3 has no support for that + if (asset->heroOnlyLights) + { + asset->heroOnlyLights = reader.readArray(asset->heroOnlyLightCount); } } void IGfxWorld::mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) { - Game::GfxWorld* asset = header.gfxWorld; - + auto* asset = header.gfxWorld; if (asset->draw.reflectionProbes) { for (unsigned int i = 0; i < asset->draw.reflectionProbeCount; ++i) @@ -455,7 +455,7 @@ namespace Assets if (asset->draw.lightmaps) { - for (int i = 0; i < asset->draw.lightmapCount; ++i) + for (auto i = 0; i < asset->draw.lightmapCount; ++i) { if (asset->draw.lightmaps[i].primary) { @@ -599,7 +599,7 @@ namespace Assets { buffer->align(Utils::Stream::ALIGN_4); - Game::GfxImage** imageDest = buffer->dest(); + auto** imageDest = buffer->dest(); buffer->saveArray(asset->reflectionProbes, asset->reflectionProbeCount); for (unsigned int i = 0; i < asset->reflectionProbeCount; ++i) @@ -648,13 +648,13 @@ namespace Assets buffer->align(Utils::Stream::ALIGN_4); - Game::GfxLightmapArray* lightmapArrayDestTable = buffer->dest(); + auto* lightmapArrayDestTable = buffer->dest(); buffer->saveArray(asset->lightmaps, asset->lightmapCount); for (int i = 0; i < asset->lightmapCount; ++i) { - Game::GfxLightmapArray* lightmapArrayDest = &lightmapArrayDestTable[i]; - Game::GfxLightmapArray* lightmapArray = &asset->lightmaps[i]; + auto* lightmapArrayDest = &lightmapArrayDestTable[i]; + auto* lightmapArray = &asset->lightmaps[i]; if (lightmapArray->primary) { @@ -854,13 +854,13 @@ namespace Assets SaveLogEnter("GfxSurface"); buffer->align(Utils::Stream::ALIGN_4); - Game::GfxSurface* destSurfaceTable = buffer->dest(); + auto* destSurfaceTable = buffer->dest(); buffer->saveArray(asset->surfaces, world->surfaceCount); for (unsigned int i = 0; i < world->surfaceCount; ++i) { - Game::GfxSurface* surface = &asset->surfaces[i]; - Game::GfxSurface* destSurface = &destSurfaceTable[i]; + auto* surface = &asset->surfaces[i]; + auto* destSurface = &destSurfaceTable[i]; if (surface->material) { @@ -890,13 +890,13 @@ namespace Assets SaveLogEnter("GfxStaticModelDrawInst"); buffer->align(Utils::Stream::ALIGN_4); - Game::GfxStaticModelDrawInst* destModelTable = buffer->dest(); + auto* destModelTable = buffer->dest(); buffer->saveArray(asset->smodelDrawInsts, asset->smodelCount); for (unsigned int i = 0; i < asset->smodelCount; ++i) { - Game::GfxStaticModelDrawInst* model = &asset->smodelDrawInsts[i]; - Game::GfxStaticModelDrawInst* destModel = &destModelTable[i]; + auto* model = &asset->smodelDrawInsts[i]; + auto* destModel = &destModelTable[i]; if (model->model) { @@ -986,8 +986,8 @@ namespace Assets Utils::Stream* buffer = builder->getBuffer(); SaveLogEnter("GfxWorld"); - Game::GfxWorld* asset = header.gfxWorld; - Game::GfxWorld* dest = buffer->dest(); + auto* asset = header.gfxWorld; + auto* dest = buffer->dest(); buffer->save(asset); buffer->pushBlock(Game::XFILE_BLOCK_VIRTUAL); @@ -1012,13 +1012,13 @@ namespace Assets SaveLogEnter("GfxSky"); buffer->align(Utils::Stream::ALIGN_4); - Game::GfxSky* destSkyTable = buffer->dest(); + auto* destSkyTable = buffer->dest(); buffer->saveArray(asset->skies, asset->skyCount); for (int i = 0; i < asset->skyCount; ++i) { - Game::GfxSky* destSky = &destSkyTable[i]; - Game::GfxSky* sky = &asset->skies[i]; + auto* destSky = &destSkyTable[i]; + auto* sky = &asset->skies[i]; if (sky->skyStartSurfs) { @@ -1059,13 +1059,13 @@ namespace Assets SaveLogEnter("GfxCellTree"); buffer->align(Utils::Stream::ALIGN_128); - Game::GfxCellTree* destCellTreeTable = buffer->dest(); + auto* destCellTreeTable = buffer->dest(); buffer->saveArray(asset->aabbTrees, cellCount); for (int i = 0; i < cellCount; ++i) { - Game::GfxCellTree* destCellTree = &destCellTreeTable[i]; - Game::GfxCellTree* cellTree = &asset->aabbTrees[i]; + auto* destCellTree = &destCellTreeTable[i]; + auto* cellTree = &asset->aabbTrees[i]; if (cellTree->aabbTree) { @@ -1073,7 +1073,7 @@ namespace Assets SaveLogEnter("GfxAabbTree"); buffer->align(Utils::Stream::ALIGN_4); - Game::GfxAabbTree* destAabbTreeTable = buffer->dest(); + auto* destAabbTreeTable = buffer->dest(); buffer->saveArray(cellTree->aabbTree, asset->aabbTreeCounts[i].aabbTreeCount); // ok this one is based on some assumptions because the actual count is this @@ -1083,8 +1083,8 @@ namespace Assets for (int j = 0; j < asset->aabbTreeCounts[i].aabbTreeCount; ++j) { - Game::GfxAabbTree* destAabbTree = &destAabbTreeTable[j]; - Game::GfxAabbTree* aabbTree = &cellTree->aabbTree[j]; + auto* destAabbTree = &destAabbTreeTable[j]; + auto* aabbTree = &cellTree->aabbTree[j]; if (aabbTree->smodelIndexes) { @@ -1122,13 +1122,13 @@ namespace Assets SaveLogEnter("GfxCell"); buffer->align(Utils::Stream::ALIGN_4); - Game::GfxCell* destCellTable = buffer->dest(); + auto* destCellTable = buffer->dest(); buffer->saveArray(asset->cells, cellCount); for (int i = 0; i < cellCount; ++i) { - Game::GfxCell* destCell = &destCellTable[i]; - Game::GfxCell* cell = &asset->cells[i]; + auto* destCell = &destCellTable[i]; + auto* cell = &asset->cells[i]; if (cell->portals) { @@ -1136,13 +1136,13 @@ namespace Assets SaveLogEnter("GfxPortal"); buffer->align(Utils::Stream::ALIGN_4); - Game::GfxPortal* destPortalTable = buffer->dest(); + auto* destPortalTable = buffer->dest(); buffer->saveArray(cell->portals, cell->portalCount); for (int j = 0; j < cell->portalCount; ++j) { - Game::GfxPortal* destPortal = &destPortalTable[j]; - Game::GfxPortal* portal = &cell->portals[j]; + auto* destPortal = &destPortalTable[j]; + auto* portal = &cell->portals[j]; if (portal->vertices) { @@ -1189,13 +1189,13 @@ namespace Assets SaveLogEnter("MaterialMemory"); buffer->align(Utils::Stream::ALIGN_4); - Game::MaterialMemory* destMaterialMemoryTable = buffer->dest(); + auto* destMaterialMemoryTable = buffer->dest(); buffer->saveArray(asset->materialMemory, asset->materialMemoryCount); for (int i = 0; i < asset->materialMemoryCount; ++i) { - Game::MaterialMemory* destMaterialMemory = &destMaterialMemoryTable[i]; - Game::MaterialMemory* materialMemory = &asset->materialMemory[i]; + auto* destMaterialMemory = &destMaterialMemoryTable[i]; + auto* materialMemory = &asset->materialMemory[i]; if (materialMemory->material) { @@ -1284,13 +1284,13 @@ namespace Assets SaveLogEnter("GfxShadowGeometry"); buffer->align(Utils::Stream::ALIGN_4); - Game::GfxShadowGeometry* destShadowGeometryTable = buffer->dest(); + auto* destShadowGeometryTable = buffer->dest(); buffer->saveArray(asset->shadowGeom, asset->primaryLightCount); for (unsigned int i = 0; i < asset->primaryLightCount; ++i) { - Game::GfxShadowGeometry* destShadowGeometry = &destShadowGeometryTable[i]; - Game::GfxShadowGeometry* shadowGeometry = &asset->shadowGeom[i]; + auto* destShadowGeometry = &destShadowGeometryTable[i]; + auto* shadowGeometry = &asset->shadowGeom[i]; if (shadowGeometry->sortedSurfIndex) { @@ -1317,13 +1317,13 @@ namespace Assets SaveLogEnter("GfxLightRegion"); buffer->align(Utils::Stream::ALIGN_4); - Game::GfxLightRegion* destLightRegionTable = buffer->dest(); + auto* destLightRegionTable = buffer->dest(); buffer->saveArray(asset->lightRegion, asset->primaryLightCount); for (unsigned int i = 0; i < asset->primaryLightCount; ++i) { - Game::GfxLightRegion* destLightRegion = &destLightRegionTable[i]; - Game::GfxLightRegion* lightRegion = &asset->lightRegion[i]; + auto* destLightRegion = &destLightRegionTable[i]; + auto* lightRegion = &asset->lightRegion[i]; if (lightRegion->hulls) { @@ -1331,13 +1331,13 @@ namespace Assets SaveLogEnter("GfxLightRegionHull"); buffer->align(Utils::Stream::ALIGN_4); - Game::GfxLightRegionHull* destLightRegionHullTable = buffer->dest(); + auto* destLightRegionHullTable = buffer->dest(); buffer->saveArray(lightRegion->hulls, lightRegion->hullCount); for (unsigned int j = 0; j < lightRegion->hullCount; ++j) { - Game::GfxLightRegionHull* destLightRegionHull = &destLightRegionHullTable[j]; - Game::GfxLightRegionHull* lightRegionHull = &lightRegion->hulls[j]; + auto* destLightRegionHull = &destLightRegionHullTable[j]; + auto* lightRegionHull = &lightRegion->hulls[j]; if (lightRegionHull->axis) { @@ -1373,7 +1373,6 @@ namespace Assets Utils::Stream::ClearPointer(&dest->heroOnlyLights); } - //buffer->setPointerAssertion(false); buffer->popBlock(); SaveLogExit(); } diff --git a/src/Components/Modules/AssetInterfaces/ILoadedSound.cpp b/src/Components/Modules/AssetInterfaces/ILoadedSound.cpp index d651e99a..5aa1533e 100644 --- a/src/Components/Modules/AssetInterfaces/ILoadedSound.cpp +++ b/src/Components/Modules/AssetInterfaces/ILoadedSound.cpp @@ -12,7 +12,7 @@ namespace Assets return; } - Game::LoadedSound* sound = builder->getAllocator()->allocate(); + auto* sound = builder->getAllocator()->allocate(); if (!sound) { Components::Logger::Print("Error allocating memory for sound structure!\n"); @@ -27,16 +27,16 @@ namespace Assets Utils::Stream::Reader reader(builder->getAllocator(), soundFile.getBuffer()); - unsigned int chunkIDBuffer = reader.read(); + auto chunkIDBuffer = reader.read(); if (chunkIDBuffer != 0x46464952) // RIFF { Components::Logger::Error(Game::ERR_FATAL, "Reading sound '{}' failed, header is invalid!", name); return; } - unsigned int chunkSize = reader.read(); + auto chunkSize = reader.read(); - unsigned int format = reader.read(); + auto format = reader.read(); if (format != 0x45564157) // WAVE { Components::Logger::Error(Game::ERR_FATAL, "Reading sound '{}' failed, header is invalid!", name); @@ -62,7 +62,10 @@ namespace Assets sound->sound.info.channels = reader.read(); sound->sound.info.rate = reader.read(); - sound->sound.info.samples = reader.read(); + + // We read samples later, this is byte rate we don't need it + reader.read(); + sound->sound.info.block_size = reader.read(); sound->sound.info.bits = reader.read(); @@ -76,6 +79,7 @@ namespace Assets case 0x61746164: // data sound->sound.info.data_len = chunkSize; + sound->sound.info.samples = chunkSize / (sound->sound.info.bits / 8); sound->sound.data = reader.readArray(chunkSize); break; @@ -102,9 +106,9 @@ namespace Assets { AssertSize(Game::LoadedSound, 44); - Utils::Stream* buffer = builder->getBuffer(); - Game::LoadedSound* asset = header.loadSnd; - Game::LoadedSound* dest = buffer->dest(); + auto* buffer = builder->getBuffer(); + auto* asset = header.loadSnd; + auto* dest = buffer->dest(); buffer->save(asset); buffer->pushBlock(Game::XFILE_BLOCK_VIRTUAL); diff --git a/src/Components/Modules/AssetInterfaces/IMaterial.cpp b/src/Components/Modules/AssetInterfaces/IMaterial.cpp index ebe509e2..7e05f08d 100644 --- a/src/Components/Modules/AssetInterfaces/IMaterial.cpp +++ b/src/Components/Modules/AssetInterfaces/IMaterial.cpp @@ -40,6 +40,8 @@ namespace Assets if (!header->data) this->loadJson(header, name, builder); // Check if we want to load a material from disk if (!header->data) this->loadBinary(header, name, builder); // Check if we want to load a material from disk (binary format) if (!header->data) this->loadNative(header, name, builder); // Check if there is a native one + + assert(header->data); } @@ -206,12 +208,17 @@ namespace Assets textureDef->u.image = nullptr; if (textureJson["image"].is_string()) { - textureDef->u.image = Components::AssetHandler::FindAssetForZone - ( - Game::XAssetType::ASSET_TYPE_IMAGE, + textureDef->u.image = Components::AssetHandler::FindAssetForZone( + Game::ASSET_TYPE_IMAGE, textureJson["image"].get(), builder ).image; + + assert(textureDef->u.image); + } + else + { + AssertUnreachable; } } } diff --git a/src/Components/Modules/AssetInterfaces/IMaterialTechniqueSet.cpp b/src/Components/Modules/AssetInterfaces/IMaterialTechniqueSet.cpp index a7519a12..c0f5253f 100644 --- a/src/Components/Modules/AssetInterfaces/IMaterialTechniqueSet.cpp +++ b/src/Components/Modules/AssetInterfaces/IMaterialTechniqueSet.cpp @@ -11,6 +11,8 @@ namespace Assets { if (!header->data) this->loadFromDisk(header, name, builder); // Check if we need to import a new one into the game if (!header->data) this->loadNative(header, name, builder); // Check if there is a native one + + assert(header->data); } void IMaterialTechniqueSet::loadNative(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* /*builder*/) @@ -28,6 +30,7 @@ namespace Assets *tech = nullptr; Components::Logger::Warning(Game::CON_CHANNEL_DONT_FILTER, "Missing technique '{}'\n", name); + AssertUnreachable; return; } diff --git a/src/Components/Modules/AssetInterfaces/IPhysPreset.cpp b/src/Components/Modules/AssetInterfaces/IPhysPreset.cpp index ad753a5c..63803140 100644 --- a/src/Components/Modules/AssetInterfaces/IPhysPreset.cpp +++ b/src/Components/Modules/AssetInterfaces/IPhysPreset.cpp @@ -1,4 +1,6 @@ #include +#include + #include "IPhysPreset.hpp" namespace Assets @@ -7,9 +9,9 @@ namespace Assets { AssertSize(Game::PhysPreset, 44); - Utils::Stream* buffer = builder->getBuffer(); - Game::PhysPreset* asset = header.physPreset; - Game::PhysPreset* dest = buffer->dest(); + auto* buffer = builder->getBuffer(); + auto* asset = header.physPreset; + auto* dest = buffer->dest(); buffer->save(asset); buffer->pushBlock(Game::XFILE_BLOCK_VIRTUAL); @@ -28,4 +30,54 @@ namespace Assets buffer->popBlock(); } + + void IPhysPreset::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) + { + loadFromDisk(header, name, builder); + } + + void IPhysPreset::loadFromDisk(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) + { + Components::FileSystem::File physPresetFile(std::format("physpreset/{}.iw4x.json", name)); + auto* asset = builder->getAllocator()->allocate(); + + if (physPresetFile.exists()) + { + nlohmann::json physPresetJson; + try + { + physPresetJson = nlohmann::json::parse(physPresetFile.getBuffer()); + } + catch (const std::exception& e) + { + Components::Logger::PrintError(Game::CON_CHANNEL_ERROR, "Invalid JSON for physpreset {}! {}", name, e.what()); + return; + } + + try + { + asset->name = builder->getAllocator()->duplicateString(physPresetJson["name"].get()); + asset->type = physPresetJson["type"].get(); + asset->bounce = physPresetJson["bounce"].get(); + asset->mass = physPresetJson["mass"].get(); + asset->friction = physPresetJson["friction"].get(); + asset->bulletForceScale = physPresetJson["bulletForceScale"].get(); + asset->explosiveForceScale = physPresetJson["explosiveForceScale"].get(); + asset->sndAliasPrefix = builder->getAllocator()->duplicateString(physPresetJson["sndAliasPrefix"].get()); + asset->piecesSpreadFraction = physPresetJson["piecesSpreadFraction"].get(); + asset->piecesUpwardVelocity = physPresetJson["piecesUpwardVelocity"].get(); + asset->tempDefaultToCylinder = physPresetJson["tempDefaultToCylinder"].get(); + asset->perSurfaceSndAlias = physPresetJson["perSurfaceSndAlias"].get(); + + assert(asset->mass > std::numeric_limits::epsilon()); + } + catch (const std::exception& e) + { + Components::Logger::PrintError(Game::CON_CHANNEL_ERROR, "Malformed JSON for physpreset {}! {}", name, e.what()); + return; + } + } + + header->physPreset = asset; + } } diff --git a/src/Components/Modules/AssetInterfaces/IPhysPreset.hpp b/src/Components/Modules/AssetInterfaces/IPhysPreset.hpp index f3939a08..47fb85e8 100644 --- a/src/Components/Modules/AssetInterfaces/IPhysPreset.hpp +++ b/src/Components/Modules/AssetInterfaces/IPhysPreset.hpp @@ -8,5 +8,7 @@ namespace Assets Game::XAssetType getType() override { return Game::XAssetType::ASSET_TYPE_PHYSPRESET; } void save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override; + void load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) override; + void loadFromDisk(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder); }; } diff --git a/src/Components/Modules/AssetInterfaces/ISndCurve.cpp b/src/Components/Modules/AssetInterfaces/ISndCurve.cpp index 1a7d0f01..3f418a16 100644 --- a/src/Components/Modules/AssetInterfaces/ISndCurve.cpp +++ b/src/Components/Modules/AssetInterfaces/ISndCurve.cpp @@ -1,4 +1,6 @@ #include +#include + #include "ISndCurve.hpp" namespace Assets @@ -7,9 +9,9 @@ namespace Assets { AssertSize(Game::SndCurve, 136); - Utils::Stream* buffer = builder->getBuffer(); - Game::SndCurve* asset = header.sndCurve; - Game::SndCurve* dest = buffer->dest(); + auto* buffer = builder->getBuffer(); + auto* asset = header.sndCurve; + auto* dest = buffer->dest(); buffer->save(asset); buffer->pushBlock(Game::XFILE_BLOCK_VIRTUAL); @@ -22,4 +24,48 @@ namespace Assets buffer->popBlock(); } + + void ISndCurve::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) + { + Components::FileSystem::File sndCurveFile(std::format("sndcurve/{}.iw4x.json", name)); + + if (!sndCurveFile.exists()) + { + Components::Logger::PrintError(Game::CON_CHANNEL_ERROR, "Missing file for sndcurve {}!", name); + return; + } + + nlohmann::json sndCurveJson; + try + { + sndCurveJson = nlohmann::json::parse(sndCurveFile.getBuffer()); + } + catch (const std::exception& e) + { + Components::Logger::PrintError(Game::CON_CHANNEL_ERROR, "Invalid JSON for sndcurve {}! {}", name, e.what()); + return; + } + + auto* sndCurve = builder->getAllocator()->allocate(); + try + { + sndCurve->filename = builder->getAllocator()->duplicateString(sndCurveJson["filename"].get()); + sndCurve->knotCount = sndCurveJson["knotCount"].get(); + + for (auto side = 0; side < 2; side++) + { + for (auto knot = 0; knot < 16; knot++) + { + sndCurve->knots[knot][side] = sndCurveJson["knots"][knot][side].get(); + } + } + } + catch (const std::exception& e) + { + Components::Logger::PrintError(Game::CON_CHANNEL_ERROR, "Malformed JSON for sndcurve {}! {}", name, e.what()); + return; + } + + header->sndCurve = sndCurve; + } } diff --git a/src/Components/Modules/AssetInterfaces/ISndCurve.hpp b/src/Components/Modules/AssetInterfaces/ISndCurve.hpp index 0b0d7564..0d2cc831 100644 --- a/src/Components/Modules/AssetInterfaces/ISndCurve.hpp +++ b/src/Components/Modules/AssetInterfaces/ISndCurve.hpp @@ -8,5 +8,6 @@ namespace Assets Game::XAssetType getType() override { return Game::XAssetType::ASSET_TYPE_SOUND_CURVE; } void save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override; + void load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) override; }; } diff --git a/src/Components/Modules/AssetInterfaces/IXAnimParts.cpp b/src/Components/Modules/AssetInterfaces/IXAnimParts.cpp index 0bd5e2ce..44104a43 100644 --- a/src/Components/Modules/AssetInterfaces/IXAnimParts.cpp +++ b/src/Components/Modules/AssetInterfaces/IXAnimParts.cpp @@ -1,7 +1,7 @@ #include #include "IXAnimParts.hpp" -#define IW4X_ANIM_VERSION 1 +#define IW4X_ANIM_VERSION 2 namespace Assets { @@ -9,100 +9,148 @@ namespace Assets { Components::FileSystem::File animFile(std::format("xanim/{}.iw4xAnim", name)); - if (animFile.exists()) + if (!animFile.exists()) { - Utils::Stream::Reader reader(builder->getAllocator(), animFile.getBuffer()); + return; + } - __int64 magic = reader.read<__int64>(); - if (std::memcmp(&magic, "IW4xAnim", 8)) + Utils::Stream::Reader reader(builder->getAllocator(), animFile.getBuffer()); + + auto magic = reader.read(); + if (std::memcmp(&magic, "IW4xAnim", 8) != 0) + { + Components::Logger::Error(Game::ERR_FATAL, "Reading animation '{}' failed, header is invalid!", name); + } + + int version = reader.read(); + if (version > IW4X_ANIM_VERSION) + { + Components::Logger::Error(Game::ERR_FATAL, "Reading animation '{}' failed, expected version is {}, but it was {}!", name, IW4X_ANIM_VERSION, version); + } + + auto* xanim = reader.readArray(); + if (!xanim) + { + return; + } + + if (xanim->name) + { + xanim->name = reader.readCString(); + } + + if (xanim->names) + { + xanim->names = builder->getAllocator()->allocateArray(xanim->boneCount[Game::PART_TYPE_ALL]); + for (int i = 0; i < xanim->boneCount[Game::PART_TYPE_ALL]; ++i) { - Components::Logger::Error(Game::ERR_FATAL, "Reading animation '{}' failed, header is invalid!", name); + xanim->names[i] = static_cast(Game::SL_GetString(reader.readCString(), 0)); } + } - int version = reader.read(); - if (version != IW4X_ANIM_VERSION) + if (xanim->notify) + { + xanim->notify = reader.readArray(xanim->notifyCount); + + for (int i = 0; i < xanim->notifyCount; ++i) { - Components::Logger::Error(Game::ERR_FATAL, "Reading animation '{}' failed, expected version is {}, but it was {}!", name, IW4X_ANIM_VERSION, version); + xanim->notify[i].name = static_cast(Game::SL_GetString(reader.readCString(), 0)); } + } - Game::XAnimParts* xanim = reader.readArray(); + if (xanim->dataByte) + { + xanim->dataByte = reader.readArray(xanim->dataByteCount); + } - if (xanim) + if (xanim->dataShort) + { + xanim->dataShort = reader.readArray(xanim->dataShortCount); + } + + if (xanim->dataInt) + { + xanim->dataInt = reader.readArray(xanim->dataIntCount); + } + + if (xanim->randomDataByte) + { + xanim->randomDataByte = reader.readArray(xanim->randomDataByteCount); + } + + if (xanim->randomDataShort) + { + xanim->randomDataShort = reader.readArray(xanim->randomDataShortCount); + } + + if (xanim->randomDataInt) + { + xanim->randomDataInt = reader.readArray(xanim->randomDataIntCount); + } + + if (xanim->indices.data) + { + if (xanim->numframes < 256) { - if (xanim->name) - { - xanim->name = reader.readCString(); - } + xanim->indices._1 = reader.readArray(xanim->indexCount); + } + else + { + xanim->indices._2 = reader.readArray(xanim->indexCount); + } + } - if (xanim->names) + if (version > 1) + { + if (xanim->deltaPart) + { + xanim->deltaPart = reader.readObject(); + auto delta = xanim->deltaPart; + if (delta->trans) { - xanim->names = builder->getAllocator()->allocateArray(xanim->boneCount[Game::PART_TYPE_ALL]); - for (int i = 0; i < xanim->boneCount[Game::PART_TYPE_ALL]; ++i) + delta->trans = reader.readObject(); + if (delta->trans->size) { - xanim->names[i] = static_cast(Game::SL_GetString(reader.readCString(), 0)); - } - } + delta->trans->u.frames = reader.read(); - if (xanim->notify) - { - xanim->notify = reader.readArray(xanim->notifyCount); + if (xanim->numframes > 0xFF) + { + auto indices2 = reader.readArray(delta->trans->size + 1); + std::memcpy(delta->trans->u.frames.indices._2, indices2, sizeof(short) * (delta->trans->size + 1)); + } + else + { + auto indices1 = reader.readArray(delta->trans->size + 1); + std::memcpy(delta->trans->u.frames.indices._1, indices1, delta->trans->size + 1); + } - for (int i = 0; i < xanim->notifyCount; ++i) - { - xanim->notify[i].name = static_cast(Game::SL_GetString(reader.readCString(), 0)); - } - } - - if (xanim->dataByte) - { - xanim->dataByte = reader.readArray(xanim->dataByteCount); - } - - if (xanim->dataShort) - { - xanim->dataShort = reader.readArray(xanim->dataShortCount); - } - - if (xanim->dataInt) - { - xanim->dataInt = reader.readArray(xanim->dataIntCount); - } - - if (xanim->randomDataByte) - { - xanim->randomDataByte = reader.readArray(xanim->randomDataByteCount); - } - - if (xanim->randomDataShort) - { - xanim->randomDataShort = reader.readArray(xanim->randomDataShortCount); - } - - if (xanim->randomDataInt) - { - xanim->randomDataInt = reader.readArray(xanim->randomDataIntCount); - } - - if (xanim->indices.data) - { - if (xanim->numframes < 256) - { - xanim->indices._1 = reader.readArray(xanim->indexCount); + if (delta->trans->u.frames.frames._1) + { + if (delta->trans->smallTrans) + { + delta->trans->u.frames.frames._1 = reinterpret_cast(3, (delta->trans->size + 1)); + } + else + { + delta->trans->u.frames.frames._2 = reinterpret_cast(6, (delta->trans->size + 1)); + } + } } else { - xanim->indices._2 = reader.readArray(xanim->indexCount); + auto frames = reader.readObject(); + std::memcpy(delta->trans->u.frame0, frames, sizeof(Game::vec3_t)); } } - - if (!reader.end()) - { - Components::Logger::Error(Game::ERR_FATAL, "Reading animation '{}' failed, remaining raw data found!", name); - } - - header->parts = xanim; } } + + if (!reader.end()) + { + Components::Logger::Error(Game::ERR_FATAL, "Reading animation '{}' failed, remaining raw data found!", name); + } + + header->parts = xanim; } void IXAnimParts::mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) diff --git a/src/Components/Modules/AssetInterfaces/IXModel.cpp b/src/Components/Modules/AssetInterfaces/IXModel.cpp index c8173537..4cbe02c1 100644 --- a/src/Components/Modules/AssetInterfaces/IXModel.cpp +++ b/src/Components/Modules/AssetInterfaces/IXModel.cpp @@ -1,7 +1,7 @@ #include #include "IXModel.hpp" -#define IW4X_MODEL_VERSION 8 +#define IW4X_MODEL_VERSION 9 namespace Assets { @@ -250,34 +250,32 @@ namespace Assets if (geom->brushWrapper) { - Game::BrushWrapper* brush = reader.readObject(); + Game::BrushWrapper* brush = reader.readArrayOnce(); geom->brushWrapper = brush; { if (brush->brush.sides) { - brush->brush.sides = reader.readArray(brush->brush.numsides); + brush->brush.sides = reader.readArrayOnce(brush->brush.numsides); for (unsigned short j = 0; j < brush->brush.numsides; ++j) { Game::cbrushside_t* side = &brush->brush.sides[j]; - // TODO: Add pointer support if (side->plane) { - side->plane = reader.readObject(); + side->plane = reader.readArrayOnce(); } } } if (brush->brush.baseAdjacentSide) { - brush->brush.baseAdjacentSide = reader.readArray(brush->totalEdgeCount); + brush->brush.baseAdjacentSide = reader.readArrayOnce(brush->totalEdgeCount); } } - // TODO: Add pointer support if (brush->planes) { - brush->planes = reader.readArray(brush->brush.numsides); + brush->planes = reader.readArrayOnce(brush->brush.numsides); } } } diff --git a/src/Components/Modules/AssetInterfaces/IclipMap_t.cpp b/src/Components/Modules/AssetInterfaces/IclipMap_t.cpp index f0524227..557eeb8b 100644 --- a/src/Components/Modules/AssetInterfaces/IclipMap_t.cpp +++ b/src/Components/Modules/AssetInterfaces/IclipMap_t.cpp @@ -1,7 +1,8 @@ #include #include "IclipMap_t.hpp" +#include "Utils/Json.hpp" -#define IW4X_CLIPMAP_VERSION 2 +#define IW4X_CLIPMAP_VERSION 3 namespace Assets { @@ -37,7 +38,7 @@ namespace Assets buffer->align(Utils::Stream::ALIGN_4); // not sure if this is needed but both brushside and brushedge need it and it can't hurt - for (int i = 0; i < asset->planeCount; ++i) + for (size_t i = 0; i < asset->planeCount; ++i) { builder->storePointer(&asset->planes[i]); buffer->save(&asset->planes[i]); @@ -192,7 +193,13 @@ namespace Assets SaveLogEnter("cLeafBrush_t"); buffer->align(Utils::Stream::ALIGN_2); - buffer->saveArray(asset->leafbrushes, asset->numLeafBrushes); + + for (size_t i = 0; i < asset->numLeafBrushes; i++) + { + builder->storePointer(&asset->leafbrushes[i]); + buffer->saveObject(asset->leafbrushes[i]); + } + Utils::Stream::ClearPointer(&dest->leafbrushes); SaveLogExit(); @@ -213,6 +220,9 @@ namespace Assets { if (node[i].data.leaf.brushes) { + assert(node[i].data.leaf.brushes >= asset->leafbrushes); + assert(node[i].data.leaf.brushes < asset->leafbrushes + sizeof(unsigned short) * asset->numLeafBrushes); + if (builder->hasPointer(node[i].data.leaf.brushes)) { node[i].data.leaf.brushes = builder->getPointer(node[i].data.leaf.brushes); @@ -278,7 +288,7 @@ namespace Assets buffer->align(Utils::Stream::ALIGN_4); - for (int i = 0; i < asset->borderCount; ++i) + for (size_t i = 0; i < asset->borderCount; ++i) { builder->storePointer(&asset->borders[i]); buffer->save(&asset->borders[i]); @@ -568,6 +578,13 @@ namespace Assets } void IclipMap_t::load(Game::XAssetHeader* header, const std::string& _name, Components::ZoneBuilder::Zone* builder) + { + if (!header->data) loadFromJSON(header, _name, builder); + if (!header->data) loadBinary(header, _name, builder); + assert(header->data); + } + + void IclipMap_t::loadBinary(Game::XAssetHeader* header, const std::string& _name, Components::ZoneBuilder::Zone* builder) { std::string name = _name; Utils::String::Replace(name, "maps/mp/", ""); @@ -680,7 +697,7 @@ namespace Assets clipMap->brushsides = builder->getAllocator()->allocateArray(clipMap->numBrushSides); for (unsigned int i = 0; i < clipMap->numBrushSides; ++i) { - int planeIndex = reader.read(); + auto planeIndex = reader.read(); if (planeIndex < 0 || planeIndex >= clipMap->planeCount) { Components::Logger::Error(Game::ERR_FATAL, "invalid plane index"); @@ -688,14 +705,17 @@ namespace Assets } clipMap->brushsides[i].plane = &clipMap->planes[planeIndex]; clipMap->brushsides[i].materialNum = static_cast(reader.read()); // materialNum + + assert(clipMap->brushsides[i].materialNum < clipMap->numMaterials); + clipMap->brushsides[i].firstAdjacentSideOffset = static_cast(reader.read()); // firstAdjacentSide - clipMap->brushsides[i].edgeCount = reader.read(); // edgeCount + clipMap->brushsides[i].edgeCount = reader.read(); // edgeCount } } if (clipMap->numBrushEdges) { - clipMap->brushEdges = reader.readArray(clipMap->numBrushEdges); + clipMap->brushEdges = reader.readArray(clipMap->numBrushEdges); } if (clipMap->numNodes) @@ -703,7 +723,7 @@ namespace Assets clipMap->nodes = builder->getAllocator()->allocateArray(clipMap->numNodes); for (unsigned int i = 0; i < clipMap->numNodes; ++i) { - int planeIndex = reader.read(); + auto planeIndex = reader.read(); if (planeIndex < 0 || planeIndex >= clipMap->planeCount) { Components::Logger::Error(Game::ERR_FATAL, "invalid plane index\n"); @@ -729,7 +749,7 @@ namespace Assets if (clipMap->leafbrushNodes[i].leafBrushCount > 0) { - clipMap->leafbrushNodes[i].data.leaf.brushes = reader.readArray(clipMap->leafbrushNodes[i].leafBrushCount); + clipMap->leafbrushNodes[i].data.leaf.brushes = reader.readArrayOnce(clipMap->leafbrushNodes[i].leafBrushCount); } } } @@ -752,7 +772,21 @@ namespace Assets if (clipMap->triCount) { clipMap->triIndices = reader.readArray(clipMap->triCount * 3); - clipMap->triEdgeIsWalkable = reader.readArray(4 * ((3 * clipMap->triCount + 31) >> 5)); + clipMap->triEdgeIsWalkable = reader.readArray(4 * ((3 * clipMap->triCount + 31) >> 5)); + + // check + for (size_t i = 0; i < clipMap->triCount; i++) + { + for (size_t x = 0; x < 3; x++) + { + auto vIndex = clipMap->triIndices[i * 3 + x]; + assert(clipMap->vertCount > vIndex); + assert(vIndex >= 0); + assert(vIndex != clipMap->triIndices[i * 3 + ((x + 1) % 3)]); + assert(vIndex != clipMap->triIndices[i * 3 + ((x + 2) % 3)]); + (void)vIndex; + } + } } if (clipMap->borderCount) @@ -765,13 +799,13 @@ namespace Assets clipMap->partitions = builder->getAllocator()->allocateArray(clipMap->partitionCount); for (int i = 0; i < clipMap->partitionCount; ++i) { - clipMap->partitions[i].triCount = reader.read(); - clipMap->partitions[i].borderCount = reader.read(); + clipMap->partitions[i].triCount = reader.read(); + clipMap->partitions[i].borderCount = reader.read(); clipMap->partitions[i].firstTri = reader.read(); if (clipMap->partitions[i].borderCount > 0) { - int index = reader.read(); + auto index = reader.read(); if (index < 0 || index > clipMap->borderCount) { Components::Logger::Error(Game::ERR_FATAL, "invalid border index\n"); @@ -779,17 +813,47 @@ namespace Assets } clipMap->partitions[i].borders = &clipMap->borders[index]; } + + for (size_t j = 0; j < clipMap->partitions[i].triCount; j++) + { + assert(clipMap->partitions[i].firstTri + j >= 0); + assert(clipMap->partitions[i].firstTri + j < clipMap->triCount); + } } } if (clipMap->aabbTreeCount) { clipMap->aabbTrees = reader.readArray(clipMap->aabbTreeCount); + + // Check + for (size_t i = 0; i < clipMap->aabbTreeCount; i++) + { + assert(clipMap->aabbTrees->materialIndex >= 0); + assert(clipMap->aabbTrees->materialIndex < clipMap->numMaterials); + assert(clipMap->materials[clipMap->aabbTrees->materialIndex].contents); + } + } if (clipMap->numSubModels) { clipMap->cmodels = reader.readArray(clipMap->numSubModels); + + // Check + for (size_t i = 0; i < clipMap->numSubModels; i++) + { + auto cmodel = clipMap->cmodels[i]; + + for (size_t j = 0; j < cmodel.leaf.collAabbCount; j++) + { + auto index = cmodel.leaf.firstCollAabbIndex + j; + assert(index >= 0); + assert(index < clipMap->aabbTreeCount); + assert(cmodel.leaf.brushContents); + (void)index; + } + } } if (clipMap->numBrushes) @@ -799,6 +863,12 @@ namespace Assets for (int i = 0; i < clipMap->numBrushes; ++i) { clipMap->brushes[i].numsides = reader.read() & 0xFFFF; // todo: check for overflow here + + if (version >= 3) + { + clipMap->brushes[i].glassPieceIndex = reader.read(); + } + if (clipMap->brushes[i].numsides > 0) { auto index = reader.read(); @@ -815,15 +885,37 @@ namespace Assets } auto index = reader.read(); - if (index > clipMap->numBrushEdges) - { - Components::Logger::Error(Game::ERR_FATAL, "invalid edge index\n"); - return; - } - clipMap->brushes[i].baseAdjacentSide = &clipMap->brushEdges[index]; - char* tmp = reader.readArray(12); - memcpy(&clipMap->brushes[i].axialMaterialNum, tmp, 12); + if (index == -1) + { + clipMap->brushes[i].baseAdjacentSide = nullptr; // Happens + } + else + { + if (index > clipMap->numBrushEdges) + { + Components::Logger::Error(Game::ERR_FATAL, "invalid edge index\n"); + return; + } + + clipMap->brushes[i].baseAdjacentSide = &clipMap->brushEdges[index]; + assert(*clipMap->brushes[i].baseAdjacentSide >= 0); + assert(*clipMap->brushes[i].baseAdjacentSide < clipMap->numBrushSides); + } + + for (size_t x = 0; x < 2; x++) + { + for (size_t y = 0; y < 3; y++) + { + auto material = reader.read(); + + assert(material >= 0); + assert(material < clipMap->numMaterials); + assert(clipMap->materials[material].contents); + + clipMap->brushes[i].axialMaterialNum[x][y] = material; + } + } //todo check for overflow for (int r = 0; r < 2; ++r) @@ -834,12 +926,24 @@ namespace Assets } } - tmp = reader.readArray(6); - memcpy(&clipMap->brushes[i].edgeCount, tmp, 6); + for (int r = 0; r < 2; ++r) + { + for (int c = 0; c < 3; ++c) + { + clipMap->brushes[i].edgeCount[r][c] = reader.read(); + } + } } clipMap->brushBounds = reader.readArray(clipMap->numBrushes); clipMap->brushContents = reader.readArray(clipMap->numBrushes); + + for (size_t i = 0; i < clipMap->numBrushes; i++) + { + assert(clipMap->brushContents[i]); + } + + assert(clipMap->brushContents); } for (int x = 0; x < 2; ++x) @@ -850,11 +954,15 @@ namespace Assets for (int i = 0; i < clipMap->dynEntCount[x]; ++i) { clipMap->dynEntDefList[x][i].type = reader.read(); + assert(clipMap->dynEntDefList[x][i].type != Game::DynEntityType::DYNENT_TYPE_INVALID); + clipMap->dynEntDefList[x][i].pose = reader.read(); std::string tempName = reader.readString(); if (tempName != "NONE"s) { - clipMap->dynEntDefList[x][i].xModel = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_XMODEL, tempName, builder).model; + auto xModel = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_XMODEL, tempName, builder).model; + clipMap->dynEntDefList[x][i].xModel = xModel; + assert(xModel); } clipMap->dynEntDefList[x][i].brushModel = reader.read(); @@ -863,13 +971,17 @@ namespace Assets tempName = reader.readString(); if (tempName != "NONE"s) { - clipMap->dynEntDefList[x][i].destroyFx = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_FX, tempName, builder).fx; + auto fx = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_FX, tempName, builder).fx; + clipMap->dynEntDefList[x][i].destroyFx = fx; + assert(fx); } tempName = reader.readString(); if (tempName != "NONE"s) { - clipMap->dynEntDefList[x][i].physPreset = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_PHYSPRESET, tempName, builder).physPreset; + auto preset = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_PHYSPRESET, tempName, builder).physPreset; + clipMap->dynEntDefList[x][i].physPreset = preset; + assert(preset); } clipMap->dynEntDefList[x][i].health = reader.read(); @@ -885,13 +997,53 @@ namespace Assets clipMap->mapEnts = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_MAP_ENTS, std::format("maps/mp/{}.d3dbsp", name), builder).mapEnts; // add triggers to mapEnts - if (version >= 2) { - if (clipMap->numSubModels > 0) { + if (version >= 3) + { + auto trigger = &clipMap->mapEnts->trigger; + trigger->count = reader.read(); + trigger->models = builder->getAllocator()->allocateArray(trigger->count); + for (size_t i = 0; i < trigger->count; i++) + { + trigger->models[i] = reader.read(); + } + + trigger->hullCount = reader.read(); + trigger->hulls = builder->getAllocator()->allocateArray(trigger->hullCount); + for (size_t i = 0; i < trigger->hullCount; i++) + { + trigger->hulls[i] = reader.read(); + } + + trigger->slabCount = reader.read(); + trigger->slabs = builder->getAllocator()->allocateArray(trigger->slabCount); + for (size_t i = 0; i < trigger->slabCount; i++) + { + trigger->slabs[i] = reader.read(); + } + + // Check +#if DEBUG + for (size_t i = 0; i < trigger->count; i++) + { + assert(trigger->models[i].firstHull < trigger->hullCount); + } + + for (size_t i = 0; i < trigger->hullCount; i++) + { + assert(trigger->hulls[i].firstSlab < trigger->slabCount); + } +#endif + } + // This code is wrong but we keep it for backwards compatability with old versions of iw3xport + else if (version == 2) + { + if (clipMap->numSubModels > 0) + { clipMap->mapEnts->trigger.count = clipMap->numSubModels; clipMap->mapEnts->trigger.hullCount = clipMap->numSubModels; - Game::TriggerHull* hulls = builder->getAllocator()->allocateArray(clipMap->mapEnts->trigger.hullCount); - Game::TriggerModel* models = builder->getAllocator()->allocateArray(clipMap->mapEnts->trigger.count); + auto* hulls = builder->getAllocator()->allocateArray(clipMap->mapEnts->trigger.hullCount); + auto* models = builder->getAllocator()->allocateArray(clipMap->mapEnts->trigger.count); for (unsigned int i = 0; i < clipMap->numSubModels; ++i) { @@ -899,14 +1051,14 @@ namespace Assets hulls[i] = reader.read(); } - size_t slabCount = reader.read(); + auto slabCount = reader.read(); clipMap->mapEnts->trigger.slabCount = slabCount; - Game::TriggerSlab* slabs = builder->getAllocator()->allocateArray(clipMap->mapEnts->trigger.slabCount); - for (unsigned int i = 0; i < clipMap->mapEnts->trigger.slabCount; i++) { + auto* slabs = builder->getAllocator()->allocateArray(clipMap->mapEnts->trigger.slabCount); + for (unsigned int i = 0; i < clipMap->mapEnts->trigger.slabCount; i++) + { slabs[i] = reader.read(); } - clipMap->mapEnts->trigger.models = &models[0]; clipMap->mapEnts->trigger.hulls = &hulls[0]; clipMap->mapEnts->trigger.slabs = &slabs[0]; @@ -941,4 +1093,1094 @@ namespace Assets header->clipMap = clipMap; } + + + void IclipMap_t::loadFromJSON(Game::XAssetHeader* header, const std::string& _name, Components::ZoneBuilder::Zone* builder) + { + std::string name = _name; + Utils::String::Replace(name, "maps/mp/", ""); + Utils::String::Replace(name, ".d3dbsp", ""); + + Components::FileSystem::File clipMapFile(std::format("clipmap/{}.iw4x.json", name)); + + if (!clipMapFile.exists()) return; + + + nlohmann::json clipMapJson; + try + { + clipMapJson = nlohmann::json::parse(clipMapFile.getBuffer()); + } + catch (const std::exception& e) + { + Components::Logger::PrintError(Game::CON_CHANNEL_ERROR, "Invalid JSON for clipmap {}! {}", name, e.what()); + return; + } + + auto clipMap = builder->getAllocator()->allocate(); + + try + { + assert(clipMapJson["version"].get() <= IW4X_CLIPMAP_VERSION); + + clipMap->name = builder->getAllocator()->duplicateString(clipMapJson["name"].get()); + clipMap->isInUse = clipMapJson["isInUse"]; + + // planes + clipMap->planeCount = clipMapJson["planes"].size(); + clipMap->planes = clipMap->planeCount == 0 ? nullptr : builder->getAllocator()->allocateArray(clipMap->planeCount); + + for (size_t i = 0; i < clipMap->planeCount; i++) + { + auto plane = &clipMap->planes[i]; + Utils::Json::CopyArray(plane->normal, clipMapJson["planes"][i]["normal"], 3); + plane->dist = clipMapJson["planes"][i]["dist"]; + plane->type = clipMapJson["planes"][i]["type"].get(); + } + + // Smodel list + clipMap->numStaticModels = clipMapJson["staticModelList"].size(); + clipMap->staticModelList = clipMap->numStaticModels == 0 ? nullptr : builder->getAllocator()->allocateArray(clipMap->numStaticModels); + + for (size_t i = 0; i < clipMap->numStaticModels; i++) + { + auto model = &clipMap->staticModelList[i]; + auto jsonModel = clipMapJson["staticModelList"][i]; + + model->xmodel = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_XMODEL, jsonModel["xmodel"], builder).model; + + Utils::Json::CopyArray(model->origin, jsonModel["origin"], 3); + for (size_t j = 0; j < 3; j++) + { + Utils::Json::CopyArray(model->invScaledAxis[j], jsonModel["invScaledAxis"][j], 3); + } + + model->absBounds = Utils::Json::ReadBounds(jsonModel["absBounds"]); + } + + clipMap->numMaterials = clipMapJson["materials"].size(); + clipMap->materials = clipMap->numMaterials == 0 ? nullptr : builder->getAllocator()->allocateArray(clipMap->numMaterials); + + for (size_t i = 0; i < clipMap->numMaterials; i++) + { + auto material = &clipMap->materials[i]; + auto json_material = clipMapJson["materials"][i]; + + material->name = builder->getAllocator()->duplicateString(json_material["name"].get()); + material->surfaceFlags = json_material["surfaceFlags"]; + material->contents = json_material["contents"]; + } + + + clipMap->numBrushSides = clipMapJson["brushsides"].size(); + clipMap->brushsides = clipMap->numBrushSides == 0 ? nullptr : builder->getAllocator()->allocateArray(clipMap->numBrushSides); + + for (size_t i = 0; i < clipMap->numBrushSides; i++) + { + auto* brushside = &clipMap->brushsides[i]; + auto json_brushside = clipMapJson["brushsides"][i]; + + assert(clipMap->planes); + brushside->plane = &clipMap->planes[std::stoi(json_brushside["plane"].get().substr(1))]; + brushside->materialNum = json_brushside["materialNum"]; + brushside->firstAdjacentSideOffset = json_brushside["firstAdjacentSideOffset"].get(); + brushside->edgeCount = json_brushside["edgeCount"].get(); + } + + clipMap->numBrushEdges = clipMapJson["brushEdges"].size(); + clipMap->brushEdges = clipMap->numBrushEdges == 0 ? nullptr : builder->getAllocator()->allocateArray(clipMap->numBrushEdges); + Utils::Json::CopyArray(clipMap->brushEdges, clipMapJson["brushEdges"]); + + clipMap->numNodes = clipMapJson["nodes"].size(); + clipMap->nodes = clipMap->numNodes == 0 ? nullptr : builder->getAllocator()->allocateArray(clipMap->numNodes); + + for (size_t i = 0; i < clipMap->numNodes; i++) + { + auto node = &clipMap->nodes[i]; + auto jsonNode = clipMapJson["nodes"][i]; + + assert(clipMap->planes); + node->plane = &clipMap->planes[std::stoi(jsonNode["plane"].get().substr(1))]; + node->children[0] = jsonNode["children"][0]; + node->children[1] = jsonNode["children"][1]; + } + + + // LEaves + clipMap->numLeafs = clipMapJson["leafs"].size(); + clipMap->leafs = clipMap->numLeafs == 0 ? nullptr : builder->getAllocator()->allocateArray(clipMap->numLeafs); + + for (size_t i = 0; i < clipMap->numLeafs; i++) + { + auto leaf = &clipMap->leafs[i]; + auto jsonLeaf = clipMapJson["leafs"][i]; + + leaf->bounds = Utils::Json::ReadBounds(jsonLeaf["bounds"]); + leaf->firstCollAabbIndex = jsonLeaf["firstCollAabbIndex"]; + leaf->collAabbCount = jsonLeaf["collAabbCount"]; + leaf->brushContents = jsonLeaf["brushContents"]; + leaf->terrainContents = jsonLeaf["terrainContents"]; + leaf->leafBrushNode = jsonLeaf["leafBrushNode"]; + } + + // Leafbrushnodes + clipMap->leafbrushNodesCount = clipMapJson["leafbrushNodes"].size(); + clipMap->leafbrushNodes = clipMap->leafbrushNodesCount == 0 ? nullptr : builder->getAllocator()->allocateArray(clipMap->leafbrushNodesCount); + + // Leafbrushes + clipMap->numLeafBrushes = clipMapJson["leafbrushes"].size(); + clipMap->leafbrushes = clipMap->numLeafBrushes == 0 ? nullptr : builder->getAllocator()->allocateArray(clipMap->numLeafBrushes); + Utils::Json::CopyArray(clipMap->leafbrushes, clipMapJson["leafbrushes"]); + + clipMap->numLeafSurfaces = clipMapJson["leafsurfaces"].size(); + clipMap->leafsurfaces = clipMap->numLeafSurfaces == 0 ? nullptr : builder->getAllocator()->allocateArray(clipMap->numLeafSurfaces); + Utils::Json::CopyArray(clipMap->leafsurfaces, clipMapJson["leafsurfaces"]); + + clipMap->vertCount = clipMapJson["verts"].size(); + clipMap->verts = clipMap->vertCount == 0 ? nullptr : builder->getAllocator()->allocateArray(clipMap->vertCount); + for (size_t i = 0; i < clipMap->vertCount; i++) + { + Utils::Json::CopyArray(clipMap->verts[i], clipMapJson["verts"][i]); + } + + for (size_t i = 0; i < clipMap->leafbrushNodesCount; i++) + { + auto* lbn = &clipMap->leafbrushNodes[i]; + auto jsonLbn = clipMapJson["leafbrushNodes"][i]; + + lbn->axis = jsonLbn["axis"]; + lbn->leafBrushCount = jsonLbn["leafBrushCount"]; + lbn->contents = jsonLbn["contents"]; + + if (lbn->leafBrushCount > 0) + { + int index = std::stoi(jsonLbn["data"].get().substr(1)); + + assert(index < clipMap->numLeafBrushes); + assert(index >= 0); + + lbn->data.leaf.brushes = &clipMap->leafbrushes[index]; + assert(lbn->data.leaf.brushes); + } + else + { + lbn->data.children.dist = jsonLbn["data"]["dist"]; + lbn->data.children.range = jsonLbn["data"]["range"]; + Utils::Json::CopyArray(lbn->data.children.childOffset, jsonLbn["data"]["childOffset"]); + } + } + + // Tri indices + auto indiceCountJson = clipMapJson["triIndices"].size(); + clipMap->triCount = indiceCountJson; + clipMap->triIndices = clipMap->triCount == 0 ? nullptr : builder->getAllocator()->allocateArray(clipMap->triCount * 3); + + for (size_t i = 0; i < clipMap->triCount * 3; i +=3) + { + Utils::Json::CopyArray(&clipMap->triIndices[i], clipMapJson["triIndices"][i/3]); + } + + // Walkable + auto walkableCount = 4 * ((3 * clipMap->triCount + 31) >> 5) * 3; + assert(clipMapJson["triEdgeIsWalkable"].size() == walkableCount); + clipMap->triEdgeIsWalkable = walkableCount == 0 ? nullptr : builder->getAllocator()->allocateArray(walkableCount); + Utils::Json::CopyArray(clipMap->triEdgeIsWalkable, clipMapJson["triEdgeIsWalkable"]); + + // Borders + clipMap->borderCount = clipMapJson["borders"].size(); + clipMap->borders = clipMap->borderCount == 0 ? nullptr : builder->getAllocator()->allocateArray(clipMap->borderCount); + for (size_t i = 0; i < clipMap->borderCount; i++) + { + auto border = &clipMap->borders[i]; + auto json_border = clipMapJson["borders"][i]; + + Utils::Json::CopyArray(border->distEq, json_border["distEq"]); + border->zBase = json_border["zBase"]; + border->zSlope = json_border["zSlope"]; + border->start = json_border["start"]; + border->length = json_border["length"]; + } + + + // Collision partitions + clipMap->partitionCount = clipMapJson["partitions"].size(); + clipMap->partitions = clipMap->partitionCount == 0 ? nullptr : builder->getAllocator()->allocateArray(clipMap->partitionCount); + for (auto i = 0; i < clipMap->partitionCount; i++) + { + auto partition = &clipMap->partitions[i]; + auto json_partition = clipMapJson["partitions"][i]; + + partition->triCount = json_partition["triCount"]; + partition->firstVertSegment = json_partition["firstVertSegment"]; + partition->firstTri = json_partition["firstTri"]; + partition->borderCount = json_partition["borderCount"]; + + if (partition->borderCount > 0) + { + // They're always consecutive (I checked) + auto index = std::stoi(json_partition["firstBorder"].get().substr(1)); + assert(clipMap->borders); + partition->borders = &clipMap->borders[index]; + } + } + + // Tree + clipMap->aabbTreeCount = clipMapJson["aabbTrees"].size(); + clipMap->aabbTrees = clipMap->aabbTreeCount == 0 ? nullptr : builder->getAllocator()->allocateArray(clipMap->aabbTreeCount); + for (size_t i = 0; i < clipMap->aabbTreeCount; i++) + { + auto tree = &clipMap->aabbTrees[i]; + auto json_tree = clipMapJson["aabbTrees"][i]; + + Utils::Json::CopyArray(tree->midPoint, json_tree["midPoint"]); + Utils::Json::CopyArray(tree->halfSize, json_tree["halfSize"]); + tree->materialIndex = json_tree["materialIndex"]; + tree->childCount = json_tree["childCount"]; + tree->u.firstChildIndex = json_tree["u"]; + } + + // CModels + clipMap->numSubModels = clipMapJson["cmodels"].size(); + clipMap->cmodels = clipMap->numSubModels == 0 ? nullptr : builder->getAllocator()->allocateArray(clipMap->numSubModels); + for (size_t i = 0; i < clipMap->numSubModels; i++) + { + auto cmodel = &clipMap->cmodels[i]; + auto json_cmodel = clipMapJson["cmodels"][i]; + + cmodel->bounds = Utils::Json::ReadBounds(json_cmodel["bounds"]); + cmodel->radius = json_cmodel["radius"]; + + auto leaf = &cmodel->leaf; + auto json_leaf = json_cmodel["leaf"]; + + leaf->firstCollAabbIndex = json_leaf["firstCollAabbIndex"]; + leaf->collAabbCount = json_leaf["collAabbCount"]; + leaf->brushContents = json_leaf["brushContents"]; + leaf->terrainContents = json_leaf["terrainContents"]; + leaf->leafBrushNode = json_leaf["leafBrushNode"]; + leaf->bounds = Utils::Json::ReadBounds(json_leaf["bounds"]); + printf(""); + } + + // Brushes + clipMap->numBrushes = static_cast(clipMapJson["brushes"].size()); + clipMap->brushes = clipMap->numBrushes == 0 ? nullptr : builder->getAllocator()->allocateArray < Game::cbrush_t >(clipMap->numBrushes); + for (size_t i = 0; i < clipMap->numBrushes; i++) + { + auto brush = &clipMap->brushes[i]; + auto json_brush = clipMapJson["brushes"][i]; + + brush->glassPieceIndex = json_brush["glassPieceIndex"]; + brush->numsides = json_brush["numsides"]; + + if (brush->numsides) + { + // Always consecutive + auto index = std::stoi(json_brush["firstSide"].get().substr(1)); + assert(clipMap->brushsides); + brush->sides = &clipMap->brushsides[index]; + } + + if (json_brush["baseAdjacentSide"].is_string()) // Not null that means + { + auto index = std::stoi(json_brush["baseAdjacentSide"].get().substr(1)); + assert(clipMap->brushEdges); + brush->baseAdjacentSide = &clipMap->brushEdges[index]; + } + + for (size_t x = 0; x < 2; x++) + { + Utils::Json::CopyArray(brush->axialMaterialNum[x], json_brush["axialMaterialNum"][x]); + Utils::Json::CopyArray(brush->firstAdjacentSideOffsets[x], json_brush["firstAdjacentSideOffsets"][x]); + Utils::Json::CopyArray(brush->edgeCount[x], json_brush["edgeCount"][x]); + } + } + + assert(clipMapJson["brushes"].size() == clipMapJson["brushBounds"].size()); + clipMap->brushBounds = builder->getAllocator()->allocateArray(clipMap->numBrushes); + for (size_t i = 0; i < clipMap->numBrushes; i++) + { + clipMap->brushBounds[i] = Utils::Json::ReadBounds(clipMapJson["brushBounds"][i]); + } + + assert(clipMapJson["brushes"].size() == clipMapJson["brushContents"].size()); + clipMap->brushContents = builder->getAllocator()->allocateArray(clipMap->numBrushes); + Utils::Json::CopyArray(clipMap->brushContents, clipMapJson["brushContents"]); + + auto json_ents= clipMapJson["mapEnts"]; + if (!json_ents.is_null()) + { + auto ents_name = json_ents["name"]; + clipMap->mapEnts = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_MAP_ENTS, ents_name, builder).mapEnts; + + auto json_trigger = json_ents["trigger"]; + auto trigger = &clipMap->mapEnts->trigger; + trigger->count = json_trigger["models"].size(); + trigger->models = builder->getAllocator()->allocateArray(trigger->count); + + for (size_t i = 0; i < trigger->count; i++) + { + trigger->models[i].contents = json_trigger["models"][i]["contents"]; + trigger->models[i].hullCount = json_trigger["models"][i]["hullCount"]; + trigger->models[i].firstHull = json_trigger["models"][i]["firstHull"]; + } + + trigger->hullCount = json_trigger["hulls"].size(); + trigger->hulls = builder->getAllocator()->allocateArray(trigger->hullCount); + for (size_t i = 0; i < trigger->hullCount; i++) + { + trigger->hulls[i].bounds = Utils::Json::ReadBounds(json_trigger["hulls"][i]["bounds"]); + + trigger->hulls[i].contents = json_trigger["hulls"][i]["contents"]; + trigger->hulls[i].firstSlab = json_trigger["hulls"][i]["firstSlab"]; + trigger->hulls[i].slabCount = json_trigger["hulls"][i]["slabCount"]; + } + + trigger->slabCount = json_trigger["slabs"].size(); + trigger->slabs = builder->getAllocator()->allocateArray(trigger->slabCount); + for (size_t i = 0; i < trigger->slabCount; i++) + { + Utils::Json::CopyArray(trigger->slabs[i].dir, json_trigger["slabs"][i]["dir"]); + trigger->slabs[i].midPoint = json_trigger["slabs"][i]["midPoint"]; + trigger->slabs[i].halfSize = json_trigger["slabs"][i]["halfSize"]; + } + + // Stages + clipMap->mapEnts->stageCount = static_cast(json_ents["stages"].size()); + clipMap->mapEnts->stages = builder->getAllocator()->allocateArray (clipMap->mapEnts->stageCount); + for (auto i = 0; i < clipMap->mapEnts->stageCount; i++) + { + auto stage = &clipMap->mapEnts->stages[i]; + auto json_stage = json_ents["stages"][i]; + + stage->name = builder->getAllocator()->duplicateString(json_stage["name"]); + Utils::Json::CopyArray(stage->origin, json_stage["origin"]); + stage->triggerIndex = json_stage["triggerIndex"]; + stage->sunPrimaryLightIndex = json_stage["sunPrimaryLightIndex"].get(); + } + } + + // SmodelNodes + clipMap->smodelNodeCount = static_cast(clipMapJson["smodelNodes"].size()); + clipMap->smodelNodes = clipMap->smodelNodeCount == 0 ? nullptr : builder->getAllocator()->allocateArray(clipMap->smodelNodeCount); + for (size_t i = 0; i < clipMap->smodelNodeCount; i++) + { + auto json_node = clipMapJson["smodelNodes"][i]; + auto node = &clipMap->smodelNodes[i]; + + node->bounds = Utils::Json::ReadBounds(json_node["bounds"]); + node->firstChild = json_node["firstChild"]; + node->childCount = json_node["childCount"]; + } + + for (size_t i = 0; i < 2; i++) + { + clipMap->dynEntCount[i] = static_cast(clipMapJson["dynEntities"][i].size()); + + auto json_entities = clipMapJson["dynEntities"][i]; + clipMap->dynEntClientList[i] = clipMap->dynEntCount[i] == 0 ? nullptr : builder->getAllocator()->allocateArray(clipMap->dynEntCount[i]); + clipMap->dynEntCollList[i] = clipMap->dynEntCount[i] == 0 ? nullptr : builder->getAllocator()->allocateArray(clipMap->dynEntCount[i]); + clipMap->dynEntPoseList[i] = clipMap->dynEntCount[i] == 0 ? nullptr : builder->getAllocator()->allocateArray(clipMap->dynEntCount[i]); + clipMap->dynEntDefList[i] = clipMap->dynEntCount[i] == 0 ? nullptr : builder->getAllocator()->allocateArray(clipMap->dynEntCount[i]); + + for (size_t j = 0; j < clipMap->dynEntCount[i]; j++) + { + auto json_entity = json_entities[j]["dynEntityDef"]; + auto entity = &clipMap->dynEntDefList[i][j]; + + entity->type = json_entity["type"]; + Utils::Json::CopyArray(entity->pose.quat, json_entity["pose"]["quat"]); + Utils::Json::CopyArray(entity->pose.origin, json_entity["pose"]["origin"]); + + entity->xModel = json_entity["xModel"].is_string() ? Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_XMODEL, json_entity["xModel"], builder).model : nullptr; + entity->brushModel = json_entity["brushModel"]; + entity->physicsBrushModel = json_entity["physicsBrushModel"]; + entity->destroyFx = json_entity["destroyFx"].is_string() ? Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_FX, json_entity["destroyFx"], builder).fx : nullptr; + entity->physPreset = json_entity["physPreset"].is_string() ? Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_PHYSPRESET, json_entity["physPreset"], builder).physPreset : nullptr; + entity->health = json_entity["health"]; + + Utils::Json::CopyArray(entity->mass.centerOfMass, json_entity["mass"]["centerOfMass"]); + Utils::Json::CopyArray(entity->mass.momentsOfInertia, json_entity["mass"]["momentsOfInertia"]); + Utils::Json::CopyArray(entity->mass.productsOfInertia, json_entity["mass"]["productsOfInertia"]); + + entity->contents = json_entity["contents"]; + } + } + + clipMap->checksum = clipMapJson["checksum"]; + } + catch (const std::exception& e) + { + Components::Logger::PrintError(Game::CON_CHANNEL_ERROR, "Malformed JSON for clipmap {}! {}", name, e.what()); + return; + } + + header->clipMap = clipMap; + } + + void IclipMap_t::dump(Game::XAssetHeader header) + { + assert(header.clipMap); + + static_assert(sizeof Game::clipMap_t == 256); + static_assert(sizeof Game::ClipMaterial == 12); + static_assert(sizeof Game::cbrushside_t == 8); + static_assert(sizeof Game::cNode_t == 8); + static_assert(sizeof Game::cLeaf_t == 40); + static_assert(sizeof Game::cLeafBrushNode_s == 20); + static_assert(sizeof Game::CollisionBorder == 28); + static_assert(sizeof Game::CollisionPartition == 12); + static_assert(sizeof Game::CollisionAabbTree == 32); + static_assert(sizeof Game::cmodel_t == 68); + static_assert(sizeof Game::SModelAabbNode == 28); + + + const auto clipMap = header.clipMap; + + if (!clipMap) return; + + std::unordered_map planes; + std::unordered_map brush_sides; + std::unordered_map brush_edges; + std::unordered_map leaf_brushes; + std::unordered_map borders; + + Utils::Memory::Allocator strDuplicator; + nlohmann::json output; + + auto float_array_to_json = [](const float* array, int size) + { + auto arr = nlohmann::json::array(); + for (auto i = 0; i < size; ++i) + { + arr.push_back(array[i]); + } + + return arr; + }; + + auto ushort_to_array = [](const unsigned short* array, int size) + { + auto arr = nlohmann::json::array(); + for (auto i = 0; i < size; ++i) + { + arr.push_back(array[i]); + } + + return arr; + }; + + auto uchar_to_array = [](const unsigned char* array, int size) + { + auto arr = nlohmann::json::array(); + for (auto i = 0; i < size; ++i) + { + arr.push_back(array[i]); + } + + return arr; + }; + + auto bounds_to_json = [&float_array_to_json](const Game::Bounds& bounds) + { + auto bounds_json= nlohmann::json::object(); + + bounds_json.emplace("midPoint", float_array_to_json(bounds.midPoint, 3)); + bounds_json.emplace("halfSize", float_array_to_json(bounds.halfSize, 3)); + + return bounds_json; + }; + + auto placement_to_json = [&float_array_to_json](const Game::GfxPlacement& placement) + { + auto placement_json= nlohmann::json::object(); + + placement_json.emplace("quat", float_array_to_json(placement.quat, 4)); + placement_json.emplace("origin", float_array_to_json(placement.origin, 3)); + + return placement_json; + }; + + auto leaf_to_json = [&bounds_to_json](const Game::cLeaf_t& leaf) + { + auto json_leave= nlohmann::json::object(); + + json_leave.emplace("firstCollAabbIndex", leaf.firstCollAabbIndex); + json_leave.emplace("collAabbCount", leaf.collAabbCount); + json_leave.emplace("brushContents", leaf.brushContents); + json_leave.emplace("terrainContents", leaf.terrainContents); + json_leave.emplace("leafBrushNode", leaf.leafBrushNode); + json_leave.emplace("bounds", bounds_to_json(leaf.bounds)); + + return json_leave; + }; + + output.emplace("version", IW4X_CLIPMAP_VERSION); + output.emplace("name", (clipMap->name)); + output.emplace("isInUse", clipMap->isInUse); + + // Planes + auto json_planes = nlohmann::json::array(); + for (size_t i = 0; i < clipMap->planeCount; i++) + { + auto json_plane= nlohmann::json::object(); + auto plane = &clipMap->planes[i]; + + json_plane.emplace( + "normal", + float_array_to_json(plane->normal, 3) + ); + + json_plane.emplace("dist", plane->dist); + json_plane.emplace("type", plane->type); + + json_planes.push_back(json_plane); + + planes[plane] = i; + } + + output.emplace("planes", json_planes); + + // Static models + auto json_static_models = nlohmann::json::array(); + for (size_t i = 0; i < clipMap->numStaticModels; i++) + { + auto json_static_model= nlohmann::json::object(); + auto static_model = &clipMap->staticModelList[i]; + + json_static_model.emplace( + "xmodel", (static_model->xmodel->name) + ); + + json_static_model.emplace( + "origin", + float_array_to_json(static_model->origin, 3) + ); + + auto inv_scaled_axis = nlohmann::json::array(); + for (size_t j = 0; j < ARRAYSIZE(static_model->invScaledAxis); j++) + { + inv_scaled_axis.push_back( + float_array_to_json(static_model->invScaledAxis[j], 3) + ); + } + + json_static_model.emplace("invScaledAxis", inv_scaled_axis); + + json_static_model.emplace( + "absBounds", + bounds_to_json(static_model->absBounds) + ); + + json_static_models.push_back(json_static_model); + } + + output.emplace("staticModelList", json_static_models); + + // Materials + auto json_materials = nlohmann::json::array(); + for (size_t i = 0; i < clipMap->numMaterials; i++) + { + auto json_material= nlohmann::json::object(); + auto material = &clipMap->materials[i]; + + json_material.emplace( + "name", + (material->name) + ); + + json_material.emplace("surfaceFlags", material->surfaceFlags); + json_material.emplace("contents", material->contents); + + json_materials.push_back(json_material); + } + + output.emplace("materials", json_materials); + + // Brushsides + auto json_brush_sides = nlohmann::json::array(); + for (size_t i = 0; i < clipMap->numBrushSides; i++) + { + auto json_brush_side= nlohmann::json::object(); + auto brush_side = &clipMap->brushsides[i]; + + auto index = planes[brush_side->plane]; + auto plane_index = std::format("#{}", index); + json_brush_side.emplace( + "plane", + (strDuplicator.duplicateString(plane_index)) + ); + + json_brush_side.emplace("materialNum", brush_side->materialNum); + json_brush_side.emplace("firstAdjacentSideOffset", brush_side->firstAdjacentSideOffset); + json_brush_side.emplace("edgeCount", brush_side->edgeCount); + + json_brush_sides.push_back(json_brush_side); + + brush_sides[brush_side] = i; + } + + output.emplace("brushsides", json_brush_sides); + + // Brush edges + auto json_brush_edges = nlohmann::json::array(); + for (size_t i = 0; i < clipMap->numBrushEdges; i++) + { + json_brush_edges.push_back(clipMap->brushEdges[i]); + brush_edges[&clipMap->brushEdges[i]] = i; + } + + output.emplace("brushEdges", json_brush_edges); + + // Brush nodes + auto json_nodes = nlohmann::json::array(); + for (size_t i = 0; i < clipMap->numNodes; i++) + { + auto node = &clipMap->nodes[i]; + + auto json_node= nlohmann::json::object(); + + auto index = planes[node->plane]; + auto plane_index = std::format("#{}", index); + json_node.emplace("plane", (strDuplicator.duplicateString(plane_index))); + + auto children = nlohmann::json::array(); + for (size_t j = 0; j < ARRAYSIZE(node->children); j++) + { + children.push_back(node->children[j]); + } + + json_node.emplace("children", children); + + json_nodes.push_back(json_node); + } + + output.emplace("nodes", json_nodes); + + // Leaves + auto json_leaves = nlohmann::json::array(); + for (size_t i = 0; i < clipMap->numLeafs; i++) + { + auto leaf = &clipMap->leafs[i]; + + json_leaves.push_back(leaf_to_json(*leaf)); + } + + output.emplace("leafs", json_leaves); + + // Brush leafs + auto json_brush_leafs = nlohmann::json::array(); + for (size_t i = 0; i < clipMap->numLeafBrushes; i++) + { + json_brush_leafs.push_back(clipMap->leafbrushes[i]); + leaf_brushes[&clipMap->leafbrushes[i]] = i; + } + + output.emplace("leafbrushes", json_brush_leafs); + + + // LeafBrushNodes + auto json_leaf_brush_nodes = nlohmann::json::array(); + for (size_t i = 0; i < clipMap->leafbrushNodesCount; i++) + { + auto json_leaf_brush_node= nlohmann::json::object(); + auto leaf_brush_node = &clipMap->leafbrushNodes[i]; + + json_leaf_brush_node.emplace("axis", leaf_brush_node->axis); + json_leaf_brush_node.emplace("leafBrushCount", leaf_brush_node->leafBrushCount); + json_leaf_brush_node.emplace("contents", leaf_brush_node->contents); + + if (leaf_brush_node->leafBrushCount > 0) + { + assert(leaf_brushes.contains(leaf_brush_node->data.leaf.brushes)); + auto index_str = std::format("#{}", leaf_brushes[leaf_brush_node->data.leaf.brushes]); + json_leaf_brush_node.emplace("data", strDuplicator.duplicateString(index_str)); + } + else + { + auto data= nlohmann::json::object(); + + data.emplace("dist", leaf_brush_node->data.children.dist); + data.emplace("range", leaf_brush_node->data.children.range); + + auto child_offset = nlohmann::json::array(); + for (size_t x = 0; x < 2; x++) + { + child_offset.push_back(leaf_brush_node->data.children.childOffset[x]); + } + + data.emplace("childOffset", child_offset); + + json_leaf_brush_node.emplace("data", data); + } + + json_leaf_brush_nodes.push_back(json_leaf_brush_node); + } + + output.emplace("leafbrushNodes", json_leaf_brush_nodes); + + + // leafsurfaces (unused) + auto json_leaf_surfaces = nlohmann::json::array(); + for (size_t i = 0; i < clipMap->numLeafSurfaces; i++) + { + json_leaf_surfaces.push_back(clipMap->leafsurfaces[i]); + } + + output.emplace("leafsurfaces", json_leaf_surfaces); + + // vertices + auto json_vertices = nlohmann::json::array(); + for (size_t i = 0; i < clipMap->vertCount; i++) + { + json_vertices.push_back(float_array_to_json(clipMap->verts[i], 3)); + } + + output.emplace("verts", json_vertices); + + // tris + auto json_tris = nlohmann::json::array(); + for (size_t i = 0; i < clipMap->triCount * 3; i += 3) + { + json_tris.push_back(ushort_to_array(&clipMap->triIndices[i], 3)); + } + + output.emplace("triIndices", json_tris); + + auto json_tris_walkable = nlohmann::json::array(); + auto walkable_count = 4 * ((3 * clipMap->triCount + 31) >> 5); + for (size_t i = 0; i < walkable_count * 3; i++) + { + json_tris_walkable.push_back(static_cast(clipMap->triEdgeIsWalkable[i])); + } + + output.emplace("triEdgeIsWalkable", json_tris_walkable); + + // Collision borders + auto json_collision_borders = nlohmann::json::array(); + for (size_t i = 0; i < clipMap->borderCount; i++) + { + auto collision_border = &clipMap->borders[i]; + + auto json_collision_border= nlohmann::json::object(); + + json_collision_border.emplace("distEq", float_array_to_json(collision_border->distEq, 3)); + json_collision_border.emplace("zBase", collision_border->zBase); + json_collision_border.emplace("zSlope", collision_border->zSlope); + json_collision_border.emplace("start", collision_border->start); + json_collision_border.emplace("length", collision_border->length); + + json_collision_borders.push_back(json_collision_border); + + borders[collision_border] = i; + } + + output.emplace("borders", json_collision_borders); + + // Collision partitions + auto json_collision_partitions = nlohmann::json::array(); + for (auto i = 0; i < clipMap->partitionCount; i++) + { + auto collision_partition = &clipMap->partitions[i]; + + auto json_collision_partition= nlohmann::json::object(); + + json_collision_partition.emplace("triCount", collision_partition->triCount); + json_collision_partition.emplace("firstVertSegment", collision_partition->firstVertSegment); + json_collision_partition.emplace("firstTri", collision_partition->firstTri); + json_collision_partition.emplace("borderCount", collision_partition->borderCount); + + if (collision_partition->borderCount) + { + auto index_str = strDuplicator.duplicateString(std::format("#{}", borders[&collision_partition->borders[0]])); + json_collision_partition.emplace("firstBorder", (index_str)); + } + + json_collision_partitions.push_back(json_collision_partition); + } + + output.emplace("partitions", json_collision_partitions); + + // Trees + auto json_aabbtrees = nlohmann::json::array(); + for (size_t i = 0; i < clipMap->aabbTreeCount; i++) + { + auto aabbtree = &clipMap->aabbTrees[i]; + + auto json_aabbtree= nlohmann::json::object(); + + json_aabbtree.emplace("midPoint", float_array_to_json(aabbtree->midPoint, 3)); + json_aabbtree.emplace("halfSize", float_array_to_json(aabbtree->halfSize, 3)); + json_aabbtree.emplace("materialIndex", aabbtree->materialIndex); + json_aabbtree.emplace("childCount", aabbtree->childCount); + json_aabbtree.emplace("u", aabbtree->u.firstChildIndex); + + json_aabbtrees.push_back(json_aabbtree); + } + + output.emplace("aabbTrees", json_aabbtrees); + + // Cmodels + auto json_cmodels = nlohmann::json::array(); + for (size_t i = 0; i < clipMap->numSubModels; i++) + { + auto cmodel = &clipMap->cmodels[i]; + + auto json_cmodel= nlohmann::json::object(); + + json_cmodel.emplace("bounds", bounds_to_json(cmodel->bounds)); + json_cmodel.emplace("radius", cmodel->radius); + json_cmodel.emplace("leaf", leaf_to_json(cmodel->leaf)); + + json_cmodels.push_back(json_cmodel); + } + + output.emplace("cmodels", json_cmodels); + + // Brushes + auto json_brushes = nlohmann::json::array(); + for (size_t i = 0; i < clipMap->numBrushes; i++) + { + auto brush = &clipMap->brushes[i]; + + auto json_brush= nlohmann::json::object(); + + json_brush.emplace("glassPieceIndex", brush->glassPieceIndex); + json_brush.emplace("numsides", brush->numsides); + + // Sides + if (brush->numsides > 0) + { + auto index_str = strDuplicator.duplicateString(std::format("#{}", brush_sides[brush->sides])); + json_brush.emplace("firstSide", (index_str)); + } + + if (brush->baseAdjacentSide) + { + auto index_str = strDuplicator.duplicateString(std::format("#{}", brush_edges[brush->baseAdjacentSide])); + json_brush.emplace("baseAdjacentSide", (index_str)); + } + else + { + json_brush.emplace("baseAdjacentSide", nlohmann::json()); + } + + auto axial_material_num = nlohmann::json::array(); + for (size_t x = 0; x < 2; x++) + { + axial_material_num.push_back(ushort_to_array(brush->axialMaterialNum[x], 3)); + } + json_brush.emplace("axialMaterialNum", axial_material_num); + + auto first_adjacent_side_offsets = nlohmann::json::array(); + for (size_t x = 0; x < 2; x++) + { + first_adjacent_side_offsets.push_back(uchar_to_array(brush->firstAdjacentSideOffsets[x], 3)); + } + json_brush.emplace("firstAdjacentSideOffsets", first_adjacent_side_offsets); + + auto edge_count = nlohmann::json::array(); + for (size_t x = 0; x < 2; x++) + { + edge_count.push_back(uchar_to_array(brush->edgeCount[x], 3)); + } + json_brush.emplace("edgeCount", edge_count); + + json_brushes.push_back(json_brush); + } + + output.emplace("brushes", json_brushes); + + // Brushbounds + auto json_brush_bounds = nlohmann::json::array(); + for (size_t i = 0; i < clipMap->numBrushes; i++) + { + json_brush_bounds.push_back(bounds_to_json(clipMap->brushBounds[i])); + } + + output.emplace("brushBounds", json_brush_bounds); + + // Brush contents + auto json_brush_contents = nlohmann::json::array(); + for (size_t i = 0; i < clipMap->numBrushes; i++) + { + json_brush_contents.push_back(clipMap->brushContents[i]); + } + + output.emplace("brushContents", json_brush_contents); + + // ENTITIES + if (clipMap->mapEnts) + { + static_assert(sizeof Game::TriggerSlab == 20); + static_assert(sizeof Game::TriggerModel == 8); + static_assert(sizeof Game::TriggerHull == 32); + + auto json_map_ents= nlohmann::json::object(); + const auto ents = clipMap->mapEnts; + + json_map_ents.emplace("name", (ents->name)); + + auto json_trigger= nlohmann::json::object(); + + auto json_trigger_models = nlohmann::json::array(); + for (size_t i = 0; i < ents->trigger.count; i++) + { + auto json_trigger_model= nlohmann::json::object(); + json_trigger_model.emplace("contents", ents->trigger.models[i].contents); + json_trigger_model.emplace("hullCount", ents->trigger.models[i].hullCount); + json_trigger_model.emplace("firstHull", ents->trigger.models[i].firstHull); + + json_trigger_models.push_back(json_trigger_model); + } + + json_trigger.emplace("models", json_trigger_models); + + auto json_trigger_hulls = nlohmann::json::array(); + for (size_t i = 0; i < ents->trigger.hullCount; i++) + { + auto json_trigger_hull= nlohmann::json::object(); + json_trigger_hull.emplace("bounds", bounds_to_json(ents->trigger.hulls[i].bounds)); + json_trigger_hull.emplace("contents", ents->trigger.hulls[i].contents); + json_trigger_hull.emplace("slabCount", ents->trigger.hulls[i].slabCount); + json_trigger_hull.emplace("firstSlab", ents->trigger.hulls[i].firstSlab); + + json_trigger_hulls.push_back(json_trigger_hull); + } + + json_trigger.emplace("hulls", json_trigger_hulls); + + auto json_trigger_slabs = nlohmann::json::array(); + for (size_t i = 0; i < ents->trigger.slabCount; i++) + { + auto json_trigger_slab= nlohmann::json::object(); + json_trigger_slab.emplace("dir", float_array_to_json(ents->trigger.slabs[i].dir, 3)); + json_trigger_slab.emplace("midPoint", ents->trigger.slabs[i].midPoint); + json_trigger_slab.emplace("halfSize", ents->trigger.slabs[i].halfSize); + + json_trigger_slabs.push_back(json_trigger_slab); + } + + json_trigger.emplace("slabs", json_trigger_slabs); + json_map_ents.emplace("trigger", json_trigger); + + auto json_stages = nlohmann::json::array(); + for (auto i = 0; i < ents->stageCount; i++) + { + auto stage = &ents->stages[i]; + auto json_stage= nlohmann::json::object(); + json_stage.emplace("name", (stage->name)); + json_stage.emplace("origin", float_array_to_json(stage->origin, 3)); + json_stage.emplace("triggerIndex", stage->triggerIndex); + json_stage.emplace("sunPrimaryLightIndex", stage->sunPrimaryLightIndex); + + json_stages.push_back(json_stage); + } + + json_map_ents.emplace("stages", json_stages); + + output.emplace("mapEnts", json_map_ents); + } + else + { + output.emplace("mapEnts", nlohmann::json()); + } + + //Smodel nodes + auto json_smodelnodes = nlohmann::json::array(); + for (size_t i = 0; i < clipMap->smodelNodeCount; i++) + { + auto smodelNode = &clipMap->smodelNodes[i]; + + auto json_smodelnode = nlohmann::json::object(); + + json_smodelnode.emplace("bounds", bounds_to_json(smodelNode->bounds)); + json_smodelnode.emplace("firstChild", smodelNode->firstChild); + json_smodelnode.emplace("childCount", smodelNode->childCount); + + json_smodelnodes.push_back(json_smodelnode); + } + + output.emplace("smodelNodes", json_smodelnodes); + + // Dynent + auto json_dyn_entities = nlohmann::json::array(); + for (size_t i = 0; i < ARRAYSIZE(clipMap->dynEntCount); i++) + { + auto def_list = clipMap->dynEntDefList[i]; + if (def_list) + { + auto json_dyn_entity_def_list = nlohmann::json::array(); + + for (size_t j = 0; j < clipMap->dynEntCount[i]; j++) + { + auto json_dyn_entity_def_pack= nlohmann::json::object(); + auto json_dyn_entity_def= nlohmann::json::object(); + auto def = &def_list[j]; + + json_dyn_entity_def.emplace("type", def->type); + json_dyn_entity_def.emplace("pose", placement_to_json(def->pose)); + json_dyn_entity_def.emplace("xModel", def->xModel ? (def->xModel->name) : nlohmann::json()); + json_dyn_entity_def.emplace("brushModel", def->brushModel); + json_dyn_entity_def.emplace("physicsBrushModel", def->physicsBrushModel); + json_dyn_entity_def.emplace("destroyFx", def->destroyFx ? (def->destroyFx->name) : nlohmann::json()); + json_dyn_entity_def.emplace("physPreset", def->physPreset ? (def->physPreset->name) : nlohmann::json()); + json_dyn_entity_def.emplace("health", def->health); + + auto json_mass= nlohmann::json::object(); + json_mass.emplace("centerOfMass", float_array_to_json(def->mass.centerOfMass, 3)); + json_mass.emplace("momentsOfInertia", float_array_to_json(def->mass.momentsOfInertia, 3)); + json_mass.emplace("productsOfInertia", float_array_to_json(def->mass.productsOfInertia, 3)); + json_dyn_entity_def.emplace("mass", json_mass); + + json_dyn_entity_def.emplace("contents", def->contents); + + json_dyn_entity_def_pack.emplace("dynEntityDef", json_dyn_entity_def); + + /// All that follows is garbage data + //auto json_dyn_entity_pose= nlohmann::json::object(); + //auto pose = &clipMap->dynEntPoseList[i][j]; + //json_dyn_entity_pose.emplace("pose", placement_to_json(pose->pose)); + //json_dyn_entity_pose.emplace("radius", pose->radius); + //json_dyn_entity_def_pack.emplace("dynEntPose", json_dyn_entity_pose); + + //auto json_dyn_entity_client= nlohmann::json::object(); + //auto client = &clipMap->dynEntClientList[i][j]; + //json_dyn_entity_client.emplace("physObjId", client->physObjId); + //json_dyn_entity_client.emplace("flags", client->flags); + //json_dyn_entity_client.emplace("lightingHandle", client->lightingHandle); + //json_dyn_entity_client.emplace("health", client->health); + //json_dyn_entity_def_pack.emplace("dynEntClient", json_dyn_entity_client); + + //auto json_dyn_entity_coll= nlohmann::json::object(); + //auto coll = &clipMap->dynEntCollList[i][j]; + //json_dyn_entity_coll.emplace("sector", coll->sector); + //json_dyn_entity_coll.emplace("nextEntInSector", coll->nextEntInSector); + //json_dyn_entity_coll.emplace("linkMins", float_array_to_json(coll->linkMins, 2)); + //json_dyn_entity_coll.emplace("linkMaxs", float_array_to_json(coll->linkMaxs, 2)); + //json_dyn_entity_def_pack.emplace("dynEntColl", json_dyn_entity_coll); + // + + json_dyn_entity_def_list.push_back(json_dyn_entity_def_pack); + } + + json_dyn_entities.push_back(json_dyn_entity_def_list); + } + else + { + + json_dyn_entities.push_back(nlohmann::json()); + } + } + output.emplace("dynEntities", json_dyn_entities); + + // Checksum + output.emplace("checksum", clipMap->checksum); + + // Write to disk + constexpr auto* prefix = "maps/mp/"; + constexpr auto* suffix = ".d3dbsp"; + Utils::IO::WriteFile(std::format("raw/clipmap/{}{}{}.iw4x.json", prefix, header.clipMap->name, suffix), output.dump(4)); + + } } diff --git a/src/Components/Modules/AssetInterfaces/IclipMap_t.hpp b/src/Components/Modules/AssetInterfaces/IclipMap_t.hpp index fbd5e307..5099b77a 100644 --- a/src/Components/Modules/AssetInterfaces/IclipMap_t.hpp +++ b/src/Components/Modules/AssetInterfaces/IclipMap_t.hpp @@ -10,6 +10,7 @@ namespace Assets void save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override; void mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override; void load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) override; + static void dump(Game::XAssetHeader /*header*/); private: class SModelQuadtree @@ -89,5 +90,9 @@ namespace Assets float x, y, z; float halfX, halfY, halfZ; }; + + void loadBinary(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder); + void loadFromJSON(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder); + }; } diff --git a/src/Components/Modules/AssetInterfaces/Isnd_alias_list_t.cpp b/src/Components/Modules/AssetInterfaces/Isnd_alias_list_t.cpp index 545acc75..7304873f 100644 --- a/src/Components/Modules/AssetInterfaces/Isnd_alias_list_t.cpp +++ b/src/Components/Modules/AssetInterfaces/Isnd_alias_list_t.cpp @@ -222,7 +222,10 @@ namespace Assets { alias->soundFile->exists = true; - alias->aliasName = builder->getAllocator()->duplicateString(aliasName.get()); + + // These must be THE SAME POINTER !! + // Wanna know why ? Check out 0x685646 + alias->aliasName = aliasList->aliasName; if (subtitle.is_string()) { @@ -244,7 +247,7 @@ namespace Assets alias->pitchMax = pitchMax.get(); alias->distMin = distMin.get(); alias->distMax = distMax.get(); - alias->flags = flags.get(); + alias->flags.intValue = flags.get(); alias->___u15.slavePercentage = slavePercentage.get(); alias->probability = probability.get(); alias->lfePercentage = lfePercentage.get(); @@ -308,6 +311,9 @@ namespace Assets } auto curve = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_SOUND_CURVE, fallOffCurve, builder).sndCurve; + + assert(curve); + alias->volumeFalloffCurve = curve; } @@ -320,11 +326,10 @@ namespace Assets { alias->soundFile->type = Game::SAT_STREAMED; - std::string streamedFile = soundFile.get(); - std::string directory = ""s; - int split = streamedFile.find_last_of('/'); - - if (split >= 0) + auto streamedFile = soundFile.get(); + std::string directory; + auto split = streamedFile.find_last_of('/'); + if (split != std::string::npos) { directory = streamedFile.substr(0, split); streamedFile = streamedFile.substr(split+1); @@ -379,6 +384,127 @@ namespace Assets } } + void Isnd_alias_list_t::dump(Game::XAssetHeader header) + { + nlohmann::json output; + Utils::Memory::Allocator strDuplicator; + auto ents = header.sound; + + auto head = nlohmann::json::array_t(); + + for (size_t i = 0; i < ents->count; i++) + { + Game::snd_alias_t alias = ents->head[i]; + + auto channelMaps = nlohmann::json::array_t(); + + for (size_t j = 0; j < 2; j++) + { + for (size_t k = 0; k < 2; k++) + { + auto iw3ChannelMap = alias.speakerMap->channelMaps[j][k]; + auto speakers = nlohmann::json::array_t(); + + for (size_t speakerIndex = 0; speakerIndex < iw3ChannelMap.speakerCount; speakerIndex++) + { + auto iw4Speaker = iw3ChannelMap.speakers[speakerIndex]; + + nlohmann::json::object_t speaker; + speaker.emplace("levels0", iw4Speaker.numLevels > 0 ? iw4Speaker.levels[0] : 0); + speaker.emplace("levels1", iw4Speaker.numLevels > 1 ? iw4Speaker.levels[1] : 0); + speaker.emplace("numLevels", iw4Speaker.numLevels); + speaker.emplace("speaker", iw4Speaker.speaker); + speakers.emplace_back(speaker); + } + + auto channelMap = nlohmann::json::object_t(); + channelMap.emplace("entryCount", iw3ChannelMap.speakerCount); + channelMap.emplace("speakers", speakers); + channelMaps.emplace_back(channelMap); + } + } + + auto speakerMap = nlohmann::json::object_t(); + speakerMap.emplace("channelMaps", channelMaps); + speakerMap.emplace("isDefault", alias.speakerMap->isDefault); + speakerMap.emplace("name", (alias.speakerMap->name)); + + std::string soundFile; + if (alias.soundFile) + { + switch (alias.soundFile->type) + { + // LOADED + case Game::snd_alias_type_t::SAT_LOADED: + // Save the LoadedSound subasset + soundFile = alias.soundFile->u.loadSnd->name; + break; + + // STREAMED + case Game::snd_alias_type_t::SAT_STREAMED: + { + soundFile = alias.soundFile->u.streamSnd.filename.info.raw.name; + + if (alias.soundFile->u.streamSnd.filename.info.raw.dir) + { + soundFile = Utils::String::VA("%s/%s", alias.soundFile->u.streamSnd.filename.info.raw.dir, soundFile.c_str()); + } + + break; + } + + // I DON'T KNOW :( + default: + Components::Logger::Print("Error dumping sound alias %s: unknown format %d\n", alias.aliasName, alias.soundFile->type); + return; + } + } + else + { + Components::Logger::Print("Error dumping sound alias %s: NULL soundfile!\n", alias.aliasName); + return; + } + + auto iw4Flags = alias.flags.intValue; + + auto json_alias = nlohmann::json::object_t(); + json_alias.emplace("aliasName", (alias.aliasName)); + json_alias.emplace("centerPercentage", alias.centerPercentage); + json_alias.emplace("chainAliasName", (alias.chainAliasName == nullptr ? nlohmann::json() : alias.chainAliasName)); + json_alias.emplace("distMax", alias.distMax); + json_alias.emplace("distMin", alias.distMin); + json_alias.emplace("envelopMax", alias.envelopMax); + json_alias.emplace("envelopMin", alias.envelopMin); + json_alias.emplace("envelopPercentage", alias.envelopPercentage); + json_alias.emplace("flags", iw4Flags); + json_alias.emplace("lfePercentage", alias.lfePercentage); + json_alias.emplace("mixerGroup", nlohmann::json()); + json_alias.emplace("pitchMax", alias.pitchMax); + json_alias.emplace("pitchMin", alias.pitchMin); + json_alias.emplace("probability", alias.probability); + json_alias.emplace("secondaryAliasName", (alias.secondaryAliasName == nullptr ? nlohmann::json() : alias.secondaryAliasName)); + json_alias.emplace("sequence", alias.sequence); + json_alias.emplace("slavePercentage", alias.___u15.slavePercentage); + json_alias.emplace("speakerMap", speakerMap); + json_alias.emplace("soundFile", (strDuplicator.duplicateString(soundFile))); + json_alias.emplace("startDelay", alias.startDelay); + json_alias.emplace("subtitle", (alias.subtitle == nullptr ? nlohmann::json() : alias.subtitle)); + json_alias.emplace("type", alias.soundFile->type); + json_alias.emplace("volMax", alias.volMax); + json_alias.emplace("volMin", alias.volMin); + json_alias.emplace("volumeFalloffCurve", (alias.volumeFalloffCurve->filename)); + + head.emplace_back(json_alias); + } + + output.emplace("aliasName", (ents->aliasName)); + output.emplace("count", ents->count); + output.emplace("head", head); + + const auto dump = output.dump(4); + Utils::IO::WriteFile(std::format("raw/sounds/{}.json", ents->aliasName), dump); + } + void Isnd_alias_list_t::save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) { AssertSize(Game::snd_alias_list_t, 12); @@ -392,8 +518,16 @@ namespace Assets if (asset->aliasName) { - buffer->saveString(builder->getAssetName(this->getType(), asset->aliasName)); - Utils::Stream::ClearPointer(&dest->aliasName); + if (builder->hasPointer(asset->aliasName)) + { + dest->aliasName = builder->getPointer(asset->aliasName); + } + else + { + builder->storePointer(asset->aliasName); + buffer->saveString(asset->aliasName); + Utils::Stream::ClearPointer(&dest->aliasName); + } } if (asset->head) @@ -419,8 +553,16 @@ namespace Assets if (alias->aliasName) { - buffer->saveString(alias->aliasName); - Utils::Stream::ClearPointer(&destAlias->aliasName); + if (builder->hasPointer(alias->aliasName)) + { + destAlias->aliasName = builder->getPointer(alias->aliasName); + } + else + { + builder->storePointer(alias->aliasName); + buffer->saveString(alias->aliasName); + Utils::Stream::ClearPointer(&destAlias->aliasName); + } } if (alias->subtitle) @@ -467,7 +609,7 @@ namespace Assets { if (alias->soundFile->type == Game::snd_alias_type_t::SAT_LOADED) { - destSoundFile->u.loadSnd = builder->saveSubAsset(Game::XAssetType::ASSET_TYPE_LOADED_SOUND, alias->soundFile->u.loadSnd).loadSnd; + destSoundFile->u.loadSnd = builder->saveSubAsset(Game::ASSET_TYPE_LOADED_SOUND, alias->soundFile->u.loadSnd).loadSnd; } else { @@ -494,7 +636,7 @@ namespace Assets if (alias->volumeFalloffCurve) { - destAlias->volumeFalloffCurve = builder->saveSubAsset(Game::XAssetType::ASSET_TYPE_SOUND_CURVE, alias->volumeFalloffCurve).sndCurve; + destAlias->volumeFalloffCurve = builder->saveSubAsset(Game::ASSET_TYPE_SOUND_CURVE, alias->volumeFalloffCurve).sndCurve; } if (alias->speakerMap) diff --git a/src/Components/Modules/AssetInterfaces/Isnd_alias_list_t.hpp b/src/Components/Modules/AssetInterfaces/Isnd_alias_list_t.hpp index d7f2337b..fca10006 100644 --- a/src/Components/Modules/AssetInterfaces/Isnd_alias_list_t.hpp +++ b/src/Components/Modules/AssetInterfaces/Isnd_alias_list_t.hpp @@ -10,5 +10,6 @@ namespace Assets void load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) override; void save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override; void mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override; + void dump(Game::XAssetHeader header) override; }; } diff --git a/src/Components/Modules/Debug.cpp b/src/Components/Modules/Debug.cpp index 949ff759..6ae52dae 100644 --- a/src/Components/Modules/Debug.cpp +++ b/src/Components/Modules/Debug.cpp @@ -242,6 +242,11 @@ namespace Components void Debug::CG_DrawDebugOverlays_Hk(const int localClientNum) { + if (!DebugOverlay) + { + return; + } + switch (DebugOverlay->current.integer) { case 2: diff --git a/src/Components/Modules/Logger.cpp b/src/Components/Modules/Logger.cpp index c1e446b7..c6db9af7 100644 --- a/src/Components/Modules/Logger.cpp +++ b/src/Components/Modules/Logger.cpp @@ -80,11 +80,12 @@ namespace Components void Logger::ErrorInternal(const Game::errorParm_t error, const std::string_view& fmt, std::format_args&& args) { + const auto msg = std::vformat(fmt, args); + #ifdef _DEBUG if (IsDebuggerPresent()) __debugbreak(); #endif - const auto msg = std::vformat(fmt, args); Game::Com_Error(error, "%s", msg.data()); } diff --git a/src/Components/Modules/Renderer.cpp b/src/Components/Modules/Renderer.cpp index 498b075b..2bde06b9 100644 --- a/src/Components/Modules/Renderer.cpp +++ b/src/Components/Modules/Renderer.cpp @@ -15,6 +15,7 @@ namespace Components Dvar::Var Renderer::r_drawAABBTrees; Dvar::Var Renderer::r_playerDrawDebugDistance; Dvar::Var Renderer::r_forceTechnique; + Dvar::Var Renderer::r_drawRunners; float cyan[4] = { 0.0f, 0.5f, 0.5f, 1.0f }; float red[4] = { 1.0f, 0.0f, 0.0f, 1.0f }; @@ -118,10 +119,13 @@ namespace Components } } - void Renderer::R_TextureFromCodeError(const char* sampler, Game::GfxCmdBufState* state) + void Renderer::R_TextureFromCodeError(const char* sampler, Game::GfxCmdBufState* state, int samplerCode) { - Logger::Error(Game::ERR_FATAL, "Tried to use sampler '{}' when it isn't valid for material '{}' and technique '{}'", - sampler, state->material->info.name, state->technique->name); + Logger::Error( + Game::ERR_FATAL, + "Tried to use sampler #{} ('{}') at the wrong timing! Additional info:\nMaterial: '{}'\nTechnique {}\nTechnique slot: {}\nTechnique flags:{}\nPass: {}\nPixel shader: {}\n", + samplerCode, sampler, state->material->info.name, state->technique->name, (int)state->techType, state->technique->flags, state->passIndex, state->pixelShader->name + ); } __declspec(naked) void Renderer::StoreGfxBufContextPtrStub1() @@ -154,10 +158,11 @@ namespace Components // show error pushad + push eax push ebx push edx call R_TextureFromCodeError - add esp, 8 + add esp, 0xC popad // go back @@ -361,7 +366,7 @@ namespace Components if (staticModel->model) { - Game::R_AddDebugBounds(staticModelsColor, b); + Game::R_AddDebugBounds(staticModelsColor, b); } } } @@ -458,6 +463,41 @@ namespace Components } } + void Renderer::DebugDrawRunners() + { + if (!Game::CL_IsCgameInitialized()) + { + return; + } + + if (r_drawRunners.get()) + { + auto* fxSystem = reinterpret_cast(0x173F200); + + if (fxSystem) + { + for (auto i = 0; i < fxSystem->activeElemCount; i++) + { + auto* elem = &fxSystem->effects[i]; + if (elem->def) + { + Game::R_AddDebugString(sceneModelsColor, elem->frameNow.origin, 1.0f, elem->def->name); + } + } + } + + auto soundCount = *reinterpret_cast(0x7C5C90); + auto* sounds = reinterpret_cast(0x7C5CA0); + + for (auto i = 0; i < soundCount; i++) + { + if (sounds[i].aliasList) + { + Game::R_AddDebugString(staticModelsColor, sounds[i].origin, 1.0f, sounds[i].aliasList->aliasName); + } + } + } + } void Renderer::DebugDrawAABBTrees() { if (!r_drawAABBTrees.get()) return; @@ -507,6 +547,7 @@ namespace Components { if (Game::CL_IsCgameInitialized()) { + DebugDrawRunners(); DebugDrawAABBTrees(); DebugDrawModelNames(); DebugDrawModelBoundingBoxes(); @@ -561,6 +602,7 @@ namespace Components nullptr }; + Renderer::r_drawRunners = Game::Dvar_RegisterBool("r_drawRunners", false, Game::DVAR_NONE, "Draw active sound & fx runners"); Renderer::r_drawModelBoundingBoxes = Game::Dvar_RegisterEnum("r_drawModelBoundingBoxes", values, 0, Game::DVAR_CHEAT, "Draw scene model bounding boxes"); Renderer::r_drawSceneModelCollisions = Game::Dvar_RegisterBool("r_drawSceneModelCollisions", false, Game::DVAR_CHEAT, "Draw scene model collisions"); Renderer::r_drawTriggers = Game::Dvar_RegisterBool("r_drawTriggers", false, Game::DVAR_CHEAT, "Draw triggers"); diff --git a/src/Components/Modules/Renderer.hpp b/src/Components/Modules/Renderer.hpp index c856376e..ab939543 100644 --- a/src/Components/Modules/Renderer.hpp +++ b/src/Components/Modules/Renderer.hpp @@ -28,7 +28,7 @@ namespace Components static void PostVidRestart(); static void PostVidRestartStub(); - static void R_TextureFromCodeError(const char* sampler, Game::GfxCmdBufState* state); + static void R_TextureFromCodeError(const char* sampler, Game::GfxCmdBufState* state, int samplerCode); static void StoreGfxBufContextPtrStub1(); static void StoreGfxBufContextPtrStub2(); @@ -38,6 +38,7 @@ namespace Components static void DebugDrawSceneModelCollisions(); static void DebugDrawModelBoundingBoxes(); static void DebugDrawModelNames(); + static void DebugDrawRunners(); static void DebugDrawAABBTrees(); static void ForceTechnique(); @@ -50,6 +51,7 @@ namespace Components static Utils::Signal SingleBackendFrameSignal; static Dvar::Var r_drawTriggers; + static Dvar::Var r_drawRunners; static Dvar::Var r_drawSceneModelCollisions; static Dvar::Var r_drawModelBoundingBoxes; static Dvar::Var r_drawModelNames; diff --git a/src/Game/Structs.hpp b/src/Game/Structs.hpp index 5297d176..caccd403 100644 --- a/src/Game/Structs.hpp +++ b/src/Game/Structs.hpp @@ -882,8 +882,8 @@ namespace Game { float normal[3]; float dist; - char type; - char pad[3]; + unsigned char type; + unsigned char pad[3]; }; struct cbrushside_t @@ -896,13 +896,13 @@ namespace Game struct cbrush_t { - unsigned __int16 numsides; - unsigned __int16 glassPieceIndex; + unsigned short numsides; + unsigned short glassPieceIndex; cbrushside_t* sides; - char* baseAdjacentSide; - __int16 axialMaterialNum[2][3]; - char firstAdjacentSideOffsets[2][3]; - char edgeCount[2][3]; + unsigned char* baseAdjacentSide; + unsigned short axialMaterialNum[2][3]; + unsigned char firstAdjacentSideOffsets[2][3]; + unsigned char edgeCount[2][3]; }; struct BrushWrapper @@ -2497,6 +2497,189 @@ namespace Game SAT_COUNT = 0x4, }; + struct snd_volume_info_t + { + float volume; + float goalvolume; + float goalrate; + }; + + struct snd_channelvolgroup + { + snd_volume_info_t channelvol[64]; + bool active; + }; + + struct snd_background_info_t + { + float goalvolume; + float goalrate; + }; + + struct snd_enveffect + { + int roomtype; + float drylevel; + float drygoal; + float dryrate; + float wetlevel; + float wetgoal; + float wetrate; + bool active; + }; + + struct orientation_t + { + float origin[3]; + float axis[3][3]; + }; + + struct snd_listener + { + orientation_t orient; + float velocity; + int clientNum; + bool active; + }; + + struct snd_amplifier + { + snd_listener* listener; + int minRadius; + int maxRadius; + float falloffExp; + float minVol; + float maxVol; + }; + + struct snd_entchannel_info_t + { + char name[64]; + int priority; + bool is3d; + bool isRestricted; + bool isPausable; + int maxVoices; + int voiceCount; + }; + + struct snd_entchan_overrides_t + { + unsigned int isPausable[2]; + float timescaleLerp[64]; + }; + + enum SndFileLoadingState + { + SFLS_UNLOADED = 0x0, + SFLS_LOADING = 0x1, + SFLS_LOADED = 0x2, + }; + + struct SndFileSpecificChannelInfo + { + SndFileLoadingState loadingState; + int srcChannelCount; + int baserate; + }; + + union SndEntHandle + { + struct + { + unsigned int entIndex; + } field; + int handle; + }; + + enum SndLengthId + { + SndLengthNotify_Subtitle = 0x0, + SndLengthNotify_EntityCustom = 0x1, + SndLengthNotifyCount = 0x2, + }; + + struct sndLengthNotifyInfo + { + SndLengthId id[4]; + void* data[4]; + int count; + }; + + enum snd_alias_system_t + { + SASYS_UI = 0x0, + SASYS_CGAME = 0x1, + SASYS_GAME = 0x2, + SASYS_COUNT = 0x3, + }; + + struct snd_channel_info_t + { + SndFileSpecificChannelInfo soundFileInfo; + SndEntHandle sndEnt; + int entchannel; + int startDelay; + int looptime; + int totalMsec; + int playbackId; + sndLengthNotifyInfo lengthNotifyInfo; + float basevolume; + float pitch; + struct snd_alias_t* alias0; + struct snd_alias_t* alias1; + int saveIndex0; + int saveIndex1; + float lerp; + float org[3]; + float offset[3]; + bool paused; + bool master; + float timescaleLerp; + snd_alias_system_t system; + }; + + struct snd_local_t + { + bool Initialized2d; + bool Initialized3d; + bool paused; + int playbackIdCounter; + unsigned int playback_rate; + int playback_channels; + float timescale; + int pausetime; + int cpu; + struct + { + char buffer[16384]; + volatile int size; + bool compress; + } restore; + float volume; + snd_volume_info_t mastervol; + snd_channelvolgroup channelVolGroups[4]; + snd_channelvolgroup* channelvol; + snd_background_info_t background[4]; + int ambient_track; + float slaveLerp; + float masterPercentage; + snd_enveffect envEffects[5]; + snd_enveffect* effect; + snd_listener listeners[2]; + int time; + int looptime; + snd_amplifier amplifier; + snd_entchannel_info_t entchaninfo[64]; + snd_entchan_overrides_t entchanOverrides; + int entchannel_count; + snd_channel_info_t chaninfo[52]; + int max_2D_channels; + int max_3D_channels; + int max_stream_channels; + }; + + struct SoundFile { char type; @@ -2504,12 +2687,6 @@ namespace Game SoundFileRef u; }; - union $C8D87EB0090687D323381DFB7A82089C - { - float slavePercentage; - float masterPercentage; - }; - struct SndCurve { const char* filename; @@ -2537,6 +2714,26 @@ namespace Game MSSChannelMap channelMaps[2][2]; }; + union SoundAliasFlags + { +#pragma warning(push) +#pragma warning(disable: 4201) + struct + { + unsigned int looping : 1; // & 1 / 0x1 / 0000 0000 0000 0001 + unsigned int isMaster : 1; // & 2 / 0x2 / 0000 0000 0000 0010 + unsigned int isSlave : 1; // & 4 / 0x4 / 0000 0000 0000 0100 + unsigned int fullDryLevel : 1; // & 8 / 0x8 / 0000 0000 0000 1000 + unsigned int noWetLevel : 1; // & 16 / 0x10 / 0000 0000 0001 0000 + unsigned int unknown : 1; // & 32 / 0x20 / 0000 0000 0010 0000 + unsigned int unk_is3D : 1; // & 64 / 0x40 / 0000 0000 0100 0000 // CONFIRMED IW4 IW5 + unsigned int type : 2; // & 384 / 0x180 / 0000 0001 1000 0000 // CONFIRMED IW4 IW5 + unsigned int channel : 6; // & 32256 / 0x7E00 / 0111 1110 0000 0000 // CONFIRMED IW4 IW5 + }; +#pragma warning(pop) + unsigned int intValue; + }; + const struct snd_alias_t { const char* aliasName; @@ -2553,8 +2750,12 @@ namespace Game float distMin; float distMax; float velocityMin; - int flags; - $C8D87EB0090687D323381DFB7A82089C ___u15; + SoundAliasFlags flags; + union + { + float slavePercentage; + float masterPercentage; + } ___u15; float probability; float lfePercentage; float centerPercentage; @@ -2637,7 +2838,7 @@ namespace Game struct cLeafBrushNode_s { - char axis; + unsigned char axis; __int16 leafBrushCount; int contents; cLeafBrushNodeData_t data; @@ -2654,9 +2855,9 @@ namespace Game struct CollisionPartition { - char triCount; - char borderCount; - char firstVertSegment; + unsigned char triCount; + unsigned char borderCount; + unsigned char firstVertSegment; int firstTri; CollisionBorder* borders; }; @@ -2993,7 +3194,7 @@ namespace Game { const char* name; int isInUse; - int planeCount; + unsigned int planeCount; cplane_s* planes; unsigned int numStaticModels; cStaticModel_s* staticModelList; @@ -3002,7 +3203,7 @@ namespace Game unsigned int numBrushSides; cbrushside_t* brushsides; unsigned int numBrushEdges; - char* brushEdges; + unsigned char* brushEdges; unsigned int numNodes; cNode_t* nodes; unsigned int numLeafs; @@ -3014,15 +3215,15 @@ namespace Game unsigned int numLeafSurfaces; unsigned int* leafsurfaces; unsigned int vertCount; - float(*verts)[3]; - int triCount; + vec3_t* verts; + unsigned int triCount; unsigned __int16* triIndices; - char* triEdgeIsWalkable; - int borderCount; + unsigned char* triEdgeIsWalkable; + unsigned int borderCount; CollisionBorder* borders; int partitionCount; CollisionPartition* partitions; - int aabbTreeCount; + unsigned int aabbTreeCount; CollisionAabbTree* aabbTrees; unsigned int numSubModels; cmodel_t* cmodels; @@ -3394,9 +3595,9 @@ namespace Game float texCoordOrigin[2]; unsigned int supportMask; float areaX2; - char defIndex; - char vertCount; - char fanDataCount; + unsigned char defIndex; + unsigned char vertCount; + unsigned char fanDataCount; char pad[1]; }; @@ -8654,6 +8855,12 @@ namespace Game unsigned __int16 children; }; + struct ClientEntSound + { + float origin[3]; + snd_alias_list_t* aliasList; + }; + struct FxEffect { const FxEffectDef* def; @@ -10672,6 +10879,124 @@ namespace Game HANDLE handle; }; + struct FxCamera + { + float origin[3]; + volatile int isValid; + float frustum[6][4]; + float axis[3][3]; + unsigned int frustumPlaneCount; + float viewOffset[3]; + bool thermal; + unsigned int pad[2]; + }; + + struct r_double_index_t + { + unsigned __int16 value[2]; + }; + + struct FxSpriteInfo + { + r_double_index_t* indices; + unsigned int indexCount; + Material* material; + const char* name; + }; + + struct FxVisBlocker + { + float origin[3]; + unsigned __int16 radius; + unsigned __int16 visibility; + }; + + struct FxVisState + { + FxVisBlocker blocker[256]; + volatile int blockerCount; + unsigned int pad[3]; + }; + + struct FxElem + { + char defIndex; + char sequence; + char atRestFraction; + char emitResidual; + unsigned __int16 nextElemHandleInEffect; + unsigned __int16 prevElemHandleInEffect; + int msecBegin; + float baseVel[3]; + union + { + int physObjId; + float origin[3]; + } ___u8; + union + { + unsigned __int16 lightingHandle; + unsigned __int16 sparkCloudHandle; + unsigned __int16 sparkFountainHandle; + } u; + }; + + struct FxSystem + { + FxCamera camera; + FxCamera cameraPrev; + FxSpriteInfo sprite; + FxEffect* effects; + FxElem *elems; + void* trails; + void* trailElems; + void* bolts; + void* sparkClouds; + void* sparkFountains; + void* sparkFountainClusters; + unsigned __int16* deferredElems; + volatile int firstFreeElem; + volatile int firstFreeTrailElem; + volatile int firstFreeTrail; + volatile int firstFreeBolt; + volatile int firstFreeSparkCloud; + volatile int firstFreeSparkFountain; + volatile int firstFreeSparkFountainCluster; + volatile int deferredElemCount; + volatile int activeElemCount; + volatile int activeTrailElemCount; + volatile int activeTrailCount; + volatile int activeBoltCount; + volatile int activeSparkCloudCount; + volatile int activeSparkFountainCount; + volatile int activeSparkFountainClusterCount; + volatile int gfxCloudCount; + FxVisState* visState; + FxVisState* visStateBufferRead; + FxVisState* visStateBufferWrite; + volatile int firstActiveEffect; + volatile int firstNewEffect; + volatile int firstFreeEffect; + unsigned __int16 allEffectHandles[1024]; + volatile int activeSpotLightEffectCount; + volatile int activeSpotLightElemCount; + unsigned __int16 activeSpotLightEffectHandle; + unsigned __int16 activeSpotLightElemHandle; + __int16 activeSpotLightBoltDobj; + volatile int iteratorCount; + int msecNow; + volatile int msecDraw; + int frameCount; + bool isInitialized; + bool needsGarbageCollection; + bool isArchiving; + char localClientNum; + unsigned int restartList[32]; + FxEffect** restartEffectsList; + unsigned int restartCount; + unsigned int pad1[14]; + }; + #pragma endregion #ifndef IDA diff --git a/src/Utils/Json.cpp b/src/Utils/Json.cpp index ff401f86..ffa427d9 100644 --- a/src/Utils/Json.cpp +++ b/src/Utils/Json.cpp @@ -62,4 +62,12 @@ namespace Utils::Json return input.to_ulong(); } + Game::Bounds ReadBounds(const nlohmann::json_abi_v3_11_2::json value) + { + Game::Bounds bounds{}; + Utils::Json::CopyArray(bounds.midPoint, value["midPoint"]); + Utils::Json::CopyArray(bounds.halfSize, value["halfSize"]); + + return bounds; + } } diff --git a/src/Utils/Json.hpp b/src/Utils/Json.hpp index c73de553..6a4d07c5 100644 --- a/src/Utils/Json.hpp +++ b/src/Utils/Json.hpp @@ -1,9 +1,25 @@ #pragma once + #include namespace Utils::Json { std::string TypeToString(nlohmann::json::value_t type); - unsigned long ReadFlags(std::string binaryFlags, size_t size); + unsigned long ReadFlags(const std::string binaryFlags, size_t size); + + Game::Bounds ReadBounds(const nlohmann::json_abi_v3_11_2::json value); + + template void CopyArray(T* destination, const nlohmann::json_abi_v3_11_2::json& json_member, size_t count = 0) + { + if (count == 0) + { + count = json_member.size(); + } + + for (size_t i = 0; i < count; i++) + { + destination[i] = json_member[i].get(); + } + } } diff --git a/src/Utils/Maths.cpp b/src/Utils/Maths.cpp index 2b840002..35f12ff3 100644 --- a/src/Utils/Maths.cpp +++ b/src/Utils/Maths.cpp @@ -7,7 +7,7 @@ namespace Utils::Maths return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2]; } - void VectorSubtract(float va[3], float vb[3], float out[3]) + void VectorSubtract(const float va[3], const float vb[3], float out[3]) { out[0] = va[0] - vb[0]; out[1] = va[1] - vb[1]; @@ -35,7 +35,7 @@ namespace Utils::Maths out[2] = v[2] * scale; } - float Vec3SqrDistance(float v1[3], float v2[3]) + float Vec3SqrDistance(const float v1[3], const float v2[3]) { float out[3]; diff --git a/src/Utils/Maths.hpp b/src/Utils/Maths.hpp index 47f37ed5..7d2111df 100644 --- a/src/Utils/Maths.hpp +++ b/src/Utils/Maths.hpp @@ -7,9 +7,9 @@ namespace Utils::Maths constexpr auto VectorNegate(float x[3]) { x[0] = -x[0]; x[1] = -x[1]; x[2] = -x[2]; } float DotProduct(float v1[3], float v2[3]); - void VectorSubtract(float va[3], float vb[3], float out[3]); + void VectorSubtract(const float va[3], const float vb[3], float out[3]); void VectorAdd(float va[3], float vb[3], float out[3]); void VectorCopy(float in[3], float out[3]); void VectorScale(float v[3], float scale, float out[3]); - float Vec3SqrDistance(float v1[3], float v2[3]); + float Vec3SqrDistance(const float v1[3], const float v2[3]); } diff --git a/src/Utils/Stream.cpp b/src/Utils/Stream.cpp index dc50f51c..fb50955c 100644 --- a/src/Utils/Stream.cpp +++ b/src/Utils/Stream.cpp @@ -16,14 +16,14 @@ namespace Utils const char* Stream::Reader::readCString() { - return this->allocator->duplicateString(this->readString()); + return this->allocator_->duplicateString(this->readString()); } char Stream::Reader::readByte() { - if ((this->position + 1) <= this->buffer.size()) + if ((this->position_ + 1) <= this->buffer_.size()) { - return this->buffer[this->position++]; + return this->buffer_[this->position_++]; } throw std::runtime_error("Reading past the buffer"); @@ -31,45 +31,45 @@ namespace Utils void* Stream::Reader::read(size_t size, std::size_t count) { - size_t bytes = size * count; + auto bytes = size * count; - if ((this->position + bytes) <= this->buffer.size()) + if ((this->position_ + bytes) <= this->buffer_.size()) { - void* _buffer = this->allocator->allocate(bytes); + auto* buffer = this->allocator_->allocate(bytes); - std::memcpy(_buffer, this->buffer.data() + this->position, bytes); - this->position += bytes; + std::memcpy(buffer, this->buffer_.data() + this->position_, bytes); + this->position_ += bytes; - return _buffer; + return buffer; } throw std::runtime_error("Reading past the buffer"); } - bool Stream::Reader::end() + bool Stream::Reader::end() const { - return (this->buffer.size() == this->position); + return (this->buffer_.size() == this->position_); } - void Stream::Reader::seek(unsigned int _position) + void Stream::Reader::seek(unsigned int position) { - if (this->buffer.size() >= _position) + if (this->buffer_.size() >= position) { - this->position = _position; + this->position_ = position; } } - void Stream::Reader::seekRelative(unsigned int _position) + void Stream::Reader::seekRelative(unsigned int position) { - return this->seek(_position + this->position); + return this->seek(position + this->position_); } void* Stream::Reader::readPointer() { - void* pointer = this->read(); + auto* pointer = this->read(); if (!this->hasPointer(pointer)) { - this->pointerMap[pointer] = nullptr; + this->pointerMap_[pointer] = nullptr; } return pointer; } @@ -78,18 +78,18 @@ namespace Utils { if (this->hasPointer(oldPointer)) { - this->pointerMap[oldPointer] = newPointer; + this->pointerMap_[oldPointer] = newPointer; } } - bool Stream::Reader::hasPointer(void* pointer) + bool Stream::Reader::hasPointer(void* pointer) const { - return this->pointerMap.find(pointer) != this->pointerMap.end(); + return this->pointerMap_.contains(pointer); } Stream::Stream() : ptrAssertion(false), criticalSectionState(0) { - memset(this->blockSize, 0, sizeof(this->blockSize)); + std::memset(this->blockSize, 0, sizeof(this->blockSize)); #ifdef WRITE_LOGS this->structLevel = 0; @@ -99,12 +99,12 @@ namespace Utils Stream::Stream(size_t size) : Stream() { - this->buffer.reserve(size); + this->buffer_.reserve(size); } Stream::~Stream() { - this->buffer.clear(); + this->buffer_.clear(); if (this->criticalSectionState != 0) { @@ -114,12 +114,12 @@ namespace Utils std::size_t Stream::length() const { - return this->buffer.length(); + return this->buffer_.length(); } std::size_t Stream::capacity() const { - return this->buffer.capacity(); + return this->buffer_.capacity(); } void Stream::assertPointer(const void* pointer, std::size_t length) @@ -159,7 +159,7 @@ namespace Utils return this->at(); } - auto data = this->data(); + auto* data = this->data(); if (this->isCriticalSection() && this->length() + (size * count) > this->capacity()) { @@ -167,7 +167,7 @@ namespace Utils __debugbreak(); } - this->buffer.append(static_cast(_str), size * count); + this->buffer_.append(static_cast(_str), size * count); if (this->data() != data && this->isCriticalSection()) { @@ -319,7 +319,7 @@ namespace Utils char* Stream::data() { - return const_cast(this->buffer.data()); + return const_cast(this->buffer_.data()); } unsigned int Stream::getBlockSize(Game::XFILE_BLOCK_TYPES stream) diff --git a/src/Utils/Stream.hpp b/src/Utils/Stream.hpp index 3739cc3a..ba2b92f2 100644 --- a/src/Utils/Stream.hpp +++ b/src/Utils/Stream.hpp @@ -21,13 +21,13 @@ namespace Utils int criticalSectionState; unsigned int blockSize[Game::MAX_XFILE_COUNT]; std::vector streamStack; - std::string buffer; + std::string buffer_; public: class Reader { public: - Reader(Memory::Allocator* _allocator, const std::string& _buffer) : position(0), buffer(_buffer), allocator(_allocator) {} + Reader(Memory::Allocator* allocator, std::string& buffer) : position_(0), buffer_(std::move(buffer)), allocator_(allocator) {} std::string readString(); const char* readCString(); @@ -53,18 +53,18 @@ namespace Utils auto ptr = read(); auto* voidPtr = reinterpret_cast(ptr); - if (allocator->isPointerMapped(voidPtr)) + if (this->allocator_->isPointerMapped(voidPtr)) { - return allocator->getPointer(voidPtr); + return this->allocator_->getPointer(voidPtr); } throw std::runtime_error("Bad data: missing ptr"); } case FOLLOWING: { - auto filePosition = position; + auto filePosition = this->position_; auto data = readArray(count); - allocator->mapPointer(reinterpret_cast(filePosition), data); + this->allocator_->mapPointer(reinterpret_cast(filePosition), data); return data; } default: @@ -89,19 +89,19 @@ namespace Utils return obj; } - bool end(); + bool end() const; void seek(unsigned int position); void seekRelative(unsigned int position); void* readPointer(); void mapPointer(void* oldPointer, void* newPointer); - bool hasPointer(void* pointer); + bool hasPointer(void* pointer) const; private: - unsigned int position; - std::string buffer; - std::map pointerMap; - Memory::Allocator* allocator; + unsigned int position_; + std::string buffer_; + std::map pointerMap_; + Memory::Allocator* allocator_; }; enum Alignment @@ -123,11 +123,13 @@ namespace Utils Stream(size_t size); ~Stream(); + std::unordered_map dataPointers; + [[nodiscard]] std::size_t length() const; [[nodiscard]] std::size_t capacity() const; - char* save(const void * _str, std::size_t size, std::size_t count = 1); - char* save(Game::XFILE_BLOCK_TYPES stream, const void * _str, std::size_t size, std::size_t count); + char* save(const void * str, std::size_t size, std::size_t count = 1); + char* save(Game::XFILE_BLOCK_TYPES stream, const void * str, std::size_t size, std::size_t count); char* save(Game::XFILE_BLOCK_TYPES stream, int value, std::size_t count); template char* save(T* object) @@ -135,6 +137,42 @@ namespace Utils return saveArray(object, 1); } + template char* saveObject(T value) + { + return saveArray(&value, 1); + } + + template void saveArrayIfNotExisting(T* data, size_t count) + { +#define POINTER 255 +#define FOLLOWING 254 + + if (const auto itr = dataPointers.find(data); itr != dataPointers.end()) + { + saveByte(POINTER); + saveObject(itr->second); + } + else + { + saveByte(FOLLOWING); + dataPointers.insert_or_assign(reinterpret_cast(data), length()); + saveArray(data, count); + } + } + + + char* save(int value, size_t count = 1) + { + auto ret = this->length(); + + for (size_t i = 0; i < count; ++i) + { + this->save(&value, 4, 1); + } + + return this->data() + ret; + } + template char* saveArray(T* array, std::size_t count) { return save(array, sizeof(T), count);