Initial DNAMP3 fixes various missing loader imps

This commit is contained in:
Phillip Stephens 2018-07-08 16:03:35 -07:00
parent ef1f9d564c
commit 56a48cd624
22 changed files with 418 additions and 17 deletions

View File

@ -264,8 +264,8 @@ ResExtractor<PAKBridge> PAKBridge::LookupExtractor(const PAK& pak, const PAK::En
{
switch (entry.type)
{
case SBIG('CAUD'):
return {CAUD::Extract, {_S(".yaml")}};
// case SBIG('CAUD'):
// return {CAUD::Extract, {_S(".yaml")}};
case SBIG('STRG'):
return {STRG::Extract, {_S(".yaml")}};
case SBIG('TXTR'):

View File

@ -157,7 +157,9 @@ struct SpecMP2 : SpecBase
m_cookPath(project.getProjectCookedPath(SpecEntMP2), _S("MP2")),
m_pakRouter(*this, m_workPath, m_cookPath),
m_idRestorer({project.getProjectWorkingPath(), "MP2/!original_ids.yaml"}, project)
{}
{
setThreadProject();
}
void buildPaks(nod::Node& root,
const std::vector<hecl::SystemString>& args,

View File

@ -25,6 +25,90 @@ static logvisor::Module Log("urde::SpecMP3");
extern hecl::Database::DataSpecEntry SpecEntMP3;
extern hecl::Database::DataSpecEntry SpecEntMP3ORIG;
static const std::unordered_set<uint64_t> IndividualOrigIDs =
{
};
struct OriginalIDs
{
static void Generate(PAKRouter<DNAMP3::PAKBridge>& pakRouter, hecl::Database::Project& project)
{
std::unordered_set<UniqueID64> addedIDs;
std::vector<UniqueID64> originalIDs;
pakRouter.enumerateResources([&](const DNAMP3::PAK::Entry* ent) {
if (ent->type == FOURCC('MLVL') ||
ent->type == FOURCC('SCAN') ||
ent->type == FOURCC('MREA') ||
IndividualOrigIDs.find(ent->id.toUint64()) != IndividualOrigIDs.end())
{
if (addedIDs.find(ent->id) == addedIDs.cend())
{
addedIDs.insert(ent->id);
originalIDs.push_back(ent->id);
}
}
return true;
});
std::sort(originalIDs.begin(), originalIDs.end());
athena::io::YAMLDocWriter yamlW("MP3OriginalIDs");
for (const UniqueID64& id : originalIDs)
{
hecl::ProjectPath path = pakRouter.getWorking(id);
yamlW.writeString(id.toString().c_str(), path.getRelativePathUTF8());
}
hecl::ProjectPath path(project.getProjectWorkingPath(), "MP3/!original_ids.yaml");
path.makeDirChain(false);
athena::io::FileWriter fileW(path.getAbsolutePath());
yamlW.finish(&fileW);
}
static void Cook(const hecl::ProjectPath& inPath, const hecl::ProjectPath& outPath)
{
hecl::Database::Project& project = inPath.getProject();
athena::io::YAMLDocReader r;
athena::io::FileReader fr(inPath.getAbsolutePath());
if (!fr.isOpen() || !r.parse(&fr))
return;
std::vector<std::pair<UniqueID64, UniqueID64>> originalIDs;
originalIDs.reserve(r.getRootNode()->m_mapChildren.size());
for (const auto& node : r.getRootNode()->m_mapChildren)
{
char* end = const_cast<char*>(node.first.c_str());
u32 id = strtoul(end, &end, 16);
if (end != node.first.c_str() + 8)
continue;
hecl::ProjectPath path(project.getProjectWorkingPath(), node.second->m_scalarString.c_str());
originalIDs.push_back(std::make_pair(id, path.hash().val32()));
}
std::sort(originalIDs.begin(), originalIDs.end(),
[](const std::pair<UniqueID64, UniqueID64>& a, const std::pair<UniqueID64, UniqueID64>& b) {
return a.first < b.first;
});
athena::io::FileWriter w(outPath.getAbsolutePath());
w.writeUint32Big(originalIDs.size());
for (const auto& idPair : originalIDs)
{
idPair.first.write(w);
idPair.second.write(w);
}
std::sort(originalIDs.begin(), originalIDs.end(),
[](const std::pair<UniqueID64, UniqueID64>& a, const std::pair<UniqueID64, UniqueID64>& b) {
return a.second < b.second;
});
for (const auto& idPair : originalIDs)
{
idPair.second.write(w);
idPair.first.write(w);
}
}
};
struct SpecMP3 : SpecBase
{
bool checkStandaloneID(const char* id) const
@ -52,6 +136,13 @@ struct SpecMP3 : SpecBase
hecl::ProjectPath m_feWorkPath;
hecl::ProjectPath m_feCookPath;
PAKRouter<DNAMP3::PAKBridge> m_fePakRouter;
IDRestorer<UniqueID64> m_idRestorer;
void setThreadProject()
{
SpecBase::setThreadProject();
UniqueIDBridge::SetIDRestorer(&m_idRestorer);
}
SpecMP3(const hecl::Database::DataSpecEntry* specEntry, hecl::Database::Project& project, bool pc)
: SpecBase(specEntry, project, pc),
@ -60,7 +151,11 @@ struct SpecMP3 : SpecBase
m_pakRouter(*this, m_workPath, m_cookPath),
m_feWorkPath(project.getProjectWorkingPath(), _S("fe")),
m_feCookPath(project.getProjectCookedPath(SpecEntMP3), _S("fe")),
m_fePakRouter(*this, m_feWorkPath, m_feCookPath) {}
m_fePakRouter(*this, m_feWorkPath, m_feCookPath),
m_idRestorer({project.getProjectWorkingPath(), "MP3/!original_ids.yaml"}, project)
{
setThreadProject();
}
void buildPaks(nod::Node& root,
const std::vector<hecl::SystemString>& args,
@ -419,6 +514,9 @@ struct SpecMP3 : SpecBase
}
process.waitUntilComplete();
/* Generate original ID mapping for MLVL and SCAN entries - marks complete project */
OriginalIDs::Generate(m_pakRouter, m_project);
}
if (doMPTFE)
@ -595,6 +693,21 @@ struct SpecMP3 : SpecBase
FCookProgress progress)
{
}
UniqueID64 newToOriginal(urde::CAssetId id) const
{
if (UniqueID64 origId = m_idRestorer.newToOriginal({id.Value(), true}))
return {origId.toUint64(), true};
return {uint32_t(id.Value()), true};
}
urde::CAssetId originalToNew(UniqueID64 id) const
{
if (UniqueID64 newId = m_idRestorer.originalToNew(id))
return newId.toUint64();
return id.toUint64();
}
};
hecl::Database::DataSpecEntry SpecEntMP3

View File

@ -216,7 +216,7 @@ bool CResLoader::FindResource(CAssetId id) const
return true;
}
Log.report(logvisor::Error, "Unable to find asset %08X", id);
Log.report(logvisor::Fatal, "Unable to find asset %08X", id);
return false;
}

