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 + +namespace detail +{ + struct CaseInsensitiveComparator + { + struct nocase_compare + { + bool operator() (const unsigned char& c1, const unsigned char& c2) const + { + return tolower(c1) < tolower(c2); + } + }; + bool operator() (const std::string& s1, const std::string& s2) const + { + return std::lexicographical_compare(s1.begin(), s1.end(), s2.begin(), s2.end(), nocase_compare()); + } + }; +} diff --git a/src/Utilities/TemplateDef.h b/src/Utilities/TemplateDef.h index 3142f81fcc..c627f813b8 100644 --- a/src/Utilities/TemplateDef.h +++ b/src/Utilities/TemplateDef.h @@ -1028,6 +1028,48 @@ namespace detail return false; } + template <> + inline bool read(AttachmentTimerConversionMode& value, INI_EX &parser, const char *pSection, const char *pKey) + { + if (parser.ReadString(pSection, pKey)) + { + bool success; + value = ParseEnum(parser.value(), success); + if(!success) + Debug::INIParseFailed(pSection, pKey, parser.value(), "Expected a time conversion mode, use default value."); + return success; + } + return false; + } + + template <> + inline bool read(AttachmentInstanceConversionMode& value, INI_EX& parser, const char* pSection, const char* pKey) + { + if (parser.ReadString(pSection, pKey)) + { + bool success; + value = ParseEnum(parser.value(), success); + if (!success) + Debug::INIParseFailed(pSection, pKey, parser.value(), "Expected a instance conversion mode, use default value."); + return success; + } + return false; + } + + template <> + inline bool read(Layer& value, INI_EX& parser, const char* pSection, const char* pKey) + { + if (parser.ReadString(pSection, pKey)) + { + bool success; + value = ParseEnum(parser.value(), success); + if (!success) + Debug::INIParseFailed(pSection, pKey, parser.value(), "Expected a layer value, use default value."); + return success; + } + return false; + } + template <> inline bool read(CLSID &value, INI_EX &parser, const char *pSection, const char *pKey) {