diff --git a/CREDITS.md b/CREDITS.md
index 6d74c4e201..e749f5a5b2 100644
--- a/CREDITS.md
+++ b/CREDITS.md
@@ -242,6 +242,8 @@ This page lists all the individual contributions to the project by their author.
- Dump variables to file on scenario end / hotkey
- "House owns TechnoType" and "House doesn't own TechnoType" trigger events
- Help with docs
+- **Multfinite**
+ - Techno Attachment logic: type conversion handling and `Attachment.ForcedLayer` flag
- **ChrisLv_CN** (work relicensed under [following permission](https://github.com/Phobos-developers/Phobos/blob/develop/images/ChrisLv-relicense.png)):
- General assistance
- Interceptor logic prototype
diff --git a/Phobos.vcxproj b/Phobos.vcxproj
index 68c5b9e056..a32fe4a7f4 100644
--- a/Phobos.vcxproj
+++ b/Phobos.vcxproj
@@ -59,8 +59,8 @@
-
-
+
+
@@ -204,8 +204,8 @@
-
-
+
+
@@ -224,6 +224,7 @@
+
diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md
index 8f04f966a0..4a8a3757fd 100644
--- a/docs/New-or-Enhanced-Logics.md
+++ b/docs/New-or-Enhanced-Logics.md
@@ -17,6 +17,32 @@ This feature is not final and is under development.
- Currently the attached techno may only be a vehicle.
- When attached, the special `Attachment` (`{C5D54B98-8C98-4275-8CE4-EF75CB0CBE3E}`) locomotor is automatically casted on a unit. You may also specify it in the child unit types manually if the unit is not intended to move without a parent (f. ex. a turret).
+```{warning}
+This feature has the same limitations as [Ares' Type Conversion](https://ares-developers.github.io/Ares-docs/new/typeconversion.html).
+```
+
+There are way to define special rules for attachments during type conversion and it's the following:
+- Attachment type and Techno type conversion rules via `AttachmentX.ConversionMode.Instance`
+ - `AlwaysPresent` which is `Default` forces attach to be always on unit and keep instance as-is
+ - `Switch` means that new attachment and techno instances will be created during type conversion - the current will be hidden and inactive, the new will be present on the unit
+ - If next attachment not present then the current just will be hidden and inactive
+ - Use it when you need to keep different state on objects
+ - `Convert` means that current instance will be converted and all state will be the same
+ - If TechnoTypes the same then chlid techno won't convert
+ - Attachment type will convert too
+ - `Switch` and `Convert` requires to be linked with attachment on other parent TechnoType via `AttachmentX.ID`. You **MUST** gurantee that each value is **unique**.
+- Attachment timers can be affected too using `AttachmentX.ConversionMode.RespawnTimer.Current` and `AttachmentX.ConversionMode.RespawnTimer.Next`
+ - `Nothing` which is `Default` will do obviosly nothing
+ - `Freeze` flag if present will pause timer and otherwise will resume timer
+ - `InheritAbsolute` will works only for `Next` and it will transfer timer value from `Current` itself
+ - `InheritRelative` will works only for `Next` and it will transfer timer value from `Current` as percent between attachment entries
+ - `Instant` set left time to zero
+ - `Reset` set left time to `RespawnDelay`
+ - `FreezeAndInstant` - both `Freeze` & `Instant` effects
+ - `FreezeAndReset` - both `Freeze` & `Reset` effects
+ - `FreezeAndInheritAbsolute` - both `Freeze` & `InheritAbsolute` effects
+ - `FreezeAndInheritRelative` - both `Freeze` & `InheritRelative` effects
+
In `rulesmd.ini`:
```ini
[AttachmentTypes]
@@ -41,14 +67,19 @@ ParentDetachmentMission= ; MissionType, queued to child when it's d
[SOMETECHNO] ; TechnoTypeClass
; used when this techno is attached
+Attachment.ForcedLayer= ; Layer None|Underground|Surface|Ground|Air|Top (don't set anything to disable this feature)
AttachmentTopLayerMinHeight= ; integer
AttachmentUndergroundLayerMaxHeight= ; integer
; used for attaching other technos
+AttachmentX.ID=-1 ; int, unique ID to link attachments during type conversion. Index must be in range [0; inf+) and any negative value treated as NO ID
AttachmentX.Type=MNT ; AttachmentType (example)
AttachmentX.TechnoType= ; TechnoType that can be attached, currently only units are supported
AttachmentX.FLH=0,0,0 ; integer - Forward, Lateral, Height
AttachmentX.IsOnTurret=false ; boolean
AttachmentX.RotationAdjust=0 ; rotation in DirType, from -255 to 255
+AttachmentX.ConversionMode.Instance=Default ; AttachmentInstanceConversionMode: Default | AlwaysPresent | Switch | Convert
+AttachmentX.ConversionMode.RespawnTimer.Current=Default ; AttachmentTimerConversionMode Default | Nothing | Freeze | InheritAbsolute | InheritRelative | Instant | Reset | FreezeAndInstant | FreezeAndReset | FreezeAndInheritAbsolute | FreezeAndInheritRelative
+AttachmentX.ConversionMode.RespawnTimer.Next=Default ; AttachmentTimerConversionMode Default | Nothing | Freeze | InheritAbsolute | InheritRelative | Instant | Reset | FreezeAndInstant | FreezeAndReset | FreezeAndInheritAbsolute | FreezeAndInheritRelative
[General]
AttachmentTopLayerMinHeight=500 ; integer,
diff --git a/src/Ext/Techno/Body.Update.cpp b/src/Ext/Techno/Body.Update.cpp
index 7f2fb2961f..3401b6ec26 100644
--- a/src/Ext/Techno/Body.Update.cpp
+++ b/src/Ext/Techno/Body.Update.cpp
@@ -482,6 +482,122 @@ void TechnoExt::ExtData::UpdateTypeData(TechnoTypeClass* pCurrentType)
pPassenger = abstract_cast(pPassenger->NextObject);
}
}
+ // Techno attachment behavior during type conversion
+ if (
+ !this->TypeExtData->AttachmentData.empty() ||
+ !pOldTypeExt->AttachmentData.empty() ||
+ !this->ChildAttachments.empty() ||
+ !this->ChildAttachmentsPerType.empty()
+ ) {
+ // Save attachment sequence for previous (it should be executed once and only for initial type in theory)
+ if (!this->ChildAttachmentsPerType.contains(pOldTypeExt))
+ {
+ auto& vector = this->ChildAttachmentsPerType[pOldTypeExt];
+ for (auto& a : this->ChildAttachments)
+ vector.push_back(a);
+ }
+ // Create or copy attachments for new type
+ // NOTE:
+ // Converting attachments are the same instances
+ // Switching attachments are new instances
+ if (!this->ChildAttachmentsPerType.contains(this->TypeExtData))
+ {
+ auto& vector = this->ChildAttachmentsPerType[this->TypeExtData];
+ for (auto& ae : this->TypeExtData->AttachmentData)
+ {
+ auto a = this->FindAttachmentForTypeByID(pOldTypeExt, ae.ID);
+ // do not make new attachment instance for jumping between types attachments
+ // i think that we should make inactive chlid & attachment which is off-field (i.e. disable updating it in-time)
+ if (!a || a->Data->ConversionMode_Instance.Get() == AttachmentInstanceConversionMode::Switch)
+ {
+ a = std::make_shared(&ae, pThis, nullptr);
+ a->Initialize();
+ a->Limbo();
+ }
+ vector.push_back(a);
+ }
+ }
+ auto& vectorNew = this->ChildAttachmentsPerType[this->TypeExtData];
+
+ // Iterate over attachments of old type and do conversions if it needed
+ for (size_t i = 0; i < this->ChildAttachments.size(); i++)
+ {
+ auto& attachment = this->ChildAttachments.at(i);
+ auto const attachmentEntry = pOldTypeExt->AttachmentData.at(i);
+ auto const attachmentType = attachment->GetType();
+ auto const timeLeftCurrent = attachment->RespawnTimer.TimeLeft;
+
+ // Linked by ID attachment on new type
+ // You should know that in this case with switching attachment and attachmentNew are same instance if it were linked properly
+ auto attachmentNew = this->FindAttachmentForTypeByID(this->TypeExtData, attachmentEntry.ID);
+
+ // Apply conversion for chlid
+ switch (attachmentEntry.ConversionMode_Instance.Get())
+ {
+ case(AttachmentInstanceConversionMode::Convert):
+ {
+ // Type to convert is not define, so need just hide this as switching does.
+ // Exact same as switching wtthout linked by ID attachment
+ if (!attachmentNew)
+ {
+ attachment->Limbo();
+ attachment->UpdateRespawnTimerAtConversion(attachmentEntry.ConversionMode_RespawnTimer_Current);
+ }
+
+ auto const attachmentEntryNew = this->TypeExtData->GetAttachmentEntryByID(attachmentEntry.ID);
+ auto const attachmentTypeNew = AttachmentTypeClass::Array[attachmentEntryNew->Type].get();
+
+ auto pTechnoTypeNew = TechnoTypeClass::Array()->GetItem(attachmentEntryNew->TechnoType);
+ if (attachment->GetChildType() != attachmentNew->GetChildType() && !AresFunctions::ConvertTypeTo(attachment->Child, pTechnoTypeNew))
+ {
+ auto pTechnoType = TechnoTypeClass::Array()->GetItem(attachmentEntry.TechnoType);
+ Debug::Log("[Developer warning] Error during attachment {%s } techno type conversion { %s } -> { %s }\n", attachmentType->Name, pTechnoType->Name, pTechnoTypeNew->Name);
+ }
+ attachment->Data = attachmentEntryNew;
+
+ // It is the same instance of attachment, so it is single call
+ attachment->UpdateRespawnTimerAtConversion(
+ attachmentEntry.ConversionMode_RespawnTimer_Next,
+ timeLeftCurrent, attachmentType
+ );
+ } break;
+ case(AttachmentInstanceConversionMode::Switch):
+ {
+ attachment->Limbo();
+ attachment->UpdateRespawnTimerAtConversion(attachmentEntry.ConversionMode_RespawnTimer_Current);
+ if (attachmentNew)
+ {
+ attachmentNew->Unlimbo();
+ attachmentNew->UpdateRespawnTimerAtConversion(
+ attachmentEntry.ConversionMode_RespawnTimer_Next,
+ timeLeftCurrent, attachmentType
+ );
+ }
+ } break;
+ case(AttachmentInstanceConversionMode::AlwaysPresent):
+ default:
+ {
+ if (attachmentNew != nullptr && attachment == attachmentNew)
+ {
+ Debug::Log("[Developer warning] Always presented attachment { %s } of type { %s } has linked by ID { %u } other attachment { %s } of type { %s }\n",
+ attachmentType->Name, pOldType->Name,
+ attachmentEntry.ID,
+ attachmentNew->GetType()->Name, this->TypeExtData->OwnerObject()->Name
+ );
+ }
+
+ attachment->Unlimbo();
+ attachment->IsMigrating = true;
+ vectorNew.AddUnique(attachment);
+ } break;
+ }
+ }
+
+ // DO MAGIC
+ this->ChildAttachments.clear();
+ this->ChildAttachments.assign(vectorNew.begin(), vectorNew.end());
+ }
+
}
void TechnoExt::ExtData::UpdateLaserTrails()
diff --git a/src/Ext/Techno/Body.cpp b/src/Ext/Techno/Body.cpp
index 42a01c46a8..2b830a446c 100644
--- a/src/Ext/Techno/Body.cpp
+++ b/src/Ext/Techno/Body.cpp
@@ -213,6 +213,26 @@ bool TechnoExt::AllowedTargetByZone(TechnoClass* pThis, TechnoClass* pTarget, Ta
return true;
}
+std::shared_ptr TechnoExt::ExtData::FindAttachmentForTypeByID(TechnoTypeExt::ExtData* pTypeExt, int entryId)
+{
+ if (entryId < 0) return nullptr;
+ auto& vector = ChildAttachmentsPerType[pTypeExt];
+ auto iter = std::find_if(vector.begin(), vector.end(), [&entryId](std::shared_ptr& item) -> bool
+ {
+ return item->Data->ID.Get() == entryId;
+ });
+ return iter != vector.end() ? *iter : nullptr;
+}
+
+void TechnoExt::ExtData::RemoveAttachmentFromPerTypeLists(AttachmentClass* pWhat)
+{
+ for (auto& vector : ChildAttachmentsPerType)
+ std::remove_if(vector.second.begin(), vector.second.end(), [&pWhat](const AttachmentClassPtr& item) -> bool
+ {
+ return item.get() == pWhat && item;
+ });
+}
+
// Feature for common usage : TechnoType conversion -- Trsdy
// BTW, who said it was merely a Type pointer replacement and he could make a better one than Ares?
bool TechnoExt::ConvertToType(FootClass* pThis, TechnoTypeClass* pToType)
@@ -393,7 +413,7 @@ void TechnoExt::InitializeAttachments(TechnoClass* pThis)
for (auto& entry : pTypeExt->AttachmentData)
{
- pExt->ChildAttachments.push_back(std::make_unique(&entry, pThis, nullptr));
+ pExt->ChildAttachments.push_back(std::make_shared(&entry, pThis, nullptr));
pExt->ChildAttachments.back()->Initialize();
}
}
@@ -439,15 +459,43 @@ void TechnoExt::TransferAttachments(TechnoClass* pThis, TechnoClass* pThat)
auto const pThisExt = TechnoExt::ExtMap.Find(pThis);
auto const pThatExt = TechnoExt::ExtMap.Find(pThat);
+ auto* pVectorOfThat = pThatExt->ChildAttachmentsPerType.contains(pThisExt->TypeExtData) ? &(pThatExt->ChildAttachmentsPerType.at(pThisExt->TypeExtData)) : nullptr;
+
for (auto& pAttachment : pThisExt->ChildAttachments)
{
pAttachment->Parent = pThat;
- pThatExt->ChildAttachments.push_back(std::move(pAttachment));
+ pThisExt->RemoveAttachmentFromPerTypeLists(pAttachment.get());
+
+ pThatExt->ChildAttachments.push_back(pAttachment);
+ if (pVectorOfThat)
+ pVectorOfThat->push_back(pAttachment); // Non-safe for unique entry ID !
}
pThisExt->ChildAttachments.clear();
}
+void TechnoExt::TransferAttachment(TechnoClass* pThis, TechnoClass* pThat, AttachmentClass* pWhat)
+{
+ auto const pThisExt = TechnoExt::ExtMap.Find(pThis);
+ auto const pThatExt = TechnoExt::ExtMap.Find(pThat);
+
+ auto srcIter = std::find_if(pThisExt->ChildAttachments.begin(), pThisExt->ChildAttachments.end(), [&pWhat](const AttachmentClassPtr& item) -> bool
+ {
+ return item.get() == pWhat;
+ });
+ if (srcIter == pThisExt->ChildAttachments.end())
+ return;
+
+ (*srcIter)->Parent = pThat;
+
+ pThatExt->ChildAttachments.push_back(*srcIter);
+ if (pThatExt->ChildAttachmentsPerType.contains(pThisExt->TypeExtData))
+ pThatExt->ChildAttachmentsPerType.at(pThisExt->TypeExtData).push_back(*srcIter); // Non-safe for unique entry ID !
+
+ pThisExt->RemoveAttachmentFromPerTypeLists(pWhat);
+ pThisExt->ChildAttachments.erase(srcIter);
+}
+
bool TechnoExt::IsAttached(TechnoClass* pThis)
{
auto const& pExt = TechnoExt::ExtMap.Find(pThis);
@@ -518,6 +566,9 @@ void TechnoExt::ExtData::Serialize(T& Stm)
.Process(this->DeployFireTimer)
.Process(this->ForceFullRearmDelay)
.Process(this->WHAnimRemainingCreationInterval)
+ // In theory it should be serialized too
+ //.Process(this->ChildAttachments)
+ //.Process(this->ChildAttachmentsPerType)
;
}
diff --git a/src/Ext/Techno/Body.h b/src/Ext/Techno/Body.h
index 7c0156346f..3b31d7c92e 100644
--- a/src/Ext/Techno/Body.h
+++ b/src/Ext/Techno/Body.h
@@ -20,6 +20,9 @@ class TechnoExt
static constexpr DWORD Canary = 0x55555555;
static constexpr size_t ExtPointerOffset = 0x34C;
+ using AttachmentClassPtr = std::shared_ptr;
+ using AttachmentVector = ValueableVector;
+
class ExtData final : public Extension
{
public:
@@ -46,7 +49,8 @@ class TechnoExt
HouseClass* OriginalPassengerOwner;
AttachmentClass* ParentAttachment;
- ValueableVector> ChildAttachments;
+ AttachmentVector ChildAttachments;
+ std::map ChildAttachmentsPerType;
ExtData(TechnoClass* OwnerObject) : Extension(OwnerObject)
, TypeExtData { nullptr }
@@ -69,6 +73,7 @@ class TechnoExt
, CanCurrentlyDeployIntoBuilding { false }
, ParentAttachment {}
, ChildAttachments {}
+ , ChildAttachmentsPerType { }
{ }
void OnEarlyUpdate();
@@ -85,6 +90,9 @@ class TechnoExt
void InitializeLaserTrails();
void UpdateMindControlAnim();
+ std::shared_ptr FindAttachmentForTypeByID(TechnoTypeExt::ExtData* pTypeExt, int entryId);
+ void RemoveAttachmentFromPerTypeLists(AttachmentClass* pWhat);
+
virtual ~ExtData() override;
virtual void InvalidatePointer(void* ptr, bool bRemoved) override
@@ -152,6 +160,7 @@ class TechnoExt
static void UnlimboAttachments(TechnoClass* pThis);
static void LimboAttachments(TechnoClass* pThis);
static void TransferAttachments(TechnoClass* pThis, TechnoClass* pThat);
+ static void TransferAttachment(TechnoClass* pThis, TechnoClass* pThat, AttachmentClass* pWhat);
static bool IsAttached(TechnoClass* pThis);
static bool HasAttachmentLoco(FootClass* pThis); // FIXME shouldn't be here
diff --git a/src/Ext/TechnoType/Body.cpp b/src/Ext/TechnoType/Body.cpp
index ccce88f2c1..64275038fc 100644
--- a/src/Ext/TechnoType/Body.cpp
+++ b/src/Ext/TechnoType/Body.cpp
@@ -125,7 +125,7 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI)
if (!pINI->GetSection(pSection))
return;
- char tempBuffer[32];
+ char tempBuffer[256];
INI_EX exINI(pINI);
this->HealthBar_Hide.Read(exINI, pSection, "HealthBar.Hide");
@@ -217,6 +217,7 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI)
this->AutoFire.Read(exINI, pSection, "AutoFire");
this->AutoFire_TargetSelf.Read(exINI, pSection, "AutoFire.TargetSelf");
+ this->AttachmentForcedLayer.Read(exINI, pSection, "Attachment.ForcedLayer");
this->AttachmentTopLayerMinHeight.Read(exINI, pSection, "AttachmentTopLayerMinHeight");
this->AttachmentUndergroundLayerMaxHeight.Read(exINI, pSection, "AttachmentUndergroundLayerMaxHeight");
@@ -231,10 +232,26 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI)
if (!type.isset())
continue;
+ Valueable id { -1 };
+ _snprintf_s(tempBuffer, sizeof(tempBuffer), "Attachment%d.ID", i);
+ id.Read(exINI, pSection, tempBuffer);
+
NullableIdx technoType;
_snprintf_s(tempBuffer, sizeof(tempBuffer), "Attachment%d.TechnoType", i);
technoType.Read(exINI, pSection, tempBuffer);
+ Valueable instanceConversionMode { AttachmentInstanceConversionMode::Default };
+ _snprintf_s(tempBuffer, sizeof(tempBuffer), "Attachment%d.ConversionMode.Instance", i);
+ instanceConversionMode.Read(exINI, pSection, tempBuffer);
+
+ Valueable currentRespawnTimerConversionMode { AttachmentTimerConversionMode::Default };
+ _snprintf_s(tempBuffer, sizeof(tempBuffer), "Attachment%d.ConversionMode.RespawnTimer.Current", i);
+ currentRespawnTimerConversionMode.Read(exINI, pSection, tempBuffer);
+
+ Valueable nextRespawnTimerConversionMode { AttachmentTimerConversionMode::Default };
+ _snprintf_s(tempBuffer, sizeof(tempBuffer), "Attachment%d.ConversionMode.RespawnTimer.Next", i);
+ nextRespawnTimerConversionMode.Read(exINI, pSection, tempBuffer);
+
Valueable flh;
_snprintf_s(tempBuffer, sizeof(tempBuffer), "Attachment%d.FLH", i);
flh.Read(exINI, pSection, tempBuffer);
@@ -247,7 +264,12 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI)
_snprintf_s(tempBuffer, sizeof(tempBuffer), "Attachment%d.RotationAdjust", i);
rotationAdjust.Read(exINI, pSection, tempBuffer);
- AttachmentDataEntry const entry { ValueableIdx(type), technoType, flh, isOnTurret, rotationAdjust };
+ AttachmentDataEntry const entry {
+ id, ValueableIdx(type),
+ technoType, instanceConversionMode,
+ currentRespawnTimerConversionMode, nextRespawnTimerConversionMode,
+ flh, isOnTurret, rotationAdjust
+ };
if (i == AttachmentData.size())
this->AttachmentData.push_back(entry);
else
@@ -642,6 +664,7 @@ void TechnoTypeExt::ExtData::Serialize(T& Stm)
.Process(this->Convert_HumanToComputer)
.Process(this->Convert_ComputerToHuman)
+ .Process(this->AttachmentForcedLayer)
.Process(this->AttachmentTopLayerMinHeight)
.Process(this->AttachmentUndergroundLayerMaxHeight)
.Process(this->AttachmentData)
@@ -695,8 +718,12 @@ template
bool TechnoTypeExt::ExtData::AttachmentDataEntry::Serialize(T& stm)
{
return stm
+ .Process(this->ID)
.Process(this->Type)
.Process(this->TechnoType)
+ .Process(this->ConversionMode_Instance)
+ .Process(this->ConversionMode_RespawnTimer_Current)
+ .Process(this->ConversionMode_RespawnTimer_Next)
.Process(this->FLH)
.Process(this->IsOnTurret)
.Process(this->RotationAdjust)
diff --git a/src/Ext/TechnoType/Body.h b/src/Ext/TechnoType/Body.h
index ef94f160b1..da5ba95d81 100644
--- a/src/Ext/TechnoType/Body.h
+++ b/src/Ext/TechnoType/Body.h
@@ -192,13 +192,18 @@ class TechnoTypeExt
Valueable Convert_HumanToComputer;
Valueable Convert_ComputerToHuman;
+ Nullable AttachmentForcedLayer;
Valueable AttachmentTopLayerMinHeight;
Valueable AttachmentUndergroundLayerMaxHeight;
struct AttachmentDataEntry
{
+ Valueable ID;
ValueableIdx Type;
NullableIdx TechnoType;
+ Valueable ConversionMode_Instance;
+ Valueable ConversionMode_RespawnTimer_Current;
+ Valueable ConversionMode_RespawnTimer_Next;
Valueable FLH;
Valueable IsOnTurret;
Valueable RotationAdjust;
@@ -400,6 +405,7 @@ class TechnoTypeExt
, Convert_HumanToComputer { }
, Convert_ComputerToHuman { }
+ , AttachmentForcedLayer { }
, AttachmentTopLayerMinHeight { RulesExt::Global()->AttachmentTopLayerMinHeight }
, AttachmentUndergroundLayerMaxHeight { RulesExt::Global()->AttachmentUndergroundLayerMaxHeight }
, AttachmentData {}
@@ -416,6 +422,17 @@ class TechnoTypeExt
void ApplyTurretOffset(Matrix3D* mtx, double factor = 1.0);
+ inline AttachmentDataEntry* GetAttachmentEntryByID(int entryId)
+ {
+ if (entryId < 0)
+ return nullptr;
+ auto entryIter = std::find_if(AttachmentData.begin(), AttachmentData.end(), [&entryId](AttachmentDataEntry& item) -> bool
+ {
+ return item.ID.Get() == entryId;
+ });
+ return entryIter == AttachmentData.end() ? nullptr : entryIter._Ptr;
+ }
+
// Ares 0.A
const char* GetSelectionGroupID() const;
diff --git a/src/Locomotion/AttachmentLocomotionClass.cpp b/src/Locomotion/AttachmentLocomotionClass.cpp
index ca68de32ad..4d50fd674b 100644
--- a/src/Locomotion/AttachmentLocomotionClass.cpp
+++ b/src/Locomotion/AttachmentLocomotionClass.cpp
@@ -352,6 +352,9 @@ ILocomotionPtr AttachmentLocomotionClass::GetAttachmentParentLoco()
Layer AttachmentLocomotionClass::CalculateLayer()
{
auto const pExt = TechnoTypeExt::ExtMap.Find(this->LinkedTo->GetTechnoType());
+ if (pExt->AttachmentForcedLayer.isset())
+ return pExt->AttachmentForcedLayer;
+
int height = this->LinkedTo->GetHeight();
if (this->LinkedTo->IsInAir())
diff --git a/src/New/Entity/AttachmentClass.cpp b/src/New/Entity/AttachmentClass.cpp
index 69d1c0cc4c..2c865e5b09 100644
--- a/src/New/Entity/AttachmentClass.cpp
+++ b/src/New/Entity/AttachmentClass.cpp
@@ -98,11 +98,6 @@ void AttachmentClass::AI()
if (this->Child)
{
- if (this->Child->InLimbo && !this->Parent->InLimbo)
- this->Unlimbo();
- else if (!this->Child->InLimbo && this->Parent->InLimbo)
- this->Limbo();
-
this->Child->SetLocation(this->GetChildLocation());
DirStruct childDir = this->Data->IsOnTurret
@@ -278,6 +273,34 @@ bool AttachmentClass::DetachChild()
return false;
}
+void AttachmentClass::UpdateRespawnTimerAtConversion(
+ AttachmentTimerConversionMode mode,
+ int timeLeftOld,
+ AttachmentTypeClass* pOldType
+) {
+ auto type = GetType();
+
+ auto isFreeze = CheckFlags(mode, AttachmentTimerConversionMode::Freeze);
+ auto isInheritAbsolute = CheckFlags(mode, AttachmentTimerConversionMode::InheritAbsolute);
+ auto isInheritRelative = CheckFlags(mode, AttachmentTimerConversionMode::InheritRelative);
+ auto isInstant = CheckFlags(mode, AttachmentTimerConversionMode::Instant);
+ auto isReset = CheckFlags(mode, AttachmentTimerConversionMode::Reset);
+
+ if (isInstant)
+ RespawnTimer.TimeLeft = 0;
+ if(isReset)
+ RespawnTimer.TimeLeft = type->RespawnDelay;
+ if (isInheritAbsolute && !pOldType)
+ RespawnTimer.TimeLeft = timeLeftOld;
+ if(isInheritRelative && !pOldType)
+ RespawnTimer.TimeLeft = (timeLeftOld / pOldType->RespawnDelay) * type->RespawnDelay;
+
+ if(isFreeze)
+ RespawnTimer.Pause();
+ else
+ RespawnTimer.Resume();
+}
+
void AttachmentClass::InvalidatePointer(void* ptr)
{
diff --git a/src/New/Entity/AttachmentClass.h b/src/New/Entity/AttachmentClass.h
index 74af53a8e1..1d3480857a 100644
--- a/src/New/Entity/AttachmentClass.h
+++ b/src/New/Entity/AttachmentClass.h
@@ -18,14 +18,18 @@ class AttachmentClass
TechnoClass* Parent;
TechnoClass* Child;
CDTimerClass RespawnTimer;
-
+ // Migrating between TechnoTypes in single unit instance
+ // This means, that this attachment was added at end of TechnoExt::ExtData::ChildAttachments vector of parent
+ // This was made for special case AttachmentTechnoTypeConversionMode::AlwaysPresent
+ bool IsMigrating;
AttachmentClass(TechnoTypeExt::ExtData::AttachmentDataEntry* data,
TechnoClass* pParent, TechnoClass* pChild = nullptr) :
Data { data },
Parent { pParent },
Child { pChild },
- RespawnTimer { }
+ RespawnTimer { },
+ IsMigrating { false }
{
Array.push_back(this);
}
@@ -34,7 +38,8 @@ class AttachmentClass
Data { },
Parent { },
Child { },
- RespawnTimer { }
+ RespawnTimer { },
+ IsMigrating { false }
{
Array.push_back(this);
}
@@ -57,6 +62,12 @@ class AttachmentClass
bool AttachChild(TechnoClass* pChild);
bool DetachChild();
+ void UpdateRespawnTimerAtConversion(
+ AttachmentTimerConversionMode mode,
+ int timeLeftOld = 0,
+ AttachmentTypeClass* pOldType = nullptr
+ );
+
void InvalidatePointer(void* ptr);
bool Load(PhobosStreamReader& stm, bool registerForChange);
diff --git a/src/Utilities/Enum.h b/src/Utilities/Enum.h
index 8e225a01af..09c015585e 100644
--- a/src/Utilities/Enum.h
+++ b/src/Utilities/Enum.h
@@ -35,6 +35,16 @@
#include
#include
#include
+#include
+
+// Guys, i f*ck current (old) way to parse enums. IT IS HARDCODED COPY-PASTE, WTF!? So, i made it's for you. Just use it. -Multfinite
+// Also see `ParseEnum` function at bottom of this file.
+// NOTICE:
+// USAGE:
+// 1. FIRST VALUE **ALWAYS** WILL BE TREATED AS DEFAULT IF PARSING FAILS OR KEY ISN'T PRESENT.
+// 2. Third parameter of map must be `CaseInsensitiveComparator` type
+template requires std::is_enum_v
+inline std::map GetEnumMapping();
enum class AttachedAnimFlag
{
@@ -322,3 +332,105 @@ class MouseCursorHotSpotY
return false;
}
};
+
+enum class AttachmentTimerConversionMode : unsigned char
+{
+ // Do not change parameters (but enable timer)
+ Nothing = 0,
+ // If defined, then pause timer, otherwise - resume.
+ Freeze = 1 << 0,
+ // Transfer absolute value from current timer to next
+ // Should be defined at next timer
+ InheritAbsolute = 1 << 1,
+ // Transfer relative value from current timer to next
+ // Should be defined at next timer
+ InheritRelative = 1 << 2,
+ // Set timer left time to zero
+ Instant = 1 << 3,
+ // Set timer left time to RespawnDelay
+ Reset = 1 << 4,
+ // Both Freeze and Instant effects
+ FreezeAndInstant = Freeze | Instant,
+ // Both Freeze and Reset effects
+ FreezeAndReset = Freeze | Reset,
+ // Both Freeze and InheritRelative effects
+ FreezeAndInheritAbsolute = Freeze | InheritAbsolute,
+ // Both Freeze and InheritRelative effects
+ FreezeAndInheritRelative = Freeze | InheritRelative,
+
+ Default = Nothing
+};
+MAKE_ENUM_FLAGS(AttachmentTimerConversionMode)
+
+template<>
+inline std::map GetEnumMapping()
+{
+ return
+ {
+ { "Default", AttachmentTimerConversionMode::Default }
+ , { "Freeze", AttachmentTimerConversionMode::Freeze }
+ , { "InheritAbsolute", AttachmentTimerConversionMode::InheritAbsolute }
+ , { "InheritRelative", AttachmentTimerConversionMode::InheritRelative }
+ , { "Instant", AttachmentTimerConversionMode::Instant }
+ , { "Reset", AttachmentTimerConversionMode::Reset }
+ , { "FreezeAndInstant", AttachmentTimerConversionMode::FreezeAndInstant }
+ , { "FreezeAndReset", AttachmentTimerConversionMode::FreezeAndReset }
+ , { "FreezeAndInheritAbsolute", AttachmentTimerConversionMode::FreezeAndInheritAbsolute }
+ , { "FreezeAndInheritRelative", AttachmentTimerConversionMode::FreezeAndInheritRelative }
+ };
+};
+
+enum class AttachmentInstanceConversionMode : unsigned char
+{
+ // This will be never convert it's type and keep as-is
+ AlwaysPresent,
+ // If new AttachmentDataEntry present:
+ // Extract (create if not present) attachment from limbo with the same ID and replace current with it
+ // Current attachment moves to limbo
+ // This means that new attachment time will applies
+ // ---
+ // If new AttachmentDataEntry NOT present OR invalid ID:
+ // Just save this attachment to limbo (AKA disable)
+ Switch,
+ // Convert chlid to new TechnoType and apply new AttachmentDataEntry
+ // It requires a linked by ID attachment at new type. Valid unique ID [0; +inf) and defined AttachmentDataEntry on TechnoType.
+ Convert,
+
+ Default = AlwaysPresent
+};
+
+template<>
+inline std::map GetEnumMapping()
+{
+ return
+ {
+ { "Default", AttachmentInstanceConversionMode::Default }
+ , { "AlwaysPresent", AttachmentInstanceConversionMode::AlwaysPresent }
+ , { "Switch", AttachmentInstanceConversionMode::Switch }
+ , { "Convert", AttachmentInstanceConversionMode::Convert }
+ };
+};
+
+template<>
+inline std::map GetEnumMapping()
+{
+ return
+ {
+ { "None", Layer::None }
+ , { "Underground", Layer::Underground }
+ , { "Surface", Layer::Surface }
+ , { "Ground", Layer::Ground }
+ , { "Air", Layer::Air }
+ , { "Top", Layer::Top }
+ };
+};
+
+// Unificate enum parsing using this please. -Multfinite
+template requires std::is_enum_v
+inline TEnum ParseEnum(const std::string& value, bool& success)
+{
+ auto mappings = GetEnumMapping();
+ auto iter = mappings.find(value);
+ success = iter != mappings.end();
+ return success ? iter->second : mappings.begin()->second;
+}
diff --git a/src/Utilities/Macro.h b/src/Utilities/Macro.h
index 4fa816b4d1..fc6a01b5f6 100644
--- a/src/Utilities/Macro.h
+++ b/src/Utilities/Macro.h
@@ -25,6 +25,12 @@ __forceinline T* Make_Pointer(const uintptr_t address)
return reinterpret_cast(address);
}
+template
+__forceinline bool CheckFlags(T value, T flags)
+{
+ return (value & flags) == flags;
+}
+
#define NAKED __declspec(naked)
#pragma region Patch Macros
diff --git a/src/Utilities/Misc.hpp b/src/Utilities/Misc.hpp
new file mode 100644
index 0000000000..8f9806af0b
--- /dev/null
+++ b/src/Utilities/Misc.hpp
@@ -0,0 +1,22 @@
+#pragma once
+
+#include
+#include