diff --git a/Phobos.vcxproj b/Phobos.vcxproj
index 80f55b8b2d..6a900f9c42 100644
--- a/Phobos.vcxproj
+++ b/Phobos.vcxproj
@@ -38,7 +38,9 @@
+
+
@@ -66,6 +68,7 @@
+
@@ -75,6 +78,7 @@
+
@@ -117,7 +121,9 @@
+
+
diff --git a/src/Ext/Rules/Body.cpp b/src/Ext/Rules/Body.cpp
index 8f2ce0062b..d9f0772673 100644
--- a/src/Ext/Rules/Body.cpp
+++ b/src/Ext/Rules/Body.cpp
@@ -7,6 +7,7 @@
#include
#include
#include
+#include
template<> const DWORD Extension::Canary = 0x12341234;
std::unique_ptr RulesExt::Data = nullptr;
@@ -32,6 +33,7 @@ void RulesExt::LoadBeforeTypeData(RulesClass* pThis, CCINIClass* pINI)
{
RadTypeClass::LoadFromINIList(pINI);
ShieldTypeClass::LoadFromINIList(pINI);
+ AttachmentTypeClass::LoadFromINIList(pINI);
Data->LoadBeforeTypeData(pThis, pINI);
}
diff --git a/src/Ext/Techno/Body.cpp b/src/Ext/Techno/Body.cpp
index 43b5de7571..0caccbd2b7 100644
--- a/src/Ext/Techno/Body.cpp
+++ b/src/Ext/Techno/Body.cpp
@@ -10,6 +10,7 @@
#include
#include
+#include
template<> const DWORD Extension::Canary = 0x55555555;
TechnoExt::ExtContainer TechnoExt::ExtMap;
@@ -214,7 +215,7 @@ void TechnoExt::InitializeLaserTrails(TechnoClass* pThis)
{
for (auto const& entry: pTypeExt->LaserTrailData)
{
- if (auto const pLaserType = LaserTrailTypeClass::Array[entry.idxType].get())
+ if (auto const pLaserType = LaserTrailTypeClass::Array[entry.Type].get())
{
pExt->LaserTrails.push_back(std::make_unique(
pLaserType, pThis->Owner, entry.FLH, entry.IsOnTurret));
@@ -371,6 +372,100 @@ void TechnoExt::EatPassengers(TechnoClass* pThis)
}
}
+bool TechnoExt::AttachmentAI(TechnoClass* pThis)
+{
+ auto const pExt = TechnoExt::ExtMap.Find(pThis);
+ // auto const pTypeExt = TechnoTypeExt::ExtMap.Find(pThis->GetTechnoType());
+
+ if (pExt && pExt->ParentAttachment)
+ {
+ pExt->ParentAttachment->AI();
+ return true;
+ }
+
+ return false;
+}
+
+// Attaches this techno in a first available attachment "slot".
+// Returns true if the attachment is successful.
+bool TechnoExt::AttachTo(TechnoClass* pThis, TechnoClass* pParent)
+{
+ auto const pParentExt = TechnoExt::ExtMap.Find(pParent);
+
+ for (auto const& pAttachment: pParentExt->ChildAttachments)
+ {
+ if (pAttachment->AttachChild(pThis))
+ return true;
+ }
+
+ return false;
+}
+
+bool TechnoExt::DetachFromParent(TechnoClass* pThis, bool isForceDetachment)
+{
+ auto const pExt = TechnoExt::ExtMap.Find(pThis);
+ return pExt->ParentAttachment->DetachChild(isForceDetachment);
+}
+
+void TechnoExt::InitializeAttachments(TechnoClass* pThis)
+{
+ auto const pExt = TechnoExt::ExtMap.Find(pThis);
+ auto const pType = pThis->GetTechnoType();
+ auto const pTypeExt = TechnoTypeExt::ExtMap.Find(pType);
+
+ for (auto& entry : pTypeExt->AttachmentData)
+ {
+ pExt->ChildAttachments.push_back(std::make_unique(&entry, pThis, nullptr));
+ pExt->ChildAttachments.back()->Initialize();
+ }
+}
+
+void TechnoExt::HandleHostDestruction(TechnoClass* pThis)
+{
+ auto const pExt = TechnoExt::ExtMap.Find(pThis);
+ for (auto const& pAttachment: pExt->ChildAttachments)
+ pAttachment->Uninitialize();
+}
+
+void TechnoExt::UnlimboAttachments(TechnoClass* pThis)
+{
+ auto const pExt = TechnoExt::ExtMap.Find(pThis);
+ for (auto const& pAttachment: pExt->ChildAttachments)
+ pAttachment->Unlimbo();
+}
+
+void TechnoExt::LimboAttachments(TechnoClass* pThis)
+{
+ auto const pExt = TechnoExt::ExtMap.Find(pThis);
+ for (auto const& pAttachment: pExt->ChildAttachments)
+ pAttachment->Limbo();
+}
+
+bool TechnoExt::IsParentOf(TechnoClass* pThis, TechnoClass* pOtherTechno)
+{
+ auto const pExt = TechnoExt::ExtMap.Find(pThis);
+
+ if (!pOtherTechno)
+ return false;
+
+ for (auto const& pAttachment: pExt->ChildAttachments)
+ {
+ if (pAttachment->Child &&
+ (pAttachment->Child == pOtherTechno ||
+ TechnoExt::IsParentOf(pAttachment->Child, pOtherTechno)))
+ {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void TechnoExt::FireWeaponAtSelf(TechnoClass* pThis, WeaponTypeClass* pWeaponType)
+{
+ WeaponTypeExt::DetonateAt(pWeaponType, pThis, pThis);
+}
+
// =============================
// load / save
diff --git a/src/Ext/Techno/Body.h b/src/Ext/Techno/Body.h
index a7a6dfcd0d..582e25806f 100644
--- a/src/Ext/Techno/Body.h
+++ b/src/Ext/Techno/Body.h
@@ -7,6 +7,7 @@
#include
#include
+#include
class BulletClass;
@@ -25,6 +26,9 @@ class TechnoExt
Valueable LastKillWasTeamTarget;
TimerStruct PassengerDeletionTimer;
+ AttachmentClass* ParentAttachment;
+ ValueableVector> ChildAttachments;
+
ExtData(TechnoClass* OwnerObject) : Extension(OwnerObject)
, InterceptedBullet(nullptr)
, Shield()
@@ -32,6 +36,8 @@ class TechnoExt
, ReceiveDamage(false)
, LastKillWasTeamTarget(false)
, PassengerDeletionTimer(-1)
+ , ParentAttachment()
+ , ChildAttachments()
{ }
virtual ~ExtData() = default;
@@ -73,6 +79,19 @@ class TechnoExt
static CoordStruct GetBurstFLH(TechnoClass* pThis, int weaponIndex, bool& FLHFound);
+ static bool AttachmentAI(TechnoClass* pThis);
+ static bool AttachTo(TechnoClass* pThis, TechnoClass* pParent);
+ static bool DetachFromParent(TechnoClass* pThis, bool force = false);
+
+ static void InitializeAttachments(TechnoClass* pThis);
+ static void HandleHostDestruction(TechnoClass* pThis);
+ static void UnlimboAttachments(TechnoClass* pThis);
+ static void LimboAttachments(TechnoClass* pThis);
+
+ static bool IsParentOf(TechnoClass* pThis, TechnoClass* pOtherTechno);
+
+ static void FireWeaponAtSelf(TechnoClass* pThis, WeaponTypeClass* pWeaponType);
+
static void TransferMindControlOnDeploy(TechnoClass* pTechnoFrom, TechnoClass* pTechnoTo);
static void ApplyMindControlRangeLimit(TechnoClass* pThis);
diff --git a/src/Ext/Techno/Hooks.Shield.cpp b/src/Ext/Techno/Hooks.Shield.cpp
index a3df20d188..b762c553c5 100644
--- a/src/Ext/Techno/Hooks.Shield.cpp
+++ b/src/Ext/Techno/Hooks.Shield.cpp
@@ -125,6 +125,8 @@ DEFINE_HOOK(0x6F9E50, TechnoClass_AI_Shield, 0x5)
DEFINE_HOOK(0x71A88D, TemporalClass_AI_Shield, 0x0)
{
GET(TemporalClass*, pThis, ESI);
+
+ // Shield logic
if (auto const pTarget = pThis->Target)
{
const auto pExt = TechnoExt::ExtMap.Find(pTarget);
diff --git a/src/Ext/Techno/Hooks.TechnoAttachment.cpp b/src/Ext/Techno/Hooks.TechnoAttachment.cpp
new file mode 100644
index 0000000000..7002b42668
--- /dev/null
+++ b/src/Ext/Techno/Hooks.TechnoAttachment.cpp
@@ -0,0 +1,102 @@
+#include "Body.h"
+
+#include
+
+DEFINE_HOOK(0x4DA86E, FootClass_AI_UpdateAttachedLocomotion, 0x0)
+{
+ GET(FootClass* const, pThis, ESI);
+ auto const pExt = TechnoExt::ExtMap.Find(pThis);
+
+ if (!pExt->ParentAttachment)
+ pThis->Locomotor->Process();
+
+ return 0x4DA87A;
+}
+
+DEFINE_HOOK(0x710460, TechnoClass_Destroy_HandleAttachments, 0x6)
+{
+ GET(TechnoClass*, pThis, ECX);
+
+ TechnoExt::HandleHostDestruction(pThis);
+
+ auto const pExt = TechnoExt::ExtMap.Find(pThis);
+ if (pExt->ParentAttachment)
+ pExt->ParentAttachment->ChildDestroyed();
+
+ pExt->ParentAttachment = nullptr;
+
+ return 0;
+}
+
+DEFINE_HOOK(0x6F6F20, TechnoClass_Unlimbo_UnlimboAttachments, 0x6)
+{
+ GET(TechnoClass*, pThis, ESI);
+
+ TechnoExt::UnlimboAttachments(pThis);
+
+ return 0;
+}
+
+DEFINE_HOOK(0x6F6B1C, TechnoClass_Limbo_LimboAttachments, 0x6)
+{
+ GET(TechnoClass*, pThis, ESI);
+
+ TechnoExt::LimboAttachments(pThis);
+
+ return 0;
+}
+
+DEFINE_HOOK(0x73F528, UnitClass_CanEnterCell_SkipChildren, 0x0)
+{
+ enum { IgnoreOccupier = 0x73FC10, Continue = 0x73F530 };
+
+ GET(UnitClass*, pThis, EBX);
+ GET(TechnoClass*, pOccupier, ESI);
+
+ if (pThis == pOccupier || TechnoExt::IsParentOf(pThis, pOccupier))
+ return IgnoreOccupier;
+
+ return Continue;
+}
+
+DEFINE_HOOK(0x51C251, InfantryClass_CanEnterCell_SkipChildren, 0x0)
+{
+ enum { IgnoreOccupier = 0x51C70F, Continue = 0x51C259 };
+
+ GET(InfantryClass*, pThis, EBP);
+ GET(TechnoClass*, pOccupier, ESI);
+
+ if ((TechnoClass*)pThis == pOccupier || TechnoExt::IsParentOf((TechnoClass*)pThis, pOccupier))
+ return IgnoreOccupier;
+
+ return Continue;
+}
+
+DEFINE_HOOK(0x6CC763, SuperClass_Place_ChronoWarp_SkipChildren, 0x6)
+{
+ enum { Skip = 0x6CCCCA, Continue = 0 };
+
+ GET(FootClass* const, pFoot, ESI);
+ auto const pExt = TechnoExt::ExtMap.Find(pFoot);
+
+ return pExt->ParentAttachment ? Skip : Continue;
+}
+
+// DEFINE_HOOK(0x6CCCCA, SuperClass_Place_ChronoWarp_HandleAttachment, 0x0)
+// {
+// enum { Loop = 0x6CC742, Break = 0x6CCCD5 };
+//
+// GET(FootClass*, pFoot, ESI)
+//
+// pFoot = abstract_cast(pFoot->NextObject);
+//
+// return pFoot ? Loop : Break;
+// }
+
+// TODO
+// 0x4DEAE0 IC for footclass
+// 0x457C90 IC (forceshield) for buildings
+// 0x6CCCCA Chrono Warp
+// 0x4694BB Temporal warhead
+// 0x4696FB Locomotor warhead
+// ...
\ No newline at end of file
diff --git a/src/Ext/Techno/Hooks.cpp b/src/Ext/Techno/Hooks.cpp
index 9556cf8b29..c9c052c57d 100644
--- a/src/Ext/Techno/Hooks.cpp
+++ b/src/Ext/Techno/Hooks.cpp
@@ -27,11 +27,12 @@ DEFINE_HOOK(0x6F9E50, TechnoClass_AI, 0x5)
}
-DEFINE_HOOK(0x6F42F7, TechnoClass_Init_SetLaserTrails, 0x2)
+DEFINE_HOOK(0x6F42F7, TechnoClass_Init, 0x2)
{
GET(TechnoClass*, pThis, ESI);
TechnoExt::InitializeLaserTrails(pThis);
+ TechnoExt::InitializeAttachments(pThis);
return 0;
}
diff --git a/src/Ext/TechnoType/Body.cpp b/src/Ext/TechnoType/Body.cpp
index e05c14937c..751ebe6c46 100644
--- a/src/Ext/TechnoType/Body.cpp
+++ b/src/Ext/TechnoType/Body.cpp
@@ -78,6 +78,7 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI)
if (!pINI->GetSection(pSection))
return;
+ char tempBuffer[32];
INI_EX exINI(pINI);
this->HealthBar_Hide.Read(exINI, pSection, "HealthBar.Hide");
@@ -129,6 +130,35 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI)
this->OpenTopped_DamageMultiplier.Read(exINI, pSection, "OpenTopped.DamageMultiplier");
this->OpenTopped_WarpDistance.Read(exINI, pSection, "OpenTopped.WarpDistance");
+ // The following loop iterates over size + 1 INI entries so that the
+ // vector contents can be properly overriden via scenario rules - Kerbiter
+ for (size_t i = 0; i <= this->AttachmentData.size(); ++i)
+ {
+ NullableIdx type;
+ _snprintf_s(tempBuffer, sizeof(tempBuffer), "Attachment%d.Type", i);
+ type.Read(exINI, pSection, tempBuffer);
+
+ if (!type.isset())
+ continue;
+
+ NullableIdx technoType;
+ _snprintf_s(tempBuffer, sizeof(tempBuffer), "Attachment%d.TechnoType", i);
+ technoType.Read(exINI, pSection, tempBuffer);
+
+ Valueable flh;
+ _snprintf_s(tempBuffer, sizeof(tempBuffer), "Attachment%d.FLH", i);
+ flh.Read(exINI, pSection, tempBuffer);
+
+ Valueable isOnTurret;
+ _snprintf_s(tempBuffer, sizeof(tempBuffer), "Attachment%d.IsOnTurret", i);
+ isOnTurret.Read(exINI, pSection, tempBuffer);
+
+ if (i == AttachmentData.size())
+ this->AttachmentData.push_back({ ValueableIdx(type), technoType, flh, isOnTurret });
+ else
+ this->AttachmentData[i] = { ValueableIdx(type), technoType, flh, isOnTurret };
+ }
+
// Ares 0.A
this->GroupAs.Read(pINI, pSection, "GroupAs");
@@ -144,7 +174,6 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI)
this->TurretOffset.Read(exArtINI, pArtSection, "TurretOffset");
- char tempBuffer[32];
for (size_t i = 0; ; ++i)
{
NullableIdx trail;
@@ -236,8 +265,8 @@ void TechnoTypeExt::ExtData::Serialize(T& Stm)
.Process(this->OreGathering_Anims)
.Process(this->OreGathering_Tiberiums)
.Process(this->OreGathering_FramesPerDir)
- .Process(this->LaserTrailData)
.Process(this->DestroyAnim_Random)
+ .Process(this->LaserTrailData)
.Process(this->NotHuman_RandomDeathSequence)
.Process(this->DefaultDisguise)
.Process(this->WeaponBurstFLHs)
@@ -250,6 +279,7 @@ void TechnoTypeExt::ExtData::Serialize(T& Stm)
.Process(this->OpenTopped_RangeBonus)
.Process(this->OpenTopped_DamageMultiplier)
.Process(this->OpenTopped_WarpDistance)
+ .Process(this->AttachmentData)
;
}
void TechnoTypeExt::ExtData::LoadFromStream(PhobosStreamReader& Stm)
@@ -264,6 +294,8 @@ void TechnoTypeExt::ExtData::SaveToStream(PhobosStreamWriter& Stm)
this->Serialize(Stm);
}
+#pragma region Data entry save/load
+
bool TechnoTypeExt::ExtData::LaserTrailDataEntry::Load(PhobosStreamReader& stm, bool registerForChange)
{
return this->Serialize(stm);
@@ -278,12 +310,35 @@ template
bool TechnoTypeExt::ExtData::LaserTrailDataEntry::Serialize(T& stm)
{
return stm
- .Process(this->idxType)
+ .Process(this->Type)
.Process(this->FLH)
.Process(this->IsOnTurret)
.Success();
}
+bool TechnoTypeExt::ExtData::AttachmentDataEntry::Load(PhobosStreamReader& stm, bool registerForChange)
+{
+ return this->Serialize(stm);
+}
+
+bool TechnoTypeExt::ExtData::AttachmentDataEntry::Save(PhobosStreamWriter& stm) const
+{
+ return const_cast(this)->Serialize(stm);
+}
+
+template
+bool TechnoTypeExt::ExtData::AttachmentDataEntry::Serialize(T& stm)
+{
+ return stm
+ .Process(this->Type)
+ .Process(this->TechnoType)
+ .Process(this->FLH)
+ .Process(this->IsOnTurret)
+ .Success();
+}
+
+#pragma endregion
+
// =============================
// container
diff --git a/src/Ext/TechnoType/Body.h b/src/Ext/TechnoType/Body.h
index 160ba9da42..90239b19c4 100644
--- a/src/Ext/TechnoType/Body.h
+++ b/src/Ext/TechnoType/Body.h
@@ -7,6 +7,7 @@
#include
#include
+#include
class Matrix3D;
@@ -74,9 +75,26 @@ class TechnoTypeExt
Nullable OpenTopped_DamageMultiplier;
Nullable OpenTopped_WarpDistance;
+ struct AttachmentDataEntry
+ {
+ ValueableIdx Type;
+ NullableIdx TechnoType;
+ Valueable FLH;
+ Valueable IsOnTurret;
+
+ bool Load(PhobosStreamReader& stm, bool registerForChange);
+ bool Save(PhobosStreamWriter& stm) const;
+
+ private:
+ template
+ bool Serialize(T& stm);
+ };
+
+ ValueableVector AttachmentData;
+
struct LaserTrailDataEntry
{
- ValueableIdx idxType;
+ ValueableIdx Type;
Valueable FLH;
Valueable IsOnTurret;
@@ -137,7 +155,8 @@ class TechnoTypeExt
DefaultDisguise(),
OpenTopped_RangeBonus(),
OpenTopped_DamageMultiplier(),
- OpenTopped_WarpDistance()
+ OpenTopped_WarpDistance(),
+ AttachmentData()
{ }
virtual ~ExtData() = default;
diff --git a/src/Ext/WeaponType/Body.cpp b/src/Ext/WeaponType/Body.cpp
index cdc58bac35..9db52e77ba 100644
--- a/src/Ext/WeaponType/Body.cpp
+++ b/src/Ext/WeaponType/Body.cpp
@@ -1,5 +1,8 @@
#include "Body.h"
+#include
+#include
+
template<> const DWORD Extension::Canary = 0x22222222;
WeaponTypeExt::ExtContainer WeaponTypeExt::ExtMap;
@@ -89,6 +92,34 @@ bool WeaponTypeExt::SaveGlobals(PhobosStreamWriter& Stm)
.Success();
}
+void WeaponTypeExt::DetonateAt(WeaponTypeClass* pThis, ObjectClass* pTarget, TechnoClass* pOwner)
+{
+ if (BulletClass* pBullet = pThis->Projectile->CreateBullet(pTarget, pOwner,
+ pThis->Damage, pThis->Warhead, 0, pThis->Bright))
+ {
+ const CoordStruct& coords = pTarget->GetCoords();
+
+ pBullet->SetWeaponType(pThis);
+ pBullet->Limbo();
+ pBullet->SetLocation(coords);
+ pBullet->Explode(true);
+ pBullet->UnInit();
+ }
+}
+
+void WeaponTypeExt::DetonateAt(WeaponTypeClass* pThis, const CoordStruct& coords, TechnoClass* pOwner)
+{
+ if (BulletClass* pBullet = pThis->Projectile->CreateBullet(nullptr, pOwner,
+ pThis->Damage, pThis->Warhead, 0, pThis->Bright))
+ {
+ pBullet->SetWeaponType(pThis);
+ pBullet->Limbo();
+ pBullet->SetLocation(coords);
+ pBullet->Explode(true);
+ pBullet->UnInit();
+ }
+}
+
// =============================
// container
diff --git a/src/Ext/WeaponType/Body.h b/src/Ext/WeaponType/Body.h
index 932c4542d5..6a571ea9c8 100644
--- a/src/Ext/WeaponType/Body.h
+++ b/src/Ext/WeaponType/Body.h
@@ -71,4 +71,7 @@ class WeaponTypeExt
static bool SaveGlobals(PhobosStreamWriter& Stm);
static int nOldCircumference;
+
+ static void DetonateAt(WeaponTypeClass* pThis, ObjectClass* pTarget, TechnoClass* pSource = nullptr);
+ static void DetonateAt(WeaponTypeClass* pThis, const CoordStruct& coords, TechnoClass* pSource = nullptr);
};
diff --git a/src/Misc/Hooks.AI.cpp b/src/Misc/Hooks.AI.cpp
new file mode 100644
index 0000000000..bd1ed4456d
--- /dev/null
+++ b/src/Misc/Hooks.AI.cpp
@@ -0,0 +1,11 @@
+#include
+
+#include
+
+DEFINE_HOOK(0x55B6B3, LogicClass_AI_After, 0x5)
+{
+ for (auto const& attachment : AttachmentClass::Array)
+ attachment->AI();
+
+ return 0;
+}
\ No newline at end of file
diff --git a/src/New/Entity/AttachmentClass.cpp b/src/New/Entity/AttachmentClass.cpp
new file mode 100644
index 0000000000..44dc955a56
--- /dev/null
+++ b/src/New/Entity/AttachmentClass.cpp
@@ -0,0 +1,242 @@
+#include "AttachmentClass.h"
+
+#include
+#include
+#include
+#include
+
+#include
+
+#include
+
+std::vector AttachmentClass::Array;
+
+AttachmentTypeClass* AttachmentClass::GetType()
+{
+ return AttachmentTypeClass::Array[this->Data->Type].get();
+}
+
+TechnoTypeClass* AttachmentClass::GetChildType()
+{
+ return this->Data->TechnoType.isset()
+ ? TechnoTypeClass::Array()->GetItem(this->Data->TechnoType)
+ : nullptr;
+}
+
+void AttachmentClass::Initialize()
+{
+ if (this->Child)
+ return;
+
+ if (this->GetType()->RestoreAtCreation)
+ this->CreateChild();
+}
+
+void AttachmentClass::CreateChild()
+{
+ if (auto const pChildType = this->GetChildType())
+ {
+ if (this->Child = static_cast(pChildType->CreateObject(this->Parent->Owner)))
+ {
+ auto const pChildExt = TechnoExt::ExtMap.Find(this->Child);
+ pChildExt->ParentAttachment = this;
+ }
+ else
+ {
+ Debug::Log("[" __FUNCTION__ "] Failed to create child %s of parent %s!\n",
+ pChildType->ID, this->Parent->GetTechnoType()->ID);
+ }
+ }
+}
+
+void AttachmentClass::AI()
+{
+ AttachmentTypeClass* pType = this->GetType();
+
+ if (this->Child)
+ {
+ this->Child->SetLocation(TechnoExt::GetFLHAbsoluteCoords(
+ this->Parent, this->Data->FLH, this->Data->IsOnTurret));
+
+ this->Child->OnBridge = this->Parent->OnBridge;
+
+ DirStruct childDir = this->Data->IsOnTurret
+ ? this->Parent->SecondaryFacing.current() : this->Parent->PrimaryFacing.current();
+
+ this->Child->PrimaryFacing.set(childDir);
+
+ if (pType->InheritTilt)
+ {
+ this->Child->AngleRotatedForwards = this->Parent->AngleRotatedForwards;
+ this->Child->AngleRotatedSideways = this->Parent->AngleRotatedSideways;
+
+ // DriveLocomotionClass doesn't tilt only with angles set, hence why we
+ // do this monstrosity in order to inherit timer and ramp data - Kerbiter
+ FootClass* pParentAsFoot = abstract_cast(this->Parent);
+ FootClass* pChildAsFoot = abstract_cast(this->Child);
+ if (pParentAsFoot && pChildAsFoot)
+ {
+ auto pParentLoco = static_cast(pParentAsFoot->Locomotor.get());
+ auto pChildLoco = static_cast(pChildAsFoot->Locomotor.get());
+
+ CLSID locoCLSID;
+ if (SUCCEEDED(pParentLoco->GetClassID(&locoCLSID)) && locoCLSID == LocomotionClass::CLSIDs::Drive &&
+ SUCCEEDED(pChildLoco->GetClassID(&locoCLSID)) && locoCLSID == LocomotionClass::CLSIDs::Drive)
+ {
+ auto pParentDriveLoco = static_cast(pParentLoco);
+ auto pChildDriveLoco = static_cast(pChildLoco);
+
+ pChildDriveLoco->SlopeTimer = pParentDriveLoco->SlopeTimer;
+ pChildDriveLoco->Ramp1 = pParentDriveLoco->Ramp1;
+ pChildDriveLoco->Ramp2 = pParentDriveLoco->Ramp2;
+ }
+ }
+ }
+
+ if (pType->InheritStateEffects)
+ {
+ this->Child->CloakState = this->Parent->CloakState;
+ // this->Child->BeingWarpedOut = this->Parent->BeingWarpedOut;
+ this->Child->Deactivated = this->Parent->Deactivated;
+ // this->Child->Flash(this->Parent->Flashing.DurationRemaining);
+
+ this->Child->IronCurtainTimer = this->Parent->IronCurtainTimer;
+ this->Child->IdleActionTimer = this->Parent->IdleActionTimer;
+ this->Child->IronTintTimer = this->Parent->IronTintTimer;
+ this->Child->CloakDelayTimer = this->Parent->CloakDelayTimer;
+ // this->Child->ChronoLockRemaining = this->Parent->ChronoLockRemaining;
+ this->Child->Berzerk = this->Parent->Berzerk;
+ // this->Child->ChronoWarpedByHouse = this->Parent->ChronoWarpedByHouse;
+ this->Child->EMPLockRemaining = this->Parent->EMPLockRemaining;
+ this->Child->ShouldLoseTargetNow = this->Parent->ShouldLoseTargetNow;
+ }
+
+ if (pType->InheritOwner)
+ this->Child->SetOwningHouse(this->Parent->GetOwningHouse(), false);
+ }
+}
+
+// Doesn't call destructor (to be managed by smart pointers)
+void AttachmentClass::Uninitialize()
+{
+ if (this->Child)
+ {
+ auto pType = this->GetType();
+ if (pType->DestructionWeapon_Child.isset())
+ TechnoExt::FireWeaponAtSelf(this->Child, pType->DestructionWeapon_Child);
+
+ if (!this->Child->InLimbo && pType->ParentDestructionMission.isset())
+ this->Child->QueueMission(pType->ParentDestructionMission.Get(), false);
+
+ auto pChildExt = TechnoExt::ExtMap.Find(this->Child);
+ pChildExt->ParentAttachment = nullptr;
+ this->Child = nullptr;
+ }
+}
+
+void AttachmentClass::ChildDestroyed()
+{
+ AttachmentTypeClass* pType = this->GetType();
+ if (pType->DestructionWeapon_Parent.isset())
+ TechnoExt::FireWeaponAtSelf(this->Parent, pType->DestructionWeapon_Parent);
+
+ this->Child = nullptr;
+}
+
+void AttachmentClass::Unlimbo()
+{
+ if (this->Child)
+ {
+ CoordStruct childCoord = TechnoExt::GetFLHAbsoluteCoords(
+ this->Parent, this->Data->FLH, this->Data->IsOnTurret);
+
+ Direction::Value childDir = this->Data->IsOnTurret
+ ? this->Parent->SecondaryFacing.current().value256() : this->Parent->PrimaryFacing.current().value256();
+
+ ++Unsorted::IKnowWhatImDoing;
+ this->Child->Unlimbo(childCoord, childDir);
+ --Unsorted::IKnowWhatImDoing;
+ }
+}
+
+void AttachmentClass::Limbo()
+{
+ if (this->Child)
+ this->Child->Limbo();
+}
+
+bool AttachmentClass::AttachChild(TechnoClass* pChild)
+{
+ if (this->Child || this->Data->TechnoType.isset())
+ return false;
+
+ this->Child = pChild;
+
+ auto pChildExt = TechnoExt::ExtMap.Find(this->Child);
+ pChildExt->ParentAttachment = this;
+
+ AttachmentTypeClass* pType = this->GetType();
+
+ if (pType->InheritOwner)
+ {
+ if (auto pController = this->Child->MindControlledBy)
+ pController->CaptureManager->FreeUnit(this->Child);
+ }
+
+ return true;
+}
+
+bool AttachmentClass::DetachChild(bool isForceDetachment)
+{
+ if (this->Child)
+ {
+ AttachmentTypeClass* pType = this->GetType();
+
+ if (isForceDetachment)
+ {
+ if (pType->ForceDetachWeapon_Parent.isset())
+ TechnoExt::FireWeaponAtSelf(this->Parent, pType->DestructionWeapon_Parent);
+
+ if (pType->ForceDetachWeapon_Child.isset())
+ TechnoExt::FireWeaponAtSelf(this->Child, pType->DestructionWeapon_Child);
+ }
+
+ if (!this->Child->InLimbo && pType->ParentDetachmentMission.isset())
+ this->Child->QueueMission(pType->ParentDetachmentMission.Get(), false);
+
+ if (pType->InheritOwner)
+ this->Child->SetOwningHouse(this->Parent->GetOriginalOwner(), false);
+
+ auto pChildExt = TechnoExt::ExtMap.Find(this->Child);
+ pChildExt->ParentAttachment = nullptr;
+ this->Child = nullptr;
+
+ return true;
+ }
+
+ return false;
+}
+
+#pragma region Save/Load
+
+template
+bool AttachmentClass::Serialize(T& stm)
+{
+ return stm
+ .Process(this->Data)
+ .Process(this->Parent)
+ .Process(this->Child)
+ .Success();
+};
+
+bool AttachmentClass::Load(PhobosStreamReader& stm, bool RegisterForChange)
+{
+ return Serialize(stm);
+}
+
+bool AttachmentClass::Save(PhobosStreamWriter& stm) const
+{
+ return const_cast(this)->Serialize(stm);
+}
+
+#pragma endregion
\ No newline at end of file
diff --git a/src/New/Entity/AttachmentClass.h b/src/New/Entity/AttachmentClass.h
new file mode 100644
index 0000000000..c601ab55fc
--- /dev/null
+++ b/src/New/Entity/AttachmentClass.h
@@ -0,0 +1,64 @@
+#pragma once
+
+#include
+
+#include
+
+#include
+#include
+
+class AttachmentClass
+{
+public:
+ static std::vector Array;
+
+ TechnoTypeExt::ExtData::AttachmentDataEntry* Data;
+ TechnoClass* Parent;
+ TechnoClass* Child;
+
+ AttachmentClass(TechnoTypeExt::ExtData::AttachmentDataEntry* data,
+ TechnoClass* pParent, TechnoClass* pChild = nullptr) :
+ Data(data),
+ Parent(pParent),
+ Child(pChild)
+ {
+ Array.push_back(this);
+ }
+
+ AttachmentClass() :
+ Data(),
+ Parent(),
+ Child()
+ {
+ Array.push_back(this);
+ }
+
+ ~AttachmentClass()
+ {
+ auto position = std::find(Array.begin(), Array.end(), this);
+ if (position != Array.end())
+ Array.erase(position);
+ }
+
+ AttachmentTypeClass* GetType();
+ TechnoTypeClass* GetChildType();
+
+ void Initialize();
+ void CreateChild();
+ void AI();
+ void Uninitialize();
+ void ChildDestroyed();
+
+ void Unlimbo();
+ void Limbo();
+
+ bool AttachChild(TechnoClass* pChild);
+ bool DetachChild(bool force = false);
+
+ bool Load(PhobosStreamReader& stm, bool registerForChange);
+ bool Save(PhobosStreamWriter& stm) const;
+
+private:
+ template
+ bool Serialize(T& stm);
+};
\ No newline at end of file
diff --git a/src/New/Type/AttachmentTypeClass.cpp b/src/New/Type/AttachmentTypeClass.cpp
new file mode 100644
index 0000000000..84ecf29b72
--- /dev/null
+++ b/src/New/Type/AttachmentTypeClass.cpp
@@ -0,0 +1,73 @@
+#include "AttachmentTypeClass.h"
+
+#include
+
+Enumerable::container_t Enumerable::Array;
+
+const char* Enumerable::GetMainSection()
+{
+ return "AttachmentTypes";
+}
+
+void AttachmentTypeClass::LoadFromINI(CCINIClass* pINI)
+{
+ const char* section = this->Name;
+
+ INI_EX exINI(pINI);
+
+ this->RestoreAtCreation.Read(exINI, section, "RestoreAtCreation");
+ this->InheritTilt.Read(exINI, section, "InheritTilt");
+ this->InheritCommands.Read(exINI, section, "InheritCommands");
+ this->InheritOwner.Read(exINI, section, "InheritOwner");
+ this->InheritStateEffects.Read(exINI, section, "InheritStateEffects");
+
+ this->SyncDamage.Read(exINI, section, "SyncDamage");
+ this->SyncDamage_IsRelative.Read(exINI, section, "SyncDamage.IsRelative");
+ this->SyncExperienceGain.Read(exINI, section, "SyncExperienceGain");
+ this->SyncExperienceGain_IsRelative.Read(exINI, section, "SyncExperienceGain.IsRelative");
+
+ this->CanBeForceDetached.Read(exINI, section, "CanBeForceDetached");
+ this->RestoreAtHealth.Read(exINI, section, "RestoreAtHealth");
+
+ this->ForceDetachWeapon_Child.Read(exINI, section, "ForceDetachWeapon.Child");
+ this->ForceDetachWeapon_Parent.Read(exINI, section, "ForceDetachWeapon.Parent");
+ this->DestructionWeapon_Child.Read(exINI, section, "DestructionWeapon.Child");
+ this->DestructionWeapon_Parent.Read(exINI, section, "DestructionWeapon.Parent");
+
+ this->ParentDestructionMission.Read(exINI, section, "ParentDestructionMission");
+ this->ParentDetachmentMission.Read(exINI, section, "ParentDetachmentMission");
+}
+
+template
+void AttachmentTypeClass::Serialize(T& Stm)
+{
+ Stm
+ .Process(this->RestoreAtCreation)
+ .Process(this->InheritTilt)
+ .Process(this->InheritCommands)
+ .Process(this->InheritOwner)
+ .Process(this->InheritStateEffects)
+ .Process(this->SyncDamage)
+ .Process(this->SyncDamage_IsRelative)
+ .Process(this->SyncExperienceGain)
+ .Process(this->SyncExperienceGain_IsRelative)
+ .Process(this->CanBeForceDetached)
+ .Process(this->RestoreAtHealth)
+ .Process(this->ForceDetachWeapon_Child)
+ .Process(this->ForceDetachWeapon_Parent)
+ .Process(this->DestructionWeapon_Child)
+ .Process(this->DestructionWeapon_Parent)
+ .Process(this->ParentDestructionMission)
+ .Process(this->ParentDetachmentMission)
+ ;
+}
+
+void AttachmentTypeClass::LoadFromStream(PhobosStreamReader& Stm)
+{
+ this->Serialize(Stm);
+}
+
+void AttachmentTypeClass::SaveToStream(PhobosStreamWriter& Stm)
+{
+ this->Serialize(Stm);
+}
diff --git a/src/New/Type/AttachmentTypeClass.h b/src/New/Type/AttachmentTypeClass.h
new file mode 100644
index 0000000000..cbf1e620ae
--- /dev/null
+++ b/src/New/Type/AttachmentTypeClass.h
@@ -0,0 +1,67 @@
+#pragma once
+
+#include
+#include
+
+#include
+
+class AttachmentTypeClass final : public Enumerable
+{
+public:
+ Valueable RestoreAtCreation; // whether to spawn the attachment initially
+ // Inherit = propagate from host to attachment
+ // Sync = propagate changes both ways
+ Valueable InheritTilt;
+ Valueable InheritCommands;
+ Valueable InheritOwner; //aka mind control inheritance
+ Valueable InheritStateEffects; // phasing out, stealth etc.
+ // Explanation: 200 damage to 400 HP host with 200 HP part will...
+ // - ...kill the part (200 - 200 = 0) if the mode is absolute
+ // - ...leave the part alive (200 - 200*(200/400) = 100) if the mode is relative
+ Valueable SyncDamage;
+ Valueable SyncDamage_IsRelative;
+ Valueable SyncExperienceGain;
+ Valueable SyncExperienceGain_IsRelative;
+ Valueable CanBeForceDetached;
+ Nullable RestoreAtHealth; // if host is healed to that health it's respawned
+ Nullable ForceDetachWeapon_Child;
+ Nullable ForceDetachWeapon_Parent;
+ Nullable DestructionWeapon_Child;
+ Nullable DestructionWeapon_Parent;
+ Nullable ParentDestructionMission;
+ Nullable ParentDetachmentMission;
+
+ // Targeting, verses, attachment health max/initial, immunities, possibility
+ // to command are to be done on TechnoType itself
+
+
+ AttachmentTypeClass(const char* pTitle = NONE_STR) : Enumerable(pTitle)
+ , RestoreAtCreation(true)
+ , InheritTilt(true)
+ , InheritCommands(false)
+ , InheritOwner(false)
+ , InheritStateEffects(true)
+ , SyncDamage(false)
+ , SyncDamage_IsRelative(false)
+ , SyncExperienceGain(false)
+ , SyncExperienceGain_IsRelative(false)
+ , CanBeForceDetached(false)
+ , RestoreAtHealth()
+ , ForceDetachWeapon_Child()
+ , ForceDetachWeapon_Parent()
+ , DestructionWeapon_Child()
+ , DestructionWeapon_Parent()
+ , ParentDestructionMission()
+ , ParentDetachmentMission()
+ { }
+
+ virtual ~AttachmentTypeClass() override = default;
+
+ virtual void LoadFromINI(CCINIClass* pINI) override;
+ virtual void LoadFromStream(PhobosStreamReader& Stm);
+ virtual void SaveToStream(PhobosStreamWriter& Stm);
+
+private:
+ template
+ void Serialize(T& Stm);
+};
\ No newline at end of file