Rewrote SCAN asset handling + loading

This commit is contained in:
Aruki
2019-01-12 21:28:04 -08:00
parent a174548750
commit a1d94cc58f
37 changed files with 342 additions and 1296 deletions

View File

@@ -119,7 +119,6 @@ HEADERS += \
Resource/CMaterialPass.h \
Resource/CMaterialSet.h \
Resource/CResource.h \
Resource/CScan.h \
Resource/CTexture.h \
Resource/CWorld.h \
Resource/EResType.h \
@@ -252,7 +251,10 @@ HEADERS += \
Tweaks/CTweakManager.h \
Tweaks/CTweakData.h \
Tweaks/CTweakLoader.h \
Tweaks/CTweakCooker.h
Tweaks/CTweakCooker.h \
Resource/Scan/CScan.h \
Resource/Scan/SScanParametersMP1.h \
Resource/Scan/ELogbookCategory.h
# Source Files
SOURCES += \
@@ -367,7 +369,8 @@ SOURCES += \
Resource/StringTable/CStringTable.cpp \
Tweaks/CTweakManager.cpp \
Tweaks/CTweakLoader.cpp \
Tweaks/CTweakCooker.cpp
Tweaks/CTweakCooker.cpp \
Resource/Scan/CScan.cpp
# Codegen
CODEGEN_DIR = $$EXTERNALS_DIR/CodeGen

View File

