From e76437301345b2686064858944fc3971aa7a6336 Mon Sep 17 00:00:00 2001 From: Jan Date: Sun, 21 Apr 2024 18:44:59 +0200 Subject: [PATCH] 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) {