diff --git a/CREDITS.md b/CREDITS.md index f3b359298d..ad068c6bd9 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -966,5 +966,7 @@ This page lists all the individual contributions to the project by their author. - **obsidianus** - Automatic conversion based on health - **Nuke** - Reload speed adjustment on promotion - **frg2089 (舰队的偶像-岛风酱!)** - Fix `Slaved.OwnerWhenMasterKilled` not being respected when the master is sold or self-destructed +- **dh381-1(dh381)** + - Add country initial elite unit lists - **weiyongxuan** - Extended `CanTargetHouses` to allow targeting neutral houses - **Sovietianqi** - Customizable crew type per country diff --git a/Phobos.vcxproj b/Phobos.vcxproj index 67dc28e6ee..a172ae1b7b 100644 --- a/Phobos.vcxproj +++ b/Phobos.vcxproj @@ -108,6 +108,7 @@ + diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index f0eb3f32c7..36de23268b 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -823,6 +823,29 @@ SpyEffect.VictimSuperWeapon= ; SuperWeaponType SpyEffect.InfiltratorSuperWeapon= ; SuperWeaponType ``` +## Countries + +### Set country initial elite + +- Now you can add `TechnoTypes` in the three lists. They will appear at an elite initial level when produced, and cloned infantry will also be affected by this effect. + +In `rulesmd.ini`: +```ini +[SOMECOUNTRY] ; Country +EliteInfantry= ; List of InfantryTypes +EliteUnits= ; List of UnitTypes +EliteAircraft= ; List of AircraftTypes +``` + +- For each `TechnoType`, you can define `SpareCameo` and `SpareCameoPCX` to specify the elite‑level icon. When a `TechnoType` is added to the initial elite list, it will preferentially use the SpareCameo as its icon. + +In `artmd.ini`: +```ini +[SOMETECHNO] ; TechnoType +SpareCameo= ; filename +SpareCameoPCX= ; filename - include ".pcx" extension +``` + ## Infantry ### Allow infantry to perform type conversion when deploying and undeploying diff --git a/docs/Whats-New.md b/docs/Whats-New.md index 2cf117e54f..33ebb2226b 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -444,6 +444,7 @@ HideShakeEffects=false ; boolean - Country-based attached effects (by Ollerus) - [Customizable crew type per country](Fixed-or-Improved-Logics.md#customizable-crew-type-per-country) (by Sovietianqi, FlyStar, Noble_Fish) - [Country-specific veteran buildings](Fixed-or-Improved-Logics.md#country-specific-veteran-buildings) (by Noble_Fish) +- [Set country initial elite](New-or-Enhanced-Logics.md#set-country-initial-elite) (by dh381) #### Vanilla fixes: - Fixed the bug where a building with `Factory=BuildingType` owned by the AI did not play `ProductionAnim` when placing a produced building (by Noble_Fish) diff --git a/src/Ext/House/Body.cpp b/src/Ext/House/Body.cpp index e53d1d2ad9..cc78b14fc9 100644 --- a/src/Ext/House/Body.cpp +++ b/src/Ext/House/Body.cpp @@ -713,6 +713,7 @@ void HouseExt::Serialize(T& Stm) .Process(this->ForceRadar) .Process(this->PlayerAutoRepair) //.Process(this->BeaconsPlacedOrder) beacon is not saved, so this follows it. + .Process(this->TechnoPCX_IsLoad) ; } diff --git a/src/Ext/House/Body.h b/src/Ext/House/Body.h index df5ba629b6..ba6ac3737a 100644 --- a/src/Ext/House/Body.h +++ b/src/Ext/House/Body.h @@ -81,6 +81,8 @@ class HouseExt final : public AbstractExt, public Detach::Listener BeaconsPlacedOrder; + ValueableVector TechnoPCX_IsLoad; + HouseExt(HouseClass* OwnerObject) : AbstractExt(OwnerObject) , PowerPlantEnhancers {} , OwnedLimboDeliveredBuildings {} @@ -116,6 +118,7 @@ class HouseExt final : public AbstractExt, public Detach::ListenerAttachEffects.LoadFromINI(pINI, pSection); this->AttachEffects_AttachOnOwnerChange.Read(exINI, pSection, "AttachEffect.AttachOnOwnerChange"); + this->Crew.Read(exINI, pSection, "Crew"); + + this->EliteInfantry.Read(exINI, pSection, "EliteInfantry"); + this->EliteUnits.Read(exINI, pSection, "EliteUnits"); + this->EliteAircraft.Read(exINI, pSection, "EliteAircraft"); this->Crew.Read(exINI, pSection, "Crew"); this->VeteranBuildings.Read(exINI, pSection, "VeteranBuildings"); @@ -33,9 +38,14 @@ void HouseTypeExt::Serialize(T& Stm) { Stm .Process(this->EVATag) + .Process(this->AttachEffects) .Process(this->AttachEffects_AttachOnOwnerChange) .Process(this->Crew) + + .Process(this->EliteInfantry) + .Process(this->EliteUnits) + .Process(this->EliteAircraft) .Process(this->VeteranBuildings) .Process(this->VeteranDefenses) ; diff --git a/src/Ext/HouseType/Body.h b/src/Ext/HouseType/Body.h index 1536aa1e78..3ee414dea1 100644 --- a/src/Ext/HouseType/Body.h +++ b/src/Ext/HouseType/Body.h @@ -32,6 +32,9 @@ class HouseTypeExt final : public AbstractTypeExt Nullable AttachEffects_AttachOnOwnerChange; Nullable Crew; + ValueableVector EliteInfantry; + ValueableVector EliteUnits; + ValueableVector EliteAircraft; ValueableVector VeteranBuildings; ValueableVector VeteranDefenses; @@ -40,6 +43,9 @@ class HouseTypeExt final : public AbstractTypeExt , AttachEffects {} , AttachEffects_AttachOnOwnerChange {} , Crew {} + , EliteInfantry {} + , EliteUnits {} + , EliteAircraft {} , VeteranBuildings {} , VeteranDefenses {} { } diff --git a/src/Ext/HouseType/Hooks.InitialElite.cpp b/src/Ext/HouseType/Hooks.InitialElite.cpp new file mode 100644 index 0000000000..107b55eda4 --- /dev/null +++ b/src/Ext/HouseType/Hooks.InitialElite.cpp @@ -0,0 +1,152 @@ +#include "Body.h" + +#include +#include + +static SHPStruct* LoadSHPCameo(TechnoTypeClass* pType) +{ + auto pTypeExt = TechnoTypeExt::Fetch(pType); + + if(pTypeExt->SHPCameo_IsLoad) + return pTypeExt->SpareCameo; + + char pFileName[0x20]; + + strcpy_s(pFileName, pTypeExt->SpareCameoFile.data()); + _strlwr_s(pFileName); + + if(!_stricmp(pFileName, pTypeExt->SpareCameoFile.data()) + && !strstr(pFileName, ".pcx")) + { + std::string Filename(pFileName); + Filename += ".shp"; + SHPStruct* pSHP = FileSystem::LoadSHPFile(Filename.c_str()); + pTypeExt->SHPCameo_IsLoad = true; + pTypeExt->SpareCameo = pSHP; + return pSHP; + } + return nullptr; +} + +static bool DrawPCXCameo(TechnoTypeClass* pType, const int destX, const int destY) +{ + auto pTypeExt = TechnoTypeExt::Fetch(pType); + char pFileName[0x20]; + + strcpy_s(pFileName, pTypeExt->SpareCameoPCX.GetFilename()); + _strlwr_s(pFileName); + + if(!_stricmp(pFileName, pTypeExt->SpareCameoPCX.GetFilename()) + && strstr(pFileName, ".pcx")) + { + PCX::Instance.LoadFile(pFileName); + if (const auto pCameoPCX = PCX::Instance.GetSurface(pFileName)) + { + RectangleStruct bounds = { destX, destY, 60, 48 }; + return PCX::Instance.BlitToSurface(&bounds, DSurface::Sidebar, pCameoPCX); + } + } + return false; +} + +DEFINE_HOOK(0x6A980A, StripClass_Draw_DrawSHPCameo, 0x8) +{ + GET(TechnoTypeClass*, pType, EBX); + + auto pHouseExt = HouseExt::Fetch(HouseClass::CurrentPlayer); + auto pHouseTypeExt = HouseTypeExt::Fetch(HouseClass::CurrentPlayer->Type); + + SHPStruct* pSHP = nullptr; + + switch(pType->WhatAmI()) + { + case AbstractType::InfantryType: + { + if(pHouseTypeExt->EliteInfantry.Contains(pType)) + { + pHouseExt->TechnoPCX_IsLoad.push_back(pType); + pSHP = LoadSHPCameo(pType); + } + break; + } + case AbstractType::UnitType: + { + if(pHouseTypeExt->EliteUnits.Contains(pType)) + { + pHouseExt->TechnoPCX_IsLoad.push_back(pType); + pSHP = LoadSHPCameo(pType); + } + break; + } + case AbstractType::AircraftType: + { + if(pHouseTypeExt->EliteAircraft.Contains(pType)) + { + pHouseExt->TechnoPCX_IsLoad.push_back(pType); + pSHP = LoadSHPCameo(pType); + } + break; + } + default: + break; + } + + if(pSHP) + R->EAX(pSHP); + + return 0; +} + +DEFINE_HOOK(0x6A99E7, StripClass_Draw_DrawPCXCameo, 0x6) +{ + enum { SkipDrawSHP = 0x6A9A43 }; + + GET(const int, destX, ESI); + GET(const int, destY, EBP); + GET_STACK(SHPStruct*, pSHP, STACK_OFFSET(0x48C, -0x444)); + + auto pHouseExt = HouseExt::Fetch(HouseClass::CurrentPlayer); + + for(auto pType : pHouseExt->TechnoPCX_IsLoad) + { + if(pSHP == pType->Cameo || pSHP == pType->AltCameo) + { + if(DrawPCXCameo(pType, destX, destY)) + return SkipDrawSHP; + } + } + return 0; +} + +DEFINE_HOOK(0x443C71, BuildingClass_ExitObject_CountryInitialElite, 0x6) +{ + GET(TechnoClass*, pTechno, EDI); + + const auto pHouseTypeExt = HouseTypeExt::Fetch(pTechno->Owner->Type); + + switch(pTechno->WhatAmI()) + { + case AbstractType::Infantry: + { + if(pHouseTypeExt->EliteInfantry.Contains(pTechno->GetTechnoType())) + pTechno->Veterancy.SetElite(); + break; + } + case AbstractType::Unit: + { + if(pHouseTypeExt->EliteUnits.Contains(pTechno->GetTechnoType())) + pTechno->Veterancy.SetElite(); + break; + } + case AbstractType::Aircraft: + { + if(pHouseTypeExt->EliteAircraft.Contains(pTechno->GetTechnoType())) + pTechno->Veterancy.SetElite(); + break; + } + default: + break; + } + + return 0; +} diff --git a/src/Ext/TechnoType/Body.cpp b/src/Ext/TechnoType/Body.cpp index c5b99aab19..16d2c72139 100644 --- a/src/Ext/TechnoType/Body.cpp +++ b/src/Ext/TechnoType/Body.cpp @@ -1441,6 +1441,9 @@ void TechnoTypeExt::LoadFromINIFile(CCINIClass* const pINI) // VoiceIFVRepair from Ares 0.2 this->VoiceIFVRepair.Read(exINI, pSection, "VoiceIFVRepair"); this->ParseVoiceWeaponAttacks(exINI, pSection, this->VoiceWeaponAttacks, this->VoiceEliteWeaponAttacks); + + this->SpareCameoFile.Read(pArtINI, pArtSection, "SpareCameo"); + this->SpareCameoPCX.Read(pArtINI, pArtSection, "SpareCameoPCX"); } template @@ -1838,6 +1841,11 @@ void TechnoTypeExt::Serialize(T& Stm) .Process(this->DecloakAnims) .Process(this->Cloak_KickOutParasite) + .Process(this->SpareCameoFile) + .Process(this->SpareCameo) + .Process(this->SHPCameo_IsLoad) + .Process(this->SpareCameoPCX) + // Ares 0.2 .Process(this->RadarJamRadius) diff --git a/src/Ext/TechnoType/Body.h b/src/Ext/TechnoType/Body.h index fda8f6f3dc..3cf2ed0af6 100644 --- a/src/Ext/TechnoType/Body.h +++ b/src/Ext/TechnoType/Body.h @@ -463,6 +463,12 @@ class TechnoTypeExt : public ObjectTypeExt Nullable Unsellable; Nullable KeepAlive; + PhobosFixedString<0x20> SpareCameoFile; + SHPStruct* SpareCameo; + bool SHPCameo_IsLoad; + PhobosPCXFile SpareCameoPCX; + + TechnoTypeExt(TechnoTypeClass* OwnerObject) : ObjectTypeExt(OwnerObject) , HealthBar_Hide { false } , HealthBar_HidePips { false } @@ -855,6 +861,11 @@ class TechnoTypeExt : public ObjectTypeExt , DecloakAnims {} , Cloak_KickOutParasite {} + , SpareCameoFile { NONE_STR } + , SpareCameo { nullptr } + , SHPCameo_IsLoad { false } + , SpareCameoPCX {} + // Ares 0.2 , RadarJamRadius { 0 }