diff --git a/src/Core/Core.pro b/src/Core/Core.pro index 970d7a54..2c7e119a 100644 --- a/src/Core/Core.pro +++ b/src/Core/Core.pro @@ -256,6 +256,7 @@ HEADERS += \ Resource/Scan/CScan.h \ Resource/Scan/SScanParametersMP1.h \ Resource/Scan/ELogbookCategory.h \ + Resource/Cooker/CScanCooker.h \ NCoreTests.h # Source Files @@ -374,6 +375,7 @@ SOURCES += \ Tweaks/CTweakCooker.cpp \ Resource/Cooker/CStringCooker.cpp \ Resource/Scan/CScan.cpp \ + Resource/Cooker/CScanCooker.cpp \ NCoreTests.cpp # Codegen diff --git a/src/Core/GameProject/DependencyListBuilders.cpp b/src/Core/GameProject/DependencyListBuilders.cpp index d0d5470e..ffccea16 100644 --- a/src/Core/GameProject/DependencyListBuilders.cpp +++ b/src/Core/GameProject/DependencyListBuilders.cpp @@ -559,3 +559,82 @@ void CAreaDependencyListBuilder::EvaluateDependencyNode(CResourceEntry *pCurEntr EvaluateDependencyNode(pCurEntry, pNode->ChildByIndex(iChild), rOut, pAudioGroupsOut); } } + +// ************ CAssetDependencyListBuilder ************ +void CAssetDependencyListBuilder::BuildDependencyList(std::vector& OutAssets) +{ + mCharacterUsageMap.FindUsagesForAsset(mpResourceEntry); + EvaluateDependencyNode(mpResourceEntry, mpResourceEntry->Dependencies(), OutAssets); +} + +void CAssetDependencyListBuilder::AddDependency(const CAssetID& kID, std::vector& Out) +{ + CResourceEntry *pEntry = mpResourceEntry->ResourceStore()->FindEntry(kID); + if (!pEntry) return; + + EResourceType ResType = pEntry->ResourceType(); + + if (mUsedAssets.find(kID) != mUsedAssets.end()) + return; + + // Dependency is valid! Evaluate the node tree + if (ResType == EResourceType::AnimSet) + { + ASSERT(!mCurrentAnimSetID.IsValid()); + mCurrentAnimSetID = pEntry->ID(); + } + + EvaluateDependencyNode(pEntry, pEntry->Dependencies(), Out); + + if (ResType == EResourceType::AnimSet) + { + ASSERT(mCurrentAnimSetID.IsValid()); + mCurrentAnimSetID = CAssetID::InvalidID(mpResourceEntry->Game()); + } + + Out.push_back(kID); + mUsedAssets.insert(kID); +} + +void CAssetDependencyListBuilder::EvaluateDependencyNode(CResourceEntry* pCurEntry, IDependencyNode* pNode, std::vector& Out) +{ + if (!pNode) return; + EDependencyNodeType Type = pNode->Type(); + bool ParseChildren = false; + + if (Type == EDependencyNodeType::Resource || Type == EDependencyNodeType::ScriptProperty || Type == EDependencyNodeType::CharacterProperty) + { + CResourceDependency* pDep = static_cast(pNode); + AddDependency(pDep->ID(), Out); + } + + else if (Type == EDependencyNodeType::AnimEvent) + { + CAnimEventDependency* pDep = static_cast(pNode); + uint32 CharIndex = pDep->CharIndex(); + + if (CharIndex == -1 || mCharacterUsageMap.IsCharacterUsed(mCurrentAnimSetID, CharIndex)) + AddDependency(pDep->ID(), Out); + } + + else if (Type == EDependencyNodeType::SetCharacter) + { + CSetCharacterDependency* pChar = static_cast(pNode); + ParseChildren = mCharacterUsageMap.IsCharacterUsed(mCurrentAnimSetID, pChar->CharSetIndex()); + } + + else if (Type == EDependencyNodeType::SetAnimation) + { + CSetAnimationDependency* pAnim = static_cast(pNode); + ParseChildren = mCharacterUsageMap.IsAnimationUsed(mCurrentAnimSetID, pAnim); + } + + else + ParseChildren = true; + + if (ParseChildren) + { + for (uint32 iChild = 0; iChild < pNode->NumChildren(); iChild++) + EvaluateDependencyNode(pCurEntry, pNode->ChildByIndex(iChild), Out); + } +} diff --git a/src/Core/GameProject/DependencyListBuilders.h b/src/Core/GameProject/DependencyListBuilders.h index 90a3c2bc..b7062b65 100644 --- a/src/Core/GameProject/DependencyListBuilders.h +++ b/src/Core/GameProject/DependencyListBuilders.h @@ -96,5 +96,25 @@ public: void EvaluateDependencyNode(CResourceEntry *pCurEntry, IDependencyNode *pNode, std::list& rOut, std::set *pAudioGroupsOut); }; +// ************ CAssetDependencyListBuilder ************ +//@todo merge with CAreaDependencyListBuilder; code is very similar +class CAssetDependencyListBuilder +{ + CResourceEntry* mpResourceEntry; + CCharacterUsageMap mCharacterUsageMap; + std::set mUsedAssets; + CAssetID mCurrentAnimSetID; + +public: + CAssetDependencyListBuilder(CResourceEntry* pEntry) + : mpResourceEntry(pEntry) + , mCharacterUsageMap(pEntry->ResourceStore()) + {} + + void BuildDependencyList(std::vector& OutAssets); + void AddDependency(const CAssetID& kID, std::vector& Out); + void EvaluateDependencyNode(CResourceEntry* pCurEntry, IDependencyNode* pNode, std::vector& Out); +}; + #endif // DEPENDENCYLISTBUILDERS diff --git a/src/Core/NCoreTests.cpp b/src/Core/NCoreTests.cpp index a598e17e..2b02bea7 100644 --- a/src/Core/NCoreTests.cpp +++ b/src/Core/NCoreTests.cpp @@ -186,6 +186,13 @@ bool ValidateCooker(EResourceType ResourceType, bool DumpInvalidFileContents) DumpFile.WriteBytes( NewData.data(), NewData.size() ); DumpFile.Close(); } + + if( NumInvalid >= 100 ) + { + debugf( "Test aborted; at least 100 invalid resources. Checked %d resources, %d passed, %d failed", + NumValid + NumInvalid, NumValid, NumInvalid ); + return false; + } } // Test complete diff --git a/src/Core/Resource/Animation/CAnimationParameters.cpp b/src/Core/Resource/Animation/CAnimationParameters.cpp index 8f2dedd2..22df32b8 100644 --- a/src/Core/Resource/Animation/CAnimationParameters.cpp +++ b/src/Core/Resource/Animation/CAnimationParameters.cpp @@ -92,7 +92,7 @@ void CAnimationParameters::Write(IOutputStream& rSCLY) { CAssetID::skInvalidID32.Write(rSCLY); rSCLY.WriteLong(0); - rSCLY.WriteLong(0xFFFFFFFF); + rSCLY.WriteLong(0); } } diff --git a/src/Core/Resource/Cooker/CResourceCooker.h b/src/Core/Resource/Cooker/CResourceCooker.h index 02db485d..8c5f0573 100644 --- a/src/Core/Resource/Cooker/CResourceCooker.h +++ b/src/Core/Resource/Cooker/CResourceCooker.h @@ -4,6 +4,7 @@ #include "CAreaCooker.h" #include "CModelCooker.h" #include "CPoiToWorldCooker.h" +#include "CScanCooker.h" #include "CStringCooker.h" #include "CWorldCooker.h" @@ -25,6 +26,7 @@ public: { case EResourceType::Area: return CAreaCooker::CookMREA((CGameArea*) pRes, rOutput); case EResourceType::Model: return CModelCooker::CookCMDL((CModel*) pRes, rOutput); + case EResourceType::Scan: return CScanCooker::CookSCAN((CScan*) pRes, rOutput); case EResourceType::StaticGeometryMap: return CPoiToWorldCooker::CookEGMC((CPoiToWorld*) pRes, rOutput); case EResourceType::StringTable: return CStringCooker::CookSTRG((CStringTable*) pRes, rOutput); case EResourceType::Tweaks: return CTweakCooker::CookCTWK((CTweakData*) pRes, rOutput); diff --git a/src/Core/Resource/Cooker/CScanCooker.cpp b/src/Core/Resource/Cooker/CScanCooker.cpp new file mode 100644 index 00000000..c0e1d986 --- /dev/null +++ b/src/Core/Resource/Cooker/CScanCooker.cpp @@ -0,0 +1,63 @@ +#include "CScanCooker.h" +#include "Core/Resource/Cooker/CScriptCooker.h" +#include "Core/GameProject/DependencyListBuilders.h" + +bool CScanCooker::CookSCAN(CScan* pScan, IOutputStream& SCAN) +{ + // File header + if (pScan->Game() <= EGame::Prime) + { + // We currently do not support cooking for the MP1 demo build + ASSERT( pScan->Game() != EGame::PrimeDemo ); + SCAN.WriteLong( 5 ); // Version number; must be 5 + SCAN.WriteLong( 0x0BADBEEF ); // SCAN magic + + CStructRef ScanProperties = pScan->ScanData(); + CScriptCooker Cooker(pScan->Game()); + Cooker.WriteProperty(SCAN, ScanProperties.Property(), ScanProperties.DataPointer(), true); + } + else + { + SCAN.WriteFourCC( FOURCC('SCAN') ); // SCAN magic + SCAN.WriteLong( 2 ); // Version number; must be 2 + SCAN.WriteByte( 1 ); // Layer version number; must be 1 + SCAN.WriteLong( 1 ); // Instance count + + // Scans in MP2/3 are saved with the script object data format + // Write a dummy script object header here + SCAN.WriteLong( FOURCC('SNFO') ); // ScannableObjectInfo object ID + uint ScanInstanceSizeOffset = SCAN.Tell(); + SCAN.WriteShort( 0 ); // Object size + SCAN.WriteLong( 0 ); // Instance ID + SCAN.WriteShort( 0 ); // Link count + + CStructRef ScanProperties = pScan->ScanData(); + CScriptCooker Cooker(pScan->Game()); + Cooker.WriteProperty(SCAN, ScanProperties.Property(), ScanProperties.DataPointer(), false); + + uint ScanInstanceEnd = SCAN.Tell(); + uint ScanInstanceSize = ScanInstanceEnd - ScanInstanceSizeOffset - 2; + SCAN.GoTo(ScanInstanceSizeOffset); + SCAN.WriteShort( (uint16) ScanInstanceSize ); + SCAN.GoTo(ScanInstanceEnd); + + // Write dependency list + // @todo this output may not be 100% correct. Some dependencies seem to be conditionally excluded in base game. + // This may cause some assets to be unnecessarily loaded into memory ingame. + std::vector Dependencies; + CAssetDependencyListBuilder Builder(pScan->Entry()); + Builder.BuildDependencyList(Dependencies); + SCAN.WriteLong(Dependencies.size()); + + for (const CAssetID& kID : Dependencies) + { + CResourceEntry* pEntry = pScan->Entry()->ResourceStore()->FindEntry(kID); + ASSERT( pEntry != nullptr ); + + pEntry->CookedExtension().Write(SCAN); + kID.Write(SCAN); + } + } + + return true; +} diff --git a/src/Core/Resource/Cooker/CScanCooker.h b/src/Core/Resource/Cooker/CScanCooker.h new file mode 100644 index 00000000..3a473501 --- /dev/null +++ b/src/Core/Resource/Cooker/CScanCooker.h @@ -0,0 +1,15 @@ +#ifndef CSCANCOOKER_H +#define CSCANCOOKER_H + +#include "Core/Resource/Scan/CScan.h" + +/** Cooker class for writing game-compatible SCAN assets */ +class CScanCooker +{ + CScanCooker() {} + +public: + static bool CookSCAN(CScan* pScan, IOutputStream& SCAN); +}; + +#endif // CSCANCOOKER_H diff --git a/templates/PropertyMap.xml b/templates/PropertyMap.xml index 12b3bddb..b5ebe213 100644 --- a/templates/PropertyMap.xml +++ b/templates/PropertyMap.xml @@ -2383,7 +2383,7 @@ - + @@ -4383,7 +4383,7 @@ - + @@ -10323,7 +10323,7 @@ - + @@ -14491,7 +14491,7 @@ - + @@ -14895,7 +14895,7 @@ - + @@ -15579,7 +15579,7 @@ - + @@ -16367,7 +16367,7 @@ - + @@ -16671,7 +16671,7 @@ - + @@ -17611,7 +17611,7 @@ - + @@ -23063,7 +23063,7 @@ - + @@ -27411,7 +27411,7 @@ - + @@ -32239,7 +32239,7 @@ - + @@ -33775,7 +33775,7 @@ - + @@ -34171,7 +34171,7 @@ - + @@ -34255,7 +34255,7 @@ - + @@ -38571,7 +38571,7 @@ - + @@ -41695,7 +41695,7 @@ - +