2
0
mirror of https://github.com/Laupetin/OpenAssetTools.git synced 2026-07-02 22:08:11 +00:00

feat: t6 flametables (#845)

* feat: add flametable loading and dumping for T6

* chore: add additional validation logic of game for t6 weapon loading
This commit is contained in:
Jan
2026-06-20 13:50:43 +02:00
committed by GitHub
parent 5b11848f4d
commit c84e3076f1
9 changed files with 447 additions and 15 deletions
+2
View File
@@ -43,6 +43,7 @@
#include "Weapon/AttachmentUniqueGdtLoaderT6.h"
#include "Weapon/AttachmentUniqueRawLoaderT6.h"
#include "Weapon/CamoJsonLoaderT6.h"
#include "Weapon/FlameTableLoaderT6.h"
#include "Weapon/WeaponGdtLoaderT6.h"
#include "Weapon/WeaponRawLoaderT6.h"
#include "ZBarrier/GdtLoaderZBarrierT6.h"
@@ -441,6 +442,7 @@ namespace T6
collection.AddSubAssetCreator(techset::CreateVertexShaderLoaderT6(memory, searchPath));
collection.AddSubAssetCreator(techset::CreatePixelShaderLoaderT6(memory, searchPath));
collection.AddSubAssetCreator(weapon::CreateFlameTableLoaderT6(memory, searchPath, zone));
}
} // namespace
@@ -0,0 +1,93 @@
#include "FlameTableLoaderT6.h"
#include "Game/T6/InfoString/InfoStringToStructConverter.h"
#include "Game/T6/ObjConstantsT6.h"
#include "Game/T6/T6.h"
#include "Game/T6/Weapon/FlameTableFields.h"
#include "Weapon/WeaponCommon.h"
#include <cassert>
#include <format>
using namespace T6;
namespace
{
class InfoStringToFlameTableConverter final : public InfoStringToStructConverter
{
protected:
bool ConvertExtensionField(const cspField_t& field, const std::string& value) override
{
assert(false);
return false;
}
public:
InfoStringToFlameTableConverter(const InfoString& infoString,
FlameTable& flameTable,
ZoneScriptStrings& zoneScriptStrings,
MemoryManager& memory,
AssetCreationContext& context,
AssetRegistration<SubAssetFlameTable>& registration,
const cspField_t* fields,
const size_t fieldCount)
: InfoStringToStructConverter(infoString, &flameTable, zoneScriptStrings, memory, context, registration, fields, fieldCount)
{
}
};
class FlameTableLoaderT6 final : public SubAssetCreator<SubAssetFlameTable>
{
public:
FlameTableLoaderT6(MemoryManager& memory, ISearchPath& searchPath, Zone& zone)
: m_memory(memory),
m_search_path(searchPath),
m_zone(zone)
{
}
AssetCreationResult CreateSubAsset(const std::string& assetName, AssetCreationContext& context) override
{
const auto fileName = weapon::GetFileNameForFlameTable(assetName);
const auto file = m_search_path.Open(fileName);
if (!file.IsOpen())
return AssetCreationResult::NoAction();
InfoString infoString;
if (!infoString.FromStream(INFO_STRING_PREFIX_FLAME_TABLE, *file.m_stream))
{
con::error("Could not parse as info string file: \"{}\"", fileName);
return AssetCreationResult::Failure();
}
auto* flameTable = m_memory.Alloc<FlameTable>();
AssetRegistration<SubAssetFlameTable> registration(assetName, flameTable);
InfoStringToFlameTableConverter converter(
infoString, *flameTable, m_zone.m_script_strings, m_memory, context, registration, flameTableFields, std::extent_v<decltype(flameTableFields)>);
if (!converter.Convert())
{
con::error("Failed to parse flame table: \"{}\"", assetName);
return AssetCreationResult::Failure();
}
// The flametable infostring contains the name but we don't to use it from there
// It's kept to keep compatiblity with the official modtools and game
flameTable->name = m_memory.Dup(assetName.c_str());
return AssetCreationResult::Success(context.AddSubAsset(std::move(registration)));
}
private:
MemoryManager& m_memory;
ISearchPath& m_search_path;
Zone& m_zone;
};
} // namespace
namespace weapon
{
std::unique_ptr<ISubAssetCreator> CreateFlameTableLoaderT6(MemoryManager& memory, ISearchPath& searchPath, Zone& zone)
{
return std::make_unique<FlameTableLoaderT6>(memory, searchPath, zone);
}
} // namespace weapon
@@ -0,0 +1,13 @@
#pragma once
#include "Asset/IAssetCreator.h"
#include "SearchPath/ISearchPath.h"
#include "Utils/MemoryManager.h"
#include "Zone/Zone.h"
#include <memory>
namespace weapon
{
std::unique_ptr<ISubAssetCreator> CreateFlameTableLoaderT6(MemoryManager& memory, ISearchPath& searchPath, Zone& zone);
} // namespace weapon
@@ -6,6 +6,7 @@
#include "Game/T6/Weapon/WeaponFields.h"
#include "Game/T6/Weapon/WeaponStrings.h"
#include "Utils/Logging/Log.h"
#include "Utils/StringUtils.h"
#include "Weapon/AccuracyGraphLoader.h"
#include <cassert>
@@ -442,6 +443,32 @@ namespace
return true;
}
bool LoadFlameTable(const char* flameTableName, FlameTable*& flameTablePtr, AssetRegistration<AssetWeapon>& registration, AssetCreationContext& context)
{
if (flameTableName && flameTableName[0] != '\0')
{
auto* flameTableAsset = context.LoadSubAsset<SubAssetFlameTable>(flameTableName);
if (!flameTableAsset)
return false;
for (auto* dependency : flameTableAsset->m_dependencies)
registration.AddDependency(dependency);
assert(flameTableAsset->m_used_script_strings.empty());
assert(flameTableAsset->m_indirect_asset_references.empty());
flameTablePtr = flameTableAsset->Asset();
}
return true;
}
bool LoadFlameTables(WeaponFullDef& weaponFullDef, AssetRegistration<AssetWeapon>& registration, AssetCreationContext& context)
{
return LoadFlameTable(weaponFullDef.weapDef.flameTableFirstPerson, weaponFullDef.weapDef.flameTableFirstPersonPtr, registration, context)
&& LoadFlameTable(weaponFullDef.weapDef.flameTableThirdPerson, weaponFullDef.weapDef.flameTableThirdPersonPtr, registration, context);
}
void LinkWeaponFullDefSubStructs(WeaponFullDef& weapon)
{
weapon.weapVariantDef.weapDef = &weapon.weapDef;
@@ -462,25 +489,109 @@ namespace
weapon.weapDef.locationDamageMultipliers = weapon.locationDamageMultipliers;
}
void CalculateWeaponFields(WeaponFullDef& weapon)
bool IsDefaultWeapon(const WeaponFullDef& weapon)
{
return strcmp(weapon.weapVariantDef.szInternalName, "defaultweapon") == 0 || strcmp(weapon.weapVariantDef.szInternalName, "defaultweapon_mp") == 0;
}
void SetWeaponDefaults(WeaponFullDef& weapon)
{
if (IsDefaultWeapon(weapon))
return;
if (!weapon.weapDef.viewLastShotEjectEffect)
weapon.weapDef.viewLastShotEjectEffect = weapon.weapDef.viewShellEjectEffect;
if (!weapon.weapDef.worldLastShotEjectEffect)
weapon.weapDef.worldLastShotEjectEffect = weapon.weapDef.worldShellEjectEffect;
if (!weapon.weapDef.raiseSound)
weapon.weapDef.raiseSound = "wpn_default_raise";
if (!weapon.weapDef.putawaySound)
weapon.weapDef.putawaySound = "wpn_default_putaway";
if (!weapon.weapDef.pickupSound)
weapon.weapDef.pickupSound = "wpn_default_pickup";
if (!weapon.weapDef.ammoPickupSound)
weapon.weapDef.ammoPickupSound = "wpn_default_ammo_pickup";
if (!weapon.weapDef.emptyFireSound)
weapon.weapDef.emptyFireSound = "wpn_default_no_ammo";
}
void SetupTransitionTimes(WeaponFullDef& weapon)
{
if (weapon.weapVariantDef.iAdsTransInTime <= 0)
weapon.weapVariantDef.fOOPosAnimLength[0] = 1.0f / 300.0f; // 0.0033333334f;
else
weapon.weapVariantDef.fOOPosAnimLength[0] = 1.0f / static_cast<float>(weapon.weapVariantDef.iAdsTransInTime);
if (weapon.weapVariantDef.iAdsTransOutTime <= 0)
weapon.weapVariantDef.fOOPosAnimLength[1] = 1.0f / 500.0f; // 0.0020000001f
else
weapon.weapVariantDef.fOOPosAnimLength[1] = 1.0f / static_cast<float>(weapon.weapVariantDef.iAdsTransOutTime);
}
void CheckWeaponDamageRanges(WeaponFullDef& weapon)
{
if (strcmp(weapon.weapVariantDef.szInternalName, "none") == 0)
return;
if (weapon.weapDef.damageRange[0] <= 0.0)
weapon.weapDef.damageRange[0] = 999999.0f;
if (weapon.weapDef.damageRange[5] <= 0.0)
weapon.weapDef.damageRange[5] = 999999.12f; // oddly specific number, no clue
}
void CheckCrosshairValues(WeaponFullDef& weapon)
{
if (weapon.weapDef.enemyCrosshairRange > 15000.0f)
con::warn("Weapon {}: Enemy crosshair ranges should be less than 15000", weapon.weapVariantDef.szInternalName);
}
void CheckProjectileValues(WeaponFullDef& weapon)
{
if (weapon.weapDef.weapType != WEAPTYPE_PROJECTILE)
return;
if (weapon.weapDef.iProjectileSpeed <= 0)
con::warn("Weapon {}: Projectile speed must be greater than 0.0", weapon.weapVariantDef.szDisplayName);
if (weapon.weapDef.destabilizationCurvatureMax >= 1000000000.0f || weapon.weapDef.destabilizationCurvatureMax < 0.0f)
con::warn("Weapon {}: Destabilization angle must be between 0 and 45 degrees", weapon.weapVariantDef.szDisplayName);
if (weapon.weapDef.destabilizationRateTime < 0.0f)
con::warn("Weapon {}: Destabilization rate time must be non-negative", weapon.weapVariantDef.szDisplayName);
}
void CheckSharedAmmoValues(const WeaponFullDef& weapon)
{
if (weapon.weapVariantDef.szAmmoName)
utils::MakeStringLowerCase(const_cast<char*>(weapon.weapVariantDef.szAmmoName));
if (weapon.weapVariantDef.szClipName)
utils::MakeStringLowerCase(const_cast<char*>(weapon.weapVariantDef.szClipName));
}
void CheckAttachModelTags(const WeaponFullDef& weapon)
{
for (auto* tag : weapon.attachViewModelTag)
{
if (tag)
utils::MakeStringLowerCase(const_cast<char*>(tag));
}
for (auto* tag : weapon.attachWorldModelTag)
{
if (tag)
utils::MakeStringLowerCase(const_cast<char*>(tag));
}
}
void SetupAttachmentField(WeaponFullDef& weapon)
{
// iAttachments
weapon.weapVariantDef.iAttachments = 0;
for (auto i = 1u; i < sizeof(WeaponVariantDef::iAttachments) * 8; i++) // Bit for default attachment always 0
{
if (weapon.attachments[i])
weapon.weapVariantDef.iAttachments |= 1 << i;
}
if (weapon.weapVariantDef.iAdsTransInTime <= 0)
weapon.weapVariantDef.fOOPosAnimLength[0] = 0.0033333334f;
else
weapon.weapVariantDef.fOOPosAnimLength[0] = 1.0f / static_cast<float>(weapon.weapVariantDef.iAdsTransInTime);
if (weapon.weapVariantDef.iAdsTransOutTime <= 0)
weapon.weapVariantDef.fOOPosAnimLength[1] = 0.0020000001f;
else
weapon.weapVariantDef.fOOPosAnimLength[1] = 1.0f / static_cast<float>(weapon.weapVariantDef.iAdsTransOutTime);
}
bool IsStringOverride(const char* baseString, const char* overrideString)
@@ -625,10 +736,27 @@ namespace weapon
return AssetCreationResult::Failure();
}
CalculateWeaponFields(*weaponFullDef);
CalculateAttachmentFields(*weaponFullDef);
if (!LoadAccuracyGraphs(*weaponFullDef, m_memory, m_search_path, context))
{
con::error("Failed to load accuracy tables of weapon: \"{}\"", assetName);
return AssetCreationResult::Failure();
}
LoadAccuracyGraphs(*weaponFullDef, m_memory, m_search_path, context);
if (!LoadFlameTables(*weaponFullDef, registration, context))
{
con::error("Failed to load flame tables of weapon: \"{}\"", assetName);
return AssetCreationResult::Failure();
}
SetWeaponDefaults(*weaponFullDef);
SetupTransitionTimes(*weaponFullDef);
CheckWeaponDamageRanges(*weaponFullDef);
SetupAttachmentField(*weaponFullDef);
CalculateAttachmentFields(*weaponFullDef);
CheckCrosshairValues(*weaponFullDef);
CheckProjectileValues(*weaponFullDef);
CheckSharedAmmoValues(*weaponFullDef);
CheckAttachModelTags(*weaponFullDef);
return AssetCreationResult::Success(context.AddAsset(std::move(registration)));
}