View File

@ -38,6 +38,19 @@ CCameraShakeData::CCameraShakeData(float duration, float magnitude)
SCameraShakePoint{1, 0.f, 0.f, 0.5f * duration, 2.f}})
{}
CCameraShakeData::CCameraShakeData(CInputStream& in)
{
in.readUint32Big();
in.readFloatBig();
in.readFloatBig();
in.readFloatBig();
in.readFloatBig();
in.readFloatBig();
in.readFloatBig();
in.readBool();
BuildProjectileCameraShake(0.5f, 0.75f);
}
CCameraShakeData CCameraShakeData::BuildLandingCameraShakeData(float duration, float magnitude)
{
return {duration, 100.f, 0, zeus::CVector3f::skZero,

View File

@ -61,6 +61,7 @@ public:
const CCameraShakerComponent& shaker1, const CCameraShakerComponent& shaker2,
const CCameraShakerComponent& shaker3);
CCameraShakeData(float duration, float magnitude);
CCameraShakeData(CInputStream&);
static CCameraShakeData BuildLandingCameraShakeData(float duration, float magnitude);
static CCameraShakeData BuildProjectileCameraShake(float duration, float magnitude);
static CCameraShakeData BuildMissileCameraShake(float duration, float magnitude, float sfxDistance,

View File

@ -13,6 +13,8 @@ set(MP1_WORLD_SOURCES
CMetroidBeta.hpp CMetroidBeta.cpp
CMetroid.hpp CMetroid.cpp
CMetaree.hpp CMetaree.cpp
CSeedling.hpp CSeedling.cpp
CRidley.hpp CRidley.cpp
CPuddleToadGamma.hpp CPuddleToadGamma.cpp
CFlaahgraProjectile.hpp CFlaahgraProjectile.cpp)

View File

@ -11,7 +11,7 @@ CParasite::CParasite(TUniqueId uid, std::string_view name, EFlavorType flavor, c
float, float, float, float, float, float, float, float, float, float, bool, u32, const CDamageVulnerability &,
const CParasiteInfo &, u16, u16, u16, u32, u32, float, const CActorParameters &aParams)
: CWallWalker(ECharacter::Parasite, uid, name, flavor, info, xf, std::move(mData), pInfo, EMovementType::Ground, EColliderType::One, EBodyType::WallWalker,
aParams, -1, false)
aParams, -1, 0)
{
}

View File

@ -0,0 +1,30 @@
#include "MP1/World/CRidley.hpp"
#include "TCastTo.hpp"
namespace urde
{
namespace MP1
{
CRidleyData::CRidleyData(CInputStream& in, u32 propCount)
: x0_(in), x4_(in), x8_(in), xc_(in), x10_(in), x14_(in), x18_(in), x1c_(in), x20_(in), x24_(in), x28_(in)
, x2c_(in), x30_(in), x34_(in.readFloatBig()), x38_(in.readFloatBig()), x3c_(in.readFloatBig()), x40_(in.readFloatBig())
, x44_(in), x48_(in), x64_(in), xa8_(CSfxManager::TranslateSFXID(in.readUint32Big())), xac_(in), xb0_(in)
, xcc_(in), x1a0_(in), x1a4_(in), x1c0_(in), x294_(CSfxManager::TranslateSFXID(in.readUint32Big()))
, x298_(in), x2b4_(in), x388_(in.readFloatBig()), x38c_(in.readFloatBig()), x390_(in), x3ac_(in.readFloatBig())
, x3b0_(in), x3cc_(in.readFloatBig()), x3d0_(in), x3f4_(in.readFloatBig()), x3f8_(CSfxManager::TranslateSFXID(in.readUint32Big()))
, x3fc_(propCount > 47 ? CDamageInfo(in) : x48_)
{
}
CRidley::CRidley(TUniqueId uid, std::string_view name, const CEntityInfo& info,
const zeus::CTransform& xf, CModelData&& mData, const CPatternedInfo& pInfo, const CActorParameters& actParms, CInputStream& in, u32 propCount)
: CPatterned(ECharacter::Ridley, uid, name, EFlavorType::Zero, info, xf, std::move(mData), pInfo,
EMovementType::Flyer, EColliderType::Zero, EBodyType::Flyer, actParms, 2)
, x568_(in, propCount)
{
}
void CRidley::Accept(IVisitor &visitor) { visitor.Visit(this); }
}
}

View File

@ -0,0 +1,72 @@
#ifndef __URDE_MP1_CRIDLEY_HPP__
#define __URDE_MP1_CRIDLEY_HPP__
#include "Camera/CCameraShakeData.hpp"
#include "Weapon/CBeamInfo.hpp"
#include "World/CDamageInfo.hpp"
#include "World/CPatterned.hpp"
namespace urde
{
namespace MP1
{
class CRidleyData
{
CAssetId x0_;
CAssetId x4_;
CAssetId x8_;
CAssetId xc_;
CAssetId x10_;
CAssetId x14_;
CAssetId x18_;
CAssetId x1c_;
CAssetId x20_;
CAssetId x24_;
CAssetId x28_;
CAssetId x2c_;
CAssetId x30_;
float x34_;
float x38_;
float x3c_;
float x40_;
CAssetId x44_;
CDamageInfo x48_;
CBeamInfo x64_;
u16 xa8_;
CAssetId xac_;
CDamageInfo xb0_;
CCameraShakeData xcc_;
CAssetId x1a0_;
CDamageInfo x1a4_;
CCameraShakeData x1c0_;
u16 x294_;
CDamageInfo x298_;
CCameraShakeData x2b4_;
float x388_;
float x38c_;
CDamageInfo x390_;
float x3ac_;
CDamageInfo x3b0_;
float x3cc_;
CDamageInfo x3d0_;
float x3ec_;
CAssetId x3f0_;
float x3f4_;
u16 x3f8_;
CDamageInfo x3fc_;
public:
CRidleyData(CInputStream&, u32);
};
class CRidley : public CPatterned
{
CRidleyData x568_;
public:
CRidley(TUniqueId, std::string_view, const CEntityInfo&, const zeus::CTransform&, CModelData&&,
const CPatternedInfo&, const CActorParameters&, CInputStream&, u32);
void Accept(IVisitor&);
};
}
}
#endif // __URDE_MP1_CRIDLEY_HPP__

View File

@ -0,0 +1,23 @@
#include "MP1/World/CSeedling.hpp"
#include "TCastTo.hpp"
namespace urde
{
namespace MP1
{
CSeedling::CSeedling(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,
CModelData&& mData, const CPatternedInfo& pInfo, const CActorParameters& actParms,
CAssetId, CAssetId, const CDamageInfo&, const CDamageInfo&, float, float, float, float)
: CWallWalker(ECharacter::Seedling, uid, name, EFlavorType::Zero, info, xf, std::move(mData), pInfo, EMovementType::Ground, EColliderType::Zero, EBodyType::WallWalker, actParms, 0, 4)
{
}
void CSeedling::Accept(IVisitor& visitor)
{
visitor.Visit(this);
}
}
}

View File

@ -0,0 +1,22 @@
#ifndef __URDE_MP1_CSEEDLING_HPP__
#define __URDE_MP1_CSEEDLING_HPP__
#include "World/CWallWalker.hpp"
namespace urde
{
namespace MP1
{
class CSeedling : public CWallWalker
{
public:
CSeedling(TUniqueId, std::string_view, const CEntityInfo&, const zeus::CTransform&,
CModelData&&, const CPatternedInfo&, const CActorParameters&,
CAssetId, CAssetId, const CDamageInfo&, const CDamageInfo&,
float, float, float, float);
void Accept(IVisitor&);
};
}
}
#endif // __URDE_MP1_CSEEDLING_HPP__

View File

@ -4,11 +4,26 @@
namespace urde::MP1
{
CSpacePirate::CSpacePirateData::CSpacePirateData(urde::CInputStream& in, u32 propCount)
: x0_(in.readFloatBig()), x4_(in.readFloatBig()), x8_(in.readFloatBig()), xc_(in.readFloatBig())
, x10_(in.readFloatBig()), x14_(in.readFloatBig()), x18_(in.readUint32Big()), x1c_(in.readBool()), x20_(in)
, x48_(CSfxManager::TranslateSFXID(in.readUint32Big())), x4c_(in), x68_(in.readFloatBig()), x6c_(in)
, x94_(in.readFloatBig()), x98_(CSfxManager::TranslateSFXID(in.readUint32Big())), x9c_(in.readFloatBig())
, xa0_(in.readFloatBig()), xa4_(CSfxManager::TranslateSFXID(in.readUint32Big())), xa8_(in.readFloatBig())
, xac_(in.readUint32Big()), xb0_(in.readFloatBig()), xb4_(in.readFloatBig()), xb8_(in.readFloatBig())
, xbc_(in.readFloatBig()), xc0_(CSfxManager::TranslateSFXID(in.readUint32Big()))
, xc2_(CSfxManager::TranslateSFXID(in.readUint32Big())), xc4_(propCount > 35 ? in.readFloatBig() : 0.2f)
, xc8_(propCount > 36 ? in.readFloatBig() : 8.f)
{
}
CSpacePirate::CSpacePirate(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,
CModelData&& mData, const CActorParameters& aParams, const CPatternedInfo& pInfo, CInputStream& in,
u32 propCount)
: CPatterned(ECharacter::SpacePirate, uid, name, EFlavorType::Zero, info, xf, std::move(mData), pInfo, EMovementType::Ground,
CModelData&& mData, const CActorParameters& aParams, const CPatternedInfo& pInfo,
CInputStream& in, u32 propCount)
: CPatterned(ECharacter::SpacePirate, uid, name, EFlavorType::Zero, info, xf, std::move(mData), pInfo, EMovementType::Ground,
EColliderType::One, EBodyType::BiPedal, aParams, true)
, x568_(in, propCount)
{
}

View File

@ -2,11 +2,73 @@
#define __URDE_MP1_CSPACEPIRATE_HPP__
#include "World/CPatterned.hpp"
#include "Weapon/CProjectileInfo.hpp"
namespace urde::MP1
{
class CSpacePirate : public CPatterned
{
class CSpacePirateData
{
float x0_;
float x4_;
float x8_;
float xc_;
float x10_;
float x14_;
u32 x18_;
bool x1c_;
CProjectileInfo x20_;
u16 x48_;
CDamageInfo x4c_;
float x68_;
CProjectileInfo x6c_;
float x94_;
u16 x98_;
float x9c_;
float xa0_;
u16 xa4_;
float xa8_;
u32 xac_;
float xb0_;
float xb4_;
float xb8_;
float xbc_;
u16 xc0_;
u16 xc2_;
float xc4_;
float xc8_;
public:
CSpacePirateData(CInputStream&, u32);
};
CSpacePirateData x568_;
union
{
struct
{
bool x634_24_ : 1;
bool x634_25_ : 1;
bool x634_26_ : 1;
bool x634_27_ : 1;
bool x634_28_ : 1;
bool x634_29_ : 1;
bool x634_30_ : 1;
bool x634_31_ : 1;
bool x635_24_ : 1;
bool x635_25_ : 1;
bool x635_26_ : 1;
bool x635_27_ : 1;
bool x635_28_ : 1;
bool x635_29_ : 1;
bool x635_30_ : 1;
bool x635_31_ : 1;
};
u32 _dummy = 0;
};
public:
CSpacePirate(TUniqueId, std::string_view, const CEntityInfo&, const zeus::CTransform&, CModelData&&,
const CActorParameters&, const CPatternedInfo&, CInputStream&, u32);

View File

@ -6,11 +6,16 @@
namespace urde
{
CProjectileInfo::CProjectileInfo(urde::CInputStream& in)
: x0_weaponDescription(g_SimplePool->GetObj({SBIG('WPSC'), CAssetId(in)}))
, xc_damageInfo(in)
{
}
CProjectileInfo::CProjectileInfo(CAssetId proj, const CDamageInfo & dInfo)
: x0_weaponDescription(g_SimplePool->GetObj({SBIG('WPSC'), proj}))
, xc_damageInfo(dInfo)
{
}
zeus::CVector3f CProjectileInfo::PredictInterceptPos(const zeus::CVector3f &, const zeus::CVector3f &, const CPlayer &, bool)

View File

@ -14,6 +14,7 @@ class CProjectileInfo
TToken<CWeaponDescription> x0_weaponDescription;
CDamageInfo xc_damageInfo;
public:
CProjectileInfo(CInputStream&);
CProjectileInfo(CAssetId, const CDamageInfo&);
zeus::CVector3f PredictInterceptPos(const zeus::CVector3f&, const zeus::CVector3f&, const CPlayer&, bool);

View File

@ -130,7 +130,7 @@ void CScriptWorldTeleporter::StartTransition(CStateManager& mgr)
transMgr->DisableTransition();
break;
case ETeleporterType::Elevator:
if (x50_playerAnim.GetACSFile().IsValid() && x50_playerAnim.GetCharacter() != -1)
if (x50_playerAnim.GetACSFile().IsValid() && x50_playerAnim.GetCharacter() != u32(-1))
{
transMgr->EnableTransition(CAnimRes(x50_playerAnim.GetACSFile(), x50_playerAnim.GetCharacter(),
x5c_playerScale, x50_playerAnim.GetInitialAnimation(),true),

View File

@ -5,7 +5,7 @@ namespace urde
CWallWalker::CWallWalker(ECharacter chr, TUniqueId uid, std::string_view name, EFlavorType flavType,
const CEntityInfo& eInfo, const zeus::CTransform& xf,
CModelData&& mData, const CPatternedInfo& pInfo, EMovementType mType,
EColliderType colType, EBodyType bType, const CActorParameters& aParms, s32 w1, bool w2)
EColliderType colType, EBodyType bType, const CActorParameters& aParms, s32 w1, u32 w2)
: CPatterned(chr, uid, name, flavType, eInfo, xf, std::move(mData), pInfo, mType, colType, bType, aParms, w1)
{}
}

View File

@ -10,7 +10,7 @@ class CWallWalker : public CPatterned
public:
CWallWalker(ECharacter, TUniqueId, std::string_view, EFlavorType, const CEntityInfo&, const zeus::CTransform&,
CModelData&&, const CPatternedInfo&, EMovementType, EColliderType, EBodyType,
const CActorParameters&, s32, bool);
const CActorParameters&, s32, u32);
};
}
#endif // __URDE_CWALLWALKER_HPP__

View File

@ -483,7 +483,7 @@ bool CWorldTransManager::WaitForModelsAndTextures()
if (tag.type == FOURCC('TXTR') || tag.type == FOURCC('CMDL'))
g_SimplePool->GetObj(tag).GetObj();
}
return true;
return false;
}
void CWorldTransManager::SfxStop()

