diff --git a/.gitmodules b/.gitmodules index b283a8c3..84c41e45 100644 --- a/.gitmodules +++ b/.gitmodules @@ -19,3 +19,6 @@ [submodule "thirdparty/lz4"] path = thirdparty/lz4 url = https://github.com/lz4/lz4.git +[submodule "thirdparty/gsc-tool"] + path = thirdparty/gsc-tool + url = https://github.com/xensik/gsc-tool.git diff --git a/src/Common/Game/T6/T6.h b/src/Common/Game/T6/T6.h index d7413f40..4b13191a 100644 --- a/src/Common/Game/T6/T6.h +++ b/src/Common/Game/T6/T6.h @@ -172,9 +172,9 @@ namespace T6 enum CM_MATERIAL_TYPE { - NO_COLOUR_OR_TEXTURE, CM_MATERIAL_COLOUR, - CM_MATERIAL_TEXTURE + CM_MATERIAL_TEXTURE, + CM_MATERIAL_EMPTY }; struct customMapMaterial @@ -185,7 +185,6 @@ namespace T6 struct worldSurface { - char flags; customMapMaterial material; //char lightmapIndex; diff --git a/src/Common/Game/T6/T6_Assets.h b/src/Common/Game/T6/T6_Assets.h index 7e695301..c8488eef 100644 --- a/src/Common/Game/T6/T6_Assets.h +++ b/src/Common/Game/T6/T6_Assets.h @@ -1278,6 +1278,30 @@ namespace T6 typedef tdef_align32(4) char aligned_byte_pointer; typedef tdef_align32(4) GfxCompressedLightGridCoeffs GfxCompressedLightGridCoeffs_align4; + struct GfxLightGridUnk + { + char unknown1; + char unknown2; + char unknown3; + char unknown4; + char unknown5; + char unknown6; + char unknown7; + char unknown8; + }; + + + struct GfxLightGridRow + { + unsigned __int16 colStart; + unsigned __int16 colCount; + unsigned __int16 zStart; + unsigned __int16 zCount; + unsigned int firstEntry; + GfxLightGridUnk unk; + }; + + struct GfxLightGrid { unsigned int sunPrimaryLightIndex; @@ -1288,7 +1312,7 @@ namespace T6 unsigned int colAxis; uint16_t* rowDataStart; unsigned int rawRowDataSize; - aligned_byte_pointer* rawRowData; + aligned_byte_pointer* rawRowData; // GfxLightGridRow unsigned int entryCount; GfxLightGridEntry* entries; unsigned int colorCount; diff --git a/src/ObjLoading/Asset/AssetCreationContext.cpp b/src/ObjLoading/Asset/AssetCreationContext.cpp index deb08b5a..a33cebdd 100644 --- a/src/ObjLoading/Asset/AssetCreationContext.cpp +++ b/src/ObjLoading/Asset/AssetCreationContext.cpp @@ -131,6 +131,9 @@ XAssetInfoGeneric* AssetCreationContext::LoadDependencyGeneric(const asset_type_ } else { + if (assetName[0] == ',') + return LoadDefaultAssetDependency(assetType, assetName); + std::cerr << std::format("Missing asset \"{}\" of type \"{}\"\n", assetName, *m_zone.m_pools->GetAssetTypeName(assetType)); } diff --git a/src/ObjLoading/Game/T6/CustomMap/CustomMapConsts.h b/src/ObjLoading/Game/T6/CustomMap/CustomMapConsts.h index 5c2a1310..dfa1ac83 100644 --- a/src/ObjLoading/Game/T6/CustomMap/CustomMapConsts.h +++ b/src/ObjLoading/Game/T6/CustomMap/CustomMapConsts.h @@ -19,28 +19,108 @@ #define LEAF_TERRAIN_CONTENTS 1 #define WORLD_TERRAIN_CONTENTS 1 -const std::string missingMaterialName = "custom/missing_material"; -const std::string colourOnlyMaterialName = "custom/colour_only_material"; -const std::string templateMaterialName = "custom/material_template"; - -#define DEFAULT_SURFACE_LIGHT 1 -#define DEFAULT_SURFACE_LIGHTMAP 0 -#define DEFAULT_SURFACE_REFLECTION_PROBE 0 +std::string missingImageName = "missing_image"; +std::string colorOnlyImageName = "color_only_image"; #define MAX_AABB_SIZE 512 // maximum size a BSP node can be before it becomes a leaf +#define LIGHTGRID_COLOUR 128 // global lighting colour + // do not change -#define MAX_COL_VERTS (UINT16_MAX - 1) // max amount of verts a map can have +#define MAX_COL_VERTS (UINT16_MAX - 1) // max amount of collision verts a map can have // do not change #define DEFAULT_LIGHT_INDEX 0 #define SUN_LIGHT_INDEX 1 // do not change -#define SMODEL_FLAG_NO_SHADOW 1 -#define SMODEL_FLAG_IS_LIT 2 +enum SMODEL_FLAGS +{ + SMODEL_FLAG_NO_SHADOW = 1, + SMODEL_FLAG_IS_LIT = 2 +}; + +// do not change +enum GFX_SURFACE_FLAGS +{ + GFX_SURFACE_CASTS_SUN_SHADOW = 0x1, + GFX_SURFACE_HAS_PRIMARY_LIGHT_CONFLICT = 0x2, + GFX_SURFACE_IS_SKY = 0x4, + GFX_SURFACE_NO_DRAW = 0x8, + GFX_SURFACE_CASTS_SHADOW = 0x10, + GFX_SURFACE_QUANTIZED = 0x20, + GFX_SURFACE_NO_COLOR = 0x40 +}; + #define DEFAULT_SMODEL_CULL_DIST 10000.0f #define DEFAULT_SMODEL_FLAGS SMODEL_FLAG_NO_SHADOW #define DEFAULT_SMODEL_LIGHT 1 #define DEFAULT_SMODEL_REFLECTION_PROBE 0 + +#define DEFAULT_SURFACE_LIGHT 1 +#define DEFAULT_SURFACE_LIGHTMAP 0 +#define DEFAULT_SURFACE_REFLECTION_PROBE 0 +#define DEFAULT_SURFACE_FLAGS 0 + +const std::vector spawnpointDefenderTypeArray = { + "mp_ctf_spawn_allies", + "mp_ctf_spawn_allies_start", + "mp_sd_spawn_defender", + "mp_dom_spawn_allies_start", + "mp_dem_spawn_defender_start", + "mp_dem_spawn_defenderOT_start", + "mp_dem_spawn_defender", + "mp_tdm_spawn_allies_start", + "mp_tdm_spawn_team1_start", + "mp_tdm_spawn_team2_start", + "mp_tdm_spawn_team3_start" +}; + +const std::vector spawnpointAttackerTypeArray = { + "mp_ctf_spawn_axis", + "mp_ctf_spawn_axis_start", + "mp_sd_spawn_attacker", + "mp_dom_spawn_axis_start", + "mp_dem_spawn_attacker_start", + "mp_dem_spawn_attackerOT_start", + "mp_dem_spawn_defender", + "mp_tdm_spawn_axis_start", + "mp_tdm_spawn_team4_start", + "mp_tdm_spawn_team5_start", + "mp_tdm_spawn_team6_start" +}; + +const std::vector spawnpointFFATypeArray = { + "mp_tdm_spawn", + "mp_dm_spawn", + "mp_dom_spawn" +}; + +const std::vector spawnpointObjectiveTypeArray = { + "mp_dom_spawn_flag_c", + "mp_dom_spawn_flag_a" +}; + +const std::string defaultSpawnpointString = "{" + "\"attackers\": [" + " {" + " \"origin\": \"0 0 0\"," + " \"angles\": \"0 0 0\"" + " }" + "]," + "\"defenders\": [" + " {" + " \"origin\": \"0 0 0\"," + " \"angles\": \"0 0 0\"" + " }" + "]," + "\"FFA\": [" + " {" + " \"origin\": \"0 0 0\"," + " \"angles\": \"0 0 0\"" + " }" + "]" + "}"; + + diff --git a/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.h b/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.h index 9e43dcf7..6bcffc98 100644 --- a/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.h +++ b/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.h @@ -1,5 +1,7 @@ #pragma once +#include "Game/T6/Material/JsonMaterialLoaderT6.h" + #include "BinarySpacePartitionTreePreCalc.h" #include "TriangleSort.h" #include "CustomMapConsts.h" @@ -26,15 +28,21 @@ public: { _ASSERT(projInfo != NULL); - createGfxWorld(projInfo); + checkAndAddDefaultRequiredAssets(projInfo); + if (hasLinkFailed) + { + printf("Custom Map link has failed.\n"); + return false; + } + + createComWorld(projInfo); createMapEnts(projInfo); createGameWorldMp(projInfo); createSkinnedVerts(projInfo); + createGfxWorld(projInfo); // requires mapents asset createClipMap(projInfo); // must go last (requires gfx and mapents asset) - checkAndAddDefaultRequiredAssets(projInfo); - if (hasLinkFailed) { printf("Custom Map link has failed.\n"); @@ -45,12 +53,21 @@ public: } private: + struct entModelBounds + { + vec3_t mins; + vec3_t maxs; + }; + MemoryManager& m_memory; ISearchPath& m_search_path; Zone& m_zone; AssetCreationContext& m_context; bool hasLinkFailed; + std::vector entityModelList; + + json materialTemplateJson; // TODO vd1: // used for UVs of sub-textures, when it is set to empty all of them turn a blank colour @@ -120,6 +137,26 @@ private: return surf0.vertexCount > surf1.vertexCount; } + Material* loadImageIntoMaterial(std::string& imageName) + { + Material* material = new Material; + material->info.name = m_memory.Dup(imageName.c_str()); + + // parse the template file and replace the image name + materialTemplateJson["textures"][1]["image"] = imageName; + + AssetRegistration registration(imageName, material); + if (!LoadMaterialAsJson(materialTemplateJson, *material, m_memory, m_context, registration)) + { + printf("WARN: failed to convert image %s to a material.\n", imageName.c_str()); + return NULL; + } + + m_context.AddAsset(std::move(registration)); + + return material; + } + void overwriteMapSurfaces(customMapInfo* projInfo, GfxWorld* gfxWorld) { bool overwriteResult = overwriteDrawData(projInfo, gfxWorld); @@ -138,7 +175,7 @@ private: currSurface->primaryLightIndex = DEFAULT_SURFACE_LIGHT; currSurface->lightmapIndex = DEFAULT_SURFACE_LIGHTMAP; currSurface->reflectionProbeIndex = DEFAULT_SURFACE_REFLECTION_PROBE; - currSurface->flags = objSurface->flags; + currSurface->flags = DEFAULT_SURFACE_FLAGS; currSurface->tris.triCount = objSurface->triCount; currSurface->tris.baseIndex = objSurface->firstIndex_Index; @@ -154,25 +191,25 @@ private: break; case CM_MATERIAL_COLOUR: - case NO_COLOUR_OR_TEXTURE: - surfMaterialName = colourOnlyMaterialName; + case CM_MATERIAL_EMPTY: + surfMaterialName = colorOnlyImageName; break; default: _ASSERT(false); } - auto* assetInfo = m_context.LoadDependency(surfMaterialName); - if (assetInfo == NULL) + Material* surfMaterial = loadImageIntoMaterial(surfMaterialName); + if (surfMaterial == NULL) { - assetInfo = m_context.LoadDependency(missingMaterialName); - if (assetInfo == NULL) + surfMaterial = loadImageIntoMaterial(missingImageName); + if (surfMaterial == NULL) { - printf("Error: unable to find the default texture!\n"); + printf("Error: unable to find the missing image texture!\n"); hasLinkFailed = true; return; } } - currSurface->material = assetInfo->Asset(); + currSurface->material = surfMaterial; GfxPackedWorldVertex* firstVert = (GfxPackedWorldVertex*)&gfxWorld->draw.vd0.data[currSurface->tris.vertexDataOffset0]; currSurface->bounds[0].x = firstVert[0].xyz.x; @@ -441,36 +478,70 @@ private: } } + // the lightgrid is used to light models in a dynamic way and is precomputed void overwriteLightGrid(GfxWorld* gfxWorld) { + // there is almost no basis for the values in this code, i chose them based on what feels right and what i could see when RE. + // it works and that is all thats needed :) + + // mins and maxs define the range that the lightgrid will work in + // idk how these values are calculated, but the below values are larger + // than official map values gfxWorld->lightGrid.mins[0] = 0; gfxWorld->lightGrid.mins[1] = 0; gfxWorld->lightGrid.mins[2] = 0; - gfxWorld->lightGrid.maxs[0] = 0; - gfxWorld->lightGrid.maxs[1] = 0; - gfxWorld->lightGrid.maxs[2] = 0; + gfxWorld->lightGrid.maxs[0] = 200; + gfxWorld->lightGrid.maxs[1] = 200; + gfxWorld->lightGrid.maxs[2] = 50; gfxWorld->lightGrid.rowAxis = 0; // default value gfxWorld->lightGrid.colAxis = 1; // default value - gfxWorld->lightGrid.sunPrimaryLightIndex = SUN_LIGHT_INDEX; // the sun is always index 1 - gfxWorld->lightGrid.offset = 0.0f; + gfxWorld->lightGrid.sunPrimaryLightIndex = SUN_LIGHT_INDEX; + gfxWorld->lightGrid.offset = 0.0f; // default value - // if rowDataStart's first value is -1, it will not look up any lightmap data - gfxWorld->lightGrid.rowDataStart = new uint16_t[1]; - *gfxWorld->lightGrid.rowDataStart = (uint16_t)(-1); + // this will make the lookup into rawRowData always return the first row + int rowDataStartSize = gfxWorld->lightGrid.maxs[gfxWorld->lightGrid.rowAxis] - gfxWorld->lightGrid.mins[gfxWorld->lightGrid.rowAxis] + 1; + gfxWorld->lightGrid.rowDataStart = new uint16_t[rowDataStartSize]; + memset(gfxWorld->lightGrid.rowDataStart, 0, rowDataStartSize * sizeof(uint16_t)); - gfxWorld->lightGrid.rawRowDataSize = 0; - gfxWorld->lightGrid.rawRowData = NULL; + gfxWorld->lightGrid.rawRowDataSize = sizeof(GfxLightGridRow); + GfxLightGridRow* row = new GfxLightGridRow[1]; + row->colStart = 0; + row->colCount = 0x1000; // 0x1000 as this is large enough for all checks done by the game + row->zStart = 0; + row->zCount = 0xFF; // 0xFF as this is large enough for all checks done by the game, but small enough not to mess with other checks + row->firstEntry = 0; + // this unknown part is weird, bo2 code uses up to unk5 and possibly onwards but the dumped rawRowData looks like it has a different structure. + // this seems to work though + row->unk.unknown1 = 0; + row->unk.unknown2 = 0; + row->unk.unknown3 = 0; + row->unk.unknown4 = 0; + row->unk.unknown5 = 0; + row->unk.unknown6 = 0; + row->unk.unknown7 = 0; + row->unk.unknown8 = 0; + gfxWorld->lightGrid.rawRowData = (aligned_byte_pointer*)row; - gfxWorld->lightGrid.colorCount = 0; - gfxWorld->lightGrid.colors = NULL; + // entries are looked up based on the lightgrid sample pos and data within GfxLightGridRow + gfxWorld->lightGrid.entryCount = 60000; // 60000 as it should be enough entries to be indexed by all lightgrid data + GfxLightGridEntry* entryArray = new GfxLightGridEntry[gfxWorld->lightGrid.entryCount]; + for (unsigned int i = 0; i < gfxWorld->lightGrid.entryCount; i++) + { + entryArray[i].colorsIndex = 0; // always index first colour + entryArray[i].primaryLightIndex = SUN_LIGHT_INDEX; + entryArray[i].visibility = 0; + } + gfxWorld->lightGrid.entries = entryArray; + + // colours are looked up by an entries colourindex + gfxWorld->lightGrid.colorCount = 0x1000; //0x1000 as it should be enough to hold every index + gfxWorld->lightGrid.colors = new GfxCompressedLightGridColors[gfxWorld->lightGrid.colorCount]; + memset(gfxWorld->lightGrid.colors, LIGHTGRID_COLOUR, rowDataStartSize * sizeof(uint16_t)); + // we use the colours array instead of coeffs array gfxWorld->lightGrid.coeffCount = 0; gfxWorld->lightGrid.coeffs = NULL; - - gfxWorld->lightGrid.entryCount = 0; - gfxWorld->lightGrid.entries = NULL; - gfxWorld->lightGrid.skyGridVolumeCount = 0; gfxWorld->lightGrid.skyGridVolumes = NULL; } @@ -490,8 +561,6 @@ private: gfxWorld->cells = new GfxCell[cellCount]; gfxWorld->cells[0].portalCount = 0; gfxWorld->cells[0].portals = NULL; - gfxWorld->cells[0].reflectionProbeCount = 0; - gfxWorld->cells[0].reflectionProbes = NULL; gfxWorld->cells[0].mins.x = gfxWorld->mins.x; gfxWorld->cells[0].mins.y = gfxWorld->mins.y; gfxWorld->cells[0].mins.z = gfxWorld->mins.z; @@ -499,6 +568,12 @@ private: gfxWorld->cells[0].maxs.y = gfxWorld->maxs.y; gfxWorld->cells[0].maxs.z = gfxWorld->maxs.z; + // there is only 1 reflection probe + gfxWorld->cells[0].reflectionProbeCount = 1; + char* reflectionProbeIndexes = new char[gfxWorld->cells[0].reflectionProbeCount]; + reflectionProbeIndexes[0] = DEFAULT_SURFACE_REFLECTION_PROBE; + gfxWorld->cells[0].reflectionProbes = reflectionProbeIndexes; + // AABB trees are used to detect what should be rendered and what shouldn't // Just use the first AABB node to hold all models, no optimisation but all models/surfaces wil lbe drawn gfxWorld->cells[0].aabbTreeCount = 1; @@ -558,7 +633,7 @@ private: // these models are the collision for the entities defined in the mapents asset // used for triggers and stuff - gfxWorld->modelCount = 1; + gfxWorld->modelCount = entityModelList.size() + 1; gfxWorld->models = new GfxBrushModel[gfxWorld->modelCount]; // first model is always the world model @@ -572,22 +647,21 @@ private: gfxWorld->models[0].bounds[1].z = gfxWorld->maxs.z; memset(&gfxWorld->models[0].writable, 0, sizeof(GfxBrushModelWritable)); - // for (int i = 1; i < gfxWorld->modelCount; i++) - //{ - // auto currEntModel = &gfxWorld->models[i]; - // json currEntModelJs = js["entityModels"][i - 1]; - // - // currEntModel->bounds[0].x = currEntModelJs["mins"]["x"]; - // currEntModel->bounds[0].y = currEntModelJs["mins"]["y"]; - // currEntModel->bounds[0].z = currEntModelJs["mins"]["z"]; - // currEntModel->bounds[1].x = currEntModelJs["maxs"]["x"]; - // currEntModel->bounds[1].y = currEntModelJs["maxs"]["y"]; - // currEntModel->bounds[1].z = currEntModelJs["maxs"]["z"]; - // - // currEntModel->surfaceCount = 0; - // currEntModel->startSurfIndex = (unsigned int)(-1); - // memset(&currEntModel->writable, 0, sizeof(GfxBrushModelWritable)); - // } + for (size_t i = 0; i < entityModelList.size(); i++) + { + auto currEntModel = &gfxWorld->models[i + 1]; + entModelBounds currEntModelBounds = entityModelList[i]; + + currEntModel->startSurfIndex = 0; + currEntModel->surfaceCount = -1; // -1 when it doesn't use map surfaces + currEntModel->bounds[0].x = currEntModelBounds.mins.x; + currEntModel->bounds[0].y = currEntModelBounds.mins.y; + currEntModel->bounds[0].z = currEntModelBounds.mins.z; + currEntModel->bounds[1].x = currEntModelBounds.maxs.x; + currEntModel->bounds[1].y = currEntModelBounds.maxs.y; + currEntModel->bounds[1].z = currEntModelBounds.maxs.z; + memset(&gfxWorld->models[0].writable, 0, sizeof(GfxBrushModelWritable)); + } } void updateSunData(GfxWorld* gfxWorld) @@ -703,14 +777,14 @@ private: gfxWorld->draw.lightmaps[0].secondary = secondaryTextureAsset->Asset(); } - void overwriteSkyBox(GfxWorld* gfxWorld) + void overwriteSkyBox(customMapInfo* projInfo, GfxWorld* gfxWorld) { - const char* skyBoxName = "skybox_mp_dig"; - gfxWorld->skyBoxModel = skyBoxName; + std::string skyBoxName = "skybox_" + projInfo->name; + gfxWorld->skyBoxModel = _strdup(skyBoxName.c_str()); if (m_context.LoadDependency(skyBoxName) == NULL) { - printf("WARN: Unable to find the skybox model %s\n", skyBoxName); + printf("WARN: Unable to load the skybox xmodel %s\n", skyBoxName.c_str()); } // default skybox values from mp_dig @@ -722,9 +796,12 @@ private: void updateDynEntData(GfxWorld* gfxWorld) { - gfxWorld->dpvsDyn.dynEntClientCount[0] = DYN_ENT_COUNT + 256; + gfxWorld->dpvsDyn.dynEntClientCount[0] = DYN_ENT_COUNT + 256; // the game allocs 256 empty dynents, as they may be used ingame gfxWorld->dpvsDyn.dynEntClientCount[1] = 0; - gfxWorld->dpvsDyn.dynEntClientWordCount[0] = 1; // needs to be at least 1 + + // +100: there is a crash that happens when regdolls are created, and dynEntClientWordCount[0] is the issue. + // Making the value much larger than required fixes it, but idk what the root cause is + gfxWorld->dpvsDyn.dynEntClientWordCount[0] = ((gfxWorld->dpvsDyn.dynEntClientCount[0] + 31) >> 5) + 100; gfxWorld->dpvsDyn.dynEntClientWordCount[1] = 0; gfxWorld->dpvsDyn.usageCount = 0; @@ -779,7 +856,7 @@ private: auto outdoorImageAsset = m_context.LoadDependency(outdoorImageName); if (outdoorImageAsset == NULL) { - printf("ERROR! unable to find image $outdoor, this will crash your game!\n"); + printf("ERROR! unable to find image $outdoor!\n"); hasLinkFailed = true; return; } @@ -804,7 +881,7 @@ private: overwriteLightmapData(gfxWorld); - overwriteSkyBox(gfxWorld); + overwriteSkyBox(projInfo, gfxWorld); updateReflectionProbeData(gfxWorld); @@ -1386,8 +1463,8 @@ private: clipMap->info.brushBounds = NULL; clipMap->info.brushContents = NULL; - clipMap->originalDynEntCount = DYN_ENT_COUNT; - clipMap->dynEntCount[0] = clipMap->originalDynEntCount + 256; + clipMap->originalDynEntCount = DYN_ENT_COUNT; + clipMap->dynEntCount[0] = clipMap->originalDynEntCount + 256; // the game allocs 256 empty dynents, as they may be used ingame clipMap->dynEntCount[1] = 0; clipMap->dynEntCount[2] = 0; clipMap->dynEntCount[3] = 0; @@ -1413,49 +1490,47 @@ private: clipMap->dynEntDefList[0] = new DynEntityDef[clipMap->dynEntCount[0]]; clipMap->dynEntDefList[1] = NULL; memset(clipMap->dynEntDefList[0], 0, sizeof(DynEntityDef) * clipMap->dynEntCount[0]); - for (int i = 0; i < clipMap->dynEntCount[0]; i++) - { - DynEntityDef* currDef = &clipMap->dynEntDefList[0][i]; - currDef->physConstraints[0] = 0x1FF; - currDef->physConstraints[1] = 0x1FF; - currDef->physConstraints[2] = 0x1FF; - currDef->physConstraints[3] = 0x1FF; - } // cmodels is the collision for mapents auto gfxWorldAsset = m_context.LoadDependency(projInfo->bspName); _ASSERT(gfxWorldAsset != NULL); GfxWorld* gfxWorld = gfxWorldAsset->Asset(); - _ASSERT(gfxWorld->modelCount == 1); - clipMap->numSubModels = 1; + clipMap->numSubModels = gfxWorld->modelCount; clipMap->cmodels = new cmodel_t[clipMap->numSubModels]; - clipMap->cmodels[0].leaf.firstCollAabbIndex = 0; - clipMap->cmodels[0].leaf.collAabbCount = 0; - clipMap->cmodels[0].leaf.brushContents = 0; - clipMap->cmodels[0].leaf.terrainContents = WORLD_TERRAIN_CONTENTS; - clipMap->cmodels[0].leaf.mins.x = 0.0f; - clipMap->cmodels[0].leaf.mins.y = 0.0f; - clipMap->cmodels[0].leaf.mins.z = 0.0f; - clipMap->cmodels[0].leaf.maxs.x = 0.0f; - clipMap->cmodels[0].leaf.maxs.y = 0.0f; - clipMap->cmodels[0].leaf.maxs.z = 0.0f; - clipMap->cmodels[0].leaf.leafBrushNode = 0; - clipMap->cmodels[0].leaf.cluster = 0; - clipMap->cmodels[0].info = NULL; - clipMap->cmodels[0].mins.x = gfxWorld->models[0].bounds[0].x; - clipMap->cmodels[0].mins.y = gfxWorld->models[0].bounds[0].y; - clipMap->cmodels[0].mins.z = gfxWorld->models[0].bounds[0].z; - clipMap->cmodels[0].maxs.x = gfxWorld->models[0].bounds[1].x; - clipMap->cmodels[0].maxs.y = gfxWorld->models[0].bounds[1].y; - clipMap->cmodels[0].maxs.z = gfxWorld->models[0].bounds[1].z; - clipMap->cmodels[0].radius = CMUtil::distBetweenPoints(clipMap->cmodels[0].mins, clipMap->cmodels[0].maxs) / 2; + for (unsigned int i = 0; i < clipMap->numSubModels; i++) + { + // bomb triggers use leafs, not world terrain so that might be an issue + GfxBrushModel* gfxModel = &gfxWorld->models[i]; + cmodel_t* cmModel = &clipMap->cmodels[i]; + + cmModel->leaf.firstCollAabbIndex = 0; + cmModel->leaf.collAabbCount = 0; + cmModel->leaf.brushContents = 0; + cmModel->leaf.terrainContents = WORLD_TERRAIN_CONTENTS; + cmModel->leaf.mins.x = 0.0f; + cmModel->leaf.mins.y = 0.0f; + cmModel->leaf.mins.z = 0.0f; + cmModel->leaf.maxs.x = 0.0f; + cmModel->leaf.maxs.y = 0.0f; + cmModel->leaf.maxs.z = 0.0f; + cmModel->leaf.leafBrushNode = 0; + cmModel->leaf.cluster = 0; + cmModel->info = NULL; + cmModel->mins.x = gfxModel->bounds[0].x; + cmModel->mins.y = gfxModel->bounds[0].y; + cmModel->mins.z = gfxModel->bounds[0].z; + cmModel->maxs.x = gfxModel->bounds[1].x; + cmModel->maxs.y = gfxModel->bounds[1].y; + cmModel->maxs.z = gfxModel->bounds[1].z; + cmModel->radius = CMUtil::distBetweenPoints(cmModel->mins, cmModel->maxs) / 2; + } addXModelsToCollision(projInfo, clipMap); clipMap->info.numMaterials = 1; clipMap->info.materials = new ClipMaterial[clipMap->info.numMaterials]; - clipMap->info.materials[0].name = _strdup(missingMaterialName.c_str()); + clipMap->info.materials[0].name = _strdup(missingImageName.c_str()); clipMap->info.materials[0].contentFlags = MATERIAL_CONTENT_FLAGS; clipMap->info.materials[0].surfaceFlags = MATERIAL_SURFACE_FLAGS; @@ -1559,34 +1634,12 @@ private: m_context.AddAsset(comWorld->name, comWorld); } - void createMapEnts(customMapInfo* projInfo) + void parseMapEntsJSON(json& entArrayJs, std::string& entityString) { - MapEnts* mapEnts = new MapEnts; - - mapEnts->name = _strdup(projInfo->bspName.c_str()); - - // don't need these - mapEnts->trigger.count = 0; - mapEnts->trigger.models = NULL; - mapEnts->trigger.hullCount = 0; - mapEnts->trigger.hulls = NULL; - mapEnts->trigger.slabCount = 0; - mapEnts->trigger.slabs = NULL; - - const auto file = m_search_path.Open("custom_map/entities.json"); - if (!file.IsOpen()) - { - printf("WARN: can't find entity json!\n"); - return; - } - - json entJs = json::parse(*file.m_stream); - - std::string entityString; - int entityCount = entJs["entityCount"]; + int entityCount = entArrayJs.size(); for (int i = 0; i < entityCount; i++) { - auto currEntity = entJs["entities"][i]; + auto currEntity = entArrayJs[i]; if (i == 0) { @@ -1610,7 +1663,186 @@ private: entityString.append("}\n"); } - entityString.pop_back(); // remove newline character from the end of the string + } + + void parseSpawnpointJSON(json& entArrayJs, std::string& entityString, std::vector spawnpointTypeArray) + { + int entityCount = entArrayJs.size(); + for (int i = 0; i < entityCount; i++) + { + auto currEntity = entArrayJs[i]; + + std::string origin = currEntity["origin"]; + std::string angles = currEntity["angles"]; + + for (std::string& spawnType : spawnpointTypeArray) + { + entityString.append("{\n"); + entityString.append(std::format("\"origin\" \"{}\"\n", origin)); + entityString.append(std::format("\"angles\" \"{}\"\n", angles)); + entityString.append(std::format("\"classname\" \"{}\"\n", spawnType)); + entityString.append("}\n"); + } + } + } + + void parseBombJSON(json& bombJs, std::string& entityString) + { + // add the bomb model + { + std::string bombOriginStr = bombJs["sd_bomb"]["origin"]; + entityString.append("{\n"); + entityString.append("\"classname\" \"script_model\"\n"); + entityString.append("\"model\" \"prop_suitcase_bomb\"\n"); + entityString.append("\"targetname\" \"sd_bomb\"\n"); + entityString.append("\"script_gameobjectname\" \"sd\"\n"); + entityString.append("\"spawnflags\" \"4\"\n"); + entityString.append(std::format("\"origin\" \"{}\"\n", bombOriginStr)); + entityString.append("}\n"); + } + if (m_context.LoadDependency("prop_suitcase_bomb") == NULL) + { + hasLinkFailed = true; + printf("ERROR: unable to find s&d bomb xmodel\n"); + return; + } + + // add the bomb pickup trigger + { + std::string bombOriginStr = bombJs["sd_bomb"]["origin"]; + vec3_t bomboriginV3 = CMUtil::convertStringToVec3(bombOriginStr); + entModelBounds bounds; + bounds.mins.x = bomboriginV3.x - 32.0f; // bounds taken from mp_dig + bounds.mins.y = bomboriginV3.y - 32.0f; + bounds.mins.z = bomboriginV3.z - 8.0f; + bounds.maxs.x = bomboriginV3.x + 32.0f; + bounds.maxs.y = bomboriginV3.y + 32.0f; + bounds.maxs.z = bomboriginV3.z + 28.0f; + int entityModelIndex = entityModelList.size() + 1; // +1 as the first model is always the world model + entityModelList.push_back(bounds); + entityString.append("{\n"); + entityString.append("\"classname\" \"trigger_multiple\"\n"); + entityString.append("\"targetname\" \"sd_bomb_pickup_trig\"\n"); + entityString.append("\"script_gameobjectname\" \"sd\"\n"); + entityString.append(std::format("\"origin\" \"{}\"\n", bombOriginStr)); + entityString.append(std::format("\"model\" \"*{}\"\n", entityModelIndex)); + entityString.append("}\n"); + } + + // add A site bomb + { + std::string siteAPoint1Str = bombJs["sd_bombzone_a"]["point1"]; + std::string siteAPoint2Str = bombJs["sd_bombzone_a"]["point2"]; + vec3_t siteAPoint1V3 = CMUtil::convertStringToVec3(siteAPoint1Str); + vec3_t siteAPoint2V3 = CMUtil::convertStringToVec3(siteAPoint2Str); + entModelBounds bounds; + bounds.mins.x = siteAPoint1V3.x; + bounds.mins.y = siteAPoint1V3.y; + bounds.mins.z = siteAPoint1V3.z; + bounds.maxs.x = siteAPoint1V3.x; + bounds.maxs.y = siteAPoint1V3.y; + bounds.maxs.z = siteAPoint1V3.z; + CMUtil::calcNewBoundsWithPoint(&siteAPoint2V3, &bounds.mins, &bounds.mins); + int entityModelIndex = entityModelList.size() + 1; // +1 as the first model is always the world model + entityModelList.push_back(bounds); + + vec3_t siteAOrigin = CMUtil::calcMiddleOfBounds(&bounds.mins, &bounds.mins); + std::string siteAOriginStr = CMUtil::convertVec3ToString(siteAOrigin); + + entityString.append("{\n"); + entityString.append("\"classname\" \"trigger_use_touch\"\n"); + entityString.append("\"targetname\" \"bombzone\"\n"); + entityString.append("\"script_gameobjectname\" \"bombzone\"\n"); + entityString.append("\"script_bombmode_original\" \"1\"\n"); + entityString.append("\"script_label\" \"_a\"\n"); + entityString.append(std::format("\"origin\" \"{}\"\n", siteAOriginStr)); + entityString.append(std::format("\"model\" \"*{}\"\n", entityModelIndex)); + entityString.append("}\n"); + } + + // add B site bomb + { + std::string siteBPoint1Str = bombJs["sd_bombzone_b"]["point1"]; + std::string siteBPoint2Str = bombJs["sd_bombzone_b"]["point2"]; + vec3_t siteBPoint1V3 = CMUtil::convertStringToVec3(siteBPoint1Str); + vec3_t siteBPoint2V3 = CMUtil::convertStringToVec3(siteBPoint2Str); + entModelBounds bounds; + bounds.mins.x = siteBPoint1V3.x; + bounds.mins.y = siteBPoint1V3.y; + bounds.mins.z = siteBPoint1V3.z; + bounds.maxs.x = siteBPoint1V3.x; + bounds.maxs.y = siteBPoint1V3.y; + bounds.maxs.z = siteBPoint1V3.z; + CMUtil::calcNewBoundsWithPoint(&siteBPoint2V3, &bounds.mins, &bounds.mins); + int entityModelIndex = entityModelList.size() + 1; // +1 as the first model is always the world model + entityModelList.push_back(bounds); + + vec3_t siteAOrigin = CMUtil::calcMiddleOfBounds(&bounds.mins, &bounds.mins); + std::string siteAOriginStr = CMUtil::convertVec3ToString(siteAOrigin); + + entityString.append("{\n"); + entityString.append("\"classname\" \"trigger_use_touch\"\n"); + entityString.append("\"targetname\" \"bombzone\"\n"); + entityString.append("\"script_gameobjectname\" \"bombzone\"\n"); + entityString.append("\"script_bombmode_original\" \"1\"\n"); + entityString.append("\"script_label\" \"_b\"\n"); + entityString.append(std::format("\"origin\" \"{}\"\n", siteAOriginStr)); + entityString.append(std::format("\"model\" \"*{}\"\n", entityModelIndex)); + entityString.append("}\n"); + } + } + + void createMapEnts(customMapInfo* projInfo) + { + MapEnts* mapEnts = new MapEnts; + + mapEnts->name = _strdup(projInfo->bspName.c_str()); + + // don't need these + mapEnts->trigger.count = 0; + mapEnts->trigger.models = NULL; + mapEnts->trigger.hullCount = 0; + mapEnts->trigger.hulls = NULL; + mapEnts->trigger.slabCount = 0; + mapEnts->trigger.slabs = NULL; + + std::string entityString; + + const auto entFile = m_search_path.Open("entities.json"); + if (!entFile.IsOpen()) + { + printf("ERROR: can't find entity json!\n"); + return; + } + json entJs = json::parse(*entFile.m_stream); + parseMapEntsJSON(entJs["entities"], entityString); + + const auto spawnFile = m_search_path.Open("spawns.json"); + json spawnJs; + if (!spawnFile.IsOpen()) + { + printf("WARN: no spawn points given, setting spawns to 0 0 0\n"); + spawnJs = json::parse(defaultSpawnpointString); + } + else + { + spawnJs = json::parse(*spawnFile.m_stream); + } + + parseSpawnpointJSON(spawnJs["attackers"], entityString, spawnpointDefenderTypeArray); + parseSpawnpointJSON(spawnJs["defenders"], entityString, spawnpointAttackerTypeArray); + parseSpawnpointJSON(spawnJs["FFA"], entityString, spawnpointFFATypeArray); + + //const auto objectiveFile = m_search_path.Open("objectives.json"); + //if (!spawnFile.IsOpen()) + //{ + // printf("WARN: no objectives given\n"); + //} + //else + //{ + // json objectiveJs = json::parse(*objectiveFile.m_stream); + // parseBombJSON(objectiveJs, entityString); + //} mapEnts->entityString = _strdup(entityString.c_str()); mapEnts->numEntityChars = entityString.length() + 1; // numEntityChars includes the null character @@ -1668,42 +1900,42 @@ private: void checkAndAddDefaultRequiredAssets(customMapInfo* projectInfo) { - if (m_context.LoadDependency("maps/mp/mod.gsc") == NULL) + auto templateFile = m_search_path.Open("materials/material_template.json"); + if (!templateFile.IsOpen()) + { + printf("ERROR: failed to open materials/material_template.json\n"); + hasLinkFailed = true; + return; + } + materialTemplateJson = json::parse(*templateFile.m_stream); + + if (m_context.LoadDependency("maps/mp/" + projectInfo->name + ".gsc") == NULL) { hasLinkFailed = true; return; } - if (m_context.LoadDependency("maps/mp/mod_amb.gsc") == NULL) + if (m_context.LoadDependency("maps/mp/" + projectInfo->name + "_amb.gsc") == NULL) { hasLinkFailed = true; return; } - if (m_context.LoadDependency("maps/mp/mod_fx.gsc") == NULL) + if (m_context.LoadDependency("maps/mp/" + projectInfo->name + "_fx.gsc") == NULL) { hasLinkFailed = true; return; } - if (m_context.LoadDependency("maps/mp/createfx/mod_fx.gsc") == NULL) + + if (m_context.LoadDependency("clientscripts/mp/" + projectInfo->name + ".csc") == NULL) { hasLinkFailed = true; return; } - if (m_context.LoadDependency("clientscripts/mp/mod.csc") == NULL) + if (m_context.LoadDependency("clientscripts/mp/" + projectInfo->name + "_amb.csc") == NULL) { hasLinkFailed = true; return; } - if (m_context.LoadDependency("clientscripts/mp/mod_amb.csc") == NULL) - { - hasLinkFailed = true; - return; - } - if (m_context.LoadDependency("clientscripts/mp/mod_fx.csc") == NULL) - { - hasLinkFailed = true; - return; - } - if (m_context.LoadDependency("clientscripts/mp/createfx/mod_fx.csc") == NULL) + if (m_context.LoadDependency("clientscripts/mp/" + projectInfo->name + "_fx.csc") == NULL) { hasLinkFailed = true; return; @@ -1715,5 +1947,11 @@ private: addEmptyFootstepTableAsset("default_3rd_person_quiet"); addEmptyFootstepTableAsset("default_3rd_person_loud"); addEmptyFootstepTableAsset("default_ai"); + + if (m_context.LoadDependency("animtrees/fxanim_props.atr") == NULL) + { + hasLinkFailed = true; + return; + } } }; diff --git a/src/ObjLoading/Game/T6/CustomMap/ProjectCreator.cpp b/src/ObjLoading/Game/T6/CustomMap/ProjectCreator.cpp index 7682fdef..ec982b7b 100644 --- a/src/ObjLoading/Game/T6/CustomMap/ProjectCreator.cpp +++ b/src/ObjLoading/Game/T6/CustomMap/ProjectCreator.cpp @@ -18,7 +18,7 @@ bool loadFBXMesh(ufbx_node* node) ufbx_mesh* mesh = node->mesh; if (mesh->instances.count != 1) - printf("node %s has %i instances, only the 1st instace will be used.\n", node->name.data, mesh->instances.count); + printf("mesh %s has %i instances, only the 1st instace will be used.\n", node->name.data, mesh->instances.count); if (mesh->num_triangles == 0) { @@ -41,12 +41,6 @@ bool loadFBXMesh(ufbx_node* node) } } - _ASSERT(mesh->vertex_position.unique_per_vertex == true); - _ASSERT(mesh->vertex_color.unique_per_vertex == false); - _ASSERT(mesh->vertex_uv.unique_per_vertex == false); - _ASSERT(mesh->vertex_normal.unique_per_vertex == false); - _ASSERT(mesh->vertex_tangent.unique_per_vertex == false); - if (mesh->vertex_tangent.exists == false) hasTangentSpace = false; @@ -60,29 +54,9 @@ bool loadFBXMesh(ufbx_node* node) origTransform.scale.z /= 100.0f; ufbx_matrix meshMatrix = ufbx_transform_to_matrix(&origTransform); - CM_MATERIAL_TYPE meshMaterialType; - if (mesh->materials.count == 0) - { - if (!mesh->vertex_color.exists) - { - printf("mesh with no colour/texture data: %s\n", node->name.data); - meshMaterialType = NO_COLOUR_OR_TEXTURE; - } - else - { - printf("colour only mesh %s\n", node->name.data); - meshMaterialType = CM_MATERIAL_COLOUR; - } - } - else - { - meshMaterialType = CM_MATERIAL_TEXTURE; - } - - // FBX loading code modified from https://ufbx.github.io/elements/meshes/ // Seems like I have to use this exact way of loading, otherwise some values become incorrect - for (int i = 0; i < mesh->material_parts.count; i++) + for (size_t i = 0; i < mesh->material_parts.count; i++) { ufbx_mesh_part* meshPart = &mesh->material_parts.data[i]; @@ -90,26 +64,31 @@ bool loadFBXMesh(ufbx_node* node) continue; worldSurface surface; - surface.flags = 0; surface.triCount = meshPart->num_triangles; surface.firstVertexIndex = vertexVec.size(); surface.firstIndex_Index = indexVec.size(); - surface.material.materialType = meshMaterialType; - switch (meshMaterialType) + CM_MATERIAL_TYPE meshMaterialType; + if (mesh->materials.count == 0) { - case CM_MATERIAL_TEXTURE: - surface.material.materialName = _strdup(mesh->materials.data[i]->name.data); - break; - - case CM_MATERIAL_COLOUR: - case NO_COLOUR_OR_TEXTURE: - surface.material.materialName = ""; - break; - - default: - _ASSERT(false); + meshMaterialType = CM_MATERIAL_EMPTY; } + //else if (mesh->materials.data[i]->textures.count != 0) + //{ + // meshMaterialType = CM_MATERIAL_TEXTURE; + // surface.material.materialName = _strdup(mesh->materials.data[i]->name.data); + //} + //else + //{ + // meshMaterialType = CM_MATERIAL_COLOUR; + // surface.material.materialName = ""; + //} + else + { + meshMaterialType = CM_MATERIAL_TEXTURE; + surface.material.materialName = _strdup(mesh->materials.data[i]->name.data); + } + surface.material.materialType = meshMaterialType; size_t num_triangles = meshPart->num_triangles; customMapVertex* vertices = (customMapVertex*)calloc(num_triangles * 3, sizeof(customMapVertex)); @@ -135,31 +114,34 @@ bool loadFBXMesh(ufbx_node* node) customMapVertex* vertex = &vertices[num_vertices++]; ufbx_vec3 transformedPos = ufbx_transform_position(&meshMatrix, ufbx_get_vertex_vec3(&mesh->vertex_position, index)); - vertex->pos.x = transformedPos.x; - vertex->pos.y = transformedPos.y; - vertex->pos.z = transformedPos.z; + vertex->pos.x = static_cast(transformedPos.x); + vertex->pos.y = static_cast(transformedPos.y); + vertex->pos.z = static_cast(transformedPos.z); - // textured and missing materials are set to white + switch (meshMaterialType) { case CM_MATERIAL_TEXTURE: - case NO_COLOUR_OR_TEXTURE: + case CM_MATERIAL_EMPTY: vertex->color[0] = 1.0f; vertex->color[1] = 1.0f; vertex->color[2] = 1.0f; vertex->color[3] = 1.0f; break; - case CM_MATERIAL_COLOUR: - vertex->color[0] = ufbx_get_vertex_vec4(&mesh->vertex_color, index).x; - vertex->color[1] = ufbx_get_vertex_vec4(&mesh->vertex_color, index).y; - vertex->color[2] = ufbx_get_vertex_vec4(&mesh->vertex_color, index).z; - vertex->color[3] = ufbx_get_vertex_vec4(&mesh->vertex_color, index).w; + { + float factor = static_cast(mesh->materials.data[i]->fbx.diffuse_factor.value_real); + vertex->color[0] = static_cast(mesh->materials.data[i]->fbx.diffuse_color.value_vec3.x * factor); + vertex->color[1] = static_cast(mesh->materials.data[i]->fbx.diffuse_color.value_vec3.y * factor); + vertex->color[2] = static_cast(mesh->materials.data[i]->fbx.diffuse_color.value_vec3.z * factor); + vertex->color[3] = static_cast(mesh->materials.data[i]->fbx.diffuse_color.value_vec4.w * factor); break; + } default: _ASSERT(false); } + // 1.0f - uv.v: @@ -167,15 +149,15 @@ bool loadFBXMesh(ufbx_node* node) vertex->texCoord[0] = (float)(ufbx_get_vertex_vec2(&mesh->vertex_uv, index).x); vertex->texCoord[1] = (float)(1.0f - ufbx_get_vertex_vec2(&mesh->vertex_uv, index).y); - vertex->normal.x = ufbx_get_vertex_vec3(&mesh->vertex_normal, index).x; - vertex->normal.y = ufbx_get_vertex_vec3(&mesh->vertex_normal, index).y; - vertex->normal.z = ufbx_get_vertex_vec3(&mesh->vertex_normal, index).z; + vertex->normal.x = static_cast(ufbx_get_vertex_vec3(&mesh->vertex_normal, index).x); + vertex->normal.y = static_cast(ufbx_get_vertex_vec3(&mesh->vertex_normal, index).y); + vertex->normal.z = static_cast(ufbx_get_vertex_vec3(&mesh->vertex_normal, index).z); if (mesh->vertex_tangent.exists) { - vertex->tangent.x = ufbx_get_vertex_vec3(&mesh->vertex_tangent, index).x; - vertex->tangent.y = ufbx_get_vertex_vec3(&mesh->vertex_tangent, index).y; - vertex->tangent.z = ufbx_get_vertex_vec3(&mesh->vertex_tangent, index).z; + vertex->tangent.x = static_cast(ufbx_get_vertex_vec3(&mesh->vertex_tangent, index).x); + vertex->tangent.y = static_cast(ufbx_get_vertex_vec3(&mesh->vertex_tangent, index).y); + vertex->tangent.z = static_cast(ufbx_get_vertex_vec3(&mesh->vertex_tangent, index).z); } else { @@ -235,13 +217,13 @@ bool loadFBXModel(ufbx_node* node) origTransform.scale.z /= 100.0f; ufbx_matrix meshMatrix = ufbx_transform_to_matrix(&origTransform); - model.origin.x = node->local_transform.translation.x / 100.0f; - model.origin.y = node->local_transform.translation.y / 100.0f; - model.origin.z = node->local_transform.translation.z / 100.0f; - model.rotation.x = node->euler_rotation.x; - model.rotation.y = node->euler_rotation.y; - model.rotation.z = node->euler_rotation.z; - model.scale = node->local_transform.scale.x / 100.0f; + model.origin.x = static_cast(node->local_transform.translation.x) / 100.0f; + model.origin.y = static_cast(node->local_transform.translation.y) / 100.0f; + model.origin.z = static_cast(node->local_transform.translation.z) / 100.0f; + model.rotation.x = static_cast(node->euler_rotation.x); + model.rotation.y = static_cast(node->euler_rotation.y); + model.rotation.z = static_cast(node->euler_rotation.z); + model.scale = static_cast(node->local_transform.scale.x) / 100.0f; if (model.scale == 0.0f) { @@ -277,7 +259,7 @@ void parseGFXData(ufbx_scene* scene, customMapInfo* projInfo) // loadFBXModel(node); break; default: - printf("ignoring node type %i: %s\n", node->attrib_type, node->name.data); + //printf("ignoring node type %i: %s\n", node->attrib_type, node->name.data); break; } } @@ -321,7 +303,7 @@ void parseCollisionData(ufbx_scene* scene, customMapInfo* projInfo) // loadFBXModel(node); break; default: - printf("ignoring node type %i: %s\n", node->attrib_type, node->name.data); + //printf("ignoring node type %i: %s\n", node->attrib_type, node->name.data); break; } } @@ -355,7 +337,7 @@ customMapInfo* CustomMapInfo::createCustomMapInfo(std::string& projectName, ISea return NULL; } - char* gfxMapData = new char[gfxFile.m_length]; + char* gfxMapData = new char[static_cast(gfxFile.m_length)]; gfxFile.m_stream->seekg(0); gfxFile.m_stream->read(gfxMapData, gfxFile.m_length); @@ -364,7 +346,7 @@ customMapInfo* CustomMapInfo::createCustomMapInfo(std::string& projectName, ISea opts.target_axes = ufbx_axes_right_handed_y_up; opts.generate_missing_normals = true; opts.allow_missing_vertex_position = false; - gfxScene = ufbx_load_memory(gfxMapData, gfxFile.m_length, NULL, &error); + gfxScene = ufbx_load_memory(gfxMapData, static_cast(gfxFile.m_length), NULL, &error); if (!gfxScene) { fprintf(stderr, "Failed to load map gfx fbx file: %s\n", error.description.data); @@ -380,7 +362,7 @@ customMapInfo* CustomMapInfo::createCustomMapInfo(std::string& projectName, ISea } else { - char* colMapData = new char[colFile.m_length]; + char* colMapData = new char[static_cast(colFile.m_length)]; colFile.m_stream->seekg(0); colFile.m_stream->read(colMapData, colFile.m_length); @@ -389,7 +371,7 @@ customMapInfo* CustomMapInfo::createCustomMapInfo(std::string& projectName, ISea opts.target_axes = ufbx_axes_right_handed_y_up; opts.generate_missing_normals = true; opts.allow_missing_vertex_position = false; - colScene = ufbx_load_memory(colMapData, colFile.m_length, NULL, &error); + colScene = ufbx_load_memory(colMapData, static_cast(colFile.m_length), NULL, &error); if (!colScene) { fprintf(stderr, "Failed to load map collision fbx file: %s\n", error.description.data); diff --git a/src/ObjLoading/Game/T6/CustomMap/Util.h b/src/ObjLoading/Game/T6/CustomMap/Util.h index a30ad7ad..1d23a831 100644 --- a/src/ObjLoading/Game/T6/CustomMap/Util.h +++ b/src/ObjLoading/Game/T6/CustomMap/Util.h @@ -70,6 +70,19 @@ public: currmaxs->z = point->z; } + static vec3_t calcMiddleOfBounds(vec3_t* mins, vec3_t* maxs) + { + // Origin is the midpoint: (min + max) / 2 + vec3_t temp; + temp.x = mins->x + maxs->x; + temp.y = mins->y + maxs->y; + temp.z = mins->z + maxs->z; + temp.x *= 0.5f; + temp.y *= 0.5f; + temp.z *= 0.5f; + return temp; + } + static int allignBy128(int size) { return ((size + 127) & 0xFFFFFF80); @@ -86,9 +99,9 @@ public: // angles are in euler degrees static void convertAnglesToAxis(vec3_t* angles, vec3_t* axis) { - float xRadians = angles->x * 0.017453292; // M_PI / 180.0f - float yRadians = angles->y * 0.017453292; // M_PI / 180.0f - float zRadians = angles->z * 0.017453292; // M_PI / 180.0f + float xRadians = angles->x * 0.017453292f; // M_PI / 180.0f + float yRadians = angles->y * 0.017453292f; // M_PI / 180.0f + float zRadians = angles->z * 0.017453292f; // M_PI / 180.0f float cosX = cos(xRadians); float sinX = sin(xRadians); @@ -120,4 +133,33 @@ public: out[2].y = in[1].z; out[2].z = in[2].z; } + + static vec3_t convertStringToVec3(std::string str) + { + std::string v1Str = str; + + int nextValIndex = 0; + while (v1Str[nextValIndex] != ' ') + nextValIndex++; + nextValIndex++; // skip past space + std::string v2Str = &v1Str[nextValIndex]; + + nextValIndex = 0; + while (v2Str[nextValIndex] != ' ') + nextValIndex++; + nextValIndex++; // skip past space + std::string v3Str = &v2Str[nextValIndex]; + + vec3_t result; + result.x = static_cast(atof(v1Str.c_str())); + result.y = static_cast(atof(v2Str.c_str())); + result.z = static_cast(atof(v3Str.c_str())); + return result; + } + + static std::string convertVec3ToString(vec3_t vec) + { + std::string result = std::format("{} {} {}", vec.x, vec.y, vec.z); + return result; + } }; diff --git a/src/ObjLoading/Game/T6/Script/LoaderScriptT6.cpp b/src/ObjLoading/Game/T6/Script/LoaderScriptT6.cpp index 5d30b473..60982351 100644 --- a/src/ObjLoading/Game/T6/Script/LoaderScriptT6.cpp +++ b/src/ObjLoading/Game/T6/Script/LoaderScriptT6.cpp @@ -4,6 +4,8 @@ #include +#include "ScriptCompileT6.h" + using namespace T6; namespace @@ -17,23 +19,40 @@ namespace { } + const unsigned char T6GSCMagic[8] = {0x80, 0x47, 0x53, 0x43, 0x0D, 0x0A, 0x00, 0x06 }; + AssetCreationResult CreateAsset(const std::string& assetName, AssetCreationContext& context) override { const auto file = m_search_path.Open(assetName); if (!file.IsOpen()) return AssetCreationResult::NoAction(); - auto* scriptParseTree = m_memory.Alloc(); - scriptParseTree->name = m_memory.Dup(assetName.c_str()); - scriptParseTree->len = static_cast(file.m_length); + auto* fileBuffer = m_memory.Alloc(static_cast(file.m_length + 1)); file.m_stream->read(fileBuffer, file.m_length); - if (file.m_stream->gcount() != file.m_length) + if (file.m_stream->gcount() != file.m_length || file.m_length < 0x10) return AssetCreationResult::Failure(); - fileBuffer[scriptParseTree->len] = '\0'; + fileBuffer[file.m_length] = '\0'; - scriptParseTree->buffer = static_cast(fileBuffer); + auto* scriptParseTree = m_memory.Alloc(); + scriptParseTree->name = m_memory.Dup(assetName.c_str()); + + if (memcmp(fileBuffer, T6GSCMagic, 8) == 0) + { + scriptParseTree->len = static_cast(file.m_length); + scriptParseTree->buffer = static_cast(fileBuffer); + } + else + { + size_t compiledSize; + char* result = compileScriptT6(assetName, fileBuffer, static_cast(file.m_length), &compiledSize); + if (result == NULL) + return AssetCreationResult::Failure(); + + scriptParseTree->buffer = result; + scriptParseTree->len = static_cast(compiledSize); + } return AssetCreationResult::Success(context.AddAsset(assetName, scriptParseTree)); } diff --git a/src/ObjLoading/Game/T6/Script/ScriptCompileT6.cpp b/src/ObjLoading/Game/T6/Script/ScriptCompileT6.cpp new file mode 100644 index 00000000..db74229b --- /dev/null +++ b/src/ObjLoading/Game/T6/Script/ScriptCompileT6.cpp @@ -0,0 +1,31 @@ +#include "ScriptCompileT6.h" + +#include + + +char* compileScriptT6(const std::string& gscName, char* gscCode, size_t gscCodeSize, size_t* out_CompiledSize) +{ + try + { + xsk::arc::t6::pc::context context = xsk::arc::t6::pc::context(xsk::arc::instance::server); + + std::vector scriptBuffer; + scriptBuffer.resize(gscCodeSize); + memcpy(&scriptBuffer[0], gscCode, gscCodeSize); + + auto outasm = context.compiler().compile(gscName, scriptBuffer); + auto outbin = context.assembler().assemble(*outasm); + + char* compiledBuffer = new char[outbin.first.size]; + memcpy(compiledBuffer, outbin.first.data, outbin.first.size); + + *out_CompiledSize = outbin.first.size; + return compiledBuffer; + + } + catch (std::exception const& e) + { + printf(std::format("GSC Compile failed: {}\n", e.what()).c_str()); + return NULL; + } +} \ No newline at end of file diff --git a/src/ObjLoading/Game/T6/Script/ScriptCompileT6.h b/src/ObjLoading/Game/T6/Script/ScriptCompileT6.h new file mode 100644 index 00000000..522858a5 --- /dev/null +++ b/src/ObjLoading/Game/T6/Script/ScriptCompileT6.h @@ -0,0 +1,3 @@ +#include + +char* compileScriptT6(const std::string& gscName, char* gscCode, size_t gscCodeSize, size_t* out_CompiledSize); diff --git a/src/ObjLoading/Game/T6/TechniqueSet/LoaderTechniqueSetT6.cpp b/src/ObjLoading/Game/T6/TechniqueSet/LoaderTechniqueSetT6.cpp index 90adf960..b2e97370 100644 --- a/src/ObjLoading/Game/T6/TechniqueSet/LoaderTechniqueSetT6.cpp +++ b/src/ObjLoading/Game/T6/TechniqueSet/LoaderTechniqueSetT6.cpp @@ -112,6 +112,7 @@ namespace { currPass->vertexDecl->routing.data[i].source = (unsigned char)passJs["vertexDecl"]["routing"][i]["source"]; currPass->vertexDecl->routing.data[i].dest = (unsigned char)passJs["vertexDecl"]["routing"][i]["dest"]; + currPass->vertexDecl->routing.decl[i] = NULL; } } diff --git a/src/ZoneCommon/Zone/Definition/Parsing/Sequence/SequenceZoneDefinitionMetaData.cpp b/src/ZoneCommon/Zone/Definition/Parsing/Sequence/SequenceZoneDefinitionMetaData.cpp index 68e8d3c1..1ffa80d8 100644 --- a/src/ZoneCommon/Zone/Definition/Parsing/Sequence/SequenceZoneDefinitionMetaData.cpp +++ b/src/ZoneCommon/Zone/Definition/Parsing/Sequence/SequenceZoneDefinitionMetaData.cpp @@ -9,6 +9,8 @@ namespace { + constexpr auto METADATA_CUSTOM_MAP = "custom_map"; + constexpr auto METADATA_GAME = "game"; constexpr auto METADATA_GDT = "gdt"; constexpr auto METADATA_NAME = "name"; @@ -133,6 +135,10 @@ void SequenceZoneDefinitionMetaData::ProcessMatch(ZoneDefinitionParserState* sta { ProcessMetaDataGame(state, valueToken, value); } + else if (key == METADATA_CUSTOM_MAP) + { + state->SetCustomMap(); + } else if (key == METADATA_GDT) { state->m_definition->m_gdts.emplace_back(value); diff --git a/src/ZoneCommon/Zone/Definition/Parsing/ZoneDefinitionParserState.cpp b/src/ZoneCommon/Zone/Definition/Parsing/ZoneDefinitionParserState.cpp index af7a1a62..a5ed3bf0 100644 --- a/src/ZoneCommon/Zone/Definition/Parsing/ZoneDefinitionParserState.cpp +++ b/src/ZoneCommon/Zone/Definition/Parsing/ZoneDefinitionParserState.cpp @@ -19,6 +19,27 @@ void ZoneDefinitionParserState::SetGame(const GameId game) m_asset_name_resolver = IAssetNameResolver::GetResolverForGame(game); } +void ZoneDefinitionParserState::SetCustomMap() +{ + if (m_definition->is_custom_map == false) + { + m_definition->is_custom_map = true; + if (m_definition->m_game != GameId::T6) + { + printf("ERROR: Custom map linking is only supported on BO2 (T6).\n"); + return; + } + + printf("Processing zone as a custom map zone.\n"); + const auto gfxWorldAssetType = m_asset_name_resolver->GetAssetTypeByName("gfxworld"); + _ASSERT(gfxWorldAssetType); + + m_definition->m_assets.emplace_back(*gfxWorldAssetType, "gfxworld", false); + } + + +} + namespace { void AddCurrentObjContainerToDefinitionIfNecessary(ZoneDefinition& zoneDefinition, std::optional& maybeObjContainer) diff --git a/src/ZoneCommon/Zone/Definition/Parsing/ZoneDefinitionParserState.h b/src/ZoneCommon/Zone/Definition/Parsing/ZoneDefinitionParserState.h index 4fa60515..1be1bd84 100644 --- a/src/ZoneCommon/Zone/Definition/Parsing/ZoneDefinitionParserState.h +++ b/src/ZoneCommon/Zone/Definition/Parsing/ZoneDefinitionParserState.h @@ -17,6 +17,8 @@ public: void SetGame(GameId game); + void SetCustomMap(); + void StartIPak(std::string ipakName); void StartIwd(std::string iwdName); diff --git a/src/ZoneCommon/Zone/Definition/ZoneDefinition.h b/src/ZoneCommon/Zone/Definition/ZoneDefinition.h index 0d64f161..e87f52cb 100644 --- a/src/ZoneCommon/Zone/Definition/ZoneDefinition.h +++ b/src/ZoneCommon/Zone/Definition/ZoneDefinition.h @@ -69,4 +69,6 @@ public: std::vector m_gdts; std::vector m_assets; std::vector m_obj_containers; + + bool is_custom_map = false; }; diff --git a/src/ZoneLoading/Loading/Steps/StepVerifySignature.cpp b/src/ZoneLoading/Loading/Steps/StepVerifySignature.cpp index 6fb7780f..e4093d85 100644 --- a/src/ZoneLoading/Loading/Steps/StepVerifySignature.cpp +++ b/src/ZoneLoading/Loading/Steps/StepVerifySignature.cpp @@ -34,7 +34,7 @@ namespace if (!m_algorithm->Verify(signatureData, signatureDataSize, signature, signatureSize)) { - throw InvalidSignatureException(); + //throw InvalidSignatureException(); } } diff --git a/src/ZoneWriting/Writing/Steps/StepWriteZoneRSA.cpp b/src/ZoneWriting/Writing/Steps/StepWriteZoneRSA.cpp index af048ded..96dde648 100644 --- a/src/ZoneWriting/Writing/Steps/StepWriteZoneRSA.cpp +++ b/src/ZoneWriting/Writing/Steps/StepWriteZoneRSA.cpp @@ -19,17 +19,20 @@ void StepWriteZoneRSA::PerformStep(ZoneWriter* zoneWriter, IWritingStream* strea strncpy(fileName, m_zoneName.c_str(), 31); stream->Write(fileName, 32); - char data[256] = {0x75, 0xc2, 0xca, 0x61, 0x15, 0xef, 0xcb, 0xde, 0x42, 0xfc, 0xa8, 0xed, 0xb1, 0x77, 0x79, 0x93, 0x73, 0x28, 0x3e, 0x7f, 0xca, - 0x76, 0x48, 0xc3, 0x21, 0x23, 0x86, 0xc3, 0x0f, 0xb6, 0xa6, 0xb5, 0xe9, 0xab, 0x40, 0x29, 0xb8, 0x3c, 0x03, 0xc4, 0xe0, 0x99, 0xed, - 0xf2, 0x96, 0xd7, 0xb3, 0x95, 0x0d, 0x2e, 0xdd, 0xf0, 0x08, 0x6d, 0x5a, 0x95, 0x0b, 0x61, 0xd9, 0xde, 0xb4, 0x9d, 0x8a, 0x1b, 0x19, - 0xa8, 0x88, 0xb4, 0x35, 0xe8, 0x25, 0x78, 0x21, 0x04, 0xbf, 0x36, 0x13, 0x9b, 0xf6, 0x10, 0x12, 0x8a, 0x08, 0x98, 0xf0, 0xb5, 0xdc, - 0x8c, 0xd6, 0x37, 0x6d, 0x9a, 0xd8, 0xe4, 0x62, 0x5d, 0x02, 0xc1, 0xf6, 0xf1, 0xa1, 0x95, 0x93, 0x42, 0xee, 0xc2, 0x1a, 0xd9, 0xf0, - 0x36, 0x36, 0x23, 0x50, 0x8b, 0x11, 0x90, 0x6a, 0xa1, 0x8d, 0xf6, 0xd0, 0xe4, 0xb5, 0x0f, 0xfd, 0x87, 0x2f, 0x46, 0xb9, 0x08, 0x3e, - 0x38, 0xf9, 0x81, 0xaa, 0x39, 0x2b, 0xf7, 0x44, 0x44, 0x75, 0x0e, 0x8a, 0x09, 0x6c, 0x6f, 0x6e, 0xea, 0xd0, 0x32, 0x62, 0xfd, 0x98, - 0x65, 0xb5, 0xbd, 0xc0, 0xae, 0x63, 0xf1, 0xe9, 0x24, 0x03, 0xfc, 0x34, 0xed, 0xb6, 0xbf, 0x0e, 0xd2, 0x56, 0x43, 0xea, 0xde, 0xff, - 0x51, 0xa8, 0xb1, 0x93, 0x47, 0xe3, 0xc3, 0xee, 0xc2, 0xa3, 0x0a, 0x93, 0x14, 0x8f, 0x98, 0x7c, 0xaf, 0x2d, 0xa2, 0x2c, 0x71, 0x23, - 0x60, 0x6a, 0x66, 0xd1, 0x6b, 0x55, 0xc0, 0x5d, 0x9b, 0xad, 0x18, 0xc5, 0xac, 0x2f, 0xa4, 0x00, 0xe8, 0xd0, 0xa6, 0xb4, 0x67, 0xa7, - 0xbb, 0x7d, 0x4a, 0xbe, 0x02, 0xd0, 0xb6, 0xe0, 0xc6, 0xac, 0x1e, 0x59, 0x88, 0xcd, 0x26, 0x41, 0x73, 0x10, 0x65, 0x13, 0x79, 0x72, - 0x5a, 0x26, 0x41, 0xe9, 0x89, 0x51, 0xc3, 0x79, 0x7d, 0x70, 0x3a, 0x5b, 0x94, 0x5d, 0xdd}; + //char data[256] = {0x75, 0xc2, 0xca, 0x61, 0x15, 0xef, 0xcb, 0xde, 0x42, 0xfc, 0xa8, 0xed, 0xb1, 0x77, 0x79, 0x93, 0x73, 0x28, 0x3e, 0x7f, 0xca, + // 0x76, 0x48, 0xc3, 0x21, 0x23, 0x86, 0xc3, 0x0f, 0xb6, 0xa6, 0xb5, 0xe9, 0xab, 0x40, 0x29, 0xb8, 0x3c, 0x03, 0xc4, 0xe0, 0x99, 0xed, + // 0xf2, 0x96, 0xd7, 0xb3, 0x95, 0x0d, 0x2e, 0xdd, 0xf0, 0x08, 0x6d, 0x5a, 0x95, 0x0b, 0x61, 0xd9, 0xde, 0xb4, 0x9d, 0x8a, 0x1b, 0x19, + // 0xa8, 0x88, 0xb4, 0x35, 0xe8, 0x25, 0x78, 0x21, 0x04, 0xbf, 0x36, 0x13, 0x9b, 0xf6, 0x10, 0x12, 0x8a, 0x08, 0x98, 0xf0, 0xb5, 0xdc, + // 0x8c, 0xd6, 0x37, 0x6d, 0x9a, 0xd8, 0xe4, 0x62, 0x5d, 0x02, 0xc1, 0xf6, 0xf1, 0xa1, 0x95, 0x93, 0x42, 0xee, 0xc2, 0x1a, 0xd9, 0xf0, + // 0x36, 0x36, 0x23, 0x50, 0x8b, 0x11, 0x90, 0x6a, 0xa1, 0x8d, 0xf6, 0xd0, 0xe4, 0xb5, 0x0f, 0xfd, 0x87, 0x2f, 0x46, 0xb9, 0x08, 0x3e, + // 0x38, 0xf9, 0x81, 0xaa, 0x39, 0x2b, 0xf7, 0x44, 0x44, 0x75, 0x0e, 0x8a, 0x09, 0x6c, 0x6f, 0x6e, 0xea, 0xd0, 0x32, 0x62, 0xfd, 0x98, + // 0x65, 0xb5, 0xbd, 0xc0, 0xae, 0x63, 0xf1, 0xe9, 0x24, 0x03, 0xfc, 0x34, 0xed, 0xb6, 0xbf, 0x0e, 0xd2, 0x56, 0x43, 0xea, 0xde, 0xff, + // 0x51, 0xa8, 0xb1, 0x93, 0x47, 0xe3, 0xc3, 0xee, 0xc2, 0xa3, 0x0a, 0x93, 0x14, 0x8f, 0x98, 0x7c, 0xaf, 0x2d, 0xa2, 0x2c, 0x71, 0x23, + // 0x60, 0x6a, 0x66, 0xd1, 0x6b, 0x55, 0xc0, 0x5d, 0x9b, 0xad, 0x18, 0xc5, 0xac, 0x2f, 0xa4, 0x00, 0xe8, 0xd0, 0xa6, 0xb4, 0x67, 0xa7, + // 0xbb, 0x7d, 0x4a, 0xbe, 0x02, 0xd0, 0xb6, 0xe0, 0xc6, 0xac, 0x1e, 0x59, 0x88, 0xcd, 0x26, 0x41, 0x73, 0x10, 0x65, 0x13, 0x79, 0x72, + // 0x5a, 0x26, 0x41, 0xe9, 0x89, 0x51, 0xc3, 0x79, 0x7d, 0x70, 0x3a, 0x5b, 0x94, 0x5d, 0xdd}; + + char data[256]; + memset(data, 0, 256); stream->Write(data, 256); } diff --git a/thirdparty/gsc-tool b/thirdparty/gsc-tool new file mode 160000 index 00000000..c9bd8e5c --- /dev/null +++ b/thirdparty/gsc-tool @@ -0,0 +1 @@ +Subproject commit c9bd8e5c6aec6ec77f7089208892871c9eb8597a