From 28949f4ee8deb63b9a39503d4a6f871598ec8f1c Mon Sep 17 00:00:00 2001 From: Jan Date: Sun, 14 Apr 2024 17:46:34 +0200 Subject: [PATCH 01/10] chore: use constants for model array sizes in iw5 attachment writing --- src/ObjCommon/Game/IW5/Weapon/JsonWeaponAttachment.h | 4 ++++ .../Game/IW5/Weapon/JsonWeaponAttachmentWriter.cpp | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/ObjCommon/Game/IW5/Weapon/JsonWeaponAttachment.h b/src/ObjCommon/Game/IW5/Weapon/JsonWeaponAttachment.h index 0c13e9a6..edb017e1 100644 --- a/src/ObjCommon/Game/IW5/Weapon/JsonWeaponAttachment.h +++ b/src/ObjCommon/Game/IW5/Weapon/JsonWeaponAttachment.h @@ -510,6 +510,10 @@ namespace IW5 {WEAPCLASS_ITEM, "item" }, }); + constexpr auto ATTACHMENT_WORLD_MODEL_COUNT = 16u; + constexpr auto ATTACHMENT_VIEW_MODEL_COUNT = 16u; + constexpr auto ATTACHMENT_RETICLE_VIEW_MODEL_COUNT = 8u; + class JsonWeaponAttachment { public: diff --git a/src/ObjWriting/Game/IW5/Weapon/JsonWeaponAttachmentWriter.cpp b/src/ObjWriting/Game/IW5/Weapon/JsonWeaponAttachmentWriter.cpp index 86f0df79..2c6fadcf 100644 --- a/src/ObjWriting/Game/IW5/Weapon/JsonWeaponAttachmentWriter.cpp +++ b/src/ObjWriting/Game/IW5/Weapon/JsonWeaponAttachmentWriter.cpp @@ -68,9 +68,9 @@ namespace jAttachment.weaponType = attachment.weaponType; jAttachment.weapClass = attachment.weapClass; - CONVERT_XMODEL_ARRAY(worldModels, 16u) - CONVERT_XMODEL_ARRAY(viewModels, 16u) - CONVERT_XMODEL_ARRAY(reticleViewModels, 8u) + CONVERT_XMODEL_ARRAY(worldModels, ATTACHMENT_WORLD_MODEL_COUNT) + CONVERT_XMODEL_ARRAY(viewModels, ATTACHMENT_VIEW_MODEL_COUNT) + CONVERT_XMODEL_ARRAY(reticleViewModels, ATTACHMENT_RETICLE_VIEW_MODEL_COUNT) CONVERT_ATTRIBUTE(JsonAttAmmoGeneral, ammoGeneral) CONVERT_ATTRIBUTE(JsonAttSight, sight) From d3c0325feaf14f7d8be64e5828cc09fe075bdacf Mon Sep 17 00:00:00 2001 From: Jan Date: Sun, 14 Apr 2024 17:47:06 +0200 Subject: [PATCH 02/10] feat: load iw5 weapon attachments from raw --- .../AssetLoaderWeaponAttachment.cpp | 47 ++ .../AssetLoaderWeaponAttachment.h | 17 + src/ObjLoading/Game/IW5/ObjLoaderIW5.cpp | 3 +- .../IW5/Weapon/JsonWeaponAttachmentLoader.cpp | 639 ++++++++++++++++++ .../IW5/Weapon/JsonWeaponAttachmentLoader.h | 17 + 5 files changed, 722 insertions(+), 1 deletion(-) create mode 100644 src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderWeaponAttachment.cpp create mode 100644 src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderWeaponAttachment.h create mode 100644 src/ObjLoading/Game/IW5/Weapon/JsonWeaponAttachmentLoader.cpp create mode 100644 src/ObjLoading/Game/IW5/Weapon/JsonWeaponAttachmentLoader.h diff --git a/src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderWeaponAttachment.cpp b/src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderWeaponAttachment.cpp new file mode 100644 index 00000000..8d328df4 --- /dev/null +++ b/src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderWeaponAttachment.cpp @@ -0,0 +1,47 @@ +#include "AssetLoaderWeaponAttachment.h" + +#include "Game/IW5/IW5.h" +#include "Game/IW5/Weapon/JsonWeaponAttachmentLoader.h" +#include "Pool/GlobalAssetPool.h" + +#include +#include +#include + +using namespace IW5; + +void* AssetLoaderWeaponAttachment::CreateEmptyAsset(const std::string& assetName, MemoryManager* memory) +{ + auto* attachment = memory->Create(); + memset(attachment, 0, sizeof(WeaponAttachment)); + attachment->szInternalName = memory->Dup(assetName.c_str()); + + return attachment; +} + +bool AssetLoaderWeaponAttachment::CanLoadFromRaw() const +{ + return true; +} + +bool AssetLoaderWeaponAttachment::LoadFromRaw( + const std::string& assetName, ISearchPath* searchPath, MemoryManager* memory, IAssetLoadingManager* manager, Zone* zone) const +{ + const auto file = searchPath->Open(std::format("attachment/{}.json", assetName)); + if (!file.IsOpen()) + return false; + + auto* attachment = static_cast(memory->Alloc(sizeof(WeaponAttachment))); + memset(attachment, 0, sizeof(Material)); + attachment->szInternalName = memory->Dup(assetName.c_str()); + + std::vector dependencies; + std::vector indirectAssetReferences; + if (LoadWeaponAttachmentAsJson(*file.m_stream, *attachment, memory, manager, dependencies, indirectAssetReferences)) + manager->AddAsset( + ASSET_TYPE_ATTACHMENT, assetName, attachment, std::move(dependencies), std::vector(), std::move(indirectAssetReferences)); + else + std::cerr << "Failed to load attachment \"" << assetName << "\"\n"; + + return true; +} diff --git a/src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderWeaponAttachment.h b/src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderWeaponAttachment.h new file mode 100644 index 00000000..56f4066b --- /dev/null +++ b/src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderWeaponAttachment.h @@ -0,0 +1,17 @@ +#pragma once +#include "AssetLoading/BasicAssetLoader.h" +#include "AssetLoading/IAssetLoadingManager.h" +#include "Game/IW5/IW5.h" +#include "SearchPath/ISearchPath.h" + +namespace IW5 +{ + class AssetLoaderWeaponAttachment final : public BasicAssetLoader + { + public: + _NODISCARD void* CreateEmptyAsset(const std::string& assetName, MemoryManager* memory) override; + _NODISCARD bool CanLoadFromRaw() const override; + bool + LoadFromRaw(const std::string& assetName, ISearchPath* searchPath, MemoryManager* memory, IAssetLoadingManager* manager, Zone* zone) const override; + }; +} // namespace IW5 diff --git a/src/ObjLoading/Game/IW5/ObjLoaderIW5.cpp b/src/ObjLoading/Game/IW5/ObjLoaderIW5.cpp index 39cff848..ed384789 100644 --- a/src/ObjLoading/Game/IW5/ObjLoaderIW5.cpp +++ b/src/ObjLoading/Game/IW5/ObjLoaderIW5.cpp @@ -7,6 +7,7 @@ #include "AssetLoaders/AssetLoaderRawFile.h" #include "AssetLoaders/AssetLoaderScriptFile.h" #include "AssetLoaders/AssetLoaderStringTable.h" +#include "AssetLoaders/AssetLoaderWeaponAttachment.h" #include "AssetLoading/AssetLoadingManager.h" #include "Game/IW5/GameAssetPoolIW5.h" #include "Game/IW5/GameIW5.h" @@ -55,7 +56,7 @@ ObjLoader::ObjLoader() REGISTER_ASSET_LOADER(AssetLoaderMenuList) REGISTER_ASSET_LOADER(AssetLoaderMenuDef) REGISTER_ASSET_LOADER(AssetLoaderLocalizeEntry) - REGISTER_ASSET_LOADER(BASIC_LOADER(ASSET_TYPE_ATTACHMENT, WeaponAttachment)) + REGISTER_ASSET_LOADER(AssetLoaderWeaponAttachment) REGISTER_ASSET_LOADER(BASIC_LOADER(ASSET_TYPE_WEAPON, WeaponCompleteDef)) REGISTER_ASSET_LOADER(BASIC_LOADER(ASSET_TYPE_FX, FxEffectDef)) REGISTER_ASSET_LOADER(BASIC_LOADER(ASSET_TYPE_IMPACT_FX, FxImpactTable)) diff --git a/src/ObjLoading/Game/IW5/Weapon/JsonWeaponAttachmentLoader.cpp b/src/ObjLoading/Game/IW5/Weapon/JsonWeaponAttachmentLoader.cpp new file mode 100644 index 00000000..c816ac1b --- /dev/null +++ b/src/ObjLoading/Game/IW5/Weapon/JsonWeaponAttachmentLoader.cpp @@ -0,0 +1,639 @@ +#include "JsonWeaponAttachmentLoader.h" + +#include "Game/IW5/CommonIW5.h" +#include "Game/IW5/Weapon/JsonWeaponAttachment.h" + +#include +#include +#include + +using namespace nlohmann; +using namespace IW5; + +namespace +{ + class JsonLoader + { + public: + JsonLoader(std::istream& stream, + MemoryManager& memory, + IAssetLoadingManager& manager, + std::vector& dependencies, + std::vector& indirectAssetReferences) + : m_stream(stream), + m_memory(memory), + m_manager(manager), + m_dependencies(dependencies), + m_indirect_asset_references(indirectAssetReferences) + + { + } + + bool Load(WeaponAttachment& attachment) const + { + const auto jRoot = json::parse(m_stream); + std::string type; + unsigned version; + + jRoot.at("_type").get_to(type); + jRoot.at("_version").get_to(version); + + if (type != "attachment" || version != 1u) + { + std::cerr << "Tried to load attachment \"" << attachment.szInternalName << "\" but did not find expected type attachment of version 1\n"; + return false; + } + + const auto jAttachment = jRoot.get(); + return CreateWeaponAttachmentFromJson(jAttachment, attachment); + } + + private: + static void PrintError(const WeaponAttachment& attachment, const std::string& message) + { + std::cerr << "Cannot load attachment \"" << attachment.szInternalName << "\": " << message << "\n"; + } + + bool CreateWeaponAttachmentFromJson(const JsonWeaponAttachment& jAttachment, WeaponAttachment& attachment) const + { +#define CONVERT_XMODEL_ARRAY(propertyName, count) \ + CreateXModelArrayFromJson(jAttachment.propertyName, attachment.propertyName, #propertyName, count, attachment); + +#define CONVERT_ATTRIBUTE(attributeClass, attributeName) \ + if (jAttachment.ammoGeneral) \ + { \ + using AttributeType = std::remove_pointer_t; \ + attachment.attributeName = static_cast(m_memory.Alloc(sizeof(AttributeType))); \ + if (!Create##attributeClass##FromJson(jAttachment.attributeName.value(), *attachment.attributeName, attachment)) \ + return false; \ + } \ + else \ + attachment.attributeName = nullptr; + + attachment.szDisplayName = m_memory.Dup(jAttachment.displayName.c_str()); + attachment.type = jAttachment.type; + attachment.weaponType = jAttachment.weaponType; + attachment.weapClass = jAttachment.weapClass; + + CONVERT_XMODEL_ARRAY(worldModels, ATTACHMENT_WORLD_MODEL_COUNT) + CONVERT_XMODEL_ARRAY(viewModels, ATTACHMENT_VIEW_MODEL_COUNT) + CONVERT_XMODEL_ARRAY(reticleViewModels, ATTACHMENT_RETICLE_VIEW_MODEL_COUNT) + + CONVERT_ATTRIBUTE(AttAmmoGeneral, ammoGeneral) + CONVERT_ATTRIBUTE(AttSight, sight) + CONVERT_ATTRIBUTE(AttReload, reload) + CONVERT_ATTRIBUTE(AttAddOns, addOns) + CONVERT_ATTRIBUTE(AttGeneral, general) + CONVERT_ATTRIBUTE(AttAimAssist, aimAssist) + CONVERT_ATTRIBUTE(AttAmmunition, ammunition) + CONVERT_ATTRIBUTE(AttDamage, damage) + CONVERT_ATTRIBUTE(AttLocationDamage, locationDamage) + CONVERT_ATTRIBUTE(AttIdleSettings, idleSettings) + CONVERT_ATTRIBUTE(AttADSSettings, adsSettings) + CONVERT_ATTRIBUTE(AttADSSettings, adsSettingsMain) + CONVERT_ATTRIBUTE(AttHipSpread, hipSpread) + CONVERT_ATTRIBUTE(AttGunKick, gunKick) + CONVERT_ATTRIBUTE(AttViewKick, viewKick) + CONVERT_ATTRIBUTE(AttADSOverlay, adsOverlay) + CONVERT_ATTRIBUTE(AttUI, ui) + CONVERT_ATTRIBUTE(AttRumbles, rumbles) + CONVERT_ATTRIBUTE(AttProjectile, projectile) + + attachment.ammunitionScale = jAttachment.ammunitionScale; + attachment.damageScale = jAttachment.damageScale; + attachment.damageScaleMin = jAttachment.damageScaleMin; + attachment.stateTimersScale = jAttachment.stateTimersScale; + attachment.fireTimersScale = jAttachment.fireTimersScale; + attachment.idleSettingsScale = jAttachment.idleSettingsScale; + attachment.adsSettingsScale = jAttachment.adsSettingsScale; + attachment.adsSettingsScaleMain = jAttachment.adsSettingsScaleMain; + attachment.hipSpreadScale = jAttachment.hipSpreadScale; + attachment.gunKickScale = jAttachment.gunKickScale; + attachment.viewKickScale = jAttachment.viewKickScale; + attachment.viewCenterScale = jAttachment.viewCenterScale; + attachment.loadIndex = jAttachment.loadIndex; + attachment.hideIronSightsWithThisAttachment = jAttachment.hideIronSightsWithThisAttachment; + attachment.shareAmmoWithAlt = jAttachment.shareAmmoWithAlt; + + return true; + } + + bool CreateTracerFromJson(const std::string& assetName, TracerDef*& tracerPtr, const WeaponAttachment& attachment) const + { + auto* tracer = static_cast*>(m_manager.LoadDependency(ASSET_TYPE_TRACER, assetName)); + if (!tracer) + { + PrintError(attachment, std::format("Could not find tracer {}", assetName)); + return false; + } + m_dependencies.push_back(tracer); + tracerPtr = tracer->Asset(); + + return true; + } + + bool CreateMaterialFromJson(const std::string& assetName, Material*& materialPtr, const WeaponAttachment& attachment) const + { + auto* material = static_cast*>(m_manager.LoadDependency(ASSET_TYPE_MATERIAL, assetName)); + if (!material) + { + PrintError(attachment, std::format("Could not find material {}", assetName)); + return false; + } + m_dependencies.push_back(material); + materialPtr = material->Asset(); + + return true; + } + + bool CreateFxFromJson(const std::string& assetName, FxEffectDef*& fxPtr, const WeaponAttachment& attachment) const + { + auto* fx = static_cast*>(m_manager.LoadDependency(ASSET_TYPE_FX, assetName)); + if (!fx) + { + PrintError(attachment, std::format("Could not find fx {}", assetName)); + return false; + } + m_dependencies.push_back(fx); + fxPtr = fx->Asset(); + + return true; + } + + bool CreateSoundFromJson(const std::string& assetName, SndAliasCustom& sndAliasCustom, const WeaponAttachment& attachment) const + { + auto sound = m_manager.LoadIndirectAssetReference(ASSET_TYPE_SOUND, assetName); + m_indirect_asset_references.push_back(std::move(sound)); + sndAliasCustom.name = static_cast(m_memory.Alloc(sizeof(snd_alias_list_name))); + sndAliasCustom.name->soundName = m_memory.Dup(assetName.c_str()); + + return true; + } + + bool CreateXModelFromJson(const std::string& assetName, XModel*& xmodelPtr, const WeaponAttachment& attachment) const + { + auto* xmodel = static_cast*>(m_manager.LoadDependency(ASSET_TYPE_XMODEL, assetName)); + if (!xmodel) + { + PrintError(attachment, std::format("Could not find xmodel {}", assetName)); + return false; + } + m_dependencies.push_back(xmodel); + xmodelPtr = xmodel->Asset(); + + return true; + } + + bool CreateXModelArrayFromJson(const std::vector& jXmodelArray, + XModel**& xmodelArray, + const char* propertyName, + size_t propertyCount, + const WeaponAttachment& attachment) const + { + if (!jXmodelArray.empty()) + { + const auto arraySize = jXmodelArray.size(); + if (arraySize > propertyCount) + { + PrintError(attachment, std::format("{} size cannot exceed {}", propertyName, propertyCount)); + return false; + } + xmodelArray = static_cast(m_memory.Alloc(sizeof(void*) * propertyCount)); + memset(xmodelArray, 0, sizeof(void*) * propertyCount); + + for (auto i = 0u; i < arraySize; i++) + { + if (!CreateXModelFromJson(jXmodelArray[i], xmodelArray[i], attachment)) + return false; + } + } + else + { + xmodelArray = nullptr; + } + + return true; + } + + bool CreateAttAmmoGeneralFromJson(const JsonAttAmmoGeneral& jAmmoGeneral, AttAmmoGeneral& ammoGeneral, const WeaponAttachment& attachment) const + { + ammoGeneral.penetrateType = jAmmoGeneral.penetrateType; + ammoGeneral.penetrateMultiplier = jAmmoGeneral.penetrateMultiplier; + ammoGeneral.impactType = jAmmoGeneral.impactType; + ammoGeneral.fireType = jAmmoGeneral.fireType; + + if (jAmmoGeneral.tracerType) + { + if (!CreateTracerFromJson(jAmmoGeneral.tracerType.value(), ammoGeneral.tracerType, attachment)) + return false; + } + else + ammoGeneral.tracerType = nullptr; + + ammoGeneral.rifleBullet = jAmmoGeneral.rifleBullet; + ammoGeneral.armorPiercing = jAmmoGeneral.armorPiercing; + + return true; + } + + static bool CreateAttSightFromJson(const JsonAttSight& jSight, AttSight& sight, const WeaponAttachment& attachment) + { + sight.aimDownSight = jSight.aimDownSight; + sight.adsFire = jSight.adsFire; + sight.rechamberWhileAds = jSight.rechamberWhileAds; + sight.noAdsWhenMagEmpty = jSight.noAdsWhenMagEmpty; + sight.canHoldBreath = jSight.canHoldBreath; + sight.canVariableZoom = jSight.canVariableZoom; + sight.hideRailWithThisScope = jSight.hideRailWithThisScope; + + return true; + } + + static bool CreateAttReloadFromJson(const JsonAttReload& jReload, AttReload& reload, const WeaponAttachment& attachment) + { + reload.noPartialReload = jReload.noPartialReload; + reload.segmentedReload = jReload.segmentedReload; + + return true; + } + + static bool CreateAttAddOnsFromJson(const JsonAttAddOns& jAddOns, AttAddOns& addOns, const WeaponAttachment& attachment) + { + addOns.motionTracker = jAddOns.motionTracker; + addOns.silenced = jAddOns.silenced; + + return true; + } + + bool CreateAttGeneralFromJson(const JsonAttGeneral& jGeneral, AttGeneral& general, const WeaponAttachment& attachment) const + { + general.boltAction = jGeneral.boltAction; + general.inheritsPerks = jGeneral.inheritsPerks; + general.enemyCrosshairRange = jGeneral.enemyCrosshairRange; + + if (jGeneral.reticleCenter) + { + if (!CreateMaterialFromJson(jGeneral.reticleCenter.value(), general.reticleCenter, attachment)) + return false; + } + else + general.reticleCenter = nullptr; + + if (jGeneral.reticleSide) + { + if (!CreateMaterialFromJson(jGeneral.reticleSide.value(), general.reticleSide, attachment)) + return false; + } + else + general.reticleSide = nullptr; + + general.reticleCenterSize = jGeneral.reticleCenterSize; + general.reticleSideSize = jGeneral.reticleSideSize; + general.moveSpeedScale = jGeneral.moveSpeedScale; + general.adsMoveSpeedScale = jGeneral.adsMoveSpeedScale; + + return true; + } + + static bool CreateAttAimAssistFromJson(const JsonAttAimAssist& jAimAssist, AttAimAssist& aimAssist, const WeaponAttachment& attachment) + { + aimAssist.autoAimRange = jAimAssist.autoAimRange; + aimAssist.aimAssistRange = jAimAssist.aimAssistRange; + aimAssist.aimAssistRangeAds = jAimAssist.aimAssistRangeAds; + + return true; + } + + static bool CreateAttAmmunitionFromJson(const JsonAttAmmunition& jAmmunition, AttAmmunition& ammunition, const WeaponAttachment& attachment) + { + ammunition.maxAmmo = jAmmunition.maxAmmo; + ammunition.startAmmo = jAmmunition.startAmmo; + ammunition.clipSize = jAmmunition.clipSize; + ammunition.shotCount = jAmmunition.shotCount; + ammunition.reloadAmmoAdd = jAmmunition.reloadAmmoAdd; + ammunition.reloadStartAdd = jAmmunition.reloadStartAdd; + + return true; + } + + static bool CreateAttDamageFromJson(const JsonAttDamage& jDamage, AttDamage& damage, const WeaponAttachment& attachment) + { + damage.damage = jDamage.damage; + damage.minDamage = jDamage.minDamage; + damage.meleeDamage = jDamage.meleeDamage; + damage.maxDamageRange = jDamage.maxDamageRange; + damage.minDamageRange = jDamage.minDamageRange; + damage.playerDamage = jDamage.playerDamage; + damage.minPlayerDamage = jDamage.minPlayerDamage; + + return true; + } + + static bool + CreateAttLocationDamageFromJson(const JsonAttLocationDamage& jLocationDamage, AttLocationDamage& locationDamage, const WeaponAttachment& attachment) + { + locationDamage.locNone = jLocationDamage.locNone; + locationDamage.locHelmet = jLocationDamage.locHelmet; + locationDamage.locHead = jLocationDamage.locHead; + locationDamage.locNeck = jLocationDamage.locNeck; + locationDamage.locTorsoUpper = jLocationDamage.locTorsoUpper; + locationDamage.locTorsoLower = jLocationDamage.locTorsoLower; + locationDamage.locRightArmUpper = jLocationDamage.locRightArmUpper; + locationDamage.locRightArmLower = jLocationDamage.locRightArmLower; + locationDamage.locRightHand = jLocationDamage.locRightHand; + locationDamage.locLeftArmUpper = jLocationDamage.locLeftArmUpper; + locationDamage.locLeftArmLower = jLocationDamage.locLeftArmLower; + locationDamage.locLeftHand = jLocationDamage.locLeftHand; + locationDamage.locRightLegUpper = jLocationDamage.locRightLegUpper; + locationDamage.locRightLegLower = jLocationDamage.locRightLegLower; + locationDamage.locRightFoot = jLocationDamage.locRightFoot; + locationDamage.locLeftLegUpper = jLocationDamage.locLeftLegUpper; + locationDamage.locLeftLegLower = jLocationDamage.locLeftLegLower; + locationDamage.locLeftFoot = jLocationDamage.locLeftFoot; + locationDamage.locGun = jLocationDamage.locGun; + + return true; + } + + static bool CreateAttIdleSettingsFromJson(const JsonAttIdleSettings& jIdleSettings, AttIdleSettings& idleSettings, const WeaponAttachment& attachment) + { + idleSettings.hipIdleAmount = jIdleSettings.hipIdleAmount; + idleSettings.hipIdleSpeed = jIdleSettings.hipIdleSpeed; + idleSettings.idleCrouchFactor = jIdleSettings.idleCrouchFactor; + idleSettings.idleProneFactor = jIdleSettings.idleProneFactor; + idleSettings.adsIdleLerpStartTime = jIdleSettings.adsIdleLerpStartTime; + idleSettings.adsIdleLerpTime = jIdleSettings.adsIdleLerpTime; + + return true; + } + + static bool CreateAttADSSettingsFromJson(const JsonAttADSSettings& jAdsSettings, AttADSSettings& adsSettings, const WeaponAttachment& attachment) + { + adsSettings.adsSpread = jAdsSettings.adsSpread; + adsSettings.adsAimPitch = jAdsSettings.adsAimPitch; + adsSettings.adsTransInTime = jAdsSettings.adsTransInTime; + adsSettings.adsTransOutTime = jAdsSettings.adsTransOutTime; + adsSettings.adsReloadTransTime = jAdsSettings.adsReloadTransTime; + adsSettings.adsCrosshairInFrac = jAdsSettings.adsCrosshairInFrac; + adsSettings.adsCrosshairOutFrac = jAdsSettings.adsCrosshairOutFrac; + adsSettings.adsZoomFov = jAdsSettings.adsZoomFov; + adsSettings.adsZoomInFrac = jAdsSettings.adsZoomInFrac; + adsSettings.adsZoomOutFrac = jAdsSettings.adsZoomOutFrac; + adsSettings.adsBobFactor = jAdsSettings.adsBobFactor; + adsSettings.adsViewBobMult = jAdsSettings.adsViewBobMult; + adsSettings.adsViewErrorMin = jAdsSettings.adsViewErrorMin; + adsSettings.adsViewErrorMax = jAdsSettings.adsViewErrorMax; + + return true; + } + + static bool CreateAttHipSpreadFromJson(const JsonAttHipSpread& jHipSpread, AttHipSpread& hipSpread, const WeaponAttachment& attachment) + { + hipSpread.hipSpreadStandMin = jHipSpread.hipSpreadStandMin; + hipSpread.hipSpreadDuckedMin = jHipSpread.hipSpreadDuckedMin; + hipSpread.hipSpreadProneMin = jHipSpread.hipSpreadProneMin; + hipSpread.hipSpreadMax = jHipSpread.hipSpreadMax; + hipSpread.hipSpreadDuckedMax = jHipSpread.hipSpreadDuckedMax; + hipSpread.hipSpreadProneMax = jHipSpread.hipSpreadProneMax; + hipSpread.hipSpreadFireAdd = jHipSpread.hipSpreadFireAdd; + hipSpread.hipSpreadTurnAdd = jHipSpread.hipSpreadTurnAdd; + hipSpread.hipSpreadMoveAdd = jHipSpread.hipSpreadMoveAdd; + hipSpread.hipSpreadDecayRate = jHipSpread.hipSpreadDecayRate; + hipSpread.hipSpreadDuckedDecay = jHipSpread.hipSpreadDuckedDecay; + hipSpread.hipSpreadProneDecay = jHipSpread.hipSpreadProneDecay; + + return true; + } + + static bool CreateAttGunKickFromJson(const JsonAttGunKick& jGunKick, AttGunKick& gunKick, const WeaponAttachment& attachment) + { + gunKick.hipGunKickReducedKickBullets = jGunKick.hipGunKickReducedKickBullets; + gunKick.hipGunKickReducedKickPercent = jGunKick.hipGunKickReducedKickPercent; + gunKick.hipGunKickPitchMin = jGunKick.hipGunKickPitchMin; + gunKick.hipGunKickPitchMax = jGunKick.hipGunKickPitchMax; + gunKick.hipGunKickYawMin = jGunKick.hipGunKickYawMin; + gunKick.hipGunKickYawMax = jGunKick.hipGunKickYawMax; + gunKick.hipGunKickAccel = jGunKick.hipGunKickAccel; + gunKick.hipGunKickSpeedMax = jGunKick.hipGunKickSpeedMax; + gunKick.hipGunKickSpeedDecay = jGunKick.hipGunKickSpeedDecay; + gunKick.hipGunKickStaticDecay = jGunKick.hipGunKickStaticDecay; + gunKick.adsGunKickReducedKickBullets = jGunKick.adsGunKickReducedKickBullets; + gunKick.adsGunKickReducedKickPercent = jGunKick.adsGunKickReducedKickPercent; + gunKick.adsGunKickPitchMin = jGunKick.adsGunKickPitchMin; + gunKick.adsGunKickPitchMax = jGunKick.adsGunKickPitchMax; + gunKick.adsGunKickYawMin = jGunKick.adsGunKickYawMin; + gunKick.adsGunKickYawMax = jGunKick.adsGunKickYawMax; + gunKick.adsGunKickAccel = jGunKick.adsGunKickAccel; + gunKick.adsGunKickSpeedMax = jGunKick.adsGunKickSpeedMax; + gunKick.adsGunKickSpeedDecay = jGunKick.adsGunKickSpeedDecay; + gunKick.adsGunKickStaticDecay = jGunKick.adsGunKickStaticDecay; + + return true; + } + + static bool CreateAttViewKickFromJson(const JsonAttViewKick& jViewKick, AttViewKick& viewKick, const WeaponAttachment& attachment) + { + viewKick.hipViewKickPitchMin = jViewKick.hipViewKickPitchMin; + viewKick.hipViewKickPitchMax = jViewKick.hipViewKickPitchMax; + viewKick.hipViewKickYawMin = jViewKick.hipViewKickYawMin; + viewKick.hipViewKickYawMax = jViewKick.hipViewKickYawMax; + viewKick.hipViewKickCenterSpeed = jViewKick.hipViewKickCenterSpeed; + viewKick.adsViewKickPitchMin = jViewKick.adsViewKickPitchMin; + viewKick.adsViewKickPitchMax = jViewKick.adsViewKickPitchMax; + viewKick.adsViewKickYawMin = jViewKick.adsViewKickYawMin; + viewKick.adsViewKickYawMax = jViewKick.adsViewKickYawMax; + viewKick.adsViewKickCenterSpeed = jViewKick.adsViewKickCenterSpeed; + + return true; + } + + bool CreateAttADSOverlayFromJson(const JsonAttADSOverlay& jAdsOverlay, AttADSOverlay& adsOverlay, const WeaponAttachment& attachment) const + { + if (jAdsOverlay.shader) + { + if (!CreateMaterialFromJson(jAdsOverlay.shader.value(), adsOverlay.overlay.shader, attachment)) + return false; + } + else + adsOverlay.overlay.shader = nullptr; + + if (jAdsOverlay.shaderLowRes) + { + if (!CreateMaterialFromJson(jAdsOverlay.shaderLowRes.value(), adsOverlay.overlay.shaderLowRes, attachment)) + return false; + } + else + adsOverlay.overlay.shaderLowRes = nullptr; + + if (jAdsOverlay.shaderEMP) + { + if (!CreateMaterialFromJson(jAdsOverlay.shaderEMP.value(), adsOverlay.overlay.shaderEMP, attachment)) + return false; + } + else + adsOverlay.overlay.shaderEMP = nullptr; + + if (jAdsOverlay.shaderEMPLowRes) + { + if (!CreateMaterialFromJson(jAdsOverlay.shaderEMPLowRes.value(), adsOverlay.overlay.shaderEMPLowRes, attachment)) + return false; + } + else + adsOverlay.overlay.shaderEMPLowRes = nullptr; + + adsOverlay.overlay.reticle = jAdsOverlay.reticle; + adsOverlay.overlay.width = jAdsOverlay.width; + adsOverlay.overlay.height = jAdsOverlay.height; + adsOverlay.overlay.widthSplitscreen = jAdsOverlay.widthSplitscreen; + adsOverlay.overlay.heightSplitscreen = jAdsOverlay.heightSplitscreen; + adsOverlay.thermalScope = jAdsOverlay.thermalScope; + + return true; + } + + bool CreateAttUIFromJson(const JsonAttUI& jUi, AttUI& ui, const WeaponAttachment& attachment) const + { + if (jUi.dpadIcon) + { + if (!CreateMaterialFromJson(jUi.dpadIcon.value(), ui.dpadIcon, attachment)) + return false; + } + else + ui.dpadIcon = nullptr; + + if (jUi.ammoCounterIcon) + { + if (!CreateMaterialFromJson(jUi.ammoCounterIcon.value(), ui.ammoCounterIcon, attachment)) + return false; + } + else + ui.ammoCounterIcon = nullptr; + + ui.dpadIconRatio = jUi.dpadIconRatio; + ui.ammoCounterIconRatio = jUi.ammoCounterIconRatio; + ui.ammoCounterClip = jUi.ammoCounterClip; + + return true; + } + + bool CreateAttRumblesFromJson(const JsonAttRumbles& jRumbles, AttRumbles& rumbles, const WeaponAttachment& attachment) const + { + if (jRumbles.fireRumble) + rumbles.fireRumble = m_memory.Dup(jRumbles.fireRumble.value().c_str()); + if (jRumbles.meleeImpactRumble) + rumbles.meleeImpactRumble = m_memory.Dup(jRumbles.meleeImpactRumble.value().c_str()); + + return true; + } + + bool CreateAttProjectileFromJson(const JsonAttProjectile& jProjectile, AttProjectile& projectile, const WeaponAttachment& attachment) const + { + projectile.explosionRadius = jProjectile.explosionRadius; + projectile.explosionInnerDamage = jProjectile.explosionInnerDamage; + projectile.explosionOuterDamage = jProjectile.explosionOuterDamage; + projectile.damageConeAngle = jProjectile.damageConeAngle; + projectile.projectileSpeed = jProjectile.projectileSpeed; + projectile.projectileSpeedUp = jProjectile.projectileSpeedUp; + projectile.projectileActivateDist = jProjectile.projectileActivateDist; + projectile.projectileLifetime = jProjectile.projectileLifetime; + + if (jProjectile.projectileModel) + { + if (!CreateXModelFromJson(jProjectile.projectileModel.value(), projectile.projectileModel, attachment)) + return false; + } + else + projectile.projectileModel = nullptr; + + projectile.projExplosionType = jProjectile.projExplosionType; + + if (jProjectile.projExplosionEffect) + { + if (!CreateFxFromJson(jProjectile.projExplosionEffect.value(), projectile.projExplosionEffect, attachment)) + return false; + } + else + projectile.projExplosionEffect = nullptr; + + projectile.projExplosionEffectForceNormalUp = jProjectile.projExplosionEffectForceNormalUp; + + if (jProjectile.projExplosionSound) + { + if (!CreateSoundFromJson(jProjectile.projExplosionSound.value(), projectile.projExplosionSound, attachment)) + return false; + } + else + projectile.projExplosionSound.name = nullptr; + + if (jProjectile.projDudEffect) + { + if (!CreateFxFromJson(jProjectile.projDudEffect.value(), projectile.projDudEffect, attachment)) + return false; + } + else + projectile.projDudEffect = nullptr; + + if (jProjectile.projDudSound) + { + if (!CreateSoundFromJson(jProjectile.projDudSound.value(), projectile.projDudSound, attachment)) + return false; + } + else + projectile.projDudSound.name = nullptr; + + projectile.projImpactExplode = jProjectile.projImpactExplode; + projectile.destabilizationRateTime = jProjectile.destabilizationRateTime; + projectile.destabilizationCurvatureMax = jProjectile.destabilizationCurvatureMax; + projectile.destabilizeDistance = jProjectile.destabilizeDistance; + + if (jProjectile.projTrailEffect) + { + if (!CreateFxFromJson(jProjectile.projTrailEffect.value(), projectile.projTrailEffect, attachment)) + return false; + } + else + projectile.projTrailEffect = nullptr; + + projectile.projIgnitionDelay = jProjectile.projIgnitionDelay; + + if (jProjectile.projIgnitionEffect) + { + if (!CreateFxFromJson(jProjectile.projIgnitionEffect.value(), projectile.projIgnitionEffect, attachment)) + return false; + } + else + projectile.projIgnitionEffect = nullptr; + + if (jProjectile.projIgnitionSound) + { + if (!CreateSoundFromJson(jProjectile.projIgnitionSound.value(), projectile.projIgnitionSound, attachment)) + return false; + } + else + projectile.projIgnitionSound.name = nullptr; + + return true; + } + + std::istream& m_stream; + MemoryManager& m_memory; + IAssetLoadingManager& m_manager; + std::vector& m_dependencies; + std::vector& m_indirect_asset_references; + }; +} // namespace + +namespace IW5 +{ + bool LoadWeaponAttachmentAsJson(std::istream& stream, + WeaponAttachment& attachment, + MemoryManager* memory, + IAssetLoadingManager* manager, + std::vector& dependencies, + std::vector& indirectAssetReferences) + { + const JsonLoader loader(stream, *memory, *manager, dependencies, indirectAssetReferences); + + return loader.Load(attachment); + } +} // namespace IW5 diff --git a/src/ObjLoading/Game/IW5/Weapon/JsonWeaponAttachmentLoader.h b/src/ObjLoading/Game/IW5/Weapon/JsonWeaponAttachmentLoader.h new file mode 100644 index 00000000..8bf8df8b --- /dev/null +++ b/src/ObjLoading/Game/IW5/Weapon/JsonWeaponAttachmentLoader.h @@ -0,0 +1,17 @@ +#pragma once + +#include "AssetLoading/IAssetLoadingManager.h" +#include "Game/IW5/IW5.h" +#include "Utils/MemoryManager.h" + +#include + +namespace IW5 +{ + bool LoadWeaponAttachmentAsJson(std::istream& stream, + WeaponAttachment& attachment, + MemoryManager* memory, + IAssetLoadingManager* manager, + std::vector& dependencies, + std::vector& indirectAssetReferences); +} // namespace IW5 From 49239f20077b8c15e9a78f8d66495983069044ef Mon Sep 17 00:00:00 2001 From: Jan Date: Sun, 21 Apr 2024 18:42:32 +0200 Subject: [PATCH 03/10] chore: rename fx related override structs in iw5 --- src/Common/Game/IW5/IW5_Assets.h | 14 +++++++------- .../Game/IW5/AssetDumpers/AssetDumperWeapon.cpp | 10 +++++----- .../Game/IW5/XAssets/WeaponCompleteDef.txt | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Common/Game/IW5/IW5_Assets.h b/src/Common/Game/IW5/IW5_Assets.h index 08cc2db1..33a21ada 100644 --- a/src/Common/Game/IW5/IW5_Assets.h +++ b/src/Common/Game/IW5/IW5_Assets.h @@ -3941,7 +3941,7 @@ namespace IW5 SoundOverrideTypes soundType; }; - enum FXOverrideTypes : unsigned int + enum FxOverrideTypes : unsigned int { FXTYPE_NONE = 0x0, FXTYPE_VIEW_FLASH = 0x1, @@ -3952,13 +3952,13 @@ namespace IW5 FXTYPE_COUNT }; - struct FXOverrideEntry + struct FxOverrideEntry { WeaponAttachmentCombination attachment1; WeaponAttachmentCombination attachment2; - FxEffectDef* overrideFX; - FxEffectDef* altmodeFX; - FXOverrideTypes fxType; + FxEffectDef* overrideFx; + FxEffectDef* altmodeFx; + FxOverrideTypes fxType; }; struct ReloadStateTimerEntry @@ -3991,8 +3991,8 @@ namespace IW5 AnimOverrideEntry* animOverrides; unsigned int numSoundOverrides; SoundOverrideEntry* soundOverrides; - unsigned int numFXOverrides; - FXOverrideEntry* fxOverrides; + unsigned int numFxOverrides; + FxOverrideEntry* fxOverrides; unsigned int numReloadStateTimerOverrides; ReloadStateTimerEntry* reloadOverrides; unsigned int numNotetrackOverrides; diff --git a/src/ObjWriting/Game/IW5/AssetDumpers/AssetDumperWeapon.cpp b/src/ObjWriting/Game/IW5/AssetDumpers/AssetDumperWeapon.cpp index ad16bdd4..d6b84ce4 100644 --- a/src/ObjWriting/Game/IW5/AssetDumpers/AssetDumperWeapon.cpp +++ b/src/ObjWriting/Game/IW5/AssetDumpers/AssetDumperWeapon.cpp @@ -427,7 +427,7 @@ namespace IW5 std::stringstream ss; bool first = true; - for (auto i = 0u; i < m_weapon->weapCompleteDef.numFXOverrides; i++) + for (auto i = 0u; i < m_weapon->weapCompleteDef.numFxOverrides; i++) { const auto& fxOverride = m_weapon->weapCompleteDef.fxOverrides[i]; @@ -455,15 +455,15 @@ namespace IW5 if (fxOverride.fxType < FXTYPE_COUNT) ss << fxOverrideTypeNames[fxOverride.fxType] << ' '; - if (fxOverride.overrideFX && fxOverride.overrideFX->name && fxOverride.overrideFX->name[0]) - ss << fxOverride.overrideFX->name; + if (fxOverride.overrideFx && fxOverride.overrideFx->name && fxOverride.overrideFx->name[0]) + ss << fxOverride.overrideFx->name; else ss << "none"; ss << ' '; - if (fxOverride.altmodeFX && fxOverride.altmodeFX->name && fxOverride.altmodeFX->name[0]) - ss << fxOverride.altmodeFX->name; + if (fxOverride.altmodeFx && fxOverride.altmodeFx->name && fxOverride.altmodeFx->name[0]) + ss << fxOverride.altmodeFx->name; else ss << "none"; } diff --git a/src/ZoneCode/Game/IW5/XAssets/WeaponCompleteDef.txt b/src/ZoneCode/Game/IW5/XAssets/WeaponCompleteDef.txt index fef404d0..4e69504f 100644 --- a/src/ZoneCode/Game/IW5/XAssets/WeaponCompleteDef.txt +++ b/src/ZoneCode/Game/IW5/XAssets/WeaponCompleteDef.txt @@ -25,7 +25,7 @@ set count animOverrides numAnimOverrides; set reusable soundOverrides; set count soundOverrides numSoundOverrides; set reusable fxOverrides; -set count fxOverrides numFXOverrides; +set count fxOverrides numFxOverrides; set reusable reloadOverrides; set count reloadOverrides numReloadStateTimerOverrides; set reusable notetrackOverrides; From 199be3183580234adb35ac106fb5537e4af18457 Mon Sep 17 00:00:00 2001 From: Jan Date: Sun, 21 Apr 2024 18:43:44 +0200 Subject: [PATCH 04/10] fix: bounce sound for iw4 weapons --- .../Game/IW4/AssetLoaders/AssetLoaderWeapon.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/ObjLoading/Game/IW4/AssetLoaders/AssetLoaderWeapon.cpp b/src/ObjLoading/Game/IW4/AssetLoaders/AssetLoaderWeapon.cpp index e6fa3547..268f9b58 100644 --- a/src/ObjLoading/Game/IW4/AssetLoaders/AssetLoaderWeapon.cpp +++ b/src/ObjLoading/Game/IW4/AssetLoaders/AssetLoaderWeapon.cpp @@ -62,7 +62,7 @@ namespace _NODISCARD bool ConvertBounceSounds(const cspField_t& field, const std::string& value) const { - auto*** bounceSound = reinterpret_cast(reinterpret_cast(m_structure) + field.iOffset); + auto** bounceSound = reinterpret_cast(reinterpret_cast(m_structure) + field.iOffset); if (value.empty()) { *bounceSound = nullptr; @@ -70,12 +70,15 @@ namespace } assert(std::extent_v == SURF_TYPE_NUM); - *bounceSound = static_cast(m_memory->Alloc(sizeof(const char*) * SURF_TYPE_NUM)); + *bounceSound = static_cast(m_memory->Alloc(sizeof(SndAliasCustom) * SURF_TYPE_NUM)); for (auto i = 0u; i < SURF_TYPE_NUM; i++) { const auto currentBounceSound = value + bounceSoundSuffixes[i]; - (*bounceSound)[i] = m_memory->Dup(currentBounceSound.c_str()); + + (*bounceSound)[i].name = static_cast(m_memory->Alloc(sizeof(snd_alias_list_name))); + (*bounceSound)[i].name->soundName = m_memory->Dup(currentBounceSound.c_str()); } + return true; } From 191965a970532eac1ad44f782c945e812f876545 Mon Sep 17 00:00:00 2001 From: Jan Date: Sun, 21 Apr 2024 18:44:28 +0200 Subject: [PATCH 05/10] chore: do not only load infostring arrays as pairs but n entries --- .../IW4/AssetLoaders/AssetLoaderWeapon.cpp | 12 +- .../InfoStringToStructConverter.cpp | 227 ++++++++++++++++++ .../T6/AssetLoaders/AssetLoaderWeapon.cpp | 12 +- .../InfoStringToStructConverterBase.cpp | 62 ----- .../InfoStringToStructConverterBase.h | 79 +++++- 5 files changed, 317 insertions(+), 75 deletions(-) diff --git a/src/ObjLoading/Game/IW4/AssetLoaders/AssetLoaderWeapon.cpp b/src/ObjLoading/Game/IW4/AssetLoaders/AssetLoaderWeapon.cpp index 268f9b58..ccc8df95 100644 --- a/src/ObjLoading/Game/IW4/AssetLoaders/AssetLoaderWeapon.cpp +++ b/src/ObjLoading/Game/IW4/AssetLoaders/AssetLoaderWeapon.cpp @@ -84,8 +84,8 @@ namespace _NODISCARD bool ConvertNotetrackMap(const cspField_t& field, const std::string& value, const char* mapName, const size_t keyAndValueCount) { - std::vector> pairs; - if (!ParseAsPairs(value, pairs)) + std::vector> pairs; + if (!ParseAsArray(value, pairs)) { std::cerr << "Failed to parse notetrack" << mapName << "map as pairs\n"; return false; @@ -110,10 +110,10 @@ namespace for (; currentEntryNum < pairs.size(); currentEntryNum++) { const auto& currentValue = pairs[currentEntryNum]; - const auto keyScriptString = !currentValue.first.empty() ? m_zone_script_strings.AddOrGetScriptString(currentValue.first) - : m_zone_script_strings.AddOrGetScriptString(nullptr); - const auto valueScriptString = !currentValue.second.empty() ? m_zone_script_strings.AddOrGetScriptString(currentValue.second) - : m_zone_script_strings.AddOrGetScriptString(nullptr); + const auto keyScriptString = !currentValue[0].empty() ? m_zone_script_strings.AddOrGetScriptString(currentValue[0]) + : m_zone_script_strings.AddOrGetScriptString(nullptr); + const auto valueScriptString = !currentValue[1].empty() ? m_zone_script_strings.AddOrGetScriptString(currentValue[1]) + : m_zone_script_strings.AddOrGetScriptString(nullptr); keys[currentEntryNum] = keyScriptString; m_used_script_string_list.emplace(keyScriptString); diff --git a/src/ObjLoading/Game/IW5/InfoString/InfoStringToStructConverter.cpp b/src/ObjLoading/Game/IW5/InfoString/InfoStringToStructConverter.cpp index b68d2bc9..e6a057f4 100644 --- a/src/ObjLoading/Game/IW5/InfoString/InfoStringToStructConverter.cpp +++ b/src/ObjLoading/Game/IW5/InfoString/InfoStringToStructConverter.cpp @@ -1,3 +1,230 @@ #include "InfoStringToStructConverter.h" +#include +#include + using namespace IW5; + +InfoStringToStructConverter::InfoStringToStructConverter(const InfoString& infoString, + void* structure, + ZoneScriptStrings& zoneScriptStrings, + MemoryManager* memory, + IAssetLoadingManager* manager, + const cspField_t* fields, + const size_t fieldCount) + : InfoStringToStructConverterBase(infoString, structure, zoneScriptStrings, memory), + m_loading_manager(manager), + m_fields(fields), + m_field_count(fieldCount) +{ +} + +bool InfoStringToStructConverter::ConvertBaseField(const cspField_t& field, const std::string& value) +{ + switch (static_cast(field.iFieldType)) + { + case CSPFT_STRING: + return ConvertString(value, field.iOffset); + + case CSPFT_STRING_MAX_STRING_CHARS: + return ConvertStringBuffer(value, field.iOffset, 1024); + + case CSPFT_STRING_MAX_QPATH: + return ConvertStringBuffer(value, field.iOffset, 64); + + case CSPFT_STRING_MAX_OSPATH: + return ConvertStringBuffer(value, field.iOffset, 256); + + case CSPFT_INT: + return ConvertInt(value, field.iOffset); + + case CSPFT_BOOL: + return ConvertBool(value, field.iOffset); + + case CSPFT_QBOOLEAN: + return ConvertQBoolean(value, field.iOffset); + + case CSPFT_FLOAT: + return ConvertFloat(value, field.iOffset); + + case CSPFT_MILLISECONDS: + return ConvertMilliseconds(value, field.iOffset); + + case CSPFT_FX: + { + if (value.empty()) + { + *reinterpret_cast(reinterpret_cast(m_structure) + field.iOffset) = nullptr; + return true; + } + + auto* fx = m_loading_manager->LoadDependency(ASSET_TYPE_FX, value); + + if (fx == nullptr) + { + std::cout << "Failed to load fx asset \"" << value << "\"\n"; + return false; + } + + m_dependencies.emplace(fx); + *reinterpret_cast(reinterpret_cast(m_structure) + field.iOffset) = fx->m_ptr; + + return true; + } + + case CSPFT_XMODEL: + { + if (value.empty()) + { + *reinterpret_cast(reinterpret_cast(m_structure) + field.iOffset) = nullptr; + return true; + } + + auto* xmodel = m_loading_manager->LoadDependency(ASSET_TYPE_XMODEL, value); + + if (xmodel == nullptr) + { + std::cout << "Failed to load xmodel asset \"" << value << "\"\n"; + return false; + } + + m_dependencies.emplace(xmodel); + *reinterpret_cast(reinterpret_cast(m_structure) + field.iOffset) = xmodel->m_ptr; + + return true; + } + + case CSPFT_MATERIAL: + { + if (value.empty()) + { + *reinterpret_cast(reinterpret_cast(m_structure) + field.iOffset) = nullptr; + return true; + } + + auto* material = m_loading_manager->LoadDependency(ASSET_TYPE_MATERIAL, value); + + if (material == nullptr) + { + std::cout << "Failed to load material asset \"" << value << "\"\n"; + return false; + } + + m_dependencies.emplace(material); + *reinterpret_cast(reinterpret_cast(m_structure) + field.iOffset) = material->m_ptr; + + return true; + } + + case CSPFT_TRACER: + { + if (value.empty()) + { + *reinterpret_cast(reinterpret_cast(m_structure) + field.iOffset) = nullptr; + return true; + } + + auto* tracer = m_loading_manager->LoadDependency(ASSET_TYPE_TRACER, value); + + if (tracer == nullptr) + { + std::cout << "Failed to load tracer asset \"" << value << "\"\n"; + return false; + } + + m_dependencies.emplace(tracer); + *reinterpret_cast(reinterpret_cast(m_structure) + field.iOffset) = tracer->m_ptr; + + return true; + } + + case CSPFT_MPH_TO_INCHES_PER_SEC: + { + char* endPtr; + *reinterpret_cast(reinterpret_cast(m_structure) + field.iOffset) = strtof(value.c_str(), &endPtr) * 17.6f; + + if (endPtr != &value[value.size()]) + { + std::cout << "Failed to parse value \"" << value << "\" as mph\n"; + return false; + } + + return true; + } + + case CSPFT_PHYS_COLLMAP: + { + if (value.empty()) + { + *reinterpret_cast(reinterpret_cast(m_structure) + field.iOffset) = nullptr; + return true; + } + + auto* collmap = m_loading_manager->LoadDependency(ASSET_TYPE_PHYSCOLLMAP, value); + + if (collmap == nullptr) + { + std::cout << "Failed to load collmap asset \"" << value << "\"\n"; + return false; + } + + m_dependencies.emplace(collmap); + *reinterpret_cast(reinterpret_cast(m_structure) + field.iOffset) = collmap->m_ptr; + + return true; + } + + case CSPFT_SOUND: + { + if (value.empty()) + { + reinterpret_cast(reinterpret_cast(m_structure) + field.iOffset)->name = nullptr; + return true; + } + + auto* name = static_cast(m_memory->Alloc(sizeof(snd_alias_list_name))); + name->soundName = m_memory->Dup(value.c_str()); + + reinterpret_cast(reinterpret_cast(m_structure) + field.iOffset)->name = name; + + m_indirect_asset_references.emplace(ASSET_TYPE_SOUND, value); + return true; + } + + case CSPFT_SCRIPT_STRING: + return ConvertScriptString(value, field.iOffset); + + case CSPFT_NUM_BASE_FIELD_TYPES: + default: + assert(false); + return false; + } +} + +bool InfoStringToStructConverter::Convert() +{ + for (auto fieldIndex = 0u; fieldIndex < m_field_count; fieldIndex++) + { + const auto& field = m_fields[fieldIndex]; + assert(field.iFieldType >= 0); + + auto foundValue = false; + const auto& value = m_info_string.GetValueForKey(std::string(field.szName), &foundValue); + + if (foundValue) + { + if (field.iFieldType < CSPFT_NUM_BASE_FIELD_TYPES) + { + if (!ConvertBaseField(field, value)) + return false; + } + else + { + if (!ConvertExtensionField(field, value)) + return false; + } + } + } + + return true; +} diff --git a/src/ObjLoading/Game/T6/AssetLoaders/AssetLoaderWeapon.cpp b/src/ObjLoading/Game/T6/AssetLoaders/AssetLoaderWeapon.cpp index 4c0b4c4a..9df4a899 100644 --- a/src/ObjLoading/Game/T6/AssetLoaders/AssetLoaderWeapon.cpp +++ b/src/ObjLoading/Game/T6/AssetLoaders/AssetLoaderWeapon.cpp @@ -81,8 +81,8 @@ namespace T6 _NODISCARD bool ConvertNotetrackSoundMap(const cspField_t& field, const std::string& value) { - std::vector> pairs; - if (!ParseAsPairs(value, pairs)) + std::vector> pairs; + if (!ParseAsArray(value, pairs)) { std::cerr << "Failed to parse notetracksoundmap as pairs\n"; return false; @@ -106,10 +106,10 @@ namespace T6 for (; currentEntryNum < pairs.size(); currentEntryNum++) { const auto& currentValue = pairs[currentEntryNum]; - const auto keyScriptString = !currentValue.first.empty() ? m_zone_script_strings.AddOrGetScriptString(currentValue.first) - : m_zone_script_strings.AddOrGetScriptString(nullptr); - const auto valueScriptString = !currentValue.second.empty() ? m_zone_script_strings.AddOrGetScriptString(currentValue.second) - : m_zone_script_strings.AddOrGetScriptString(nullptr); + const auto keyScriptString = !currentValue[0].empty() ? m_zone_script_strings.AddOrGetScriptString(currentValue[0]) + : m_zone_script_strings.AddOrGetScriptString(nullptr); + const auto valueScriptString = !currentValue[1].empty() ? m_zone_script_strings.AddOrGetScriptString(currentValue[1]) + : m_zone_script_strings.AddOrGetScriptString(nullptr); keys[currentEntryNum] = keyScriptString; m_used_script_string_list.emplace(keyScriptString); diff --git a/src/ObjLoading/InfoString/InfoStringToStructConverterBase.cpp b/src/ObjLoading/InfoString/InfoStringToStructConverterBase.cpp index a5d28a0f..02ebfeec 100644 --- a/src/ObjLoading/InfoString/InfoStringToStructConverterBase.cpp +++ b/src/ObjLoading/InfoString/InfoStringToStructConverterBase.cpp @@ -41,68 +41,6 @@ bool InfoStringToStructConverterBase::ParseAsArray(const std::string& value, std return true; } -bool InfoStringToStructConverterBase::ParseAsPairs(const std::string& value, std::vector>& valueArray) -{ - std::string key; - auto isKey = true; - - for (auto ci = 0u; ci < value.size(); ci++) - { - auto c = value[ci]; - - if (c == '\r' && ci + 1 < value.size() && value[ci + 1] == '\n') - c = value[++ci]; - - if (c == '\n' && !isKey) - { - std::cout << "Expected value but got new line\n"; - return false; - } - - if (isspace(c)) - continue; - - int separator; - const auto startPos = ci; - while (true) - { - ci++; - if (ci >= value.size()) - { - separator = EOF; - break; - } - c = value[ci]; - if (c == '\r' && ci + 1 < value.size() && value[ci + 1] == '\n') - c = value[++ci]; - if (isspace(c)) - { - separator = static_cast(static_cast(c)); - break; - } - } - - if (isKey) - { - if (separator == '\n' || separator == EOF) - { - std::cout << "Expected value but got new line\n"; - return false; - } - key = std::string(value, startPos, ci - startPos); - } - else - { - auto parsedValue = std::string(value, startPos, ci - startPos); - valueArray.emplace_back(std::make_pair(std::move(key), std::move(parsedValue))); - key = std::string(); - } - isKey = !isKey; - } - - return true; -} - bool InfoStringToStructConverterBase::ConvertString(const std::string& value, const size_t offset) { *reinterpret_cast(reinterpret_cast(m_structure) + offset) = m_memory->Dup(value.c_str()); diff --git a/src/ObjLoading/InfoString/InfoStringToStructConverterBase.h b/src/ObjLoading/InfoString/InfoStringToStructConverterBase.h index 8d74fcdc..d25e2084 100644 --- a/src/ObjLoading/InfoString/InfoStringToStructConverterBase.h +++ b/src/ObjLoading/InfoString/InfoStringToStructConverterBase.h @@ -6,6 +6,8 @@ #include "Utils/MemoryManager.h" #include "Zone/ZoneScriptStrings.h" +#include +#include #include #include #include @@ -22,7 +24,82 @@ protected: void* m_structure; static bool ParseAsArray(const std::string& value, std::vector& valueArray); - static bool ParseAsPairs(const std::string& value, std::vector>& valueArray); + + template static bool ParseAsArray(const std::string& value, std::vector>& valueArray) + { + static_assert(ARRAY_SIZE >= 1); + + std::array currentEntry; + auto currentEntryOffset = 0u; + + for (auto ci = 0u; ci < value.size(); ci++) + { + auto c = value[ci]; + + if (c == '\r' && ci + 1 < value.size() && value[ci + 1] == '\n') + c = value[++ci]; + + if (c == '\n' && currentEntryOffset != ARRAY_SIZE) + { + std::cerr << "Expected value but got new line\n"; + return false; + } + + if (isspace(c)) + continue; + + int separator; + const auto startPos = ci; + while (true) + { + ci++; + + // If value ends we use EOF as separator + if (ci >= value.size()) + { + separator = EOF; + break; + } + + c = value[ci]; + + // Skip \r from \r\n + if (c == '\r' && ci + 1 < value.size() && value[ci + 1] == '\n') + c = value[++ci]; + + // Newline is considered space + if (isspace(c)) + { + separator = static_cast(static_cast(c)); + break; + } + } + + const auto isNextEntrySeparator = separator == '\n' || separator == EOF; + const auto isLastEntry = currentEntryOffset >= (ARRAY_SIZE - 1); + if (isNextEntrySeparator != isLastEntry) + { + std::cout << "Expected " << ARRAY_SIZE << " values but got new line\n"; + return false; + } + + currentEntry[currentEntryOffset++] = std::string(value, startPos, ci - startPos); + + if (isLastEntry) + { + valueArray.emplace_back(std::move(currentEntry)); + currentEntryOffset = 0u; + } + } + + if (currentEntryOffset > 0) + { + std::cout << "Expected " << ARRAY_SIZE << " values but got new line\n"; + return false; + } + + return true; + } bool ConvertString(const std::string& value, size_t offset); bool ConvertStringBuffer(const std::string& value, size_t offset, size_t bufferSize); From fedd955e6831295f83b7d62bb5dd23751bd5e360 Mon Sep 17 00:00:00 2001 From: Jan Date: Sun, 21 Apr 2024 18:44:51 +0200 Subject: [PATCH 06/10] fix: attachment loader not properly loading attributes --- .../Game/IW5/Weapon/JsonWeaponAttachmentLoader.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/ObjLoading/Game/IW5/Weapon/JsonWeaponAttachmentLoader.cpp b/src/ObjLoading/Game/IW5/Weapon/JsonWeaponAttachmentLoader.cpp index c816ac1b..9dcce10a 100644 --- a/src/ObjLoading/Game/IW5/Weapon/JsonWeaponAttachmentLoader.cpp +++ b/src/ObjLoading/Game/IW5/Weapon/JsonWeaponAttachmentLoader.cpp @@ -60,7 +60,7 @@ namespace CreateXModelArrayFromJson(jAttachment.propertyName, attachment.propertyName, #propertyName, count, attachment); #define CONVERT_ATTRIBUTE(attributeClass, attributeName) \ - if (jAttachment.ammoGeneral) \ + if (jAttachment.attributeName) \ { \ using AttributeType = std::remove_pointer_t; \ attachment.attributeName = static_cast(m_memory.Alloc(sizeof(AttributeType))); \ @@ -520,8 +520,13 @@ namespace { if (jRumbles.fireRumble) rumbles.fireRumble = m_memory.Dup(jRumbles.fireRumble.value().c_str()); + else + rumbles.fireRumble = nullptr; + if (jRumbles.meleeImpactRumble) rumbles.meleeImpactRumble = m_memory.Dup(jRumbles.meleeImpactRumble.value().c_str()); + else + rumbles.meleeImpactRumble = nullptr; return true; } From e76437301345b2686064858944fc3971aa7a6336 Mon Sep 17 00:00:00 2001 From: Jan Date: Sun, 21 Apr 2024 18:44:59 +0200 Subject: [PATCH 07/10] feat: load iw5 weapons --- .../Game/IW5/InfoString/WeaponFields.h | 4 +- .../IW5/AssetLoaders/AssetLoaderWeapon.cpp | 900 ++++++++++++++++++ .../Game/IW5/AssetLoaders/AssetLoaderWeapon.h | 20 + src/ObjLoading/Game/IW5/ObjLoaderIW5.cpp | 3 +- .../IW5/AssetDumpers/AssetDumperWeapon.cpp | 2 + 5 files changed, 926 insertions(+), 3 deletions(-) create mode 100644 src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderWeapon.cpp create mode 100644 src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderWeapon.h diff --git a/src/ObjCommon/Game/IW5/InfoString/WeaponFields.h b/src/ObjCommon/Game/IW5/InfoString/WeaponFields.h index e08f9b2c..ed07cf4b 100644 --- a/src/ObjCommon/Game/IW5/InfoString/WeaponFields.h +++ b/src/ObjCommon/Game/IW5/InfoString/WeaponFields.h @@ -972,12 +972,12 @@ namespace IW5 }; static_assert(std::extent_v == VEH_AXLE_COUNT); - inline const char* bounceSoundSuffixes[]{ + inline const char* surfaceTypeSoundSuffixes[]{ "_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", }; - static_assert(std::extent_v == SURF_TYPE_COUNT); + static_assert(std::extent_v == SURF_TYPE_COUNT); inline const char* weapAnimFilesNames[]{ "root", "idle", "empty_idle", "fire", "hold_fire", "lastshot", "rechamber", "melee", diff --git a/src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderWeapon.cpp b/src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderWeapon.cpp new file mode 100644 index 00000000..07274569 --- /dev/null +++ b/src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderWeapon.cpp @@ -0,0 +1,900 @@ +#include "AssetLoaderWeapon.h" + +#include "Game/IW5/IW5.h" +#include "Game/IW5/InfoString/InfoStringToStructConverter.h" +#include "Game/IW5/InfoString/WeaponFields.h" +#include "Game/IW5/ObjConstantsIW5.h" +#include "InfoString/InfoString.h" +#include "ObjLoading.h" +#include "Pool/GlobalAssetPool.h" +#include "Utils/StringUtils.h" + +#include +#include +#include + +using namespace IW5; + +namespace +{ + class InfoStringToWeaponConverter final : public InfoStringToStructConverter + { + bool ConvertHideTags(const cspField_t& field, const std::string& value) + { + std::vector valueArray; + if (!ParseAsArray(value, valueArray)) + { + std::cerr << "Failed to parse hide tags as array\n"; + return false; + } + + if (valueArray.size() > std::extent_v) + { + std::cerr << "Cannot have more than " << std::extent_v << " hide tags!\n"; + return false; + } + + auto* hideTags = reinterpret_cast(reinterpret_cast(m_structure) + field.iOffset); + + if (valueArray.size() < std::extent_v) + { + m_used_script_string_list.emplace(m_zone_script_strings.AddOrGetScriptString(nullptr)); + } + + auto currentHideTag = 0u; + for (; currentHideTag < valueArray.size(); currentHideTag++) + { + const auto& currentValue = valueArray[currentHideTag]; + const auto scrString = + !currentValue.empty() ? m_zone_script_strings.AddOrGetScriptString(currentValue) : m_zone_script_strings.AddOrGetScriptString(nullptr); + hideTags[currentHideTag] = scrString; + m_used_script_string_list.emplace(scrString); + } + + for (; currentHideTag < std::extent_v; currentHideTag++) + { + hideTags[currentHideTag] = m_zone_script_strings.GetScriptString(nullptr); + } + + return true; + } + + _NODISCARD bool ConvertPerSurfaceTypeSound(const cspField_t& field, const std::string& value) const + { + auto** perSurfaceTypeSound = reinterpret_cast(reinterpret_cast(m_structure) + field.iOffset); + if (value.empty()) + { + *perSurfaceTypeSound = nullptr; + return true; + } + + *perSurfaceTypeSound = static_cast(m_memory->Alloc(sizeof(SndAliasCustom) * SURF_TYPE_COUNT)); + for (auto i = 0u; i < SURF_TYPE_COUNT; i++) + { + const auto currentPerSurfaceTypeSound = value + surfaceTypeSoundSuffixes[i]; + + (*perSurfaceTypeSound)[i].name = static_cast(m_memory->Alloc(sizeof(snd_alias_list_name))); + (*perSurfaceTypeSound)[i].name->soundName = m_memory->Dup(currentPerSurfaceTypeSound.c_str()); + } + + return true; + } + + _NODISCARD bool ConvertNoteTrackMap(const cspField_t& field, const std::string& value, const char* mapName, const size_t keyAndValueCount) + { + std::vector> pairs; + if (!ParseAsArray(value, pairs)) + { + std::cerr << "Failed to parse notetrack" << mapName << "map as pairs\n"; + return false; + } + + if (pairs.size() > std::extent_v) + { + std::cerr << "Cannot have more than " << std::extent_v << " notetrack" << mapName + << "map entries!\n"; + return false; + } + + auto* keys = reinterpret_cast(reinterpret_cast(m_structure) + field.iOffset); + auto* values = &keys[keyAndValueCount]; + auto currentEntryNum = 0u; + + if (pairs.size() < keyAndValueCount) + { + m_used_script_string_list.emplace(m_zone_script_strings.AddOrGetScriptString(nullptr)); + } + + for (; currentEntryNum < pairs.size(); currentEntryNum++) + { + const auto& currentValue = pairs[currentEntryNum]; + const auto keyScriptString = !currentValue[0].empty() ? m_zone_script_strings.AddOrGetScriptString(currentValue[0]) + : m_zone_script_strings.AddOrGetScriptString(nullptr); + const auto valueScriptString = !currentValue[1].empty() ? m_zone_script_strings.AddOrGetScriptString(currentValue[1]) + : m_zone_script_strings.AddOrGetScriptString(nullptr); + + keys[currentEntryNum] = keyScriptString; + m_used_script_string_list.emplace(keyScriptString); + + values[currentEntryNum] = valueScriptString; + m_used_script_string_list.emplace(valueScriptString); + } + + for (; currentEntryNum < keyAndValueCount; currentEntryNum++) + { + const auto emptyScr = m_zone_script_strings.GetScriptString(nullptr); + keys[currentEntryNum] = emptyScr; + values[currentEntryNum] = emptyScr; + } + + return true; + } + + bool ConvertAnimName(const cspField_t& field, const std::string& value) + { + if (ConvertString(value, field.iOffset)) + { + if (!value.empty()) + { + auto lowerValue = value; + utils::MakeStringLowerCase(lowerValue); + m_indirect_asset_references.emplace(m_loading_manager->LoadIndirectAssetReference(ASSET_TYPE_XANIMPARTS, lowerValue)); + } + return true; + } + + return false; + } + + bool ConvertAttachments(const std::string& value) + { + std::vector valueArray; + if (!ParseAsArray(value, valueArray)) + { + std::cerr << "Failed to parse attachments as array\n"; + return false; + } + + auto currentScope = 0u; + auto currentUnderBarrel = 0u; + auto currentOther = 0u; + for (const auto& attachmentName : valueArray) + { + auto* attachmentInfo = static_cast*>(m_loading_manager->LoadDependency(ASSET_TYPE_ATTACHMENT, attachmentName)); + if (!attachmentInfo) + return false; + m_dependencies.emplace(attachmentInfo); + auto* attachment = attachmentInfo->Asset(); + + if (attachment->type == ATTACHMENT_SCOPE) + { + if (currentScope >= std::extent_v) + { + std::cerr << "Cannot have more than " << std::extent_v << " scopes\n"; + return false; + } + + m_weapon->scopes[currentScope++] = attachment; + } + else if (attachment->type == ATTACHMENT_UNDERBARREL) + { + if (currentUnderBarrel >= std::extent_v) + { + std::cerr << "Cannot have more than " << std::extent_v << " under barrels\n"; + return false; + } + + m_weapon->underBarrels[currentUnderBarrel++] = attachment; + } + else if (attachment->type == ATTACHMENT_OTHER) + { + if (currentOther >= std::extent_v) + { + std::cerr << "Cannot have more than " << std::extent_v << " other attachments\n"; + return false; + } + + m_weapon->others[currentOther++] = attachment; + } + } + + return true; + } + + bool ConvertAnimOverrides(const std::string& value) + { + std::vector> valueArray; + if (!ParseAsArray(value, valueArray)) + { + std::cerr << "Failed to parse anim overrides as array\n"; + return false; + } + + auto* animOverrides = static_cast(m_memory->Alloc(sizeof(AnimOverrideEntry) * valueArray.size())); + + auto i = 0u; + for (const auto& overrideValues : valueArray) + { + auto& animOverride = animOverrides[i++]; + + if (!ParseSingleWeaponAttachment(overrideValues[0], animOverride.attachment1)) + return false; + + if (!ParseSingleWeaponAttachment(overrideValues[1], animOverride.attachment2)) + return false; + + if (!ParseAnimFile(overrideValues[2], animOverride.animTreeType)) + return false; + + ParseAnim(overrideValues[3], animOverride.overrideAnim); + ParseAnim(overrideValues[4], animOverride.altmodeAnim); + + if (!ParseInt(overrideValues[5], animOverride.animTime)) + return false; + + if (!ParseInt(overrideValues[6], animOverride.altTime)) + return false; + } + + m_weapon->weapCompleteDef.animOverrides = animOverrides; + + return true; + } + + bool ConvertSoundOverrides(const std::string& value) + { + std::vector> valueArray; + if (!ParseAsArray(value, valueArray)) + { + std::cerr << "Failed to parse sound overrides as array\n"; + return false; + } + + auto* soundOverrides = static_cast(m_memory->Alloc(sizeof(SoundOverrideEntry) * valueArray.size())); + + auto i = 0u; + for (const auto& overrideValues : valueArray) + { + auto& soundOverride = soundOverrides[i++]; + + if (!ParseSingleWeaponAttachment(overrideValues[0], soundOverride.attachment1)) + return false; + + if (!ParseSingleWeaponAttachment(overrideValues[1], soundOverride.attachment2)) + return false; + + if (!ParseSoundType(overrideValues[2], soundOverride.soundType)) + return false; + + ParseSoundAlias(overrideValues[3], soundOverride.overrideSound); + ParseSoundAlias(overrideValues[4], soundOverride.altmodeSound); + } + + m_weapon->weapCompleteDef.soundOverrides = soundOverrides; + + return true; + } + + bool ConvertFxOverrides(const std::string& value) + { + std::vector> valueArray; + if (!ParseAsArray(value, valueArray)) + { + std::cerr << "Failed to parse attachments as array\n"; + return false; + } + + auto* fxOverrides = static_cast(m_memory->Alloc(sizeof(FxOverrideEntry) * valueArray.size())); + + auto i = 0u; + for (const auto& overrideValues : valueArray) + { + auto& fxOverride = fxOverrides[i++]; + + if (!ParseSingleWeaponAttachment(overrideValues[0], fxOverride.attachment1)) + return false; + + if (!ParseSingleWeaponAttachment(overrideValues[1], fxOverride.attachment2)) + return false; + + if (!ParseFxType(overrideValues[2], fxOverride.fxType)) + return false; + + if (!ParseFxEffectDef(overrideValues[3], fxOverride.overrideFx)) + return false; + + if (!ParseFxEffectDef(overrideValues[4], fxOverride.altmodeFx)) + return false; + } + + m_weapon->weapCompleteDef.fxOverrides = fxOverrides; + + return true; + } + + bool ConvertReloadOverrides(const std::string& value) + { + std::vector> valueArray; + if (!ParseAsArray(value, valueArray)) + { + std::cerr << "Failed to parse reload overrides as array\n"; + return false; + } + + auto* reloadOverrides = static_cast(m_memory->Alloc(sizeof(ReloadStateTimerEntry) * valueArray.size())); + + auto i = 0u; + for (const auto& overrideValues : valueArray) + { + auto& reloadOverride = reloadOverrides[i++]; + + if (!ParseSingleWeaponAttachment(overrideValues[0], reloadOverride.attachment)) + return false; + + reloadOverride.unused = 0u; + + if (!ParseInt(overrideValues[1], reloadOverride.reloadAddTime)) + return false; + + if (!ParseInt(overrideValues[2], reloadOverride.reloadStartAddTime)) + return false; + } + + m_weapon->weapCompleteDef.reloadOverrides = reloadOverrides; + + return true; + } + + bool ConvertNoteTrackOverrides(const std::string& value) + { + std::vector> valueArray; + if (!ParseAsArray(value, valueArray)) + { + std::cerr << "Failed to parse note track overrides as array\n"; + return false; + } + + std::vector overrideVector; + NoteTrackToSoundEntry currentOverride; + auto currentOverrideKeyOffset = 0u; + currentOverride.unused = 0u; + std::string lastAttachment; + + auto i = 0u; + for (const auto& overrideValues : valueArray) + { + if (i == 0u || lastAttachment != overrideValues[0]) + { + if (currentOverrideKeyOffset > 0u) + overrideVector.emplace_back(currentOverride); + + if (!ParseSingleWeaponAttachment(overrideValues[0], currentOverride.attachment)) + return false; + + currentOverride.notetrackSoundMapKeys = static_cast(m_memory->Alloc(sizeof(ScriptString) * 24u)); + memset(currentOverride.notetrackSoundMapKeys, 0u, sizeof(ScriptString) * 24u); + currentOverride.notetrackSoundMapValues = static_cast(m_memory->Alloc(sizeof(ScriptString) * 24u)); + memset(currentOverride.notetrackSoundMapValues, 0u, sizeof(ScriptString) * 24u); + lastAttachment = overrideValues[0]; + currentOverrideKeyOffset = 0u; + } + + if (currentOverrideKeyOffset >= 24u) + { + std::cerr << "Cannot have more than " << 24u << " note track overrides per attachment\n"; + return false; + } + + ParseScriptString(overrideValues[1], currentOverride.notetrackSoundMapKeys[currentOverrideKeyOffset]); + ParseScriptString(overrideValues[2], currentOverride.notetrackSoundMapValues[currentOverrideKeyOffset]); + currentOverrideKeyOffset++; + } + + if (currentOverrideKeyOffset > 0u) + overrideVector.emplace_back(currentOverride); + + m_weapon->weapCompleteDef.notetrackOverrides = + static_cast(m_memory->Alloc(sizeof(NoteTrackToSoundEntry) * overrideVector.size())); + memcpy(m_weapon->weapCompleteDef.notetrackOverrides, overrideVector.data(), sizeof(NoteTrackToSoundEntry) * overrideVector.size()); + + return true; + } + + bool ParseSingleWeaponAttachment(const std::string& value, WeaponAttachmentCombination& attachment) + { + attachment.fields = 0u; + if (value == "none") + return true; + + for (auto i = 0u; i < std::extent_v; i++) + { + const auto* scope = m_weapon->scopes[i]; + if (scope && scope->szInternalName && value == scope->szInternalName) + { + attachment.scope = static_cast(i); + return true; + } + } + + for (auto i = 0u; i < std::extent_v; i++) + { + const auto* underBarrel = m_weapon->underBarrels[i]; + if (underBarrel && underBarrel->szInternalName && value == underBarrel->szInternalName) + { + attachment.underBarrel = static_cast(i); + return true; + } + } + + for (auto i = 0u; i < std::extent_v; i++) + { + const auto* other = m_weapon->others[i]; + if (other && other->szInternalName && value == other->szInternalName) + { + attachment.other = static_cast(1u << i); + return true; + } + } + + std::cerr << "Weapon does not have attachment \"" << value << "\"\n"; + return false; + } + + void ParseAnim(const std::string& value, const char*& animName) + { + if (value == "none") + { + animName = nullptr; + return; + } + + animName = m_memory->Dup(value.c_str()); + m_indirect_asset_references.emplace(m_loading_manager->LoadIndirectAssetReference(ASSET_TYPE_XANIMPARTS, value)); + } + + void ParseSoundAlias(const std::string& value, SndAliasCustom& soundAlias) + { + if (value == "none") + { + soundAlias.name = nullptr; + return; + } + + soundAlias.name = static_cast(m_memory->Alloc(sizeof(snd_alias_list_name))); + soundAlias.name->soundName = m_memory->Dup(value.c_str()); + m_indirect_asset_references.emplace(m_loading_manager->LoadIndirectAssetReference(ASSET_TYPE_SOUND, value)); + } + + bool ParseFxEffectDef(const std::string& value, FxEffectDef*& fx) + { + if (value == "none") + { + fx = nullptr; + return true; + } + + auto* fxInfo = static_cast*>(m_loading_manager->LoadDependency(ASSET_TYPE_FX, value)); + if (!fxInfo) + { + std::cerr << "Failed to load fx for override \"" << value << "\"\n"; + return false; + } + + fx = fxInfo->Asset(); + m_dependencies.emplace(fxInfo); + + return true; + } + + static bool ParseAnimFile(const std::string& value, weapAnimFiles_t& animFile) + { + for (auto i = 0u; i < std::extent_v; i++) + { + if (value == weapAnimFilesNames[i]) + { + animFile = static_cast(i); + return true; + } + } + + std::cerr << "Unknown anim file \"" << value << "\"\n"; + return false; + } + + static bool ParseSoundType(const std::string& value, SoundOverrideTypes& soundType) + { + for (auto i = 0u; i < std::extent_v; i++) + { + if (value == soundOverrideTypeNames[i]) + { + soundType = static_cast(i); + return true; + } + } + + std::cerr << "Unknown sound type \"" << value << "\"\n"; + return false; + } + + static bool ParseFxType(const std::string& value, FxOverrideTypes& fxType) + { + for (auto i = 0u; i < std::extent_v; i++) + { + if (value == fxOverrideTypeNames[i]) + { + fxType = static_cast(i); + return true; + } + } + + std::cerr << "Unknown fx type \"" << value << "\"\n"; + return false; + } + + static bool ParseInt(const std::string& value, int& out) + { + size_t size; + out = std::stoi(value, &size); + + if (size != value.size()) + { + std::cerr << "Invalid int value: \"" << value << "\"\n"; + return false; + } + + return true; + } + + void ParseScriptString(const std::string& value, ScriptString& out) + { + out = m_zone_script_strings.AddOrGetScriptString(value); + m_used_script_string_list.emplace(out); + } + + protected: + bool ConvertExtensionField(const cspField_t& field, const std::string& value) override + { + switch (static_cast(field.iFieldType)) + { + case WFT_WEAPONTYPE: + return ConvertEnumInt(value, field.iOffset, szWeapTypeNames, std::extent_v); + + case WFT_WEAPONCLASS: + return ConvertEnumInt(value, field.iOffset, szWeapClassNames, std::extent_v); + + case WFT_OVERLAYRETICLE: + return ConvertEnumInt(value, field.iOffset, szWeapOverlayReticleNames, std::extent_v); + + case WFT_PENETRATE_TYPE: + return ConvertEnumInt(value, field.iOffset, penetrateTypeNames, std::extent_v); + + case WFT_IMPACT_TYPE: + return ConvertEnumInt(value, field.iOffset, impactTypeNames, std::extent_v); + + case WFT_STANCE: + return ConvertEnumInt(value, field.iOffset, szWeapStanceNames, std::extent_v); + + case WFT_PROJ_EXPLOSION: + return ConvertEnumInt(value, field.iOffset, szProjectileExplosionNames, std::extent_v); + + case WFT_OFFHAND_CLASS: + return ConvertEnumInt(value, field.iOffset, offhandClassNames, std::extent_v); + + case WFT_ANIMTYPE: + return ConvertEnumInt(value, field.iOffset, playerAnimTypeNames, std::extent_v); + + case WFT_ACTIVE_RETICLE_TYPE: + return ConvertEnumInt(value, field.iOffset, activeReticleNames, std::extent_v); + + case WFT_GUIDED_MISSILE_TYPE: + return ConvertEnumInt(value, field.iOffset, guidedMissileNames, std::extent_v); + + case WFT_PER_SURFACE_TYPE_SOUND: + return ConvertPerSurfaceTypeSound(field, value); + + case WFT_STICKINESS: + return ConvertEnumInt(value, field.iOffset, stickinessNames, std::extent_v); + + case WFT_OVERLAYINTERFACE: + return ConvertEnumInt(value, field.iOffset, overlayInterfaceNames, std::extent_v); + + case WFT_INVENTORYTYPE: + return ConvertEnumInt(value, field.iOffset, szWeapInventoryTypeNames, std::extent_v); + + case WFT_FIRETYPE: + return ConvertEnumInt(value, field.iOffset, szWeapFireTypeNames, std::extent_v); + + case WFT_AMMOCOUNTER_CLIPTYPE: + return ConvertEnumInt(value, field.iOffset, ammoCounterClipNames, std::extent_v); + + case WFT_ICONRATIO_HUD: + case WFT_ICONRATIO_PICKUP: + case WFT_ICONRATIO_AMMOCOUNTER: + case WFT_ICONRATIO_KILL: + case WFT_ICONRATIO_DPAD: + return ConvertEnumInt(value, field.iOffset, weapIconRatioNames, std::extent_v); + + case WFT_HIDETAGS: + return ConvertHideTags(field, value); + + case WFT_NOTETRACKSOUNDMAP: + return ConvertNoteTrackMap(field, value, "sound", std::extent_v); + + case WFT_NOTETRACKRUMBLEMAP: + return ConvertNoteTrackMap(field, value, "rumble", std::extent_v); + + case WFT_ANIM_NAME: + return ConvertAnimName(field, value); + + case WFT_ATTACHMENT: + return ConvertAttachments(value); + + case WFT_ANIM_OVERRIDES: + return ConvertAnimOverrides(value); + + case WFT_SOUND_OVERRIDES: + return ConvertSoundOverrides(value); + + case WFT_FX_OVERRIDES: + return ConvertFxOverrides(value); + + case WFT_RELOAD_OVERRIDES: + return ConvertReloadOverrides(value); + + case WFT_NOTETRACK_OVERRIDES: + return ConvertNoteTrackOverrides(value); + + default: + assert(false); + return false; + } + } + + public: + InfoStringToWeaponConverter(const InfoString& infoString, + WeaponFullDef* weaponFullDef, + ZoneScriptStrings& zoneScriptStrings, + MemoryManager* memory, + IAssetLoadingManager* manager, + const cspField_t* fields, + const size_t fieldCount) + : InfoStringToStructConverter(infoString, weaponFullDef, zoneScriptStrings, memory, manager, fields, fieldCount) + { + m_weapon = weaponFullDef; + } + + private: + WeaponFullDef* m_weapon; + }; + + void InitWeaponFullDef(WeaponFullDef* weapon) + { + weapon->weapCompleteDef.weapDef = &weapon->weapDef; + weapon->weapCompleteDef.hideTags = weapon->hideTags; + weapon->weapCompleteDef.szXAnims = weapon->szXAnims; + weapon->weapDef.gunXModel = weapon->gunXModel; + weapon->weapDef.szXAnimsRightHanded = weapon->szXAnimsRightHanded; + weapon->weapDef.szXAnimsLeftHanded = weapon->szXAnimsLeftHanded; + weapon->weapDef.notetrackSoundMapKeys = weapon->notetrackSoundMapKeys; + weapon->weapDef.notetrackSoundMapValues = weapon->notetrackSoundMapValues; + weapon->weapDef.notetrackRumbleMapKeys = weapon->notetrackRumbleMapKeys; + weapon->weapDef.notetrackRumbleMapValues = weapon->notetrackRumbleMapValues; + weapon->weapDef.worldModel = weapon->worldModel; + weapon->weapDef.parallelBounce = weapon->parallelBounce; + weapon->weapDef.perpendicularBounce = weapon->perpendicularBounce; + weapon->weapDef.locationDamageMultipliers = weapon->locationDamageMultipliers; + weapon->weapCompleteDef.szInternalName = ""; + + for (const auto& field : weapon_fields) + { + if (field.iFieldType != CSPFT_STRING) + continue; + + *reinterpret_cast(reinterpret_cast(weapon) + field.iOffset) = ""; + } + } + + snd_alias_list_name* CreateSoundAliasListName(const char* value, MemoryManager* memory) + { + auto* name = static_cast(memory->Alloc(sizeof(snd_alias_list_name))); + name->soundName = memory->Dup(value); + return name; + } + + void CheckProjectileValues(const WeaponCompleteDef& weaponCompleteDef, const WeaponDef& weaponDef) + { + if (weaponDef.iProjectileSpeed <= 0) + std::cerr << std::format("Projectile speed for WeapType {} must be greater than 0.0", weaponCompleteDef.szDisplayName); + if (weaponDef.destabilizationCurvatureMax >= 1000000000.0f || weaponDef.destabilizationCurvatureMax < 0.0f) + std::cerr << std::format("Destabilization angle for for WeapType {} must be between 0 and 45 degrees", weaponCompleteDef.szDisplayName); + if (weaponDef.destabilizationRateTime < 0.0f) + std::cerr << std::format("Destabilization rate time for for WeapType {} must be non-negative", weaponCompleteDef.szDisplayName); + } + + void CheckTurretBarrelSpin(const WeaponCompleteDef& weaponCompleteDef, const WeaponDef& weaponDef) + { + if (weaponDef.weapClass != WEAPCLASS_TURRET) + std::cerr << std::format("Rotating barrel set for non-turret weapon {}.", weaponCompleteDef.szInternalName); + + if (0.0f == weaponDef.turretBarrelSpinSpeed) + { + std::cerr << std::format( + "Rotating barrel spin speed '{}' is invalid for weapon {}.", weaponDef.turretBarrelSpinSpeed, weaponCompleteDef.szInternalName); + } + if (0.0f < weaponDef.turretOverheatUpRate && 0.0f >= weaponDef.turretOverheatDownRate) + { + std::cerr << std::format("Turret overheat Up rate is set, but the down rate '{}' is invalid for weapon {}.", + weaponDef.turretOverheatDownRate, + weaponCompleteDef.szInternalName); + } + } + + void CheckThermalScope(const WeaponCompleteDef& weaponCompleteDef, const WeaponDef& weaponDef) + { + if (0.0f != weaponDef.fAdsZoomInFrac) + { + std::cerr << std::format("Weapon {} ({}) has thermal scope set. ADS Zoom In frac should be 0 to prevent zoom-in blur ({}).\n", + weaponCompleteDef.szInternalName, + weaponCompleteDef.szDisplayName, + weaponDef.fAdsZoomInFrac); + } + + if (0.0f != weaponDef.fAdsZoomOutFrac) + { + std::cerr << std::format("Weapon {} ({}) has thermal scope set. ADS Zoom Out frac should be 0 to prevent zoom-out blur ({}).\n", + weaponCompleteDef.szInternalName, + weaponCompleteDef.szDisplayName, + weaponDef.fAdsZoomOutFrac); + } + } + + void CalculateWeaponFields(WeaponFullDef* weapon, MemoryManager* memory) + { + auto& weaponCompleteDef = weapon->weapCompleteDef; + auto& weaponDef = weapon->weapDef; + + if (!weaponDef.viewLastShotEjectEffect) + weaponDef.viewLastShotEjectEffect = weaponDef.viewShellEjectEffect; + if (!weaponDef.worldLastShotEjectEffect) + weaponDef.worldLastShotEjectEffect = weaponDef.worldShellEjectEffect; + + if (!weaponDef.raiseSound.name) + weaponDef.raiseSound.name = CreateSoundAliasListName("weap_raise", memory); + if (!weaponDef.putawaySound.name) + weaponDef.putawaySound.name = CreateSoundAliasListName("weap_putaway", memory); + if (!weaponDef.pickupSound.name) + weaponDef.pickupSound.name = CreateSoundAliasListName("weap_pickup", memory); + if (!weaponDef.ammoPickupSound.name) + weaponDef.ammoPickupSound.name = CreateSoundAliasListName("weap_ammo_pickup", memory); + if (!weaponDef.emptyFireSound.name) + weaponDef.emptyFireSound.name = CreateSoundAliasListName("weap_dryfire_smg_npc", memory); + + if (weaponCompleteDef.iAdsTransInTime <= 0) + weaponDef.fOOPosAnimLength[0] = 0.0033333334f; + else + weaponDef.fOOPosAnimLength[0] = 1.0f / static_cast(weaponCompleteDef.iAdsTransInTime); + + if (weaponCompleteDef.iAdsTransOutTime <= 0) + weaponDef.fOOPosAnimLength[1] = 0.0020000001f; + else + weaponDef.fOOPosAnimLength[1] = 1.0f / static_cast(weaponCompleteDef.iAdsTransOutTime); + + if (weaponDef.fMaxDamageRange <= 0.0f) + weaponDef.fMaxDamageRange = 999999.0f; + if (weaponDef.fMinDamageRange <= 0.0f) + weaponDef.fMinDamageRange = 999999.12f; + + if (weaponDef.enemyCrosshairRange > 15000.0f) + std::cerr << std::format("Enemy crosshair ranges should be less than {}\n", 15000.0f); + + if (weaponDef.weapType == WEAPTYPE_PROJECTILE) + CheckProjectileValues(weaponCompleteDef, weaponDef); + + if (weaponDef.turretBarrelSpinEnabled) + CheckTurretBarrelSpin(weaponCompleteDef, weaponDef); + + if (weaponDef.thermalScope) + CheckThermalScope(weaponCompleteDef, weaponDef); + + if (weaponDef.offhandClass && !weaponDef.bClipOnly) + { + std::cerr << std::format( + "Weapon {} ({}) is an offhand weapon but is not set to clip only, which is not supported since we can't reload the offhand slot.\n", + weaponCompleteDef.szInternalName, + weaponCompleteDef.szDisplayName); + } + + if (weaponDef.weapType == WEAPTYPE_BULLET) + { + if (weaponDef.bulletExplDmgMult <= 0.0f) + std::cerr << std::format("Detected invalid bulletExplDmgMult of '{}' for weapon '{}'; please update weapon settings.\n", + weaponDef.bulletExplDmgMult, + weaponCompleteDef.szInternalName); + if (weaponDef.bulletExplRadiusMult <= 0.0f) + std::cerr << std::format("Detected invalid bulletExplRadiusMult of '{}' for weapon '{}'; please update weapon settings.\n", + weaponDef.bulletExplRadiusMult, + weaponCompleteDef.szInternalName); + } + } + + bool LoadFromInfoString(const InfoString& infoString, const std::string& assetName, MemoryManager* memory, IAssetLoadingManager* manager, Zone* zone) + { + auto* weaponFullDef = memory->Create(); + memset(weaponFullDef, 0, sizeof(WeaponFullDef)); + InitWeaponFullDef(weaponFullDef); + + InfoStringToWeaponConverter converter( + infoString, weaponFullDef, zone->m_script_strings, memory, manager, weapon_fields, std::extent_v); + if (!converter.Convert()) + { + std::cerr << "Failed to parse weapon: \"" << assetName << "\"\n"; + return true; + } + + weaponFullDef->weapCompleteDef.szInternalName = memory->Dup(assetName.c_str()); + + CalculateWeaponFields(weaponFullDef, memory); + + manager->AddAsset(ASSET_TYPE_WEAPON, + assetName, + &weaponFullDef->weapCompleteDef, + converter.GetDependencies(), + converter.GetUsedScriptStrings(), + converter.GetIndirectAssetReferences()); + + return true; + } +} // namespace + +void* AssetLoaderWeapon::CreateEmptyAsset(const std::string& assetName, MemoryManager* memory) +{ + auto* weapon = memory->Create(); + memset(weapon, 0, sizeof(WeaponCompleteDef)); + weapon->szInternalName = memory->Dup(assetName.c_str()); + return weapon; +} + +bool AssetLoaderWeapon::CanLoadFromGdt() const +{ + return true; +} + +bool AssetLoaderWeapon::LoadFromGdt( + const std::string& assetName, IGdtQueryable* gdtQueryable, MemoryManager* memory, IAssetLoadingManager* manager, Zone* zone) const +{ + const auto* gdtEntry = gdtQueryable->GetGdtEntryByGdfAndName(ObjConstants::GDF_FILENAME_WEAPON, assetName); + if (gdtEntry == nullptr) + return false; + + InfoString infoString; + if (!infoString.FromGdtProperties(*gdtEntry)) + { + std::cerr << "Failed to read weapon gdt entry: \"" << assetName << "\"\n"; + return true; + } + + return LoadFromInfoString(infoString, assetName, memory, manager, zone); +} + +bool AssetLoaderWeapon::CanLoadFromRaw() const +{ + return true; +} + +bool AssetLoaderWeapon::LoadFromRaw( + const std::string& assetName, ISearchPath* searchPath, MemoryManager* memory, IAssetLoadingManager* manager, Zone* zone) const +{ + const auto fileName = std::format("weapons/{}", assetName); + const auto file = searchPath->Open(fileName); + if (!file.IsOpen()) + return false; + + InfoString infoString; + if (!infoString.FromStream(ObjConstants::INFO_STRING_PREFIX_WEAPON, *file.m_stream)) + { + std::cerr << "Failed to read weapon raw file: \"" << fileName << "\"\n"; + return true; + } + + return LoadFromInfoString(infoString, assetName, memory, manager, zone); +} diff --git a/src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderWeapon.h b/src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderWeapon.h new file mode 100644 index 00000000..e1cc9bca --- /dev/null +++ b/src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderWeapon.h @@ -0,0 +1,20 @@ +#pragma once + +#include "AssetLoading/BasicAssetLoader.h" +#include "Game/IW5/IW5.h" +#include "SearchPath/ISearchPath.h" + +namespace IW5 +{ + class AssetLoaderWeapon final : public BasicAssetLoader + { + public: + _NODISCARD void* CreateEmptyAsset(const std::string& assetName, MemoryManager* memory) override; + _NODISCARD bool CanLoadFromGdt() const override; + bool LoadFromGdt( + const std::string& assetName, IGdtQueryable* gdtQueryable, MemoryManager* memory, IAssetLoadingManager* manager, Zone* zone) const override; + _NODISCARD bool CanLoadFromRaw() const override; + bool + LoadFromRaw(const std::string& assetName, ISearchPath* searchPath, MemoryManager* memory, IAssetLoadingManager* manager, Zone* zone) const override; + }; +} // namespace IW5 diff --git a/src/ObjLoading/Game/IW5/ObjLoaderIW5.cpp b/src/ObjLoading/Game/IW5/ObjLoaderIW5.cpp index ed384789..cab88cc9 100644 --- a/src/ObjLoading/Game/IW5/ObjLoaderIW5.cpp +++ b/src/ObjLoading/Game/IW5/ObjLoaderIW5.cpp @@ -7,6 +7,7 @@ #include "AssetLoaders/AssetLoaderRawFile.h" #include "AssetLoaders/AssetLoaderScriptFile.h" #include "AssetLoaders/AssetLoaderStringTable.h" +#include "AssetLoaders/AssetLoaderWeapon.h" #include "AssetLoaders/AssetLoaderWeaponAttachment.h" #include "AssetLoading/AssetLoadingManager.h" #include "Game/IW5/GameAssetPoolIW5.h" @@ -57,7 +58,7 @@ ObjLoader::ObjLoader() REGISTER_ASSET_LOADER(AssetLoaderMenuDef) REGISTER_ASSET_LOADER(AssetLoaderLocalizeEntry) REGISTER_ASSET_LOADER(AssetLoaderWeaponAttachment) - REGISTER_ASSET_LOADER(BASIC_LOADER(ASSET_TYPE_WEAPON, WeaponCompleteDef)) + REGISTER_ASSET_LOADER(AssetLoaderWeapon) REGISTER_ASSET_LOADER(BASIC_LOADER(ASSET_TYPE_FX, FxEffectDef)) REGISTER_ASSET_LOADER(BASIC_LOADER(ASSET_TYPE_IMPACT_FX, FxImpactTable)) REGISTER_ASSET_LOADER(BASIC_LOADER(ASSET_TYPE_SURFACE_FX, SurfaceFxTable)) diff --git a/src/ObjWriting/Game/IW5/AssetDumpers/AssetDumperWeapon.cpp b/src/ObjWriting/Game/IW5/AssetDumpers/AssetDumperWeapon.cpp index d6b84ce4..519d00f5 100644 --- a/src/ObjWriting/Game/IW5/AssetDumpers/AssetDumperWeapon.cpp +++ b/src/ObjWriting/Game/IW5/AssetDumpers/AssetDumperWeapon.cpp @@ -688,6 +688,8 @@ bool AssetDumperWeapon::ShouldDump(XAssetInfo* asset) void AssetDumperWeapon::DumpAsset(AssetDumpingContext& context, XAssetInfo* asset) { + // TODO: only dump infostring fields when non-default + // Only dump raw when no gdt available if (context.m_gdt) { From 1601c8748120fafbe6810a243a275d73e5166c1b Mon Sep 17 00:00:00 2001 From: Jan Date: Sun, 21 Apr 2024 19:34:28 +0200 Subject: [PATCH 08/10] chore: set internal name earlier --- src/ObjLoading/Game/IW4/AssetLoaders/AssetLoaderWeapon.cpp | 4 +--- src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderWeapon.cpp | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/ObjLoading/Game/IW4/AssetLoaders/AssetLoaderWeapon.cpp b/src/ObjLoading/Game/IW4/AssetLoaders/AssetLoaderWeapon.cpp index ccc8df95..a4edbf06 100644 --- a/src/ObjLoading/Game/IW4/AssetLoaders/AssetLoaderWeapon.cpp +++ b/src/ObjLoading/Game/IW4/AssetLoaders/AssetLoaderWeapon.cpp @@ -258,7 +258,6 @@ namespace weapon->weapDef.parallelBounce = weapon->parallelBounce; weapon->weapDef.perpendicularBounce = weapon->perpendicularBounce; weapon->weapDef.locationDamageMultipliers = weapon->locationDamageMultipliers; - weapon->weapCompleteDef.szInternalName = ""; for (const auto& field : weapon_fields) { @@ -397,6 +396,7 @@ namespace auto* weaponFullDef = memory->Create(); memset(weaponFullDef, 0, sizeof(WeaponFullDef)); InitWeaponFullDef(weaponFullDef); + weaponFullDef->weapCompleteDef.szInternalName = memory->Dup(assetName.c_str()); InfoStringToWeaponConverter converter( infoString, weaponFullDef, zone->m_script_strings, memory, manager, weapon_fields, std::extent_v); @@ -406,8 +406,6 @@ namespace return true; } - weaponFullDef->weapCompleteDef.szInternalName = memory->Dup(assetName.c_str()); - CalculateWeaponFields(weaponFullDef, memory); manager->AddAsset(ASSET_TYPE_WEAPON, diff --git a/src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderWeapon.cpp b/src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderWeapon.cpp index 07274569..0da50ba4 100644 --- a/src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderWeapon.cpp +++ b/src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderWeapon.cpp @@ -683,7 +683,6 @@ namespace weapon->weapDef.parallelBounce = weapon->parallelBounce; weapon->weapDef.perpendicularBounce = weapon->perpendicularBounce; weapon->weapDef.locationDamageMultipliers = weapon->locationDamageMultipliers; - weapon->weapCompleteDef.szInternalName = ""; for (const auto& field : weapon_fields) { @@ -822,6 +821,7 @@ namespace auto* weaponFullDef = memory->Create(); memset(weaponFullDef, 0, sizeof(WeaponFullDef)); InitWeaponFullDef(weaponFullDef); + weaponFullDef->weapCompleteDef.szInternalName = memory->Dup(assetName.c_str()); InfoStringToWeaponConverter converter( infoString, weaponFullDef, zone->m_script_strings, memory, manager, weapon_fields, std::extent_v); @@ -831,8 +831,6 @@ namespace return true; } - weaponFullDef->weapCompleteDef.szInternalName = memory->Dup(assetName.c_str()); - CalculateWeaponFields(weaponFullDef, memory); manager->AddAsset(ASSET_TYPE_WEAPON, From 94aee91592ed68f1f0ce699dd9413f9d6a9473bc Mon Sep 17 00:00:00 2001 From: Jan Date: Sun, 21 Apr 2024 19:34:52 +0200 Subject: [PATCH 09/10] chore: remove unnecessary weapon defaults --- .../Game/IW4/AssetLoaders/AssetLoaderWeapon.cpp | 15 ++------------- .../Game/IW5/AssetLoaders/AssetLoaderWeapon.cpp | 15 ++------------- 2 files changed, 4 insertions(+), 26 deletions(-) diff --git a/src/ObjLoading/Game/IW4/AssetLoaders/AssetLoaderWeapon.cpp b/src/ObjLoading/Game/IW4/AssetLoaders/AssetLoaderWeapon.cpp index a4edbf06..87659970 100644 --- a/src/ObjLoading/Game/IW4/AssetLoaders/AssetLoaderWeapon.cpp +++ b/src/ObjLoading/Game/IW4/AssetLoaders/AssetLoaderWeapon.cpp @@ -332,17 +332,6 @@ namespace if (!weaponDef.worldLastShotEjectEffect) weaponDef.worldLastShotEjectEffect = weaponDef.worldShellEjectEffect; - if (!weaponDef.raiseSound.name) - weaponDef.raiseSound.name = CreateSoundAliasListName("weap_raise", memory); - if (!weaponDef.putawaySound.name) - weaponDef.putawaySound.name = CreateSoundAliasListName("weap_putaway", memory); - if (!weaponDef.pickupSound.name) - weaponDef.pickupSound.name = CreateSoundAliasListName("weap_pickup", memory); - if (!weaponDef.ammoPickupSound.name) - weaponDef.ammoPickupSound.name = CreateSoundAliasListName("weap_ammo_pickup", memory); - if (!weaponDef.emptyFireSound.name) - weaponDef.emptyFireSound.name = CreateSoundAliasListName("weap_dryfire_smg_npc", memory); - if (weaponCompleteDef.iAdsTransInTime <= 0) weaponDef.fOOPosAnimLength[0] = 0.0033333334f; else @@ -353,9 +342,9 @@ namespace else weaponDef.fOOPosAnimLength[1] = 1.0f / static_cast(weaponCompleteDef.iAdsTransOutTime); - if (weaponDef.fMaxDamageRange <= 0.0f) + if (weaponDef.fMaxDamageRange < 0.0f) weaponDef.fMaxDamageRange = 999999.0f; - if (weaponDef.fMinDamageRange <= 0.0f) + if (weaponDef.fMinDamageRange < 0.0f) weaponDef.fMinDamageRange = 999999.12f; if (weaponDef.enemyCrosshairRange > 15000.0f) diff --git a/src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderWeapon.cpp b/src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderWeapon.cpp index 0da50ba4..25c3d866 100644 --- a/src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderWeapon.cpp +++ b/src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderWeapon.cpp @@ -757,17 +757,6 @@ namespace if (!weaponDef.worldLastShotEjectEffect) weaponDef.worldLastShotEjectEffect = weaponDef.worldShellEjectEffect; - if (!weaponDef.raiseSound.name) - weaponDef.raiseSound.name = CreateSoundAliasListName("weap_raise", memory); - if (!weaponDef.putawaySound.name) - weaponDef.putawaySound.name = CreateSoundAliasListName("weap_putaway", memory); - if (!weaponDef.pickupSound.name) - weaponDef.pickupSound.name = CreateSoundAliasListName("weap_pickup", memory); - if (!weaponDef.ammoPickupSound.name) - weaponDef.ammoPickupSound.name = CreateSoundAliasListName("weap_ammo_pickup", memory); - if (!weaponDef.emptyFireSound.name) - weaponDef.emptyFireSound.name = CreateSoundAliasListName("weap_dryfire_smg_npc", memory); - if (weaponCompleteDef.iAdsTransInTime <= 0) weaponDef.fOOPosAnimLength[0] = 0.0033333334f; else @@ -778,9 +767,9 @@ namespace else weaponDef.fOOPosAnimLength[1] = 1.0f / static_cast(weaponCompleteDef.iAdsTransOutTime); - if (weaponDef.fMaxDamageRange <= 0.0f) + if (weaponDef.fMaxDamageRange < 0.0f) weaponDef.fMaxDamageRange = 999999.0f; - if (weaponDef.fMinDamageRange <= 0.0f) + if (weaponDef.fMinDamageRange < 0.0f) weaponDef.fMinDamageRange = 999999.12f; if (weaponDef.enemyCrosshairRange > 15000.0f) From 6ca7d892f9f9785b64adec06e6c9e6e1e3b12df7 Mon Sep 17 00:00:00 2001 From: Jan Date: Sun, 21 Apr 2024 19:35:04 +0200 Subject: [PATCH 10/10] fix: iw5 weapon attachment and override loading --- .../Game/IW5/AssetLoaders/AssetLoaderWeapon.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderWeapon.cpp b/src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderWeapon.cpp index 25c3d866..f14bb2c0 100644 --- a/src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderWeapon.cpp +++ b/src/ObjLoading/Game/IW5/AssetLoaders/AssetLoaderWeapon.cpp @@ -237,6 +237,7 @@ namespace } m_weapon->weapCompleteDef.animOverrides = animOverrides; + m_weapon->weapCompleteDef.numAnimOverrides = valueArray.size(); return true; } @@ -271,6 +272,7 @@ namespace } m_weapon->weapCompleteDef.soundOverrides = soundOverrides; + m_weapon->weapCompleteDef.numSoundOverrides = valueArray.size(); return true; } @@ -308,6 +310,7 @@ namespace } m_weapon->weapCompleteDef.fxOverrides = fxOverrides; + m_weapon->weapCompleteDef.numFxOverrides = valueArray.size(); return true; } @@ -341,6 +344,7 @@ namespace } m_weapon->weapCompleteDef.reloadOverrides = reloadOverrides; + m_weapon->weapCompleteDef.numReloadStateTimerOverrides = valueArray.size(); return true; } @@ -396,6 +400,7 @@ namespace m_weapon->weapCompleteDef.notetrackOverrides = static_cast(m_memory->Alloc(sizeof(NoteTrackToSoundEntry) * overrideVector.size())); memcpy(m_weapon->weapCompleteDef.notetrackOverrides, overrideVector.data(), sizeof(NoteTrackToSoundEntry) * overrideVector.size()); + m_weapon->weapCompleteDef.numNotetrackOverrides = overrideVector.size(); return true; } @@ -411,7 +416,7 @@ namespace const auto* scope = m_weapon->scopes[i]; if (scope && scope->szInternalName && value == scope->szInternalName) { - attachment.scope = static_cast(i); + attachment.scope = static_cast(i + 1); return true; } } @@ -421,7 +426,7 @@ namespace const auto* underBarrel = m_weapon->underBarrels[i]; if (underBarrel && underBarrel->szInternalName && value == underBarrel->szInternalName) { - attachment.underBarrel = static_cast(i); + attachment.underBarrel = static_cast(i + 1); return true; } } @@ -672,6 +677,9 @@ namespace weapon->weapCompleteDef.weapDef = &weapon->weapDef; weapon->weapCompleteDef.hideTags = weapon->hideTags; weapon->weapCompleteDef.szXAnims = weapon->szXAnims; + weapon->weapCompleteDef.scopes = weapon->scopes; + weapon->weapCompleteDef.underBarrels = weapon->underBarrels; + weapon->weapCompleteDef.others = weapon->others; weapon->weapDef.gunXModel = weapon->gunXModel; weapon->weapDef.szXAnimsRightHanded = weapon->szXAnimsRightHanded; weapon->weapDef.szXAnimsLeftHanded = weapon->szXAnimsLeftHanded;