diff --git a/CREDITS.md b/CREDITS.md index 6050417244..8b08c4c8a8 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -685,6 +685,8 @@ This page lists all the individual contributions to the project by their author. - Fix an issue that the AI would set anger towards friendly houses, causing it to act stupidly - Fix an issue that the AI would look for the first house in the array as an enemy instead of the nearest one when there were no enemies - `AllowBerzerkOnAllies` + - Allow techno conversion working on buildings + - Fixed the issue that multiple attributes such as cloaking and sensor would not be updated correctly in techno conversion - **solar-III (凤九歌)** - Target scanning delay customization (documentation) - Skip target scanning function calling for unarmed technos (documentation) diff --git a/docs/Fixed-or-Improved-Logics.md b/docs/Fixed-or-Improved-Logics.md index 2430a39745..a24b7d6bdf 100644 --- a/docs/Fixed-or-Improved-Logics.md +++ b/docs/Fixed-or-Improved-Logics.md @@ -336,6 +336,8 @@ This page describes all ingame logics that are fixed or improved in Phobos witho - Fixed the issue that technos cannot spawn survivors due to non-probabilistic reasons when the tech type was destroyed. - Fixed the bug that vehicle survivor can spawn on wrong position when transport has been destroyed. - Fixed the bug that building with `Explodes=yes` use Ares's rubble logic will cause it's owner cannot defeat normally. +- Allow techno conversion working on buildings (Convert building to a bigger one is not recommended, as this may lead to problems). +- Fixed the issue that multiple attributes such as cloaking and sensor would not be updated correctly in techno conversion. ## Newly added global settings diff --git a/docs/Whats-New.md b/docs/Whats-New.md index 72198e71d8..efb144f5cb 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -529,6 +529,8 @@ New: - [Return warhead](New-or-Enhanced-Logics.md#return-warhead) (by Ollerus) - [`AllowBerzerkOnAllies`](Fixed-or-Improved-Logics.md#berzerk-on-allies) (by TaranDahl) - Customize whether warhead can be used to targeting ironcurtained technos or not (by NetsuNegi) +- Allow techno conversion working on buildings (by TaranDahl) +- Fixed the issue that multiple attributes such as cloaking and sensor would not be updated correctly in techno conversion (by TaranDahl) Vanilla fixes: - Fixed sidebar not updating queued unit numbers when adding or removing units when the production is on hold (by CrimRecya) diff --git a/src/Ext/SWType/FireSuperWeapon.cpp b/src/Ext/SWType/FireSuperWeapon.cpp index ae7be77c36..7602e231e8 100644 --- a/src/Ext/SWType/FireSuperWeapon.cpp +++ b/src/Ext/SWType/FireSuperWeapon.cpp @@ -318,8 +318,8 @@ void SWTypeExt::ExtData::ApplySWNext(SuperClass* pSW, const CellStruct& cell) void SWTypeExt::ExtData::ApplyTypeConversion(SuperClass* pSW) { - for (const auto pTargetFoot : FootClass::Array) - TypeConvertGroup::Convert(pTargetFoot, this->Convert_Pairs, pSW->Owner); + for (const auto pTarget : TechnoClass::Array) + TypeConvertGroup::Convert(pTarget, this->Convert_Pairs, pSW->Owner); } void SWTypeExt::ExtData::HandleEMPulseLaunch(SuperClass* pSW, const CellStruct& cell) const diff --git a/src/Ext/Techno/Body.Update.cpp b/src/Ext/Techno/Body.Update.cpp index 8a83d9a2bb..d28a9db0ae 100644 --- a/src/Ext/Techno/Body.Update.cpp +++ b/src/Ext/Techno/Body.Update.cpp @@ -947,10 +947,9 @@ void TechnoExt::ExtData::UpdateTypeData(TechnoTypeClass* pCurrentType) bool hasTemporal = false; bool hasAirstrike = false; bool hasLocomotor = false; - bool hasParasite = false; auto checkWeapon = [&maxCapture, &infiniteCapture, &hasTemporal, - &hasAirstrike, &hasLocomotor, &hasParasite](WeaponTypeClass* pWeaponType) + &hasAirstrike, &hasLocomotor](WeaponTypeClass* pWeaponType) { if (!pWeaponType) return; @@ -974,9 +973,6 @@ void TechnoExt::ExtData::UpdateTypeData(TechnoTypeClass* pCurrentType) if (pWH->IsLocomotor) hasLocomotor = true; - - if (pWH->Parasite) - hasParasite = true; }; for (int i = 0; i < TechnoTypeClass::MaxWeapons; i++) @@ -1087,31 +1083,28 @@ void TechnoExt::ExtData::UpdateTypeData(TechnoTypeClass* pCurrentType) barrelRecoil.HoldFrames = barrelAnimData.HoldFrames; } - // Only FootClass* can use this. - if (const auto pFoot = abstract_cast(pThis)) - { - auto& pParasiteImUsing = pFoot->ParasiteImUsing; + if (pThis->Cloakable && !pCurrentType->Cloakable) + pThis->Uncloak(true); + pThis->Cloakable = pCurrentType->Cloakable; - if (hasParasite) - { - if (!pParasiteImUsing) - { - // Rebuild a ParasiteClass - pParasiteImUsing = GameCreate(pFoot); - } - } - else if (pParasiteImUsing) - { - if (pParasiteImUsing->Victim) - { - // Release of victims. - pParasiteImUsing->ExitUnit(); - } + if (pOldType->BombSight) + BombListClass::Instance.RemoveDetector(pThis); + if (pCurrentType->BombSight) + BombListClass::Instance.AddDetector(pThis); - // Delete it - GameDelete(pParasiteImUsing); - pParasiteImUsing = nullptr; - } + // TODO : Fix this + //pThis->UpdateSight(0, 0, 0, 0, 0); + //MapClass::Instance.RevealArea3(&pThis->Location, 0, pThis->LastSightRange + 3, 0); + + if (pOldType->GapGenerator) + pThis->DestroyGap(); + if (pCurrentType->GapGenerator) + { + auto temp = pOldType->GapRadiusInCells; + pThis->GapRadius = pCurrentType->GapRadiusInCells; + pOldType->GapRadiusInCells = pCurrentType->GapRadiusInCells; + pThis->CreateGap(); + pOldType->GapRadiusInCells = temp; } // handle AutoTargetOwnPosition @@ -1226,6 +1219,57 @@ void TechnoExt::ExtData::UpdateTypeData_Foot() pThis->ClearDisguise(); } + bool hasParasite = false; + + auto checkWeapon = [&hasParasite](WeaponTypeClass* pWeaponType) + { + if (!pWeaponType) + return; + + const auto pWH = pWeaponType->Warhead; + + if (pWH->Parasite) + hasParasite = true; + }; + + for (int i = 0; i < TechnoTypeClass::MaxWeapons; i++) + { + checkWeapon(pThis->GetWeapon(i)->WeaponType); + } + + auto& pParasiteImUsing = pThis->ParasiteImUsing; + + if (hasParasite) + { + if (!pParasiteImUsing) + { + // Rebuild a ParasiteClass + pParasiteImUsing = GameCreate(pThis); + } + } + else if (pParasiteImUsing) + { + if (pParasiteImUsing->Victim) + { + // Release of victims. + pParasiteImUsing->ExitUnit(); + } + + // Delete it + GameDelete(pParasiteImUsing); + pParasiteImUsing = nullptr; + } + + if (pOldType->SensorsSight) + pThis->RemoveSensorsAt(CellStruct::Empty); + if (pCurrentType->SensorsSight) + { + auto temp = pOldType->SensorsSight; + pOldType->SensorsSight = pCurrentType->SensorsSight; + pThis->AddSensorsAt(CellStruct::Empty); + pOldType->SensorsSight = temp; + } + if (abs != AbstractType::Aircraft) { auto const pLocomotorType = pCurrentType->Locomotor; @@ -1313,6 +1357,69 @@ void TechnoExt::ExtData::UpdateTypeData_Foot() this->PreviousType = nullptr; } +void TechnoExt::ExtData::UpdateTypeData_Building() +{ + auto const pThis = static_cast(this->OwnerObject()); + auto const pOldType = static_cast(this->PreviousType); + auto const pNewType = static_cast(this->TypeExtData->OwnerObject()); + + pThis->Type = pOldType; + + pThis->DestroyNthAnim(BuildingAnimSlot::All); + + // Skip audio related + + // Maybe buggy + auto dockNumber = std::max(pNewType->NumberOfDocks, 1); + pThis->SetLinkCount(dockNumber); + + if (pNewType->LoadBuildup()) + pThis->HasBuildUp = true; + else + pThis->AI_Sellable = false; + + // Skip SecretLab related + + HouseClass* const pOwner = pThis->Owner; + + // TODO : Handle addon logics () + + if (!pThis->InLimbo) + pOwner->RegisterLoss(pThis, false); + pOwner->RemoveTracking(pThis); + + if (pThis->Factory) + pThis->Factory->AbandonProduction(); + + // Maybe buggy + if (!pThis->InLimbo) + { + auto pCrd = pThis->Location; + pThis->Limbo(); + pThis->Type = pNewType; + pThis->ActuallyPlacedOnMap = false; + ++Unsorted::ScenarioInit; + pThis->Unlimbo(pCrd, DirType::North); + --Unsorted::ScenarioInit; + pThis->Place(false); + pThis->Mark(MarkType::Change); + } + + pThis->Type = pNewType; + + pOwner->AddTracking(pThis); + if (!pThis->InLimbo) + pOwner->RegisterGain(pThis, false); + pOwner->RecheckTechTree = true; + + pThis->Ammo = Math::min(pThis->Ammo, pNewType->Ammo); + + pThis->SecondaryFacing.SetROT(pNewType->ROT); + pThis->PrimaryFacing.SetROT(pNewType->ROT); + + this->PreviousType = nullptr; +} + void TechnoExt::ExtData::UpdateLaserTrails() { if (this->LaserTrails.size() <= 0) diff --git a/src/Ext/Techno/Body.cpp b/src/Ext/Techno/Body.cpp index 26cddf886c..1d9bac13a7 100644 --- a/src/Ext/Techno/Body.cpp +++ b/src/Ext/Techno/Body.cpp @@ -309,9 +309,7 @@ bool TechnoExt::AllowedTargetByZone(TechnoClass* pThis, TechnoClass* pTarget, Ta return true; } -// 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) +bool TechnoExt::ConvertToType(TechnoClass* pThis, TechnoTypeClass* pToType) { const auto pType = pThis->GetTechnoType(); @@ -322,7 +320,9 @@ bool TechnoExt::ConvertToType(FootClass* pThis, TechnoTypeClass* pToType) return false; } - if (AresFunctions::ConvertTypeTo) + auto pFoot = abstract_cast(pThis); + + if (AresFunctions::ConvertTypeTo && pFoot) // Ares processed most of the footclass stuff { const int oldHealth = pThis->Health; @@ -341,9 +341,12 @@ bool TechnoExt::ConvertToType(FootClass* pThis, TechnoTypeClass* pToType) return false; } - // In case not using Ares 3.0. Only update necessary vanilla properties + // For buildingclass + // Also for footclass if no Ares + AbstractType rtti; TechnoTypeClass** nowTypePtr; + auto const pExt = TechnoExt::ExtMap.Find(pThis); // Different types prohibited switch (pThis->WhatAmI()) @@ -360,85 +363,106 @@ bool TechnoExt::ConvertToType(FootClass* pThis, TechnoTypeClass* pToType) nowTypePtr = reinterpret_cast(&(static_cast(pThis)->Type)); rtti = AbstractType::AircraftType; break; + case AbstractType::Building: + nowTypePtr = reinterpret_cast(&(static_cast(pThis)->Type)); + rtti = AbstractType::BuildingType; + break; default: - Debug::Log("%s is not FootClass, conversion not allowed\n", pToType->get_ID()); + Debug::Log("%s is not TechnoClass, conversion not allowed\n", pToType->get_ID()); return false; } - // Detach CLEG targeting - auto const tempUsing = pThis->TemporalImUsing; - if (tempUsing && tempUsing->Target) - tempUsing->LetGo(); - - auto const pOwner = pThis->Owner; - - // Remove tracking of old techno - if (!pThis->InLimbo) - pOwner->RegisterLoss(pThis, false); - pOwner->RemoveTracking(pThis); - - const int oldHealth = pThis->Health; - - // Generic type-conversion - auto const prevType = *nowTypePtr; - *nowTypePtr = pToType; - - // Readjust health according to percentage - pThis->SetHealthPercentage((double)(oldHealth) / (double)prevType->Strength); - pThis->EstimatedHealth = pThis->Health; - - // Add tracking of new techno - pOwner->AddTracking(pThis); - if (!pThis->InLimbo) - pOwner->RegisterGain(pThis, false); - pOwner->RecheckTechTree = true; - - // Update Ares AttachEffects -- skipped - // Ares RecalculateStats -- skipped + auto updateTechnoTypeDataLikeAres = [pThis, pToType, rtti, nowTypePtr, pExt]() + { + // Detach CLEG targeting + auto const tempUsing = pThis->TemporalImUsing; + if (tempUsing && tempUsing->Target) + tempUsing->LetGo(); + + auto const pOwner = pThis->Owner; + + // Remove tracking of old techno + if (!pThis->InLimbo) + pOwner->RegisterLoss(pThis, false); + pOwner->RemoveTracking(pThis); + + const int oldHealth = pThis->Health; + + // Generic type-conversion + auto const prevType = *nowTypePtr; + *nowTypePtr = pToType; + + // Readjust health according to percentage + pThis->SetHealthPercentage((double)(oldHealth) / (double)prevType->Strength); + pThis->EstimatedHealth = pThis->Health; + + // Add tracking of new techno + pOwner->AddTracking(pThis); + if (!pThis->InLimbo) + pOwner->RegisterGain(pThis, false); + pOwner->RecheckTechTree = true; + + // Update Ares AttachEffects -- skipped + // Ares RecalculateStats -- skipped + + // Adjust ammo + const int originalAmmo = pThis->Ammo; + const int maxAmmo = pToType->Ammo; + pThis->Ammo = Math::min(originalAmmo, maxAmmo); + + if (originalAmmo > maxAmmo) + pThis->Mark(MarkType::Change); + + // Ares ResetSpotlights -- skipped + + // Adjust ROT + if (rtti == AbstractType::AircraftType) + pThis->SecondaryFacing.SetROT(pToType->ROT); + else + pThis->PrimaryFacing.SetROT(pToType->ROT); + // Adjust Ares TurretROT -- skipped + // pThis->SecondaryFacing.SetROT(TechnoTypeExt::ExtMap.Find(pToType)->TurretROT.Get(pToType->ROT)); + + pExt->UpdateTypeData(pToType); + }; + auto updateFootTypeDataLikeAres = [pFoot, pToType, pExt]() + { + // Locomotor change, referenced from Ares 0.A's abduction code, not sure if correct, untested + CLSID nowLocoID; + ILocomotion* iloco = pFoot->Locomotor; + const auto& toLoco = pToType->Locomotor; + if ((SUCCEEDED(static_cast(iloco)->GetClassID(&nowLocoID)) && nowLocoID != toLoco)) + { + // because we are throwing away the locomotor in a split second, piggybacking + // has to be stopped. otherwise the object might remain in a weird state. + while (LocomotionClass::End_Piggyback(pFoot->Locomotor)); + // throw away the current locomotor and instantiate + // a new one of the default type for this unit. + if (auto const newLoco = LocomotionClass::CreateInstance(toLoco)) + { + newLoco->Link_To_Object(pFoot); + pFoot->Locomotor = std::move(newLoco); + } + } - // Adjust ammo - const int originalAmmo = pThis->Ammo; - const int maxAmmo = pToType->Ammo; - pThis->Ammo = Math::min(originalAmmo, maxAmmo); + const auto& jjLoco = LocomotionClass::CLSIDs::Jumpjet; + if (pToType->BalloonHover && pToType->DeployToLand && prevType->Locomotor != jjLoco && toLoco == jjLoco) + pFoot->Locomotor->Move_To(pFoot->Location); - if (originalAmmo > maxAmmo) - pThis->Mark(MarkType::Change); + pExt->UpdateTypeData_Foot(); + }; - // Ares ResetSpotlights -- skipped + updateTechnoTypeDataLikeAres(); - // Adjust ROT - if (rtti == AbstractType::AircraftType) - pThis->SecondaryFacing.SetROT(pToType->ROT); + if (rtti != AbstractType::BuildingType) + { + updateFootTypeDataLikeAres(); + } else - pThis->PrimaryFacing.SetROT(pToType->ROT); - // Adjust Ares TurretROT -- skipped - // pThis->SecondaryFacing.SetROT(TechnoTypeExt::ExtMap.Find(pToType)->TurretROT.Get(pToType->ROT)); - - // Locomotor change, referenced from Ares 0.A's abduction code, not sure if correct, untested - CLSID nowLocoID; - ILocomotion* iloco = pThis->Locomotor; - const auto& toLoco = pToType->Locomotor; - if ((SUCCEEDED(static_cast(iloco)->GetClassID(&nowLocoID)) && nowLocoID != toLoco)) { - // because we are throwing away the locomotor in a split second, piggybacking - // has to be stopped. otherwise the object might remain in a weird state. - while (LocomotionClass::End_Piggyback(pThis->Locomotor)); - // throw away the current locomotor and instantiate - // a new one of the default type for this unit. - if (auto const newLoco = LocomotionClass::CreateInstance(toLoco)) - { - newLoco->Link_To_Object(pThis); - pThis->Locomotor = std::move(newLoco); - } + pExt->UpdateTypeData_Building(); } - const auto& jjLoco = LocomotionClass::CLSIDs::Jumpjet; - if (pToType->BalloonHover && pToType->DeployToLand && prevType->Locomotor != jjLoco && toLoco == jjLoco) - pThis->Locomotor->Move_To(pThis->Location); - - auto const pTypeExt = TechnoExt::ExtMap.Find(pThis); - pTypeExt->UpdateTypeData(pToType); - pTypeExt->UpdateTypeData_Foot(); return true; } diff --git a/src/Ext/Techno/Body.h b/src/Ext/Techno/Body.h index 41b6eb42c2..7e2f7549c4 100644 --- a/src/Ext/Techno/Body.h +++ b/src/Ext/Techno/Body.h @@ -182,6 +182,7 @@ class TechnoExt void ApplySpawnLimitRange(); void UpdateTypeData(TechnoTypeClass* pCurrentType); void UpdateTypeData_Foot(); + void UpdateTypeData_Building(); void UpdateLaserTrails(); void UpdateAttachEffects(); void UpdateGattlingRateDownReset(); @@ -269,7 +270,7 @@ class TechnoExt static CoordStruct PassengerKickOutLocation(TechnoClass* pThis, FootClass* pPassenger, int maxAttempts); static bool AllowedTargetByZone(TechnoClass* pThis, TechnoClass* pTarget, TargetZoneScanType zoneScanType, WeaponTypeClass* pWeapon = nullptr, bool useZone = false, int zone = -1); static void UpdateAttachedAnimLayers(TechnoClass* pThis); - static bool ConvertToType(FootClass* pThis, TechnoTypeClass* toType); + static bool ConvertToType(TechnoClass* pThis, TechnoTypeClass* toType); static bool CanDeployIntoBuilding(UnitClass* pThis, bool noDeploysIntoDefaultValue = false); static bool IsTypeImmune(TechnoClass* pThis, TechnoClass* pSource); static int GetTintColor(TechnoClass* pThis, bool invulnerability, bool airstrike, bool berserk); diff --git a/src/Ext/WarheadType/Detonate.cpp b/src/Ext/WarheadType/Detonate.cpp index f7e8759f9c..9353c7d0fd 100644 --- a/src/Ext/WarheadType/Detonate.cpp +++ b/src/Ext/WarheadType/Detonate.cpp @@ -592,12 +592,7 @@ void WarheadTypeExt::ExtData::InterceptBullets(TechnoClass* pOwner, BulletClass* void WarheadTypeExt::ExtData::ApplyConvert(HouseClass* pHouse, TechnoClass* pTarget) { - const auto pTargetFoot = abstract_cast(pTarget); - - if (!pTargetFoot) - return; - - TypeConvertGroup::Convert(pTargetFoot, this->Convert_Pairs, pHouse); + TypeConvertGroup::Convert(pTarget, this->Convert_Pairs, pHouse); } void WarheadTypeExt::ExtData::ApplyLocomotorInfliction(TechnoClass* pTarget) diff --git a/src/Misc/Hooks.Ares.cpp b/src/Misc/Hooks.Ares.cpp index d96fdeb303..1e6558ac86 100644 --- a/src/Misc/Hooks.Ares.cpp +++ b/src/Misc/Hooks.Ares.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include @@ -27,10 +28,7 @@ static void __fastcall LetGo(TemporalClass* pTemporal) static bool __stdcall ConvertToType(TechnoClass* pThis, TechnoTypeClass* pToType) { - if (const auto pFoot = abstract_cast(pThis)) - return TechnoExt::ConvertToType(pFoot, pToType); - - return false; + return TechnoExt::ConvertToType(pThis, pToType); } // Technically this replaces GetTechnoType() call. diff --git a/src/New/Type/Affiliated/TypeConvertGroup.cpp b/src/New/Type/Affiliated/TypeConvertGroup.cpp index 94c611179c..4f67699223 100644 --- a/src/New/Type/Affiliated/TypeConvertGroup.cpp +++ b/src/New/Type/Affiliated/TypeConvertGroup.cpp @@ -1,14 +1,14 @@ #include #include "TypeConvertGroup.h" -void TypeConvertGroup::Convert(FootClass* pTargetFoot, const std::vector& convertPairs, HouseClass* pOwner) +void TypeConvertGroup::Convert(TechnoClass* pTarget, const std::vector& convertPairs, HouseClass* pOwner) { for (const auto& [fromTypes, toType, affectedHouses] : convertPairs) { if (!toType.Get()) continue; - if (pOwner && !EnumFunctions::CanTargetHouse(affectedHouses, pOwner, pTargetFoot->Owner)) + if (pOwner && !EnumFunctions::CanTargetHouse(affectedHouses, pOwner, pTarget->Owner)) continue; if (fromTypes.size()) @@ -16,16 +16,16 @@ void TypeConvertGroup::Convert(FootClass* pTargetFoot, const std::vectorGetTechnoType()) + if (from == pTarget->GetTechnoType()) { - TechnoExt::ConvertToType(pTargetFoot, toType); + TechnoExt::ConvertToType(pTarget, toType); goto end; // Breaking out of nested loops without extra checks one of the very few remaining valid usecases for goto, leave it be. } } } else { - TechnoExt::ConvertToType(pTargetFoot, toType); + TechnoExt::ConvertToType(pTarget, toType); break; } } diff --git a/src/New/Type/Affiliated/TypeConvertGroup.h b/src/New/Type/Affiliated/TypeConvertGroup.h index 79fd7c0cdd..8489bd117b 100644 --- a/src/New/Type/Affiliated/TypeConvertGroup.h +++ b/src/New/Type/Affiliated/TypeConvertGroup.h @@ -14,7 +14,7 @@ class TypeConvertGroup static void Parse(std::vector& list, INI_EX& exINI, const char* section, AffectedHouse defaultAffectHouse); - static void Convert(FootClass* pTargetFoot, const std::vector& convertPairs, HouseClass* pOwner); + static void Convert(TechnoClass* pTarget, const std::vector& convertPairs, HouseClass* pOwner); private: template