feat: templated vehicle loading and dumping for IW4 and IW5 (#929)

* feat: templated vehicle loading and dumping for IW4 and IW5

* chore: restructure vehicle tests into separate files

---------

Co-authored-by: Jan Laupetin <[email protected]>
This commit is contained in:
mo
2026-07-26 00:28:44 +02:00
committed by GitHub
co-authored by Jan Laupetin
parent f8415e33cc
commit 76e5bb17a2
26 changed files with 1741 additions and 65 deletions
+4 -1
View File
@@ -11,6 +11,8 @@
#include "Game/IW4/Techset/VertexShaderLoaderIW4.h"
#include "Game/IW4/Tracer/GdtLoaderTracerIW4.h"
#include "Game/IW4/Tracer/RawLoaderTracerIW4.h"
#include "Game/IW4/Vehicle/GdtLoaderVehicleIW4.h"
#include "Game/IW4/Vehicle/RawLoaderVehicleIW4.h"
#include "Game/IW4/Weapon/AccuracyGraphLoaderIW4.h"
#include "Game/IW4/XAnim/XAnimLoaderIW4.h"
#include "Game/IW4/XModel/LoaderXModelIW4.h"
@@ -163,7 +165,8 @@ namespace
collection.AddAssetCreator(structured_data_def::CreateLoaderIW4(memory, searchPath));
collection.AddAssetCreator(tracer::CreateRawLoaderIW4(memory, searchPath, zone));
collection.AddAssetCreator(tracer::CreateGdtLoaderIW4(memory, gdt, zone));
// collection.AddAssetCreator(std::make_unique<AssetLoaderVehicle>(memory));
collection.AddAssetCreator(vehicle::CreateRawLoaderIW4(memory, searchPath, zone));
collection.AddAssetCreator(vehicle::CreateGdtLoaderIW4(memory, gdt, zone));
// collection.AddAssetCreator(std::make_unique<AssetLoaderAddonMapEnts>(memory));
collection.AddSubAssetCreator(weapon::CreateAccuracyGraphLoaderIW4(memory, searchPath));
+4 -1
View File
@@ -11,6 +11,8 @@
#include "Game/IW5/Techset/VertexShaderLoaderIW5.h"
#include "Game/IW5/Tracer/GdtLoaderTracerIW5.h"
#include "Game/IW5/Tracer/RawLoaderTracerIW5.h"
#include "Game/IW5/Vehicle/GdtLoaderVehicleIW5.h"
#include "Game/IW5/Vehicle/RawLoaderVehicleIW5.h"
#include "Game/IW5/Weapon/AccuracyGraphLoaderIW5.h"
#include "Game/IW5/XAnim/XAnimLoaderIW5.h"
#include "Game/IW5/XModel/LoaderXModelIW5.h"
@@ -174,7 +176,8 @@ namespace
// collection.AddAssetCreator(std::make_unique<AssetLoaderStructuredDataDef>(memory));
collection.AddAssetCreator(tracer::CreateRawLoaderIW5(memory, searchPath, zone));
collection.AddAssetCreator(tracer::CreateGdtLoaderIW5(memory, gdt, zone));
// collection.AddAssetCreator(std::make_unique<AssetLoaderVehicle>(memory));
collection.AddAssetCreator(vehicle::CreateRawLoaderIW5(memory, searchPath, zone));
collection.AddAssetCreator(vehicle::CreateGdtLoaderIW5(memory, gdt, zone));
// collection.AddAssetCreator(std::make_unique<AssetLoaderAddonMapEnts>(memory));
collection.AddSubAssetCreator(weapon::CreateAccuracyGraphLoaderIW5(memory, searchPath));
@@ -0,0 +1,63 @@
#options GAME(IW4, IW5)
#filename "Game/" + GAME + "/Vehicle/GdtLoaderVehicle" + GAME + ".cpp"
#set LOADER_HEADER "\"GdtLoaderVehicle" + GAME + ".h\""
#set INFO_STRING_LOADER_HEADER "\"InfoStringLoaderVehicle" + GAME + ".h\""
#set OBJ_CONSTANTS_HEADER "\"Game/" + GAME + "/ObjConstants" + GAME + ".h\""
#set INFO_STRING_LOADER_NAME "InfoStringLoader" + GAME
#set FACTORY_NAME "CreateGdtLoader" + GAME
// This file was templated.
// See GdtLoaderVehicle.cpp.template.
// Do not modify, changes will be lost.
#include LOADER_HEADER
#include INFO_STRING_LOADER_HEADER
#include OBJ_CONSTANTS_HEADER
#include "InfoString/InfoString.h"
#include "Utils/Logging/Log.h"
using namespace GAME;
namespace
{
class GdtLoaderVehicle final : public AssetCreator<AssetVehicle>
{
public:
GdtLoaderVehicle(MemoryManager& memory, IGdtQueryable& gdt, Zone& zone)
: m_gdt(gdt),
m_info_string_loader(memory, zone)
{
}
AssetCreationResult CreateAsset(const std::string& assetName, AssetCreationContext& context) override
{
const auto* gdtEntry = m_gdt.GetGdtEntryByGdfAndName(GDF_FILENAME_VEHICLE, assetName);
if (gdtEntry == nullptr)
return AssetCreationResult::NoAction();
InfoString infoString;
if (!infoString.FromGdtProperties(*gdtEntry))
{
con::error("Failed to read vehicle gdt entry: \"{}\"", assetName);
return AssetCreationResult::Failure();
}
return m_info_string_loader.CreateAsset(assetName, infoString, context);
}
private:
IGdtQueryable& m_gdt;
vehicle::INFO_STRING_LOADER_NAME m_info_string_loader;
};
} // namespace
namespace vehicle
{
std::unique_ptr<AssetCreator<AssetVehicle>> FACTORY_NAME(MemoryManager& memory, IGdtQueryable& gdt, Zone& zone)
{
return std::make_unique<GdtLoaderVehicle>(memory, gdt, zone);
}
} // namespace vehicle
@@ -0,0 +1,25 @@
#options GAME(IW4, IW5)
#filename "Game/" + GAME + "/Vehicle/GdtLoaderVehicle" + GAME + ".h"
#set GAME_HEADER "\"Game/" + GAME + "/" + GAME + ".h\""
#set FACTORY_NAME "CreateGdtLoader" + GAME
// This file was templated.
// See GdtLoaderVehicle.h.template.
// Do not modify, changes will be lost.
#pragma once
#include GAME_HEADER
#include "Asset/IAssetCreator.h"
#include "Gdt/IGdtQueryable.h"
#include "Utils/MemoryManager.h"
#include "Zone/Zone.h"
#include <memory>
namespace vehicle
{
std::unique_ptr<AssetCreator<GAME::AssetVehicle>> FACTORY_NAME(MemoryManager& memory, IGdtQueryable& gdt, Zone& zone);
} // namespace vehicle
@@ -0,0 +1,191 @@
#options GAME(IW4, IW5)
#filename "Game/" + GAME + "/Vehicle/InfoStringLoaderVehicle" + GAME + ".cpp"
#set LOADER_HEADER "\"InfoStringLoaderVehicle" + GAME + ".h\""
#set GAME_HEADER "\"Game/" + GAME + "/" + GAME + ".h\""
#set INFO_STRING_CONVERTER_HEADER "\"Game/" + GAME + "/InfoString/InfoStringToStructConverter.h\""
#set VEHICLE_FIELDS_HEADER "\"Game/" + GAME + "/Vehicle/VehicleFields" + GAME + ".h\""
#set VEHICLE_STRINGS_HEADER "\"Game/" + GAME + "/Vehicle/VehicleStrings" + GAME + ".h\""
#set CLASS_NAME "InfoStringLoader" + GAME
#if GAME == "IW5"
#define FEATURE_IW5
#endif
// This file was templated.
// See InfoStringLoaderVehicle.cpp.template.
// Do not modify, changes will be lost.
#include LOADER_HEADER
#include GAME_HEADER
#include INFO_STRING_CONVERTER_HEADER
#include VEHICLE_FIELDS_HEADER
#include VEHICLE_STRINGS_HEADER
#include "Utils/Logging/Log.h"
#include <cassert>
#include <string>
#include <type_traits>
#include <vector>
using namespace GAME;
namespace
{
constexpr const char* SURFACE_SOUND_SUFFIXES[]{
"_default", "_bark", "_brick", "_carpet", "_cloth", "_concrete", "_dirt", "_flesh", "_foliage", "_glass", "_grass",
"_gravel", "_ice", "_metal", "_mud", "_paper", "_plaster", "_rock", "_sand", "_snow", "_water", "_wood",
"_asphalt", "_ceramic", "_plastic", "_rubber", "_cushion", "_fruit", "_painted_metal", "_riot_shield", "_slush",
};
class InfoStringToVehicleConverter final : public InfoStringToStructConverter
{
bool ConvertTrophyTags(const cspField_t& field, const std::string& value)
{
std::vector<std::string> valueArray;
if (!ParseAsArray(value, valueArray))
{
con::error("Failed to parse trophy tags as array");
return false;
}
if (valueArray.size() > std::extent_v<decltype(VehicleDef::trophyTags)>)
{
con::error("Cannot have more than {} trophy tags!", std::extent_v<decltype(VehicleDef::trophyTags)>);
return false;
}
auto* trophyTags = reinterpret_cast<scr_string_t*>(reinterpret_cast<uintptr_t>(m_structure) + field.iOffset);
for (auto i = 0u; i < valueArray.size(); i++)
{
if (valueArray[i].empty())
continue;
const auto scriptString = m_zone_script_strings.AddOrGetScriptString(valueArray[i]);
trophyTags[i] = scriptString;
m_registration.AddScriptString(scriptString);
}
return true;
}
protected:
bool ConvertExtensionField(const cspField_t& field, const std::string& value) override
{
switch (static_cast<VehicleFieldType>(field.iFieldType))
{
case VFT_TYPE:
return ConvertEnumInt(field.szName, value, field.iOffset, s_vehicleClassNames, std::extent_v<decltype(s_vehicleClassNames)>);
case VFT_AXLE_STEERING:
case VFT_AXLE_POWER:
case VFT_AXLE_BRAKING:
return ConvertEnumInt(field.szName, value, field.iOffset, s_vehicleAxleTypeNames, std::extent_v<decltype(s_vehicleAxleTypeNames)>);
case VFT_TROPHY_TAGS:
return ConvertTrophyTags(field, value);
#ifdef FEATURE_IW5
case VFT_CAM_Z_OFFSET_MODE:
return ConvertEnumInt(
field.szName, value, field.iOffset, s_vehicleCameraZOffsetModeNames, std::extent_v<decltype(s_vehicleCameraZOffsetModeNames)>);
#endif
case VFT_NUM:
default:
assert(false);
return false;
}
}
public:
InfoStringToVehicleConverter(const InfoString& infoString,
VehicleDef& vehicle,
ZoneScriptStrings& zoneScriptStrings,
MemoryManager& memory,
AssetCreationContext& context,
AssetRegistration<AssetVehicle>& registration,
const cspField_t* fields,
const size_t fieldCount)
: InfoStringToStructConverter(infoString, &vehicle, zoneScriptStrings, memory, context, registration, fields, fieldCount)
{
}
};
bool LoadDependencies(VehicleDef& vehicle, AssetCreationContext& context, AssetRegistration<AssetVehicle>& registration)
{
if (vehicle.vehPhysDef.physPresetName && vehicle.vehPhysDef.physPresetName[0])
{
auto* physPreset = context.LoadDependency<AssetPhysPreset>(vehicle.vehPhysDef.physPresetName);
if (physPreset == nullptr)
{
con::error("Failed to load phys preset asset \"{}\"", vehicle.vehPhysDef.physPresetName);
return false;
}
vehicle.vehPhysDef.physPreset = physPreset->Asset();
registration.AddDependency(physPreset);
}
if (vehicle.turretWeaponName && vehicle.turretWeaponName[0])
{
auto* weapon = context.LoadDependency<AssetWeapon>(vehicle.turretWeaponName);
if (weapon == nullptr)
{
con::error("Failed to load weapon asset \"{}\"", vehicle.turretWeaponName);
return false;
}
vehicle.turretWeapon = weapon->Asset();
registration.AddDependency(weapon);
}
return true;
}
void CreateSurfaceSounds(VehicleDef& vehicle, MemoryManager& memory, AssetCreationContext& context, AssetRegistration<AssetVehicle>& registration)
{
if (vehicle.surfaceSndPrefix == nullptr || vehicle.surfaceSndPrefix[0] == '\0')
return;
static_assert(std::extent_v<decltype(VehicleDef::surfaceSnds)> == std::extent_v<decltype(SURFACE_SOUND_SUFFIXES)>);
for (auto i = 0u; i < std::extent_v<decltype(VehicleDef::surfaceSnds)>; i++)
{
const auto soundName = std::string(vehicle.surfaceSndPrefix) + SURFACE_SOUND_SUFFIXES[i];
vehicle.surfaceSnds[i].name = memory.Alloc<snd_alias_list_name>();
vehicle.surfaceSnds[i].name->soundName = memory.Dup(soundName.c_str());
registration.AddIndirectAssetReference(context.LoadIndirectAssetReference<AssetSound>(soundName));
}
}
} // namespace
namespace vehicle
{
CLASS_NAME::CLASS_NAME(MemoryManager& memory, Zone& zone)
: m_memory(memory),
m_zone(zone)
{
}
AssetCreationResult CLASS_NAME::CreateAsset(const std::string& assetName, const InfoString& infoString, AssetCreationContext& context)
{
auto* vehicle = m_memory.Alloc<VehicleDef>();
vehicle->name = m_memory.Dup(assetName.c_str());
AssetRegistration<AssetVehicle> registration(assetName, vehicle);
InfoStringToVehicleConverter converter(
infoString, *vehicle, m_zone.m_script_strings, m_memory, context, registration, vehicle_fields, std::extent_v<decltype(vehicle_fields)>);
if (!converter.Convert() || !LoadDependencies(*vehicle, context, registration))
{
con::error("Failed to parse vehicle: \"{}\"", assetName);
return AssetCreationResult::Failure();
}
vehicle->vehPhysDef.topSpeed = vehicle->topSpeed;
vehicle->vehPhysDef.suspensionTravelFront = vehicle->suspensionTravel;
CreateSurfaceSounds(*vehicle, m_memory, context, registration);
return AssetCreationResult::Success(context.AddAsset(std::move(registration)));
}
} // namespace vehicle
@@ -0,0 +1,33 @@
#options GAME(IW4, IW5)
#filename "Game/" + GAME + "/Vehicle/InfoStringLoaderVehicle" + GAME + ".h"
#set GAME_HEADER "\"Game/" + GAME + "/" + GAME + ".h\""
#set CLASS_NAME "InfoStringLoader" + GAME
// This file was templated.
// See InfoStringLoaderVehicle.h.template.
// Do not modify, changes will be lost.
#pragma once
#include GAME_HEADER
#include "Asset/AssetCreationContext.h"
#include "Asset/AssetRegistration.h"
#include "InfoString/InfoString.h"
#include "Utils/MemoryManager.h"
#include "Zone/Zone.h"
namespace vehicle
{
class CLASS_NAME
{
public:
CLASS_NAME(MemoryManager& memory, Zone& zone);
AssetCreationResult CreateAsset(const std::string& assetName, const InfoString& infoString, AssetCreationContext& context);
private:
MemoryManager& m_memory;
Zone& m_zone;
};
} // namespace vehicle
@@ -0,0 +1,65 @@
#options GAME(IW4, IW5)
#filename "Game/" + GAME + "/Vehicle/RawLoaderVehicle" + GAME + ".cpp"
#set LOADER_HEADER "\"RawLoaderVehicle" + GAME + ".h\""
#set INFO_STRING_LOADER_HEADER "\"InfoStringLoaderVehicle" + GAME + ".h\""
#set OBJ_CONSTANTS_HEADER "\"Game/" + GAME + "/ObjConstants" + GAME + ".h\""
#set INFO_STRING_LOADER_NAME "InfoStringLoader" + GAME
#set FACTORY_NAME "CreateRawLoader" + GAME
// This file was templated.
// See RawLoaderVehicle.cpp.template.
// Do not modify, changes will be lost.
#include LOADER_HEADER
#include INFO_STRING_LOADER_HEADER
#include OBJ_CONSTANTS_HEADER
#include "InfoString/InfoString.h"
#include "Utils/Logging/Log.h"
#include "Vehicle/VehicleCommon.h"
using namespace GAME;
namespace
{
class RawLoaderVehicle final : public AssetCreator<AssetVehicle>
{
public:
RawLoaderVehicle(MemoryManager& memory, ISearchPath& searchPath, Zone& zone)
: m_search_path(searchPath),
m_info_string_loader(memory, zone)
{
}
AssetCreationResult CreateAsset(const std::string& assetName, AssetCreationContext& context) override
{
const auto fileName = vehicle::GetFileNameForAssetName(assetName);
const auto file = m_search_path.Open(fileName);
if (!file.IsOpen())
return AssetCreationResult::NoAction();
InfoString infoString;
if (!infoString.FromStream(INFO_STRING_PREFIX_VEHICLE, *file.m_stream))
{
con::error("Could not parse as info string file: \"{}\"", fileName);
return AssetCreationResult::Failure();
}
return m_info_string_loader.CreateAsset(assetName, infoString, context);
}
private:
ISearchPath& m_search_path;
vehicle::INFO_STRING_LOADER_NAME m_info_string_loader;
};
} // namespace
namespace vehicle
{
std::unique_ptr<AssetCreator<AssetVehicle>> FACTORY_NAME(MemoryManager& memory, ISearchPath& searchPath, Zone& zone)
{
return std::make_unique<RawLoaderVehicle>(memory, searchPath, zone);
}
} // namespace vehicle
@@ -0,0 +1,25 @@
#options GAME(IW4, IW5)
#filename "Game/" + GAME + "/Vehicle/RawLoaderVehicle" + GAME + ".h"
#set GAME_HEADER "\"Game/" + GAME + "/" + GAME + ".h\""
#set FACTORY_NAME "CreateRawLoader" + GAME
// This file was templated.
// See RawLoaderVehicle.h.template.
// Do not modify, changes will be lost.
#pragma once
#include GAME_HEADER
#include "Asset/IAssetCreator.h"
#include "SearchPath/ISearchPath.h"
#include "Utils/MemoryManager.h"
#include "Zone/Zone.h"
#include <memory>
namespace vehicle
{
std::unique_ptr<AssetCreator<GAME::AssetVehicle>> FACTORY_NAME(MemoryManager& memory, ISearchPath& searchPath, Zone& zone);
} // namespace vehicle