@@ -3,9 +3,10 @@
#include "CResourceIterator.h"
#include "Core/Resource/CAudioMacro.h"
#include "Core/Resource/CFont.h"
#include "Core/Resource/CScan.h"
#include "Core/Resource/CWorld.h"
#include "Core/Resource/Animation/CAnimSet.h"
#include "Core/Resource/Scan/CScan.h"
#include "Core/Resource/Scan/SScanParametersMP1.h"
#include "Core/Resource/Script/CScriptLayer.h"
#include <Common/Math/MathUtil.h>
@@ -311,10 +312,15 @@ void GenerateAssetNames(CGameProject *pProj)
ApplyGeneratedName(pEntry, pEntry->DirectoryPath(), ScanName);
CScan *pScan = (CScan*) pEntry->Load();
if (pScan && pScan->ScanText())
if (pScan)
{
CResourceEntry *pStringEntry = pScan->ScanText()->Entry();
ApplyGeneratedName(pStringEntry, pStringEntry->DirectoryPath(), ScanName);
CAssetID StringID = pScan->ScanStringPropertyRef();
CResourceEntry* pStringEntry = gpResourceStore->FindEntry(StringID);
if (pStringEntry)
{
ApplyGeneratedName(pStringEntry, pStringEntry->DirectoryPath(), ScanName);
}
}
}
}
@@ -609,16 +615,10 @@ void GenerateAssetNames(CGameProject *pProj)
CScan *pScan = (CScan*) It->Load();
TString ScanName;
if (pProj->Game() >= EGame::EchoesDemo)
{
CAssetID DisplayAsset = pScan->LogbookDisplayAssetID();
CResourceEntry *pEntry = pStore->FindEntry(DisplayAsset);
if (pEntry && pEntry->IsNamed()) ScanName = pEntry->Name();
}
if (ScanName.IsEmpty())
{
CStringTable *pString = pScan->ScanText();
CAssetID StringID = pScan->ScanStringPropertyRef().Get();
CStringTable *pString = (CStringTable*) gpResourceStore->LoadResource(StringID, EResourceType::StringTable);
if (pString) ScanName = pString->Entry()->Name();
}
@@ -626,13 +626,14 @@ void GenerateAssetNames(CGameProject *pProj)
if (!ScanName.IsEmpty() && pProj->Game() <= EGame::Prime)
{
CAssetID FrameID = pScan->GuiFrame();
CResourceEntry *pEntry = pStore->FindEntry(FrameID);
const SScanParametersMP1& kParms = *static_cast<SScanParametersMP1*>(pScan->ScanData().DataPointer());
CResourceEntry *pEntry = pStore->FindEntry(kParms.GuiFrame);
if (pEntry) ApplyGeneratedName(pEntry, pEntry->DirectoryPath(), "ScanFrame");
for (uint32 iImg = 0; iImg < 4; iImg++)
{
CAssetID ImageID = pScan->ScanImage(iImg);
CAssetID ImageID = kParms.ScanImages[iImg].Texture;
CResourceEntry *pImgEntry = pStore->FindEntry(ImageID);
if (pImgEntry) ApplyGeneratedName(pImgEntry, pImgEntry->DirectoryPath(), TString::Format("%s_Image%d", *ScanName, iImg));
}

View File

@@ -30,6 +30,70 @@ void IDependencyNode::GetAllResourceReferences(std::set<CAssetID>& rOutSet) cons
mChildren[iChild]->GetAllResourceReferences(rOutSet);
}
void IDependencyNode::ParseProperties(CResourceEntry* pParentEntry, CStructProperty* pProperties, void* pData)
{
// Recursive function for parsing dependencies in properties
for (uint32 PropertyIdx = 0; PropertyIdx < pProperties->NumChildren(); PropertyIdx++)
{
IProperty* pProp = pProperties->ChildByIndex(PropertyIdx);
EPropertyType Type = pProp->Type();
// Technically we aren't parsing array children, but it's not really worth refactoring this function
// to support it when there aren't any array properties that contain any asset references anyway...
if (Type == EPropertyType::Struct)
ParseProperties( pParentEntry, TPropCast<CStructProperty>(pProp), pData );
else if (Type == EPropertyType::Sound)
{
uint32 SoundID = TPropCast<CSoundProperty>(pProp)->Value(pData);
if (SoundID != -1)
{
CGameProject* pProj = pParentEntry->Project();
SSoundInfo Info = pProj->AudioManager()->GetSoundInfo(SoundID);
if (Info.pAudioGroup)
{
CPropertyDependency *pDep = new CPropertyDependency(pProp->IDString(true), Info.pAudioGroup->ID());
mChildren.push_back(pDep);
}
}
}
else if (Type == EPropertyType::Asset)
{
CAssetID ID = TPropCast<CAssetProperty>(pProp)->Value(pData);
if (ID.IsValid())
{
CPropertyDependency *pDep = new CPropertyDependency(pProp->IDString(true), ID);
mChildren.push_back(pDep);
}
}
else if (Type == EPropertyType::AnimationSet)
{
CAnimationParameters Params = TPropCast<CAnimationSetProperty>(pProp)->Value(pData);
CAssetID ID = Params.ID();
if (ID.IsValid())
{
// Character sets are removed starting in MP3, so we only need char property dependencies in Echoes and earlier
if (pProperties->Game() <= EGame::Echoes)
{
CCharPropertyDependency *pDep = new CCharPropertyDependency(pProp->IDString(true), ID, Params.CharacterIndex());
mChildren.push_back(pDep);
}
else
{
CPropertyDependency *pDep = new CPropertyDependency(pProp->IDString(true), ID);
mChildren.push_back(pDep);
}
}
}
}
}
// Serialization constructor
IDependencyNode* IDependencyNode::ArchiveConstructor(EDependencyNodeType Type)
{
@@ -149,76 +213,10 @@ CScriptInstanceDependency* CScriptInstanceDependency::BuildTree(CScriptObject *p
{
CScriptInstanceDependency *pInst = new CScriptInstanceDependency();
pInst->mObjectType = pInstance->ObjectTypeID();
ParseStructDependencies(pInst, pInstance, pInstance->Template()->Properties());
pInst->ParseProperties(pInstance->Area()->Entry(), pInstance->Template()->Properties(), pInstance->PropertyData());
return pInst;
}
void CScriptInstanceDependency::ParseStructDependencies(CScriptInstanceDependency* pInst, CScriptObject* pInstance, CStructProperty *pStruct)
{
// Recursive function for parsing script dependencies and loading them into the script instance dependency
void* pPropertyData = pInstance->PropertyData();
for (uint32 PropertyIdx = 0; PropertyIdx < pStruct->NumChildren(); PropertyIdx++)
{
IProperty *pProp = pStruct->ChildByIndex(PropertyIdx);
EPropertyType Type = pProp->Type();
// Technically we aren't parsing array children, but it's not really worth refactoring this function
// to support it when there aren't any array properties that contain any asset references anyway...
if (Type == EPropertyType::Struct)
ParseStructDependencies(pInst, pInstance, TPropCast<CStructProperty>(pProp));
else if (Type == EPropertyType::Sound)
{
uint32 SoundID = TPropCast<CSoundProperty>(pProp)->Value(pPropertyData);
if (SoundID != -1)
{
CGameProject *pProj = pInstance->Area()->Entry()->Project();
SSoundInfo Info = pProj->AudioManager()->GetSoundInfo(SoundID);
if (Info.pAudioGroup)
{
CPropertyDependency *pDep = new CPropertyDependency(pProp->IDString(true), Info.pAudioGroup->ID());
pInst->mChildren.push_back(pDep);
}
}
}
else if (Type == EPropertyType::Asset)
{
CAssetID ID = TPropCast<CAssetProperty>(pProp)->Value(pPropertyData);
if (ID.IsValid())
{
CPropertyDependency *pDep = new CPropertyDependency(pProp->IDString(true), ID);
pInst->mChildren.push_back(pDep);
}
}
else if (Type == EPropertyType::AnimationSet)
{
CAnimationParameters Params = TPropCast<CAnimationSetProperty>(pProp)->Value(pPropertyData);
CAssetID ID = Params.ID();
if (ID.IsValid())
{
// Character sets are removed starting in MP3, so we only need char property dependencies in Echoes and earlier
if (pStruct->Game() <= EGame::Echoes)
{
CCharPropertyDependency *pDep = new CCharPropertyDependency(pProp->IDString(true), ID, Params.CharacterIndex());
pInst->mChildren.push_back(pDep);
}
else
{
CPropertyDependency *pDep = new CPropertyDependency(pProp->IDString(true), ID);
pInst->mChildren.push_back(pDep);
}
}
}
}
}
// ************ CSetCharacterDependency ************
EDependencyNodeType CSetCharacterDependency::Type() const
{

View File

@@ -39,6 +39,7 @@ public:
virtual void Serialize(IArchive& rArc) = 0;
virtual void GetAllResourceReferences(std::set<CAssetID>& rOutSet) const;
virtual bool HasDependency(const CAssetID& rkID) const;
void ParseProperties(CResourceEntry* pParentEntry, CStructProperty* pProperties, void* pData);
// Serialization constructor
static IDependencyNode* ArchiveConstructor(EDependencyNodeType Type);
@@ -144,8 +145,6 @@ public:
// Static
static CScriptInstanceDependency* BuildTree(CScriptObject *pInstance);
protected:
static void ParseStructDependencies(CScriptInstanceDependency *pTree, CScriptObject* pInstance, CStructProperty *pStruct);
};
// Node representing an animset character. Indicates what index the character is within the animset.

View File

@@ -2,9 +2,10 @@
#define CANIMATIONPARAMETERS_H
#include "Core/Resource/TResPtr.h"
#include "Core/Resource/Model/CModel.h"
#include <Common/EGame.h>
class CModel;
class CAnimationParameters
{
EGame mGame;

View File

@@ -381,12 +381,12 @@ void CResTypeInfo::CResTypeInfoFactory::InitTypes()
{
CResTypeInfo *pType = new CResTypeInfo(EResourceType::StringTable, "String Table", "strg");
AddExtension(pType, "STRG", EGame::PrimeDemo, EGame::DKCReturns);
pType->mCanBeSerialized = true;
}
{
CResTypeInfo *pType = new CResTypeInfo(EResourceType::Texture, "Texture", "txtr");
AddExtension(pType, "TXTR", EGame::PrimeDemo, EGame::DKCReturns);
pType->mCanHaveDependencies = false;
pType->mCanBeSerialized = true;
}
{
CResTypeInfo *pType = new CResTypeInfo(EResourceType::Tweaks, "Tweak Data", "ctwk");

View File

@@ -1,122 +0,0 @@
#ifndef CSCAN_H
#define CSCAN_H
#include "CResource.h"
#include "TResPtr.h"
#include "Core/Resource/Animation/CAnimationParameters.h"
#include "Core/Resource/StringTable/CStringTable.h"
#include <Common/EGame.h>
class CScan : public CResource
{
DECLARE_RESOURCE_TYPE(Scan)
friend class CScanLoader;
public:
// This likely needs revising when MP2/MP3 support is added
enum class ELogbookCategory
{
None,
PirateData,
ChozoLore,
Creatures,
Research
};
struct SScanInfoSecondaryModel
{
CAssetID ModelID;
CAnimationParameters AnimParams;
TString AttachBoneName;
};
private:
// Common
TResPtr<CStringTable> mpStringTable;
bool mIsSlow;
bool mIsImportant;
ELogbookCategory mCategory;
// MP1
CAssetID mFrameID;
CAssetID mScanImageTextures[4];
// MP2/3
bool mUseLogbookModelPostScan;
CAssetID mPostOverrideTexture;
float mLogbookDefaultRotX;
float mLogbookDefaultRotZ;
float mLogbookScale;
CAssetID mLogbookModel;
CAnimationParameters mLogbookAnimParams;
CAnimationParameters mUnknownAnimParams;
std::vector<SScanInfoSecondaryModel> mSecondaryModels;
// MP3
std::vector<CAssetID> mDependencyList;
public:
CScan(CResourceEntry *pEntry = 0)
: CResource(pEntry)
, mpStringTable(nullptr)
, mIsSlow(false)
, mIsImportant(false)
, mCategory(ELogbookCategory::None)
{}
CDependencyTree* BuildDependencyTree() const
{
CDependencyTree *pTree = new CDependencyTree();
// Corruption's SCAN has a list of all assets - just grab that
if (Game() >= EGame::CorruptionProto)
{
for (uint32 iDep = 0; iDep < mDependencyList.size(); iDep++)
{
pTree->AddDependency(mDependencyList[iDep]);
}
return pTree;
}
// Otherwise add all the dependencies we need from the properties
if (Game() <= EGame::Prime)
pTree->AddDependency(mFrameID);
pTree->AddDependency(mpStringTable);
if (Game() <= EGame::Prime)
{
for (uint32 iImg = 0; iImg < 4; iImg++)
pTree->AddDependency(mScanImageTextures[iImg]);
}
else if (Game() <= EGame::Echoes)
{
pTree->AddDependency(mPostOverrideTexture);
pTree->AddDependency(mLogbookModel);
pTree->AddCharacterDependency(mLogbookAnimParams);
pTree->AddCharacterDependency(mUnknownAnimParams);
for (uint32 iSec = 0; iSec < mSecondaryModels.size(); iSec++)
{
const SScanInfoSecondaryModel& rkSecModel = mSecondaryModels[iSec];
pTree->AddDependency(rkSecModel.ModelID);
pTree->AddCharacterDependency(rkSecModel.AnimParams);
}
}
return pTree;
}
// Accessors
inline CStringTable* ScanText() const { return mpStringTable; }
inline bool IsImportant() const { return mIsImportant; }
inline bool IsSlow() const { return mIsSlow; }
inline ELogbookCategory LogbookCategory() const { return mCategory; }
inline CAssetID GuiFrame() const { return mFrameID; }
inline CAssetID ScanImage(uint32 ImgIndex) const { return mScanImageTextures[ImgIndex]; }
inline CAssetID LogbookDisplayAssetID() const { return (mLogbookAnimParams.ID().IsValid() ? mLogbookAnimParams.ID() : mLogbookModel); }
};
#endif // CSCAN_H

View File

@@ -1,306 +1,83 @@
#include "CScanLoader.h"
#include "Core/GameProject/CResourceStore.h"
#include "CScriptLoader.h"
#include <Common/Log.h>
CScanLoader::CScanLoader()
CScan* CScanLoader::LoadScanMP1(IInputStream& SCAN, CResourceEntry* pEntry)
{
}
CScan* CScanLoader::LoadScanMP1(IInputStream& rSCAN)
{
// Basic support at the moment - don't read animation/scan image data
mpScan->mFrameID = CAssetID(rSCAN, k32Bit);
mpScan->mpStringTable = gpResourceStore->LoadResource(rSCAN.ReadLong(), EResourceType::StringTable);
mpScan->mIsSlow = (rSCAN.ReadLong() != 0);
mpScan->mCategory = (CScan::ELogbookCategory) rSCAN.ReadLong();
mpScan->mIsImportant = (rSCAN.ReadByte() == 1);
for (uint32 iImg = 0; iImg < 4; iImg++)
{
mpScan->mScanImageTextures[iImg] = CAssetID(rSCAN, k32Bit);
rSCAN.Seek(0x18, SEEK_CUR);
}
return mpScan;
}
CScan* CScanLoader::LoadScanMP2(IInputStream& rSCAN)
{
// The SCAN format in MP2 embeds a SNFO object using the same format as SCLY
// However since the contents of the file are consistent there's no need to delegate to CScriptLoader
rSCAN.Skip(0x1);
uint32 NumInstances = rSCAN.ReadLong();
if (NumInstances != 1)
{
errorf("%s: SCAN has multiple instances", *rSCAN.GetSourceString());
return nullptr;
}
uint32 ScanInfoStart = rSCAN.Tell();
CFourCC SNFO(rSCAN);
if (SNFO != FOURCC('SNFO'))
{
errorf("%s [0x%X]: Unrecognized SCAN object type: %s", *rSCAN.GetSourceString(), ScanInfoStart, *SNFO.ToString());
return nullptr;
}
uint16 InstanceSize = rSCAN.ReadShort();
uint32 InstanceEnd = rSCAN.Tell() + InstanceSize;
rSCAN.Skip(0x4);
uint16 NumConnections = rSCAN.ReadShort();
if (NumConnections > 0)
{
warnf("%s [0x%X]: SNFO object in SCAN has connections", *rSCAN.GetSourceString(), ScanInfoStart);
rSCAN.Skip(NumConnections * 0xC);
}
uint32 BasePropID = rSCAN.ReadLong();
if (BasePropID != 0xFFFFFFFF)
{
errorf("%s [0x%X]: Invalid base property ID: 0x%08X", *rSCAN.GetSourceString(), rSCAN.Tell() - 4, BasePropID);
return nullptr;
}
rSCAN.Skip(0x2);
uint16 NumProperties = rSCAN.ReadShort();
switch (NumProperties)
{
case 0x14:
case 0xB:
mpScan = new CScan(mpEntry);
LoadParamsMP2(rSCAN, NumProperties);
break;
case 0x12:
case 0x15:
case 0x16:
mpScan = new CScan(mpEntry);
LoadParamsMP3(rSCAN, NumProperties);
break;
default:
errorf("%s [0x%X]: Invalid SNFO property count: 0x%X", *rSCAN.GetSourceString(), rSCAN.Tell() - 2, NumProperties);
return nullptr;
}
// Load MP3 dependency list
if (mpScan->Game() == EGame::Corruption)
{
rSCAN.GoTo(InstanceEnd);
uint32 NumDeps = rSCAN.ReadLong();
for (uint32 DepIdx = 0; DepIdx < NumDeps; DepIdx++)
{
rSCAN.Skip(4);
CAssetID ID(rSCAN, mpScan->Game());
mpScan->mDependencyList.push_back(ID);
}
}
return mpScan;
}
void CScanLoader::LoadParamsMP2(IInputStream& rSCAN, uint16 NumProperties)
{
// Function begins after the SNFO property count
mpScan->mSecondaryModels.resize(9);
for (uint32 iProp = 0; iProp < NumProperties; iProp++)
{
uint32 PropertyID = rSCAN.ReadLong();
uint16 PropertySize = rSCAN.ReadShort();
uint32 Next = rSCAN.Tell() + PropertySize;
switch (PropertyID)
{
case 0x2F5B6423:
mpScan->mpStringTable = gpResourceStore->LoadResource(rSCAN.ReadLong(), EResourceType::StringTable);
break;
case 0xC308A322:
mpScan->mIsSlow = (rSCAN.ReadLong() != 0);
break;
case 0x7B714814:
mpScan->mIsImportant = rSCAN.ReadBool();
break;
case 0x1733B1EC:
mpScan->mUseLogbookModelPostScan = rSCAN.ReadBool();
break;
case 0x53336141:
mpScan->mPostOverrideTexture = CAssetID(rSCAN, mVersion);
break;
case 0x3DE0BA64:
mpScan->mLogbookDefaultRotX = rSCAN.ReadFloat();
break;
case 0x2ADD6628:
mpScan->mLogbookDefaultRotZ = rSCAN.ReadFloat();
break;
case 0xD0C15066:
mpScan->mLogbookScale = rSCAN.ReadFloat();
break;
case 0xB7ADC418:
mpScan->mLogbookModel = CAssetID(rSCAN, mVersion);
break;
case 0x15694EE1:
mpScan->mLogbookAnimParams = CAnimationParameters(rSCAN, mVersion);
break;
case 0x58F9FE99:
mpScan->mUnknownAnimParams = CAnimationParameters(rSCAN, mVersion);
break;
case 0x1C5B4A3A:
LoadScanInfoSecondaryModel( rSCAN, mpScan->mSecondaryModels[0] );
break;
case 0x8728A0EE:
LoadScanInfoSecondaryModel( rSCAN, mpScan->mSecondaryModels[1] );
break;
case 0xF1CD99D3:
LoadScanInfoSecondaryModel( rSCAN, mpScan->mSecondaryModels[2] );
break;
case 0x6ABE7307:
LoadScanInfoSecondaryModel( rSCAN, mpScan->mSecondaryModels[3] );
break;
case 0x1C07EBA9:
LoadScanInfoSecondaryModel( rSCAN, mpScan->mSecondaryModels[4] );
break;
case 0x8774017D:
LoadScanInfoSecondaryModel( rSCAN, mpScan->mSecondaryModels[5] );
break;
case 0xF1913840:
LoadScanInfoSecondaryModel( rSCAN, mpScan->mSecondaryModels[6] );
break;
case 0x6AE2D294:
LoadScanInfoSecondaryModel( rSCAN, mpScan->mSecondaryModels[7] );
break;
case 0x1CE2091C:
LoadScanInfoSecondaryModel( rSCAN, mpScan->mSecondaryModels[8] );
break;
}
rSCAN.GoTo(Next);
}
mpScan->mCategory = CScan::ELogbookCategory::None;
}
void CScanLoader::LoadParamsMP3(IInputStream& rSCAN, uint16 NumProperties)
{
// Function begins after the SNFO property count
for (uint32 iProp = 0; iProp < NumProperties; iProp++)
{
uint32 PropertyID = rSCAN.ReadLong();
uint16 PropertySize = rSCAN.ReadShort();
uint32 Next = rSCAN.Tell() + PropertySize;
switch (PropertyID)
{
case 0x2F5B6423:
mpScan->mpStringTable = gpResourceStore->LoadResource(rSCAN.ReadLongLong(), EResourceType::StringTable);
break;
case 0xC308A322:
mpScan->mIsSlow = (rSCAN.ReadLong() != 0);
break;
case 0x7B714814:
mpScan->mIsImportant = (rSCAN.ReadByte() != 0);
break;
}
rSCAN.GoTo(Next);
}
mpScan->mCategory = CScan::ELogbookCategory::None;
}
void CScanLoader::LoadScanInfoSecondaryModel(IInputStream& rSCAN, CScan::SScanInfoSecondaryModel& rSecondaryModel)
{
uint16 NumProperties = rSCAN.ReadShort();
for (uint32 iProp = 0; iProp < NumProperties; iProp++)
{
uint32 PropertyID = rSCAN.ReadLong();
uint16 PropertySize = rSCAN.ReadShort();
uint32 Next = rSCAN.Tell() + PropertySize;
switch (PropertyID)
{
case 0x1F7921BC:
rSecondaryModel.ModelID = CAssetID(rSCAN, mVersion);
break;
case 0xCDD202D1:
rSecondaryModel.AnimParams = CAnimationParameters(rSCAN, mVersion);
break;
case 0x3EA2BED8:
rSecondaryModel.AttachBoneName = rSCAN.ReadString();
break;
}
rSCAN.GoTo(Next);
}
}
// ************ STATIC/PUBLIC ************
CScan* CScanLoader::LoadSCAN(IInputStream& rSCAN, CResourceEntry *pEntry)
{
if (!rSCAN.IsValid()) return nullptr;
/* Switching to EGame enum here isn't really useful unfortunately
* because the MP1 demo can be 1, 2, or 3, while MP1 is 5 and MP2+ is 2
* MP1 is the only one that starts with 5 so that is a consistent check for now
* Better version checks will be implemented when the other versions are
* better-understood. */
uint32 FileVersion = rSCAN.ReadLong();
uint32 Magic = rSCAN.ReadLong();
// Echoes+
if (FileVersion == FOURCC('SCAN'))
{
// The MP2 load function will check for MP3
CScanLoader Loader;
Loader.mVersion = EGame::Echoes;
Loader.mpEntry = pEntry;
if (Magic == 0x01000000) rSCAN.Seek(-4, SEEK_CUR); // The version number isn't present in the Echoes demo
return Loader.LoadScanMP2(rSCAN);
}
// Validate magic
uint Magic = SCAN.ReadLong();
if (Magic != 0x0BADBEEF)
{
errorf("%s: Invalid SCAN magic: 0x%08X", *rSCAN.GetSourceString(), Magic);
errorf("Invalid magic in SCAN asset: 0x%08X", Magic);
return nullptr;
}
if (FileVersion != 5)
{
errorf("%s: Unsupported SCAN version: 0x%X", *rSCAN.GetSourceString(), FileVersion);
return nullptr;
}
// MP1 SCAN - read the file!
CScanLoader Loader;
Loader.mVersion = EGame::Prime;
Loader.mpScan = new CScan(pEntry);
Loader.mpEntry = pEntry;
return Loader.LoadScanMP1(rSCAN);
// The SCAN format in MP2 and later games uses the script loader to load SCAN parameters.
// The MP1 format is not loaded the same way, as far as I'm aware, and is loaded the same
// way as a normal file format... however, since we support all games, we need to support
// the script object method for proper MP2/3 support (including dealing with property names/IDs).
// So, it's simplest to use the script loader to load the MP1 SCAN format as well... that enables
// us to just create one class for all SCAN assets that works for every game.
mpScan = new CScan(pEntry);
CScriptLoader::LoadStructData(SCAN, mpScan->ScanData());
return mpScan;
}
CScan* CScanLoader::LoadScanMP2(IInputStream& SCAN, CResourceEntry* pEntry)
{
// Validate version
uint Version = SCAN.ReadLong();
if (Version != 2)
{
errorf("Unrecognized SCAN version: %d", Version);
return nullptr;
}
// The SCAN format in MP2 embeds a ScannableObjectInfo script object using the same file format as SCLY.
// As such we use CScriptLoader to load parameters, but since we don't actually want to create a script
// object, we will skip past the script object/layer header and just load the properties directly.
SCAN.Skip(0x17);
mpScan = new CScan(pEntry);
CScriptLoader::LoadStructData(SCAN, mpScan->ScanData());
return mpScan;
}
// ************ STATIC/PUBLIC ************
CScan* CScanLoader::LoadSCAN(IInputStream& SCAN, CResourceEntry *pEntry)
{
if (!SCAN.IsValid()) return nullptr;
// MP1 SCAN format starts with a version number and then follows with a magic.
// The demo can be 1, 2, or 3, while the final build is 5.
// The MP2 SCAN format starts with a 'SCAN' magic.
uint VersionCheck = SCAN.ReadLong();
// Echoes+
if (VersionCheck == FOURCC('SCAN'))
{
CScanLoader Loader;
return Loader.LoadScanMP2(SCAN, pEntry);
}
// MP1
else if (VersionCheck <= 5)
{
if (VersionCheck == 5)
{
CScanLoader Loader;
return Loader.LoadScanMP1(SCAN, pEntry);
}
else
{
errorf("%s: Unsupported SCAN version: %d", VersionCheck);
return nullptr;
}
}
else
{
errorf("Failed to identify SCAN version: 0x%X", VersionCheck);
return nullptr;
}
}

View File

@@ -7,15 +7,10 @@
class CScanLoader
{
TResPtr<CScan> mpScan;
CResourceEntry *mpEntry;
EGame mVersion;
CScanLoader();
CScan* LoadScanMP1(IInputStream& rSCAN);
CScan* LoadScanMP2(IInputStream& rSCAN);
void LoadParamsMP2(IInputStream& rSCAN, uint16 NumProperties);
void LoadParamsMP3(IInputStream& rSCAN, uint16 NumProperties);
void LoadScanInfoSecondaryModel(IInputStream& rSCAN, CScan::SScanInfoSecondaryModel& rSecondaryModel);
CScanLoader() {}
CScan* LoadScanMP1(IInputStream& SCAN, CResourceEntry* pEntry);
CScan* LoadScanMP2(IInputStream& SCAN, CResourceEntry* pEntry);
public:
static CScan* LoadSCAN(IInputStream& rSCAN, CResourceEntry *pEntry);

View File

@@ -0,0 +1,51 @@
#include "CScan.h"
CScan::CScan(CResourceEntry* pEntry /*= 0*/)
: CResource(pEntry)
{
CGameTemplate* pGameTemplate = NGameList::GetGameTemplate( Game() );
mpTemplate = pGameTemplate->FindMiscTemplate("ScannableObjectInfo");
ASSERT( mpTemplate != nullptr );
CStructProperty* pProperties = mpTemplate->Properties();
mPropertyData.resize( pProperties->DataSize() );
pProperties->Construct( mPropertyData.data() );
}
CStructRef CScan::ScanData() const
{
return CStructRef((void*) mPropertyData.data(), mpTemplate->Properties());
}
/** Convenience property accessors */
CAssetRef CScan::ScanStringPropertyRef() const
{
const uint kStringIdMP1 = 0x1;
const uint kStringIdMP2 = 0x2F5B6423;
IProperty* pProperty = mpTemplate->Properties()->ChildByID(
Game() <= EGame::Prime ? kStringIdMP1 : kStringIdMP2
);
return CAssetRef( (void*) mPropertyData.data(), pProperty );
}
CBoolRef CScan::IsCriticalPropertyRef() const
{
const uint kIsCriticalIdMP1 = 0x4;
const uint kIsCriticalIdMP2 = 0x7B714814;
IProperty* pProperty = mpTemplate->Properties()->ChildByID(
Game() <= EGame::Prime ? kIsCriticalIdMP1 : kIsCriticalIdMP2
);
return CBoolRef( (void*) mPropertyData.data(), pProperty );
}
/** CResource interface */
CDependencyTree* CScan::BuildDependencyTree() const
{
CDependencyTree* pTree = new CDependencyTree();
pTree->ParseProperties(Entry(), ScanData().Property(), ScanData().DataPointer());
return pTree;
}

View File

@@ -0,0 +1,34 @@
#ifndef CSCAN_H
#define CSCAN_H
#include <Common/Common.h>
#include "Core/Resource/Animation/CAnimationParameters.h"
#include "Core/Resource/Script/CGameTemplate.h"
#include "Core/Resource/Script/NGameList.h"
/** Scannable object parameters from SCAN assets */
class CScan : public CResource
{
DECLARE_RESOURCE_TYPE(Scan)
friend class CScanLoader;
friend class CScanCooker;
/** Script template specifying scan data layout */
CScriptTemplate* mpTemplate;
/** Scan property data */
std::vector<uint8> mPropertyData;
public:
CScan(CResourceEntry* pEntry = 0);
CStructRef ScanData() const;
/** Convenience property accessors */
CAssetRef ScanStringPropertyRef() const;
CBoolRef IsCriticalPropertyRef() const;
/** CResource interface */
virtual CDependencyTree* BuildDependencyTree() const override;
};
#endif // CSCAN_H

View File

@@ -0,0 +1,14 @@
#ifndef ELOGBOOKCATEGORY_H
#define ELOGBOOKCATEGORY_H
/** Logbook category for scannable objects in MP1, used in SCAN and SAVW */
enum class ELogbookCategory
{
None = 0,
SpacePirateData = 1,
ChozoLore = 2,
Creatures = 3,
Research = 4
};
#endif // ELOGBOOKCATEGORY_H

View File

@@ -0,0 +1,60 @@
#ifndef SSCANPARAMETERSMP1_H
#define SSCANPARAMETERSMP1_H
#include "ELogbookCategory.h"
#include <Common/Common.h>
/** Struct mapping to SCAN property layout in MP1 */
enum class EScanSpeed
{
Normal = 0,
Slow = 1,
};
enum class EScanImagePane
{
Pane0 = 0,
Pane1 = 1,
Pane2 = 2,
Pane3 = 3,
Pane01 = 4,
Pane12 = 5,
Pane23 = 6,
Pane012 = 7,
Pane123 = 8,
Pane0123 = 9,
Pane4 = 10,
Pane5 = 11,
Pane6 = 12,
Pane7 = 13,
Pane45 = 14,
Pane56 = 15,
Pane67 = 16,
Pane456 = 17,
Pane567 = 18,
Pane4567 = 19,
None = -1
};
struct SScanImage
{
CAssetID Texture;
float AppearPercentage;
EScanImagePane Pane;
int32 AnimationCellWidth;
int32 AnimationCellHeight;
float AnimationSwapInterval;
float FadeDuration;
};
struct SScanParametersMP1
{
CAssetID GuiFrame;
CAssetID String;
EScanSpeed Speed;
ELogbookCategory LogbookCategory;
bool IsCritical;
SScanImage ScanImages[4];
};
#endif // SSCANPARAMETERSMP1_H

View File

@@ -2,6 +2,7 @@
#define CANIMATIONSETPROPERTY_H
#include "IProperty.h"
#include "Core/Resource/Animation/CAnimationParameters.h"
class CAnimationSetProperty : public TSerializeableTypedProperty< CAnimationParameters, EPropertyType::AnimationSet >
{

View File

@@ -1,7 +1,6 @@
#ifndef IPROPERTY_H
#define IPROPERTY_H
#include "Core/Resource/Animation/CAnimationParameters.h"
#include <Common/Common.h>
#include <Common/CFourCC.h>
#include <Common/Math/CVector3f.h>

View File

@@ -1,5 +1,6 @@
#include "CPointOfInterestExtra.h"
//@todo pull these values from tweaks instead of hardcoding them
const CColor CPointOfInterestExtra::skRegularColor = CColor::Integral(0xFF,0x70,0x00);
const CColor CPointOfInterestExtra::skImportantColor = CColor::Integral(0xFF,0x00,0x00);
@@ -19,14 +20,17 @@ CPointOfInterestExtra::CPointOfInterestExtra(CScriptObject *pInstance, CScene *p
void CPointOfInterestExtra::PropertyModified(IProperty* pProperty)
{
if (mScanProperty.Property() == pProperty)
{
mpScanData = gpResourceStore->LoadResource<CScan>( mScanProperty.Get() );
mScanIsCritical = (mpScanData ? mpScanData->IsCriticalPropertyRef() : CBoolRef());
}
}
void CPointOfInterestExtra::ModifyTintColor(CColor& Color)
{
if (mpScanData)
{
if (mpScanData->IsImportant()) Color *= skImportantColor;
if (mScanIsCritical) Color *= skImportantColor;
else Color *= skRegularColor;
}
}

View File

@@ -2,7 +2,7 @@
#define CPOINTOFINTERESTEXTRA_H
#include "CScriptExtra.h"
#include "Core/Resource/CScan.h"
#include "Core/Resource/Scan/CScan.h"
#include <Common/CColor.h>
class CPointOfInterestExtra : public CScriptExtra
@@ -10,6 +10,7 @@ class CPointOfInterestExtra : public CScriptExtra
// Tint POI billboard orange/red depending on scan importance
CAssetRef mScanProperty;
TResPtr<CScan> mpScanData;
CBoolRef mScanIsCritical;
public:
explicit CPointOfInterestExtra(CScriptObject *pInstance, CScene *pScene, CScriptNode *pParent = 0);