From af088c2958e626c70d3ac0fe87c5c45bb3eb2ef7 Mon Sep 17 00:00:00 2001 From: LJW-Dev <48092720+LJW-Dev@users.noreply.github.com> Date: Wed, 18 Mar 2026 22:49:20 +0800 Subject: [PATCH] feat: spawn points are now defined in the GLTF file using custom properties. Initial xmodel implementation also included. --- src/ObjLoading/Game/T6/BSP/BSP.h | 96 +++------ src/ObjLoading/Game/T6/BSP/BSPCreator.cpp | 186 ++++++++++++------ src/ObjLoading/Game/T6/BSP/BSPUtil.cpp | 25 ++- src/ObjLoading/Game/T6/BSP/BSPUtil.h | 1 + .../Game/T6/BSP/Linker/MapEntsLinker.cpp | 127 ++++++++---- 5 files changed, 275 insertions(+), 160 deletions(-) diff --git a/src/ObjLoading/Game/T6/BSP/BSP.h b/src/ObjLoading/Game/T6/BSP/BSP.h index a3bf5a1d..3a2f1dfe 100644 --- a/src/ObjLoading/Game/T6/BSP/BSP.h +++ b/src/ObjLoading/Game/T6/BSP/BSP.h @@ -74,6 +74,35 @@ namespace BSP float outerConeAngle; }; + struct BSPXModel + { + std::string name; + + vec3_t origin; + float scale; + vec3_t forward; + vec3_t right; + vec3_t up; + + bool areBoundsValid; + vec3_t mins; + vec3_t maxs; + }; + + enum BSPSpawnPointType + { + SPAWNPOINT_TYPE_DEFENDER, + SPAWNPOINT_TYPE_ATTACKER, + SPAWNPOINT_TYPE_ALL, + }; + + struct BSPSpawnPoint + { + vec3_t origin; + vec3_t forward; + BSPSpawnPointType type; + }; + struct BSPData { std::string name; @@ -83,6 +112,8 @@ namespace BSP BSPWorld colWorld; std::vector lights; + std::vector xmodels; + std::vector spawnpoints; }; // BSPGameConstants: @@ -99,32 +130,6 @@ namespace BSP SUN_LIGHT_INDEX = 1, BSP_DEFAULT_LIGHT_COUNT = 2 }; - - inline const char* DEFENDER_SPAWN_POINT_NAMES[] = {"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"}; - - inline const char* ATTACKER_SPAWN_POINT_NAMES[] = {"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"}; - - inline const char* FFA_SPAWN_POINT_NAMES[] = {"mp_tdm_spawn", "mp_dm_spawn", "mp_dom_spawn"}; } // namespace BSPGameConstants // BSPLinkingConstants: @@ -133,45 +138,6 @@ namespace BSP { constexpr const char* MISSING_IMAGE_NAME = ",mc/lambert1"; constexpr const char* COLOR_ONLY_IMAGE_NAME = ",mc/lambert1"; - - constexpr const char* DEFAULT_SPAWN_POINT_STRING = R"({ - "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" - } - ] - })"; - - constexpr const char* DEFAULT_MAP_ENTS_STRING = R"({ - "entities": [ - { - "classname": "worldspawn" - }, - { - "angles": "0 0 0", - "classname": "info_player_start", - "origin": "0 0 0" - }, - { - "angles": "0 0 0", - "classname": "mp_global_intermission", - "origin": "0 0 0" - } - ] - })"; } // namespace BSPLinkingConstants // BSPEditableConstants: diff --git a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp index f35e5809..be01a7a1 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp @@ -191,8 +191,7 @@ namespace return T.matrix(); } - unsigned CreateVertices( - const AccessorsForVertex& accessorsForVertex, const gltf::JsonNode& node, Eigen::Matrix4f& nodeMatrix, BSPSurface& surface, vec4_t vertexColor) + unsigned CreateVertices(const AccessorsForVertex& accessorsForVertex, Eigen::Matrix4f& nodeMatrix, BSPSurface& surface, vec4_t vertexColor) { // clang-format off const auto* positionAccessor = GetAccessorForIndex( @@ -320,77 +319,148 @@ namespace return vertexOffset; } - bool addNodeToBSP(const JsonRoot& jRoot, const gltf::JsonNode& node) + bool addLightNode(const gltf::JsonNode& node) { + assert(m_is_world_gfx); + assert(node.extensions); + assert(node.extensions->KHR_lights_punctual); + Eigen::Matrix4f nodeMatrix = createNodeMatrix(node); - if (m_is_world_gfx && node.extensions && node.extensions->KHR_lights_punctual) + int lightIndex = node.extensions->KHR_lights_punctual->light; + + assert(lightIndex >= 0); + if (m_bsp->lights[lightIndex].hasPosBeenSet == true) + con::warn("Internal error, multiple nodes reference the same light. Light positions/rotations are likely incorrect."); + + Eigen::Vector4f position(0, 0, 0, 1.0f); + Eigen::Vector4f transformedPosition = nodeMatrix * position; + m_bsp->lights[lightIndex].pos = vec3_t{transformedPosition.x(), transformedPosition.y(), transformedPosition.z()}; + RhcToLhcCoordinates(m_bsp->lights[lightIndex].pos.v); + + // GLTF spec uses +Y up and the default light direction is straight down + Eigen::Vector3f defaultDirection(0.0f, -1.0f, 0.0f); + Eigen::Affine3f affineTransform(nodeMatrix); + Eigen::Matrix3f rotationMatrix = affineTransform.rotation(); + Eigen::Vector3f outputDirection = rotationMatrix * defaultDirection; + outputDirection.normalize(); + m_bsp->lights[lightIndex].direction = vec3_t{outputDirection.x(), outputDirection.y(), outputDirection.z()}; + + m_bsp->lights[lightIndex].hasPosBeenSet = true; + + return true; + } + + bool addMeshNode(const JsonRoot& jRoot, const gltf::JsonNode& node) + { + assert(node.mesh); + assert(jRoot.meshes); + + Eigen::Matrix4f nodeMatrix = createNodeMatrix(node); + + const auto& mesh = jRoot.meshes.value()[node.mesh.value()]; + for (const auto& primitive : mesh.primitives) { - int lightIndex = node.extensions->KHR_lights_punctual->light; + if (!primitive.indices) + throw GltfLoadException("Requires primitives indices"); + if (primitive.mode.value_or(JsonMeshPrimitivesMode::TRIANGLES) != JsonMeshPrimitivesMode::TRIANGLES) + throw GltfLoadException("Only triangles are supported"); + if (!primitive.attributes.POSITION) + throw GltfLoadException("Requires primitives attribute POSITION"); + if (!primitive.attributes.NORMAL) + throw GltfLoadException("Requires primitives attribute NORMAL"); - assert(lightIndex >= 0); - if (m_bsp->lights[lightIndex].hasPosBeenSet == true) - con::warn("Internal error, multiple nodes reference the same light. Light positions/rotations are likely incorrect."); + const AccessorsForVertex accessorsForVertex{ + .m_position_accessor = *primitive.attributes.POSITION, + .m_normal_accessor = *primitive.attributes.NORMAL, + .m_color_accessor = primitive.attributes.COLOR_0, + .m_uv_accessor = primitive.attributes.TEXCOORD_0, + .m_index_accessor = *primitive.indices, + }; - Eigen::Vector4f position(0, 0, 0, 1.0f); - Eigen::Vector4f transformedPosition = nodeMatrix * position; - m_bsp->lights[lightIndex].pos = vec3_t{transformedPosition.x(), transformedPosition.y(), transformedPosition.z()}; - RhcToLhcCoordinates(m_bsp->lights[lightIndex].pos.v); + BSPSurface surface; + if (primitive.material) + surface.materialIndex = *primitive.material; + else + surface.materialIndex = m_curr_bsp_world->materials.size() - 1; // last material is used for colour only meshes + vec4_t vertexColour = m_curr_bsp_world->materials.at(surface.materialIndex).materialColour; + CreateVertices(accessorsForVertex, nodeMatrix, surface, vertexColour); - // GLTF spec uses +Y up and the default light direction is straight down - Eigen::Vector3f defaultDirection(0.0f, -1.0f, 0.0f); - Eigen::Affine3f affineTransform(nodeMatrix); - Eigen::Matrix3f rotationMatrix = affineTransform.rotation(); - Eigen::Vector3f outputDirection = rotationMatrix * defaultDirection; - outputDirection.normalize(); - m_bsp->lights[lightIndex].direction = vec3_t{outputDirection.x(), outputDirection.y(), outputDirection.z()}; + m_curr_bsp_world->surfaces.emplace_back(surface); + } - m_bsp->lights[lightIndex].hasPosBeenSet = true; + return true; + } - return true; + bool addXModelNode(const gltf::JsonNode& node) + { + return true; + } + + bool addSpawnPointNode(const gltf::JsonNode& node) + { + assert(node.extras); + assert(node.extras->spawnpoint); + + Eigen::Matrix4f nodeMatrix = createNodeMatrix(node); + BSPSpawnPoint spawnPoint; + + if (!node.extras->spawnpoint->compare("attacker")) + spawnPoint.type = SPAWNPOINT_TYPE_ATTACKER; + else if (!node.extras->spawnpoint->compare("defender")) + spawnPoint.type = SPAWNPOINT_TYPE_DEFENDER; + else if (!node.extras->spawnpoint->compare("all")) + spawnPoint.type = SPAWNPOINT_TYPE_ALL; + else + { + con::warn("Ignoring spawn point with an invalid type (must be attacker, defender or all)"); + return false; + } + + Eigen::Vector4f position(0, 0, 0, 1.0f); + Eigen::Vector4f transformedPosition = nodeMatrix * position; + spawnPoint.origin.x = transformedPosition.x(); + spawnPoint.origin.y = transformedPosition.y(); + spawnPoint.origin.z = transformedPosition.z(); + RhcToLhcCoordinates(spawnPoint.origin.v); + + // GLTF default direction is +Y up + Eigen::Vector3f defaultDirection(0.0f, 1.0f, 0.0f); + Eigen::Affine3f affineTransform(nodeMatrix); + Eigen::Matrix3f rotationMatrix = affineTransform.rotation(); + Eigen::Vector3f outputDirection = rotationMatrix * defaultDirection; + outputDirection.normalize(); + spawnPoint.forward.x = outputDirection.x(); + spawnPoint.forward.y = outputDirection.y(); + spawnPoint.forward.z = outputDirection.z(); + RhcToLhcCoordinates(spawnPoint.forward.v); + + m_bsp->spawnpoints.emplace_back(spawnPoint); + + return true; + } + + bool addNodeToBSP(const JsonRoot& jRoot, const gltf::JsonNode& node) + { + if (m_is_world_gfx && node.extensions && node.extensions->KHR_lights_punctual) + return addLightNode(node); + + if (node.extras) + { + if (m_is_world_gfx && node.extras->xmodel) + return addXModelNode(node); + + if (m_is_world_gfx && node.extras->spawnpoint) + return addSpawnPointNode(node); } if (node.mesh) - { - assert(jRoot.meshes); - const auto& mesh = jRoot.meshes.value()[node.mesh.value()]; - for (const auto& primitive : mesh.primitives) - { - if (!primitive.indices) - throw GltfLoadException("Requires primitives indices"); - if (primitive.mode.value_or(JsonMeshPrimitivesMode::TRIANGLES) != JsonMeshPrimitivesMode::TRIANGLES) - throw GltfLoadException("Only triangles are supported"); - if (!primitive.attributes.POSITION) - throw GltfLoadException("Requires primitives attribute POSITION"); - if (!primitive.attributes.NORMAL) - throw GltfLoadException("Requires primitives attribute NORMAL"); - - const AccessorsForVertex accessorsForVertex{ - .m_position_accessor = *primitive.attributes.POSITION, - .m_normal_accessor = *primitive.attributes.NORMAL, - .m_color_accessor = primitive.attributes.COLOR_0, - .m_uv_accessor = primitive.attributes.TEXCOORD_0, - .m_index_accessor = *primitive.indices, - }; - - BSPSurface surface; - if (primitive.material) - surface.materialIndex = *primitive.material; - else - surface.materialIndex = m_curr_bsp_world->materials.size() - 1; // last material is used for colour only meshes - vec4_t vertexColour = m_curr_bsp_world->materials.at(surface.materialIndex).materialColour; - CreateVertices(accessorsForVertex, node, nodeMatrix, surface, vertexColour); - - m_curr_bsp_world->surfaces.emplace_back(surface); - return true; - } - } + return addMeshNode(jRoot, node); return false; } static std::vector GetRootNodes(const JsonRoot& jRoot) - { if (!jRoot.nodes || jRoot.nodes->empty()) return {}; @@ -488,14 +558,16 @@ namespace void LoadLights(const JsonRoot& jRoot) { + if (!m_is_world_gfx) + return; if (!jRoot.extensions) return; if (!jRoot.extensions->KHR_lights_punctual) return; if (!jRoot.extensions->KHR_lights_punctual->lights) return; - const std::vector& jsLightArray = jRoot.extensions->KHR_lights_punctual->lights.value(); + const std::vector& jsLightArray = jRoot.extensions->KHR_lights_punctual->lights.value(); m_bsp->lights.reserve(jsLightArray.size()); for (const JsonPunctualLight& jsLight : jsLightArray) { diff --git a/src/ObjLoading/Game/T6/BSP/BSPUtil.cpp b/src/ObjLoading/Game/T6/BSP/BSPUtil.cpp index 795e7a56..28c170db 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPUtil.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPUtil.cpp @@ -4,6 +4,7 @@ #include #include +#include namespace BSP { @@ -123,6 +124,28 @@ namespace BSP axis[2].z = cosZ * cosX; } + vec3_t BSPUtil::convertForwardVectorToViewAngles(vec3_t& forwardVec) + { + vec3_t viewAngles; + + float xAndYDist = sqrtf((forwardVec.x * forwardVec.x) + (forwardVec.y * forwardVec.y)); + float atanXRadians = atan2f(forwardVec.z, xAndYDist); + float atanXDegrees = atanXRadians * (-180.0f / std::numbers::pi_v); + if (atanXDegrees < 0.0f) + atanXDegrees += 360.0f; + viewAngles.x = atanXDegrees; + + float atanYRadians = atan2f(forwardVec.y, forwardVec.x); + float atanYDegrees = atanYRadians * (180.0f / std::numbers::pi_v); + if (atanYDegrees < 0.0f) + atanYDegrees += 360.0f; + viewAngles.y = atanYDegrees; + + viewAngles.z = 0.0f; + + return viewAngles; + } + void BSPUtil::matrixTranspose3x3(const vec3_t* in, vec3_t* out) { out[0].x = in[0].x; @@ -161,7 +184,7 @@ namespace BSP std::string BSPUtil::convertVec3ToString(vec3_t& vec) { - std::string result = std::format("{} {} {}", vec.x, vec.y, vec.z); + std::string result = std::format("{} {} {}", roundf(vec.x), roundf(vec.y), roundf(vec.z)); return result; } diff --git a/src/ObjLoading/Game/T6/BSP/BSPUtil.h b/src/ObjLoading/Game/T6/BSP/BSPUtil.h index 8b7664f8..1e3b2ba7 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPUtil.h +++ b/src/ObjLoading/Game/T6/BSP/BSPUtil.h @@ -17,6 +17,7 @@ namespace BSP static size_t allignBy128(size_t size); static float distBetweenPoints(vec3_t& p1, vec3_t& p2); static void convertAnglesToAxis(vec3_t* angles, vec3_t* axis); + static vec3_t convertForwardVectorToViewAngles(vec3_t& forwardVec); static void matrixTranspose3x3(const vec3_t* in, vec3_t* out); static vec3_t convertStringToVec3(std::string& str); static std::string convertVec3ToString(vec3_t& vec); diff --git a/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp index 09c43af7..a752395b 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/MapEntsLinker.cpp @@ -7,7 +7,7 @@ using namespace nlohmann; namespace { - bool parseMapEntsJSON(json& entArrayJs, std::string& entityString) + bool addJSONToEntString(json& entArrayJs, std::string& entityString) { for (size_t entIdx = 0; entIdx < entArrayJs.size(); entIdx++) { @@ -39,28 +39,99 @@ namespace return true; } - void parseSpawnpointJSON(json& entArrayJs, std::string& entityString, const char* spawnpointNames[], size_t nameCount) - { - for (auto& element : entArrayJs.items()) - { - std::string origin; - std::string angles; - auto& entity = element.value(); - entity.at("origin").get_to(origin); - entity.at("angles").get_to(angles); + inline const std::vector DEFENDER_SPAWN_POINT_NAMES = {"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"}; - for (size_t nameIdx = 0; nameIdx < nameCount; nameIdx++) + inline const std::vector ATTACKER_SPAWN_POINT_NAMES = {"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_attacker", + "mp_tdm_spawn_axis_start", + "mp_tdm_spawn_team4_start", + "mp_tdm_spawn_team5_start", + "mp_tdm_spawn_team6_start"}; + + inline const std::vector FFA_SPAWN_POINT_NAMES = {"mp_tdm_spawn", "mp_dm_spawn", "mp_dom_spawn"}; + + void addSpawnsToEntString(BSP::BSPData* bsp, std::string& entityString) + { + if (bsp->spawnpoints.size() == 0) + { + BSP::BSPSpawnPoint defaultSpawnPoint; + defaultSpawnPoint.origin.x = 0.0f; + defaultSpawnPoint.origin.y = 0.0f; + defaultSpawnPoint.origin.z = 0.0f; + defaultSpawnPoint.forward.x = 1.0f; + defaultSpawnPoint.forward.y = 0.0f; + defaultSpawnPoint.forward.z = 0.0f; + defaultSpawnPoint.type = BSP::BSPSpawnPointType::SPAWNPOINT_TYPE_DEFENDER; + bsp->spawnpoints.emplace_back(defaultSpawnPoint); + defaultSpawnPoint.type = BSP::BSPSpawnPointType::SPAWNPOINT_TYPE_ATTACKER; + bsp->spawnpoints.emplace_back(defaultSpawnPoint); + defaultSpawnPoint.type = BSP::BSPSpawnPointType::SPAWNPOINT_TYPE_ALL; + bsp->spawnpoints.emplace_back(defaultSpawnPoint); + } + + size_t defenderNameCount = std::extent::value; + size_t attackerNameCount = std::extent::value; + size_t ffaNameCount = std::extent::value; + + for (auto& spawnPoint : bsp->spawnpoints) + { + vec3_t origin = spawnPoint.origin; + vec3_t angles = BSP::BSPUtil::convertForwardVectorToViewAngles(spawnPoint.forward); + + std::string originStr = std::format("\"origin\" \"{}\"\n", BSP::BSPUtil::convertVec3ToString(origin)); + std::string anglesStr = std::format("\"angles\" \"{}\"\n", BSP::BSPUtil::convertVec3ToString(angles)); + + const std::vector* spawnPointList; + if (spawnPoint.type == BSP::BSPSpawnPointType::SPAWNPOINT_TYPE_DEFENDER) + spawnPointList = &DEFENDER_SPAWN_POINT_NAMES; + else if (spawnPoint.type == BSP::BSPSpawnPointType::SPAWNPOINT_TYPE_ATTACKER) + spawnPointList = &ATTACKER_SPAWN_POINT_NAMES; + else // SPAWNPOINT_TYPE_ALL + spawnPointList = &FFA_SPAWN_POINT_NAMES; + + for (const char* spawnName : *spawnPointList) { entityString.append("{\n"); - entityString.append(std::format("\"origin\" \"{}\"\n", origin)); - entityString.append(std::format("\"angles\" \"{}\"\n", angles)); - entityString.append(std::format("\"classname\" \"{}\"\n", spawnpointNames[nameIdx])); + entityString.append(originStr); + entityString.append(anglesStr); + entityString.append(std::format("\"classname\" \"{}\"\n", spawnName)); entityString.append("}\n"); } } } - std::string loadMapEnts() {} + constexpr const char* DEFAULT_MAP_ENTS_STRING = R"({ + "entities": [ + { + "classname": "worldspawn" + }, + { + "angles": "0 0 0", + "classname": "info_player_start", + "origin": "0 0 0" + }, + { + "angles": "0 0 0", + "classname": "mp_global_intermission", + "origin": "0 0 0" + } + ] + })"; } // namespace namespace BSP @@ -83,35 +154,17 @@ namespace BSP if (!entFile.IsOpen()) { con::warn("Can't find entity file {}, using default entities instead", entityFilePath); - entJs = json::parse(BSPLinkingConstants::DEFAULT_MAP_ENTS_STRING); + entJs = json::parse(DEFAULT_MAP_ENTS_STRING); } else { entJs = json::parse(*entFile.m_stream); } std::string entityString; - if (!parseMapEntsJSON(entJs["entities"], entityString)) + if (!addJSONToEntString(entJs["entities"], entityString)) return nullptr; - json spawnJs; - std::string spawnFileName = "spawns.json"; - std::string spawnFilePath = BSPUtil::getFileNameForBSPAsset(spawnFileName); - const auto spawnFile = m_search_path.Open(spawnFilePath); - if (!spawnFile.IsOpen()) - { - con::warn("Cant find spawn file {}, setting spawns to 0 0 0", spawnFilePath); - spawnJs = json::parse(BSPLinkingConstants::DEFAULT_SPAWN_POINT_STRING); - } - else - { - spawnJs = json::parse(*spawnFile.m_stream); - } - size_t defenderNameCount = std::extent::value; - size_t attackerNameCount = std::extent::value; - size_t ffaNameCount = std::extent::value; - parseSpawnpointJSON(spawnJs["attackers"], entityString, BSPGameConstants::DEFENDER_SPAWN_POINT_NAMES, defenderNameCount); - parseSpawnpointJSON(spawnJs["defenders"], entityString, BSPGameConstants::ATTACKER_SPAWN_POINT_NAMES, attackerNameCount); - parseSpawnpointJSON(spawnJs["FFA"], entityString, BSPGameConstants::FFA_SPAWN_POINT_NAMES, ffaNameCount); + addSpawnsToEntString(bsp, entityString); MapEnts* mapEnts = m_memory.Alloc(); mapEnts->name = m_memory.Dup(bsp->bspName.c_str()); @@ -119,7 +172,7 @@ namespace BSP mapEnts->entityString = m_memory.Dup(entityString.c_str()); mapEnts->numEntityChars = static_cast(entityString.length() + 1); // numEntityChars includes the null character - // don't need these + // don't need these, unused by the game mapEnts->trigger.count = 0; mapEnts->trigger.models = nullptr; mapEnts->trigger.hullCount = 0;