diff --git a/CREDITS.md b/CREDITS.md index 24236c02d5..f13b61a3cf 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -134,6 +134,7 @@ This page lists all the individual contributions to the project by their author. - Customizable FLH when infantry is prone or deployed - Initial strength for cloned infantry - Map Events 604 & 605 for checking if a specific Techno enters in a cell + - Grant new superweapons in superweapons - **Starkku**: - Misc. minor bugfixes & improvements - AI script actions: diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index 75fc0ddacf..4897d80de4 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -1132,6 +1132,31 @@ In `rulesmd.ini`: TabIndex=1 ; integer ``` +### Grant new superweapons in superweapons + +- Superweapons can add 1-time superweapons to the firer like the nuke crate. Granted types can be additionally randomized using the same rules as with LimboDelivery (see above). +- `SW.GrantOneTime.InitialReady` specifies if all new granted superweapons will be ready for launch. If not set this behaviour will be managed by `SW.InitialReady` of the granted superweapon. +- `SW.GrantOneTime.ReadyIfExists` specifies if superweapons should be ready for launch if already exists. If not set this behaviour will be managed by `SW.GrantOneTime.InitialReady` or `SW.InitialReady` of the granted superweapon. +- `SW.GrantOneTime.ResetIfExists` specifies if superweapons timers should be reset if already exists. Takes precedence over `SW.GrantOneTime.ReadyIfExists`, `SW.GrantOneTime.InitialReady` and `SW.InitialReady`. +- `Message.GrantOneTimeLaunched` will be displayed to the firer when the main superweapon is launched. +- `EVA.GrantOneTimeLaunched` will be played to the firer when the main superweapon is launched. +- These superweapons can be made random with these optional tags. The game will randomly choose only a single superweapon from the list for each roll chance provided. + - `SW.GrantOneTime.RollChances` lists chances of each "dice roll" happening. Valid values range from 0% (never happens) to 100% (always happens). Defaults to a single sure roll. + - `SW.GrantOneTime.RandomWeightsN` lists the weights for each "dice roll" that increase the probability of picking a specific superweapon. Valid values are 0 (don't pick) and above (the higher value, the bigger the likelyhood). `RandomWeights` are a valid alias for `RandomWeights0`. If a roll attempt doesn't have weights specified, the last weights will be used. + +In `rulesmd.ini`: +```ini +[SOMESW] ; Super Weapon +SW.GrantOneTime= ; List of super weapons +SW.GrantOneTime.RollChances= ; List of percentages. +SW.GrantOneTime.RandomWeightsN= ; List of integers. +SW.GrantOneTime.InitialReady= ; boolean +SW.GrantOneTime.ReadyIfExists= ; boolean +SW.GrantOneTime.ResetIfExists=false ; boolean +Message.GrantOneTimeLaunched= ; CSF entry key +EVA.GrantOneTimeLaunched= ; EVA entry +``` + ## Technos ### Aircraft spawner customizations diff --git a/docs/Whats-New.md b/docs/Whats-New.md index 45a8d28ae9..0be3083e59 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -425,6 +425,7 @@ New: - Allow setting default singleplayer map loading screen and briefing offsets (by Starkku) - Allow toggling whether or not fire particle systems adjust target coordinates when firer rotates (by Starkku) - `AmbientDamage` warhead & main target ignore customization (by Starkku) +- Grant new superweapons in superweapons (by FS-21). - Flashing Technos on selecting (by Fryone) - Customizable DropPod properties on a per-InfantryType basis (by Trsdy) - Projectile return weapon (by Starkku) diff --git a/src/Ext/SWType/Body.cpp b/src/Ext/SWType/Body.cpp index 15c847f068..f0d6f9acd2 100644 --- a/src/Ext/SWType/Body.cpp +++ b/src/Ext/SWType/Body.cpp @@ -58,6 +58,14 @@ void SWTypeExt::ExtData::Serialize(T& Stm) .Process(this->EMPulse_SuspendOthers) .Process(this->EMPulse_Cannons) .Process(this->EMPulse_TargetSelf) + .Process(this->SW_GrantOneTime) + .Process(this->SW_GrantOneTime_InitialReady) + .Process(this->SW_GrantOneTime_ReadyIfExists) + .Process(this->SW_GrantOneTime_ResetIfExists) + .Process(this->SW_GrantOneTime_RandomWeightsData) + .Process(this->SW_GrantOneTime_RollChances) + .Process(this->Message_GrantOneTimeLaunched) + .Process(this->EVA_GrantOneTimeLaunched) ; } @@ -163,6 +171,36 @@ void SWTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->SW_Next_RandomWeightsData.push_back(std::move(weights2)); } + this->SW_GrantOneTime.Read(exINI, pSection, "SW.GrantOneTime"); + this->SW_GrantOneTime_InitialReady.Read(exINI, pSection, "SW.GrantOneTime.InitialReady"); + this->SW_GrantOneTime_ReadyIfExists.Read(exINI, pSection, "SW.GrantOneTime.ReadyIfExists"); + this->SW_GrantOneTime_ResetIfExists.Read(exINI, pSection, "SW.GrantOneTime.ResetIfExists"); + this->Message_GrantOneTimeLaunched.Read(exINI, pSection, "Message.GrantOneTimeLaunched"); + this->EVA_GrantOneTimeLaunched.Read(exINI, pSection, "EVA.GrantOneTimeLaunched"); + this->SW_GrantOneTime_RollChances.Read(exINI, pSection, "SW.GrantOneTime.RollChances"); + + // SW.GrantOneTime.RandomWeights + for (size_t i = 0; ; ++i) + { + ValueableVector weights3; + _snprintf_s(tempBuffer, sizeof(tempBuffer), "SW.GrantOneTime.RandomWeights%d", i); + weights3.Read(exINI, pSection, tempBuffer); + + if (!weights3.size()) + break; + + this->SW_GrantOneTime_RandomWeightsData.push_back(std::move(weights3)); + } + ValueableVector weights3; + weights3.Read(exINI, pSection, "SW.GrantOneTime.RandomWeights"); + if (weights3.size()) + { + if (this->SW_GrantOneTime_RandomWeightsData.size()) + this->SW_GrantOneTime_RandomWeightsData[0] = std::move(weights3); + else + this->SW_GrantOneTime_RandomWeightsData.push_back(std::move(weights3)); + } + this->Detonate_Warhead.Read(exINI, pSection, "Detonate.Warhead"); this->Detonate_Weapon.Read(exINI, pSection, "Detonate.Weapon"); this->Detonate_Damage.Read(exINI, pSection, "Detonate.Damage"); diff --git a/src/Ext/SWType/Body.h b/src/Ext/SWType/Body.h index 2232ca71f6..a4394f491f 100644 --- a/src/Ext/SWType/Body.h +++ b/src/Ext/SWType/Body.h @@ -67,6 +67,7 @@ class SWTypeExt std::vector> LimboDelivery_RandomWeightsData; std::vector> SW_Next_RandomWeightsData; + std::vector> SW_GrantOneTime_RandomWeightsData; std::vector Convert_Pairs; @@ -80,6 +81,14 @@ class SWTypeExt ValueableVector EMPulse_Cannons; Valueable EMPulse_TargetSelf; + ValueableIdxVector SW_GrantOneTime; + Nullable SW_GrantOneTime_InitialReady; + Nullable SW_GrantOneTime_ReadyIfExists; + Valueable SW_GrantOneTime_ResetIfExists; + ValueableVector SW_GrantOneTime_RollChances; + Valueable Message_GrantOneTimeLaunched; + NullableIdx EVA_GrantOneTimeLaunched; + ExtData(SuperWeaponTypeClass* OwnerObject) : Extension(OwnerObject) , Money_Amount { 0 } , SW_Inhibitors {} @@ -128,6 +137,14 @@ class SWTypeExt , EMPulse_SuspendOthers { false } , EMPulse_Cannons {} , EMPulse_TargetSelf { false } + , SW_GrantOneTime {} + , SW_GrantOneTime_InitialReady {} + , SW_GrantOneTime_ReadyIfExists {} + , SW_GrantOneTime_ResetIfExists { false } + , SW_GrantOneTime_RollChances {} + , SW_GrantOneTime_RandomWeightsData {} + , Message_GrantOneTimeLaunched {} + , EVA_GrantOneTimeLaunched {} { } // Ares 0.A functions @@ -151,6 +168,8 @@ class SWTypeExt std::vector GetEMPulseCannons(HouseClass* pOwner, const CellStruct& cell) const; std::pair GetEMPulseCannonRange(BuildingClass* pBuilding) const; + void GrantOneTimeFromList(SuperClass* pSW); + virtual void LoadFromINIFile(CCINIClass* pINI) override; virtual ~ExtData() = default; diff --git a/src/Ext/SWType/FireSuperWeapon.cpp b/src/Ext/SWType/FireSuperWeapon.cpp index 131b22fcef..e4a6c67bba 100644 --- a/src/Ext/SWType/FireSuperWeapon.cpp +++ b/src/Ext/SWType/FireSuperWeapon.cpp @@ -9,6 +9,7 @@ #include #include "Ext/House/Body.h" +#include #include "Ext/WarheadType/Body.h" #include "Ext/WeaponType/Body.h" #include @@ -34,6 +35,9 @@ void SWTypeExt::FireSuperWeaponExt(SuperClass* pSW, const CellStruct& cell) if (pTypeExt->Convert_Pairs.size() > 0) pTypeExt->ApplyTypeConversion(pSW); + if (pTypeExt->SW_GrantOneTime.size() > 0) + pTypeExt->GrantOneTimeFromList(pSW); + if (static_cast(pSW->Type->Type) == 28 && !pTypeExt->EMPulse_TargetSelf) // Ares' Type=EMPulse SW pTypeExt->HandleEMPulseLaunch(pSW, cell); @@ -345,3 +349,70 @@ void SWTypeExt::ExtData::HandleEMPulseLaunch(SuperClass* pSW, const CellStruct& } } } + +void SWTypeExt::ExtData::GrantOneTimeFromList(SuperClass* pSW) +{ + // SW.GrantOneTime proper SW granting mechanic + HouseClass* pHouse = pSW->Owner; + bool notObserver = !pHouse->IsObserver() || !pHouse->IsCurrentPlayerObserver(); + + auto grantTheSW = [=](const int swIdxToAdd) -> bool + { + if (const auto pSuper = pHouse->Supers.GetItem(swIdxToAdd)) + { + bool forceReadyness = pSuper->IsPresent && this->SW_GrantOneTime_ReadyIfExists.isset() && this->SW_GrantOneTime_ReadyIfExists.Get(); + bool forceReset = pSuper->IsPresent && this->SW_GrantOneTime_ResetIfExists.Get(); + bool granted = pSuper->Grant(true, false, false); + + if (granted || forceReadyness || forceReset) + { + auto const pTypeExt = SWTypeExt::ExtMap.Find(pSuper->Type); + bool isReady = this->SW_GrantOneTime_InitialReady.isset() && this->SW_GrantOneTime_InitialReady.Get() ? true : false; + isReady = !this->SW_GrantOneTime_InitialReady.isset() && pTypeExt->SW_InitialReady ? true : isReady; + isReady = forceReadyness ? true : isReady; + + if (isReady && !forceReset) + { + pSuper->RechargeTimer.TimeLeft = 0; + pSuper->SetReadiness(true); + } + else + { + pSuper->Reset(); + } + + if (notObserver && pHouse->IsCurrentPlayer()) + { + if (MouseClass::Instance->AddCameo(AbstractType::Special, swIdxToAdd)) + MouseClass::Instance->RepaintSidebar(1); + } + } + + return granted; + } + + return false; + }; + + // random mode + if (this->SW_GrantOneTime_RandomWeightsData.size()) + { + auto results = this->WeightedRollsHandler(&this->SW_GrantOneTime_RollChances, &this->SW_GrantOneTime_RandomWeightsData, this->SW_GrantOneTime.size()); + for (int result : results) + grantTheSW(this->SW_GrantOneTime[result]); + } + // no randomness mode + else + { + for (const auto swType : this->SW_GrantOneTime) + grantTheSW(swType); + } + + if (notObserver && pHouse->IsCurrentPlayer()) + { + if (this->EVA_GrantOneTimeLaunched.isset()) + VoxClass::PlayIndex(this->EVA_GrantOneTimeLaunched.Get(), -1, -1); + + MessageListClass::Instance->PrintMessage(this->Message_GrantOneTimeLaunched.Get(), RulesClass::Instance->MessageDelay, HouseClass::CurrentPlayer->ColorSchemeIndex, true); + } +}