diff --git a/CREDITS.md b/CREDITS.md index 59b2dc7b82..29dac315ed 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -642,3 +642,4 @@ This page lists all the individual contributions to the project by their author. - **Damfoos** - extensive and thorough testing - **Dmitry Volkov** - extensive and thorough testing - **Rise of the East community** - extensive playtesting of in-dev features +- **ahasasjeb** - Add music to super weapons \ No newline at end of file diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index 2d314b67d1..4ffd697b0d 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -1217,6 +1217,28 @@ Detonate.Damage= ; integer Detonate.AtFirer=false ; boolean ``` +### Superweapon music control + +- Superweapons can now play a soundtrack theme when fired and optionally stop after a configurable duration. + +In `rulesmd.ini`: +```ini +[SOMESW] ; SuperWeaponType +Music.Theme= ; Soundtrack theme ID from thememd.ini (such as GodsendOne) +Music.Duration=0 ; integer, game frames; 0 or below means do not auto-stop,with the game speed set to 4, 15 frames equal 1 second. +Music.AffectedHouses= ; owner|allies|enemies|all (default all) +``` + +- `Music.Theme` selects the soundtrack theme by its ID defined in `thememd.ini` (such as `GodsendOne`). +- `Music.Duration` sets how long to keep playing, in game frames. 0 or below means no auto-stop. +- If a different theme is already playing, it will be replaced when the superweapon fires. +- When the timer completes, the theme is stopped only if the currently playing theme still equals the configured `Music.Theme`; if music was changed during the countdown, it will not be altered. +- `Music.AffectedHouses` determines which houses will hear and be affected by the superweapon music on their client: `owner`, `allies`, `enemies`, or `all` (default). Playback and auto-stop are applied only for those houses. + +```{note} +To loop the music correctly during this period, set `Repeat=yes` for the corresponding theme in `thememd.ini`. Otherwise, the track may stop at its end even if `Music.Duration` has not elapsed. +``` + ## Technos ### Aggressive attack move mission diff --git a/docs/Whats-New.md b/docs/Whats-New.md index 787d5253bc..b2a47afcaf 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -329,6 +329,7 @@ HideLightFlashEffects=false ; boolean :open: New: +- [Superweapon music control](New-or-Enhanced-Logics.md#superweapon-music-control) (by ahasasjeb) - [Allow using waypoints, area guard and attack move with aircraft](Fixed-or-Improved-Logics.md#extended-aircraft-missions) (by CrimRecya) - [Enhanced Straight trajectory](New-or-Enhanced-Logics.md#straight-trajectory) (by CrimRecya) - [Enable building production queue](User-Interface.md#building-production-queue) (by CrimRecya) diff --git a/src/Ext/House/Body.h b/src/Ext/House/Body.h index 8f99bffc31..61e65beb9b 100644 --- a/src/Ext/House/Body.h +++ b/src/Ext/House/Body.h @@ -6,6 +6,7 @@ #include #include +#include #include @@ -61,6 +62,10 @@ class HouseExt struct SWExt { int ShotCount; + CDTimerClass MusicTimer; + bool MusicActive; + + SWExt() : ShotCount(0), MusicTimer(), MusicActive(false) { } }; std::vector SuperExts; diff --git a/src/Ext/SWType/Body.cpp b/src/Ext/SWType/Body.cpp index f230746dc7..17320a0a38 100644 --- a/src/Ext/SWType/Body.cpp +++ b/src/Ext/SWType/Body.cpp @@ -12,6 +12,10 @@ void SWTypeExt::ExtData::Initialize() this->EVA_SelectTarget = VoxClass::FindIndex("EVA_SelectTarget"); this->Message_CannotFire = CSFText("MSG:CannotFire"); + + // defaults for music control + this->Music_Theme = -1; + this->Music_Duration = 0; } // ============================= @@ -58,6 +62,10 @@ void SWTypeExt::ExtData::Serialize(T& Stm) .Process(this->LimboKill_Affected) .Process(this->LimboKill_IDs) .Process(this->RandomBuffer) + // music control + .Process(this->Music_Theme) + .Process(this->Music_Duration) + .Process(this->Music_AffectedHouses) .Process(this->Detonate_Warhead) .Process(this->Detonate_Weapon) .Process(this->Detonate_Damage) @@ -131,6 +139,11 @@ void SWTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->SW_MaxCount.Read(exINI, pSection, "SW.MaxCount"); this->SW_Shots.Read(exINI, pSection, "SW.Shots"); + // music control + this->Music_Theme = pINI->ReadTheme(pSection, "Music.Theme", this->Music_Theme); + this->Music_Duration.Read(exINI, pSection, "Music.Duration"); + this->Music_AffectedHouses.Read(exINI, pSection, "Music.AffectedHouses"); + this->Message_CannotFire.Read(exINI, pSection, "Message.CannotFire"); this->Message_InsufficientFunds.Read(exINI, pSection, "Message.InsufficientFunds"); diff --git a/src/Ext/SWType/Body.h b/src/Ext/SWType/Body.h index e4af1c3879..176d61bb74 100644 --- a/src/Ext/SWType/Body.h +++ b/src/Ext/SWType/Body.h @@ -63,6 +63,9 @@ class SWTypeExt Valueable LimboKill_Affected; ValueableVector LimboKill_IDs; Valueable RandomBuffer; + Valueable Music_Theme; + Valueable Music_Duration; + Valueable Music_AffectedHouses; ValueableIdxVector SW_Next; Valueable SW_Next_RealLaunch; Valueable SW_Next_IgnoreInhibitors; @@ -187,6 +190,7 @@ class SWTypeExt , SW_Link_RandomWeightsData {} , Message_LinkedSWAcquired {} , EVA_LinkedSWAcquired {} + , Music_AffectedHouses { AffectedHouse::All } { } // Ares 0.A functions diff --git a/src/Ext/SWType/FireSuperWeapon.cpp b/src/Ext/SWType/FireSuperWeapon.cpp index b43b5c35fe..b0b8094caa 100644 --- a/src/Ext/SWType/FireSuperWeapon.cpp +++ b/src/Ext/SWType/FireSuperWeapon.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -46,6 +47,23 @@ void SWTypeExt::FireSuperWeaponExt(SuperClass* pSW, const CellStruct& cell) auto& sw_ext = HouseExt::ExtMap.Find(pHouse)->SuperExts[pType->ArrayIndex]; sw_ext.ShotCount++; + // Music: play theme and start timer if configured + if (pTypeExt->Music_Theme.Get() >= 0) + { + const auto affected = pTypeExt->Music_AffectedHouses.Get(); + if (EnumFunctions::CanTargetHouse(affected, pHouse, HouseClass::CurrentPlayer)) + { + ThemeClass::Instance.Play(pTypeExt->Music_Theme); + } + + const int duration = pTypeExt->Music_Duration.Get(); + if (duration > 0) + { + sw_ext.MusicTimer.Start(duration); + sw_ext.MusicActive = true; + } + } + const auto pTags = &pHouse->RelatedTags; if (pTags->Count > 0) { diff --git a/src/Ext/Scenario/Body.cpp b/src/Ext/Scenario/Body.cpp index 6463387b5b..1d3c28d1e4 100644 --- a/src/Ext/Scenario/Body.cpp +++ b/src/Ext/Scenario/Body.cpp @@ -2,6 +2,10 @@ #include #include +#include +#include +#include +#include std::unique_ptr ScenarioExt::Data = nullptr; @@ -265,5 +269,46 @@ DEFINE_HOOK(0x55B4E1, LogicClass_Update_BeforeAll, 0x5) ScenarioExt::Global()->UpdateAutoDeathObjectsInLimbo(); ScenarioExt::Global()->UpdateTransportReloaders(); + // SW music timers: stop music when timer completes + for (auto const pHouse : HouseClass::Array) + { + if (!pHouse) { continue; } + auto& houseExt = *HouseExt::ExtMap.Find(pHouse); + for (size_t i = 0; i < houseExt.SuperExts.size(); ++i) + { + auto& swExt = houseExt.SuperExts[i]; + if (swExt.MusicActive && swExt.MusicTimer.Completed()) + { + int configuredTheme = -1; + SuperClass* pSuper = nullptr; + SWTypeExt::ExtData* pTypeExt = nullptr; + if (pHouse->Supers.Count > static_cast(i)) + { + pSuper = pHouse->Supers[static_cast(i)]; + if (pSuper && pSuper->Type) + { + pTypeExt = SWTypeExt::ExtMap.Find(pSuper->Type); + configuredTheme = pTypeExt->Music_Theme.Get(); + } + } + if (configuredTheme >= 0 && ThemeClass::Instance.CurrentTheme == configuredTheme) + { + // stop only if same theme and local house is affected + AffectedHouse affected = AffectedHouse::All; + if (pTypeExt) + { + affected = pTypeExt->Music_AffectedHouses.Get(); + } + if (EnumFunctions::CanTargetHouse(affected, pHouse, HouseClass::CurrentPlayer)) + { + ThemeClass::Instance.Stop(); + } + } + swExt.MusicTimer.Stop(); + swExt.MusicActive = false; + } + } + } + return 0; }