From 0b4c6ab84ad98f071b1b32ad7b53c55b44dbe119 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=88=AA=E5=91=B3=E9=BA=BB=E9=85=B1?= <93972760+TaranDahl@users.noreply.github.com> Date: Fri, 4 Apr 2025 00:06:58 +0800 Subject: [PATCH 1/7] impl --- src/Ext/SWType/FireSuperWeapon.cpp | 4 +- src/Ext/Techno/Body.cpp | 287 ++++++++++++++++--- src/Ext/Techno/Body.h | 2 +- src/Ext/WarheadType/Detonate.cpp | 7 +- src/Misc/Hooks.Ares.cpp | 6 +- src/New/Type/Affiliated/TypeConvertGroup.cpp | 10 +- src/New/Type/Affiliated/TypeConvertGroup.h | 2 +- 7 files changed, 267 insertions(+), 51 deletions(-) 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.cpp b/src/Ext/Techno/Body.cpp index 26cddf886c..f1fccc0b52 100644 --- a/src/Ext/Techno/Body.cpp +++ b/src/Ext/Techno/Body.cpp @@ -309,39 +309,10 @@ 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 ConvertToType_ProcessLikeAres(FootClass* pThis, TechnoTypeClass* pToType) { - const auto pType = pThis->GetTechnoType(); - - // It really should be at the beginning. - if (pType == pToType || pType->WhatAmI() != pToType->WhatAmI()) - { - Debug::Log("Incompatible types between %s and %s\n", pThis->get_ID(), pToType->get_ID()); - return false; - } - - if (AresFunctions::ConvertTypeTo) - { - const int oldHealth = pThis->Health; - - if (AresFunctions::ConvertTypeTo(pThis, pToType)) - { - // Fixed an issue where morphing could result in -1 health. - const double ratio = static_cast(pToType->Strength) / pType->Strength; - pThis->Health = static_cast(oldHealth * ratio + 0.5); - - auto const pTypeExt = TechnoExt::ExtMap.Find(pThis); - pTypeExt->UpdateTypeData(pToType); - pTypeExt->UpdateTypeData_Foot(); - return true; - } - - return false; - } - // In case not using Ares 3.0. Only update necessary vanilla properties + AbstractType rtti; TechnoTypeClass** nowTypePtr; @@ -412,7 +383,7 @@ bool TechnoExt::ConvertToType(FootClass* pThis, TechnoTypeClass* pToType) else pThis->PrimaryFacing.SetROT(pToType->ROT); // Adjust Ares TurretROT -- skipped - // pThis->SecondaryFacing.SetROT(TechnoTypeExt::ExtMap.Find(pToType)->TurretROT.Get(pToType->ROT)); + // 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; @@ -442,6 +413,258 @@ bool TechnoExt::ConvertToType(FootClass* pThis, TechnoTypeClass* pToType) 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(TechnoClass* pThis, TechnoTypeClass* pToType) +{ + auto pPrevType = pThis->GetTechnoType(); + + // Different types prohibited + if (pPrevType->WhatAmI() != pToType->WhatAmI()) + { + Debug::Log("Incompatible types between %s and %s\n", pThis->get_ID(), pToType->get_ID()); + return false; + } + + if (pToType->Spawns) + { + if (!pThis->SpawnManager) + { + pThis->SpawnManager = GameCreate(pThis, pToType->Spawns, pToType->SpawnsNumber, pToType->SpawnRegenRate, pToType->SpawnReloadRate); + } + else + { + auto pManager = pThis->SpawnManager; + pManager->SpawnType = pToType->Spawns; + pManager->SpawnCount = pToType->SpawnsNumber; + pManager->RegenRate = pToType->SpawnRegenRate; + pManager->ReloadRate = pToType->SpawnReloadRate; + } + } + + if (pToType->Enslaves) + { + if (!pThis->SlaveManager) + { + pThis->SlaveManager = GameCreate(pThis, pToType->Enslaves, pToType->SlavesNumber, pToType->SlaveRegenRate, pToType->SlaveReloadRate); + } + else + { + auto pManager = pThis->SlaveManager; + pManager->SlaveType = pToType->Enslaves; + pManager->SlaveCount = pToType->SlavesNumber; + pManager->RegenRate = pToType->SlaveRegenRate; + pManager->ReloadRate = pToType->SlaveReloadRate; + } + } + + if (auto pWeapon = pToType->GetWeapon(0, pThis->Veterancy.IsElite()).WeaponType) + { + if (pWeapon->Warhead->MindControl) + { + if (!pThis->CaptureManager) + { + pThis->CaptureManager = GameCreate(pThis, pWeapon->Damage, pWeapon->InfiniteMindControl); + } + else + { + auto pManager = pThis->CaptureManager; + pManager->MaxControlNodes = pWeapon->Damage; + pManager->InfiniteMindControl = pWeapon->InfiniteMindControl; + } + } + + if (pWeapon->Warhead->Temporal) + { + if (!pThis->TemporalImUsing) + { + pThis->TemporalImUsing = GameCreate(pThis); + pThis->TemporalImUsing->WarpPerStep = pWeapon->Damage; + } + } + } + + if (pToType->AirstrikeTeam > 0) + { + if (!pThis->Airstrike) + { + pThis->Airstrike = GameCreate(pThis); + } + + if (pThis->Airstrike->Owner == pThis) + { + auto pAirstrike = pThis->Airstrike; + pAirstrike->AirstrikeTeam = pToType->AirstrikeTeam; + pAirstrike->EliteAirstrikeTeam = pToType->EliteAirstrikeTeam; + pAirstrike->AirstrikeRechargeTime = pToType->AirstrikeRechargeTime; + pAirstrike->EliteAirstrikeRechargeTime = pToType->EliteAirstrikeRechargeTime; + pAirstrike->AirstrikeTeamType = pToType->AirstrikeTeamType; + pAirstrike->EliteAirstrikeTeamType = pToType->EliteAirstrikeTeamType; + } + } + + // Skip disguise related + // if (pToType->CanDisguise && pToType->PermaDisguise) + + auto pTurretRecoil = pThis->TurretRecoil.Turret; + auto pTurretData = pToType->TurretAnimData; + pTurretRecoil.Travel = pTurretData.Travel; + pTurretRecoil.CompressFrames = pTurretData.CompressFrames; + pTurretRecoil.RecoverFrames = pTurretData.RecoverFrames; + pTurretRecoil.HoldFrames = pTurretData.HoldFrames; + auto pBarrelRecoil = pThis->BarrelRecoil.Turret; + auto pBarrelData = pToType->BarrelAnimData; + pBarrelRecoil.Travel = pBarrelData.Travel; + pBarrelRecoil.CompressFrames = pBarrelData.CompressFrames; + pBarrelRecoil.RecoverFrames = pBarrelData.RecoverFrames; + pBarrelRecoil.HoldFrames = pBarrelData.HoldFrames; + + if (pThis->Cloakable && !pToType->Cloakable) + pThis->Uncloak(true); + pThis->Cloakable = pToType->Cloakable; + + if (pPrevType->BombSight) + BombListClass::Instance.RemoveDetector(pThis); + if (pToType->BombSight) + BombListClass::Instance.AddDetector(pThis); + + auto dir = DirStruct(); + dir.Raw = 0x4000 - pToType->FireAngle; + pThis->BarrelFacing.SetCurrent(dir); + + pThis->UpdateSight(0, 0, 0, 0, 0); + + if (pPrevType->GapGenerator) + pThis->DestroyGap(); + if (pToType->GapGenerator) + { + auto temp = pPrevType->GapRadiusInCells; + pThis->GapRadius = pToType->GapRadiusInCells; + pPrevType->GapRadiusInCells = pToType->GapRadiusInCells; + pThis->CreateGap(); + pPrevType->GapRadiusInCells = temp; + } + + if (pThis->WhatAmI() == AbstractType::Building) + { + auto pBuilding = (BuildingClass*)pThis; + auto pToBuildingType = (BuildingTypeClass*)pToType; + auto pPrevBuildingType = (BuildingTypeClass*)pPrevType; + + // Maybe buggy + for (auto pAnim = pBuilding->Anims[0]; pAnim; pAnim++) + GameDelete(pAnim); + + // Skip audio related + + // Maybe buggy + auto dockNumber = std::max(pToBuildingType->NumberOfDocks, 1); + pBuilding->SetLinkCount(dockNumber); + + if (pToBuildingType->LoadBuildup()) + pBuilding->HasBuildUp = true; + else + pBuilding->AI_Sellable = false; + + // Skip SecretLab related + + // Same as foot + + auto tempUsing = pThis->TemporalImUsing; + if (tempUsing && tempUsing->Target) + tempUsing->LetGo(); + + HouseClass* const pOwner = pThis->Owner; + + if (!pThis->InLimbo) + pOwner->RegisterLoss(pThis, false); + pOwner->RemoveTracking(pThis); + + int oldHealth = pThis->Health; + + // Maybe buggy + auto pCrd = pBuilding->Location; + pBuilding->Limbo(); + pBuilding->Type = pToBuildingType; + pBuilding->ActuallyPlacedOnMap = false; + ++Unsorted::ScenarioInit; + pBuilding->Unlimbo(pCrd, DirType::North); + --Unsorted::ScenarioInit; + pBuilding->Place(false); + + pThis->SetHealthPercentage((double)(oldHealth) / (double)pPrevBuildingType->Strength); + pThis->EstimatedHealth = pThis->Health; + + pOwner->AddTracking(pThis); + if (!pThis->InLimbo) + pOwner->RegisterGain(pThis, false); + pOwner->RecheckTechTree = true; + + pThis->Ammo = Math::min(pThis->Ammo, pToType->Ammo); + + pThis->SecondaryFacing.SetROT(pToType->ROT); + pThis->PrimaryFacing.SetROT(pToType->ROT); + + + return true; + } + else + { + auto pFoot = (FootClass*)pThis; + + if (auto pWeapon = pToType->GetWeapon(0, pThis->Veterancy.IsElite()).WeaponType) + { + if (!pFoot->ParasiteImUsing && pWeapon->Warhead->Parasite) + pFoot->ParasiteImUsing = GameCreate(pFoot); + } + + if (pPrevType->SensorsSight) + pFoot->RemoveSensorsAt(CellStruct::Empty); + if (pToType->SensorsSight) + { + auto temp = pPrevType->SensorsSight; + pPrevType->SensorsSight = pToType->SensorsSight; + pFoot->AddSensorsAt(CellStruct::Empty); + pPrevType->SensorsSight = temp; + } + + if (auto pInfantry = abstract_cast(pFoot)) + { + auto pToInfantryType = (InfantryTypeClass*)pToType; + auto pInfantryPrevType = (InfantryTypeClass*)pPrevType; + } + + if (auto pUnit = abstract_cast(pFoot)) + { + auto pToUnitType = (UnitTypeClass*)pToType; + auto pUnitPrevType = (UnitTypeClass*)pPrevType; + + // Maybe buggy + if (pUnitPrevType->Gunner) + pUnit->RemoveGunner(pUnit->Passengers.GetFirstPassenger()); + if (pToUnitType->Gunner) + pUnit->ReceiveGunner(pUnit->Passengers.GetFirstPassenger()); + + + } + + if (auto pAircraft = abstract_cast(pFoot)) + { + auto pToAircraftType = (AircraftTypeClass*)pToType; + auto pAircraftPrevType = (AircraftTypeClass*)pPrevType; + } + + if (AresFunctions::ConvertTypeTo) + { + return AresFunctions::ConvertTypeTo(pThis, pToType); + } + else + { + return ConvertToType_ProcessLikeAres((FootClass*)pThis, pToType); + } + } +} + // Checks if vehicle can deploy into a building at its current location. If unit has no DeploysInto set returns noDeploysIntoDefaultValue (def = false) instead. bool TechnoExt::CanDeployIntoBuilding(UnitClass* pThis, bool noDeploysIntoDefaultValue) { diff --git a/src/Ext/Techno/Body.h b/src/Ext/Techno/Body.h index 41b6eb42c2..e035bd426b 100644 --- a/src/Ext/Techno/Body.h +++ b/src/Ext/Techno/Body.h @@ -269,7 +269,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 bc74a5b6ca..6f41a3d9ce 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 From 54f537ebc903154a1c3b5789b1dddb155e579309 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=88=AA=E5=91=B3=E9=BA=BB=E9=85=B1?= <93972760+TaranDahl@users.noreply.github.com> Date: Thu, 29 Jan 2026 01:39:38 +0800 Subject: [PATCH 2/7] code --- src/Ext/Techno/Body.Update.cpp | 149 ++++++++++++--- src/Ext/Techno/Body.cpp | 326 ++++++--------------------------- src/Ext/Techno/Body.h | 1 + 3 files changed, 180 insertions(+), 296 deletions(-) diff --git a/src/Ext/Techno/Body.Update.cpp b/src/Ext/Techno/Body.Update.cpp index 8a83d9a2bb..2cf6d5fafd 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,26 @@ 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; - } + pThis->UpdateSight(0, 0, 0, 0, 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 +1217,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 +1355,57 @@ 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 pCurrentType = static_cast(this->TypeExtData->OwnerObject()); + auto const abs = pThis->WhatAmI(); + + // Maybe buggy + for (auto pAnim = pThis->Anims[0]; pAnim; pAnim++) + GameDelete(pAnim); + + // Skip audio related + + // Maybe buggy + auto dockNumber = std::max(pCurrentType->NumberOfDocks, 1); + pThis->SetLinkCount(dockNumber); + + if (pCurrentType->LoadBuildup()) + pThis->HasBuildUp = true; + else + pThis->AI_Sellable = false; + + // Skip SecretLab related + + HouseClass* const pOwner = pThis->Owner; + + if (!pThis->InLimbo) + pOwner->RegisterLoss(pThis, false); + pOwner->RemoveTracking(pThis); + + // Maybe buggy + auto pCrd = pThis->Location; + pThis->Limbo(); + pThis->Type = pCurrentType; + pThis->ActuallyPlacedOnMap = false; + ++Unsorted::ScenarioInit; + pThis->Unlimbo(pCrd, DirType::North); + --Unsorted::ScenarioInit; + pThis->Place(false); + + pOwner->AddTracking(pThis); + if (!pThis->InLimbo) + pOwner->RegisterGain(pThis, false); + pOwner->RecheckTechTree = true; + + pThis->Ammo = Math::min(pThis->Ammo, pCurrentType->Ammo); + + pThis->SecondaryFacing.SetROT(pCurrentType->ROT); + pThis->PrimaryFacing.SetROT(pCurrentType->ROT); +} + void TechnoExt::ExtData::UpdateLaserTrails() { if (this->LaserTrails.size() <= 0) diff --git a/src/Ext/Techno/Body.cpp b/src/Ext/Techno/Body.cpp index f1fccc0b52..f19299b2a6 100644 --- a/src/Ext/Techno/Body.cpp +++ b/src/Ext/Techno/Body.cpp @@ -309,8 +309,38 @@ bool TechnoExt::AllowedTargetByZone(TechnoClass* pThis, TechnoClass* pTarget, Ta return true; } -bool ConvertToType_ProcessLikeAres(FootClass* pThis, TechnoTypeClass* pToType) +bool TechnoExt::ConvertToType(TechnoClass* pThis, TechnoTypeClass* pToType) { + const auto pType = pThis->GetTechnoType(); + + // It really should be at the beginning. + if (pType == pToType || pType->WhatAmI() != pToType->WhatAmI()) + { + Debug::Log("Incompatible types between %s and %s\n", pThis->get_ID(), pToType->get_ID()); + return false; + } + + auto pFoot = abstract_cast(pThis); + + if (AresFunctions::ConvertTypeTo && pFoot) + { + const int oldHealth = pThis->Health; + + if (AresFunctions::ConvertTypeTo(pThis, pToType)) + { + // Fixed an issue where morphing could result in -1 health. + const double ratio = static_cast(pToType->Strength) / pType->Strength; + pThis->Health = static_cast(oldHealth * ratio + 0.5); + + auto const pTypeExt = TechnoExt::ExtMap.Find(pThis); + pTypeExt->UpdateTypeData(pToType); + pTypeExt->UpdateTypeData_Foot(); + return true; + } + + return false; + } + // In case not using Ares 3.0. Only update necessary vanilla properties AbstractType rtti; @@ -331,8 +361,11 @@ bool ConvertToType_ProcessLikeAres(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; 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; } @@ -385,284 +418,41 @@ bool ConvertToType_ProcessLikeAres(FootClass* pThis, TechnoTypeClass* pToType) // 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); - } - } - - 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; -} - -// 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(TechnoClass* pThis, TechnoTypeClass* pToType) -{ - auto pPrevType = pThis->GetTechnoType(); - - // Different types prohibited - if (pPrevType->WhatAmI() != pToType->WhatAmI()) - { - Debug::Log("Incompatible types between %s and %s\n", pThis->get_ID(), pToType->get_ID()); - return false; - } - - if (pToType->Spawns) - { - if (!pThis->SpawnManager) - { - pThis->SpawnManager = GameCreate(pThis, pToType->Spawns, pToType->SpawnsNumber, pToType->SpawnRegenRate, pToType->SpawnReloadRate); - } - else - { - auto pManager = pThis->SpawnManager; - pManager->SpawnType = pToType->Spawns; - pManager->SpawnCount = pToType->SpawnsNumber; - pManager->RegenRate = pToType->SpawnRegenRate; - pManager->ReloadRate = pToType->SpawnReloadRate; - } - } - - if (pToType->Enslaves) - { - if (!pThis->SlaveManager) - { - pThis->SlaveManager = GameCreate(pThis, pToType->Enslaves, pToType->SlavesNumber, pToType->SlaveRegenRate, pToType->SlaveReloadRate); - } - else - { - auto pManager = pThis->SlaveManager; - pManager->SlaveType = pToType->Enslaves; - pManager->SlaveCount = pToType->SlavesNumber; - pManager->RegenRate = pToType->SlaveRegenRate; - pManager->ReloadRate = pToType->SlaveReloadRate; - } - } + auto const pExt = TechnoExt::ExtMap.Find(pThis); + pExt->UpdateTypeData(pToType); - if (auto pWeapon = pToType->GetWeapon(0, pThis->Veterancy.IsElite()).WeaponType) + if (rtti != AbstractType::BuildingType) { - if (pWeapon->Warhead->MindControl) + // 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)) { - if (!pThis->CaptureManager) - { - pThis->CaptureManager = GameCreate(pThis, pWeapon->Damage, pWeapon->InfiniteMindControl); - } - else + // 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)) { - auto pManager = pThis->CaptureManager; - pManager->MaxControlNodes = pWeapon->Damage; - pManager->InfiniteMindControl = pWeapon->InfiniteMindControl; + newLoco->Link_To_Object(pFoot); + pFoot->Locomotor = std::move(newLoco); } } - if (pWeapon->Warhead->Temporal) - { - if (!pThis->TemporalImUsing) - { - pThis->TemporalImUsing = GameCreate(pThis); - pThis->TemporalImUsing->WarpPerStep = pWeapon->Damage; - } - } - } + const auto& jjLoco = LocomotionClass::CLSIDs::Jumpjet; + if (pToType->BalloonHover && pToType->DeployToLand && prevType->Locomotor != jjLoco && toLoco == jjLoco) + pFoot->Locomotor->Move_To(pFoot->Location); - if (pToType->AirstrikeTeam > 0) - { - if (!pThis->Airstrike) - { - pThis->Airstrike = GameCreate(pThis); - } - - if (pThis->Airstrike->Owner == pThis) - { - auto pAirstrike = pThis->Airstrike; - pAirstrike->AirstrikeTeam = pToType->AirstrikeTeam; - pAirstrike->EliteAirstrikeTeam = pToType->EliteAirstrikeTeam; - pAirstrike->AirstrikeRechargeTime = pToType->AirstrikeRechargeTime; - pAirstrike->EliteAirstrikeRechargeTime = pToType->EliteAirstrikeRechargeTime; - pAirstrike->AirstrikeTeamType = pToType->AirstrikeTeamType; - pAirstrike->EliteAirstrikeTeamType = pToType->EliteAirstrikeTeamType; - } - } - - // Skip disguise related - // if (pToType->CanDisguise && pToType->PermaDisguise) - - auto pTurretRecoil = pThis->TurretRecoil.Turret; - auto pTurretData = pToType->TurretAnimData; - pTurretRecoil.Travel = pTurretData.Travel; - pTurretRecoil.CompressFrames = pTurretData.CompressFrames; - pTurretRecoil.RecoverFrames = pTurretData.RecoverFrames; - pTurretRecoil.HoldFrames = pTurretData.HoldFrames; - auto pBarrelRecoil = pThis->BarrelRecoil.Turret; - auto pBarrelData = pToType->BarrelAnimData; - pBarrelRecoil.Travel = pBarrelData.Travel; - pBarrelRecoil.CompressFrames = pBarrelData.CompressFrames; - pBarrelRecoil.RecoverFrames = pBarrelData.RecoverFrames; - pBarrelRecoil.HoldFrames = pBarrelData.HoldFrames; - - if (pThis->Cloakable && !pToType->Cloakable) - pThis->Uncloak(true); - pThis->Cloakable = pToType->Cloakable; - - if (pPrevType->BombSight) - BombListClass::Instance.RemoveDetector(pThis); - if (pToType->BombSight) - BombListClass::Instance.AddDetector(pThis); - - auto dir = DirStruct(); - dir.Raw = 0x4000 - pToType->FireAngle; - pThis->BarrelFacing.SetCurrent(dir); - - pThis->UpdateSight(0, 0, 0, 0, 0); - - if (pPrevType->GapGenerator) - pThis->DestroyGap(); - if (pToType->GapGenerator) - { - auto temp = pPrevType->GapRadiusInCells; - pThis->GapRadius = pToType->GapRadiusInCells; - pPrevType->GapRadiusInCells = pToType->GapRadiusInCells; - pThis->CreateGap(); - pPrevType->GapRadiusInCells = temp; - } - - if (pThis->WhatAmI() == AbstractType::Building) - { - auto pBuilding = (BuildingClass*)pThis; - auto pToBuildingType = (BuildingTypeClass*)pToType; - auto pPrevBuildingType = (BuildingTypeClass*)pPrevType; - - // Maybe buggy - for (auto pAnim = pBuilding->Anims[0]; pAnim; pAnim++) - GameDelete(pAnim); - - // Skip audio related - - // Maybe buggy - auto dockNumber = std::max(pToBuildingType->NumberOfDocks, 1); - pBuilding->SetLinkCount(dockNumber); - - if (pToBuildingType->LoadBuildup()) - pBuilding->HasBuildUp = true; - else - pBuilding->AI_Sellable = false; - - // Skip SecretLab related - - // Same as foot - - auto tempUsing = pThis->TemporalImUsing; - if (tempUsing && tempUsing->Target) - tempUsing->LetGo(); - - HouseClass* const pOwner = pThis->Owner; - - if (!pThis->InLimbo) - pOwner->RegisterLoss(pThis, false); - pOwner->RemoveTracking(pThis); - - int oldHealth = pThis->Health; - - // Maybe buggy - auto pCrd = pBuilding->Location; - pBuilding->Limbo(); - pBuilding->Type = pToBuildingType; - pBuilding->ActuallyPlacedOnMap = false; - ++Unsorted::ScenarioInit; - pBuilding->Unlimbo(pCrd, DirType::North); - --Unsorted::ScenarioInit; - pBuilding->Place(false); - - pThis->SetHealthPercentage((double)(oldHealth) / (double)pPrevBuildingType->Strength); - pThis->EstimatedHealth = pThis->Health; - - pOwner->AddTracking(pThis); - if (!pThis->InLimbo) - pOwner->RegisterGain(pThis, false); - pOwner->RecheckTechTree = true; - - pThis->Ammo = Math::min(pThis->Ammo, pToType->Ammo); - - pThis->SecondaryFacing.SetROT(pToType->ROT); - pThis->PrimaryFacing.SetROT(pToType->ROT); - - - return true; + pExt->UpdateTypeData_Foot(); } else { - auto pFoot = (FootClass*)pThis; - - if (auto pWeapon = pToType->GetWeapon(0, pThis->Veterancy.IsElite()).WeaponType) - { - if (!pFoot->ParasiteImUsing && pWeapon->Warhead->Parasite) - pFoot->ParasiteImUsing = GameCreate(pFoot); - } - - if (pPrevType->SensorsSight) - pFoot->RemoveSensorsAt(CellStruct::Empty); - if (pToType->SensorsSight) - { - auto temp = pPrevType->SensorsSight; - pPrevType->SensorsSight = pToType->SensorsSight; - pFoot->AddSensorsAt(CellStruct::Empty); - pPrevType->SensorsSight = temp; - } - - if (auto pInfantry = abstract_cast(pFoot)) - { - auto pToInfantryType = (InfantryTypeClass*)pToType; - auto pInfantryPrevType = (InfantryTypeClass*)pPrevType; - } - - if (auto pUnit = abstract_cast(pFoot)) - { - auto pToUnitType = (UnitTypeClass*)pToType; - auto pUnitPrevType = (UnitTypeClass*)pPrevType; - - // Maybe buggy - if (pUnitPrevType->Gunner) - pUnit->RemoveGunner(pUnit->Passengers.GetFirstPassenger()); - if (pToUnitType->Gunner) - pUnit->ReceiveGunner(pUnit->Passengers.GetFirstPassenger()); - - - } - - if (auto pAircraft = abstract_cast(pFoot)) - { - auto pToAircraftType = (AircraftTypeClass*)pToType; - auto pAircraftPrevType = (AircraftTypeClass*)pPrevType; - } - - if (AresFunctions::ConvertTypeTo) - { - return AresFunctions::ConvertTypeTo(pThis, pToType); - } - else - { - return ConvertToType_ProcessLikeAres((FootClass*)pThis, pToType); - } + pExt->UpdateTypeData_Building(); } + + return true; } // Checks if vehicle can deploy into a building at its current location. If unit has no DeploysInto set returns noDeploysIntoDefaultValue (def = false) instead. diff --git a/src/Ext/Techno/Body.h b/src/Ext/Techno/Body.h index e035bd426b..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(); From 9c9261499839753768ee1b482d1ce2ed787f2c7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=88=AA=E5=91=B3=E9=BA=BB=E9=85=B1?= <93972760+TaranDahl@users.noreply.github.com> Date: Thu, 29 Jan 2026 01:45:43 +0800 Subject: [PATCH 3/7] docs --- CREDITS.md | 2 ++ docs/Fixed-or-Improved-Logics.md | 2 ++ docs/Whats-New.md | 2 ++ 3 files changed, 6 insertions(+) diff --git a/CREDITS.md b/CREDITS.md index d4df23943f..3c5575df34 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -682,6 +682,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 ba0f39517a..092fa7fb08 100644 --- a/docs/Fixed-or-Improved-Logics.md +++ b/docs/Fixed-or-Improved-Logics.md @@ -334,6 +334,8 @@ This page describes all ingame logics that are fixed or improved in Phobos witho - Fixed a bug introduced by Ares where building types that have `UndeploysInto` cannot display `AltCameo` or `AltCameoPCX` even when you infiltrate enemy buildings with `Factory=UnitType`. - 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. +- 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. ## Newly added global settings diff --git a/docs/Whats-New.md b/docs/Whats-New.md index 6f4c1141d1..004716ac9a 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -528,6 +528,8 @@ New: - Maximum amount for power plant enhancer (by Ollerus) - [Return warhead](New-or-Enhanced-Logics.md#return-warhead) (by Ollerus) - [`AllowBerzerkOnAllies`](Fixed-or-Improved-Logics.md#berzerk-on-allies) (by TaranDahl) +- 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) From f7f6b9b94812abe6afd48cfa56f04259c3583588 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=88=AA=E5=91=B3=E9=BA=BB=E9=85=B1?= <93972760+TaranDahl@users.noreply.github.com> Date: Thu, 29 Jan 2026 19:06:29 +0800 Subject: [PATCH 4/7] Try fix sight bug --- src/Ext/Techno/Body.Update.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Ext/Techno/Body.Update.cpp b/src/Ext/Techno/Body.Update.cpp index 2cf6d5fafd..bfbf44d1f4 100644 --- a/src/Ext/Techno/Body.Update.cpp +++ b/src/Ext/Techno/Body.Update.cpp @@ -1093,6 +1093,7 @@ void TechnoExt::ExtData::UpdateTypeData(TechnoTypeClass* pCurrentType) BombListClass::Instance.AddDetector(pThis); pThis->UpdateSight(0, 0, 0, 0, 0); + MapClass::Instance.RevealArea3(&pThis->Location, 0, pThis->LastSightRange + 3, 0); if (pOldType->GapGenerator) pThis->DestroyGap(); From e0cda09fb7c8c416b4b1c0203e3ff815de79af08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=88=AA=E5=91=B3=E9=BA=BB=E9=85=B1?= <93972760+TaranDahl@users.noreply.github.com> Date: Thu, 29 Jan 2026 20:02:55 +0800 Subject: [PATCH 5/7] update --- docs/Fixed-or-Improved-Logics.md | 2 +- src/Ext/Techno/Body.Update.cpp | 24 +++++++++++++----------- src/Ext/Techno/Body.cpp | 3 ++- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/docs/Fixed-or-Improved-Logics.md b/docs/Fixed-or-Improved-Logics.md index 092fa7fb08..19c7854035 100644 --- a/docs/Fixed-or-Improved-Logics.md +++ b/docs/Fixed-or-Improved-Logics.md @@ -334,7 +334,7 @@ This page describes all ingame logics that are fixed or improved in Phobos witho - Fixed a bug introduced by Ares where building types that have `UndeploysInto` cannot display `AltCameo` or `AltCameoPCX` even when you infiltrate enemy buildings with `Factory=UnitType`. - 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. -- Allow techno conversion working on buildings. +- 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/src/Ext/Techno/Body.Update.cpp b/src/Ext/Techno/Body.Update.cpp index bfbf44d1f4..ec5d159262 100644 --- a/src/Ext/Techno/Body.Update.cpp +++ b/src/Ext/Techno/Body.Update.cpp @@ -1092,8 +1092,9 @@ void TechnoExt::ExtData::UpdateTypeData(TechnoTypeClass* pCurrentType) if (pCurrentType->BombSight) BombListClass::Instance.AddDetector(pThis); - pThis->UpdateSight(0, 0, 0, 0, 0); - MapClass::Instance.RevealArea3(&pThis->Location, 0, pThis->LastSightRange + 3, 0); + // 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(); @@ -1359,9 +1360,8 @@ void TechnoExt::ExtData::UpdateTypeData_Foot() void TechnoExt::ExtData::UpdateTypeData_Building() { auto const pThis = static_cast(this->OwnerObject()); - auto const pOldType = static_cast(this->PreviousType); - auto const pCurrentType = static_cast(this->TypeExtData->OwnerObject()); - auto const abs = pThis->WhatAmI(); + //auto const pOldType = static_cast(this->PreviousType); + auto const pNewType = static_cast(this->TypeExtData->OwnerObject()); // Maybe buggy for (auto pAnim = pThis->Anims[0]; pAnim; pAnim++) @@ -1370,10 +1370,10 @@ void TechnoExt::ExtData::UpdateTypeData_Building() // Skip audio related // Maybe buggy - auto dockNumber = std::max(pCurrentType->NumberOfDocks, 1); + auto dockNumber = std::max(pNewType->NumberOfDocks, 1); pThis->SetLinkCount(dockNumber); - if (pCurrentType->LoadBuildup()) + if (pNewType->LoadBuildup()) pThis->HasBuildUp = true; else pThis->AI_Sellable = false; @@ -1389,7 +1389,7 @@ void TechnoExt::ExtData::UpdateTypeData_Building() // Maybe buggy auto pCrd = pThis->Location; pThis->Limbo(); - pThis->Type = pCurrentType; + pThis->Type = pNewType; pThis->ActuallyPlacedOnMap = false; ++Unsorted::ScenarioInit; pThis->Unlimbo(pCrd, DirType::North); @@ -1401,10 +1401,12 @@ void TechnoExt::ExtData::UpdateTypeData_Building() pOwner->RegisterGain(pThis, false); pOwner->RecheckTechTree = true; - pThis->Ammo = Math::min(pThis->Ammo, pCurrentType->Ammo); + pThis->Ammo = Math::min(pThis->Ammo, pNewType->Ammo); + + pThis->SecondaryFacing.SetROT(pNewType->ROT); + pThis->PrimaryFacing.SetROT(pNewType->ROT); - pThis->SecondaryFacing.SetROT(pCurrentType->ROT); - pThis->PrimaryFacing.SetROT(pCurrentType->ROT); + this->PreviousType = nullptr; } void TechnoExt::ExtData::UpdateLaserTrails() diff --git a/src/Ext/Techno/Body.cpp b/src/Ext/Techno/Body.cpp index f19299b2a6..d594b75fd7 100644 --- a/src/Ext/Techno/Body.cpp +++ b/src/Ext/Techno/Body.cpp @@ -320,7 +320,7 @@ bool TechnoExt::ConvertToType(TechnoClass* pThis, TechnoTypeClass* pToType) return false; } - auto pFoot = abstract_cast(pThis); + auto pFoot = abstract_cast(pThis); if (AresFunctions::ConvertTypeTo && pFoot) { @@ -364,6 +364,7 @@ bool TechnoExt::ConvertToType(TechnoClass* pThis, TechnoTypeClass* pToType) case AbstractType::Building: nowTypePtr = reinterpret_cast(&(static_cast(pThis)->Type)); rtti = AbstractType::BuildingType; + break; default: Debug::Log("%s is not TechnoClass, conversion not allowed\n", pToType->get_ID()); return false; From 3c5fbac77b496d6b3b37e2ea623311e933d94a88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=88=AA=E5=91=B3=E9=BA=BB=E9=85=B1?= <93972760+TaranDahl@users.noreply.github.com> Date: Fri, 30 Jan 2026 12:08:00 +0800 Subject: [PATCH 6/7] Try fix visual issue --- src/Ext/Techno/Body.Update.cpp | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/Ext/Techno/Body.Update.cpp b/src/Ext/Techno/Body.Update.cpp index ec5d159262..965ca9399e 100644 --- a/src/Ext/Techno/Body.Update.cpp +++ b/src/Ext/Techno/Body.Update.cpp @@ -1363,9 +1363,7 @@ void TechnoExt::ExtData::UpdateTypeData_Building() //auto const pOldType = static_cast(this->PreviousType); auto const pNewType = static_cast(this->TypeExtData->OwnerObject()); - // Maybe buggy - for (auto pAnim = pThis->Anims[0]; pAnim; pAnim++) - GameDelete(pAnim); + pThis->DestroyNthAnim(BuildingAnimSlot::All); // Skip audio related @@ -1382,19 +1380,25 @@ void TechnoExt::ExtData::UpdateTypeData_Building() HouseClass* const pOwner = pThis->Owner; + // TODO : Handle addon logics () + if (!pThis->InLimbo) pOwner->RegisterLoss(pThis, false); pOwner->RemoveTracking(pThis); // Maybe buggy - auto pCrd = pThis->Location; - pThis->Limbo(); - pThis->Type = pNewType; - pThis->ActuallyPlacedOnMap = false; - ++Unsorted::ScenarioInit; - pThis->Unlimbo(pCrd, DirType::North); - --Unsorted::ScenarioInit; - pThis->Place(false); + 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); + } pOwner->AddTracking(pThis); if (!pThis->InLimbo) From b0d2ef38faf48d301374c2b7ba7c7063af5dbb31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=88=AA=E5=91=B3=E9=BA=BB=E9=85=B1?= <93972760+TaranDahl@users.noreply.github.com> Date: Mon, 2 Feb 2026 15:43:16 +0800 Subject: [PATCH 7/7] update --- src/Ext/Techno/Body.Update.cpp | 9 +- src/Ext/Techno/Body.cpp | 156 ++++++++++++++++++--------------- 2 files changed, 91 insertions(+), 74 deletions(-) diff --git a/src/Ext/Techno/Body.Update.cpp b/src/Ext/Techno/Body.Update.cpp index 965ca9399e..d28a9db0ae 100644 --- a/src/Ext/Techno/Body.Update.cpp +++ b/src/Ext/Techno/Body.Update.cpp @@ -1360,9 +1360,11 @@ void TechnoExt::ExtData::UpdateTypeData_Foot() void TechnoExt::ExtData::UpdateTypeData_Building() { auto const pThis = static_cast(this->OwnerObject()); - //auto const pOldType = static_cast(this->PreviousType); + 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 @@ -1386,6 +1388,9 @@ void TechnoExt::ExtData::UpdateTypeData_Building() pOwner->RegisterLoss(pThis, false); pOwner->RemoveTracking(pThis); + if (pThis->Factory) + pThis->Factory->AbandonProduction(); + // Maybe buggy if (!pThis->InLimbo) { @@ -1400,6 +1405,8 @@ void TechnoExt::ExtData::UpdateTypeData_Building() pThis->Mark(MarkType::Change); } + pThis->Type = pNewType; + pOwner->AddTracking(pThis); if (!pThis->InLimbo) pOwner->RegisterGain(pThis, false); diff --git a/src/Ext/Techno/Body.cpp b/src/Ext/Techno/Body.cpp index d594b75fd7..1d9bac13a7 100644 --- a/src/Ext/Techno/Body.cpp +++ b/src/Ext/Techno/Body.cpp @@ -322,7 +322,7 @@ bool TechnoExt::ConvertToType(TechnoClass* pThis, TechnoTypeClass* pToType) auto pFoot = abstract_cast(pThis); - if (AresFunctions::ConvertTypeTo && pFoot) + if (AresFunctions::ConvertTypeTo && pFoot) // Ares processed most of the footclass stuff { const int oldHealth = pThis->Health; @@ -341,10 +341,12 @@ bool TechnoExt::ConvertToType(TechnoClass* 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()) @@ -370,83 +372,91 @@ bool TechnoExt::ConvertToType(TechnoClass* pThis, TechnoTypeClass* pToType) 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 - - // 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); + 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); + } + } - // Ares ResetSpotlights -- skipped + const auto& jjLoco = LocomotionClass::CLSIDs::Jumpjet; + if (pToType->BalloonHover && pToType->DeployToLand && prevType->Locomotor != jjLoco && toLoco == jjLoco) + pFoot->Locomotor->Move_To(pFoot->Location); - // 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_Foot(); + }; - auto const pExt = TechnoExt::ExtMap.Find(pThis); - pExt->UpdateTypeData(pToType); + updateTechnoTypeDataLikeAres(); if (rtti != AbstractType::BuildingType) { - // 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); - } - } - - const auto& jjLoco = LocomotionClass::CLSIDs::Jumpjet; - if (pToType->BalloonHover && pToType->DeployToLand && prevType->Locomotor != jjLoco && toLoco == jjLoco) - pFoot->Locomotor->Move_To(pFoot->Location); - - pExt->UpdateTypeData_Foot(); + updateFootTypeDataLikeAres(); } else {