View File

@ -11,12 +11,14 @@
#include "CScriptActorRotate.hpp"
#include "CScriptAiJumpPoint.hpp"
#include "CScriptAreaAttributes.hpp"
#include "MP1/World/CSeedling.hpp"
#include "CScriptBeam.hpp"
#include "CScriptCameraBlurKeyframe.hpp"
#include "CScriptCameraFilterKeyframe.hpp"
#include "CScriptCameraHint.hpp"
#include "CScriptCameraHintTrigger.hpp"
#include "CAmbientAI.hpp"
#include "MP1/World/CRidley.hpp"
#include "CScriptCameraPitchVolume.hpp"
#include "CTeamAiMgr.hpp"
#include "CSnakeWeedSwarm.hpp"
@ -2733,12 +2735,50 @@ CEntity* ScriptLoader::LoadTryclops(CStateManager& mgr, CInputStream& in, int pr
CEntity* ScriptLoader::LoadRidley(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info)
{
return nullptr;
if (!EnsurePropertyCount(propCount, 47, "Ridley"))
return nullptr;
SScaledActorHead aHead = LoadScaledActorHead(in, mgr);
auto pair = CPatternedInfo::HasCorrectParameterCount(in);
if (!pair.first)
return nullptr;
CPatternedInfo pInfo(in, pair.second);
CActorParameters actParms = LoadActorParameters(in);
if (!pInfo.GetAnimationParameters().GetACSFile().IsValid())
return nullptr;
CModelData mData(CAnimRes(pInfo.GetAnimationParameters().GetACSFile(), pInfo.GetAnimationParameters().GetCharacter(), aHead.x40_scale, pInfo.GetAnimationParameters().GetInitialAnimation(), true));
return new MP1::CRidley(mgr.AllocateUniqueId(), aHead.x0_name, info, aHead.x10_transform, std::move(mData), pInfo, actParms, in, propCount);
}
CEntity* ScriptLoader::LoadSeedling(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info)
{
return nullptr;
if (!EnsurePropertyCount(propCount, 14, "Seedling"))
return nullptr;
SScaledActorHead aHead = LoadScaledActorHead(in, mgr);
auto pair = CPatternedInfo::HasCorrectParameterCount(in);
if (!pair.first)
return nullptr;
CPatternedInfo pInfo(in, pair.second);
if (!pInfo.GetAnimationParameters().GetACSFile().IsValid())
return nullptr;
CActorParameters actParms = LoadActorParameters(in);
CAssetId needleId(in);
CAssetId weaponId(in);
CDamageInfo dInfo1(in);
CDamageInfo dInfo2(in);
float f1 = in.readFloatBig();
float f2 = in.readFloatBig();
float f3 = in.readFloatBig();
float f4 = in.readFloatBig();
CModelData mData(CAnimRes(pInfo.GetAnimationParameters().GetACSFile(), pInfo.GetAnimationParameters().GetCharacter(), aHead.x40_scale, pInfo.GetAnimationParameters().GetInitialAnimation(), true));
return new MP1::CSeedling(mgr.AllocateUniqueId(), aHead.x0_name, info, aHead.x10_transform, std::move(mData), pInfo, actParms, needleId, weaponId, dInfo1, dInfo2, f1, f2, f3, f4);
}
CEntity* ScriptLoader::LoadThermalHeatFader(CStateManager& mgr, CInputStream& in, int propCount,

2
nod

@ -1 +1 @@
Subproject commit 42589c36046e0cc4b00ab14e74f24024d75346fe
Subproject commit 1ad101897c865524a9129c2afa665fa0ab2db3cc