diff --git a/CREDITS.md b/CREDITS.md index 0fb9954050..a931955c49 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -149,6 +149,7 @@ This page lists all the individual contributions to the project by their author. - Warhead activation target health thresholds enhancements - Event 606: AttachEffect is attaching to a Techno - Linked superweapons + - New EVA voice after deploying a building - **Starkku**: - Misc. minor bugfixes & improvements - AI script actions: diff --git a/YRpp b/YRpp index 1a4e510539..2d19944aa0 160000 --- a/YRpp +++ b/YRpp @@ -1 +1 @@ -Subproject commit 1a4e510539459bc3535b477e2b1a873fc62821f0 +Subproject commit 2d19944aa0846a098826b4cdd2e0ac00f36915b0 diff --git a/docs/User-Interface.md b/docs/User-Interface.md index 30b4f2c345..24936f5cda 100644 --- a/docs/User-Interface.md +++ b/docs/User-Interface.md @@ -27,6 +27,24 @@ IngameScore.WinTheme= ; Soundtrack theme ID IngameScore.LoseTheme= ; Soundtrack theme ID ``` +### New EVA voice after deploying a building + +- You can now replace the current EVA voice when a specific building is placed/deployed. +- If any buiding is undeployed/sold/destroyed EVA voice will be evaluated again looking all the `NewEVAVoice.Index`. +- `NewEVAVoice.Index` is the index of the new EVA voice. Ares is hightly recomended because these indexes are reading the new section `[EVATypes]` at `evamd.ini` introduced by Ares. Look at Ares documentation regarding new EVA voices for more information. +- In case of multiple buildings with different `NewEVAVoice.Index` EVA voices then `NewEVAVoice.Priority` establish a priority queue, being the highest value the selected one. +- `NewEVAVoice.RecheckOnDeath` re-checks a new EVA voice after the destruction/undeployment of of of these buildings. +- `NewEVAVoice.InitialMessage` plays an EVA message to the player when a different EVA has been selected. + +In `rulesmd.ini`: +```ini +[SOMEBUILDING] ; BuildingType +NewEVAVoice.Index= ; integer +NewEVAVoice.Priority=1 ; integer +NewEVAVoice.RecheckOnDeath=false ; boolean +NewEVAVoice.InitialMessage= ; EVA entry +``` + ## Battle screen UI/UX ### Digital display diff --git a/docs/Whats-New.md b/docs/Whats-New.md index 007025be34..8d7288ea9f 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -425,6 +425,7 @@ New: - [Damaged aircraft image changes](New-or-Enhanced-Logics.md#damaged-aircraft-image-changes) (by Fryone) - [Additional attached animation position customizations](Fixed-or-Improved-Logics.md#attached-animation-position-customization) (by Starkku) - Use `SkipCrushSlowdown=true` to avoid the bug related to `Accelerates=true` and `MovementZone=CrushAll` (by TaranDahl) +- New EVA voice after deploying a building (by FS-21) 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/Building/Body.cpp b/src/Ext/Building/Body.cpp index 79757c55eb..117abc94d3 100644 --- a/src/Ext/Building/Body.cpp +++ b/src/Ext/Building/Body.cpp @@ -3,6 +3,9 @@ #include #include +#include +#include +#include BuildingExt::ExtContainer BuildingExt::ExtMap; diff --git a/src/Ext/Building/Hooks.cpp b/src/Ext/Building/Hooks.cpp index 82d9372d04..bdf8739c8a 100644 --- a/src/Ext/Building/Hooks.cpp +++ b/src/Ext/Building/Hooks.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -257,10 +258,13 @@ DEFINE_HOOK(0x440B4F, BuildingClass_Unlimbo_SetShouldRebuild, 0x5) { enum { ContinueCheck = 0x440B58, SkipSetShouldRebuild = 0x440B81 }; + GET(BuildingClass* const, pThis, ESI); + + if (BuildingTypeExt::ExtMap.Find(pThis->Type)->NewEvaVoice_Index.isset()) + SideExt::UpdateMainEvaVoice(pThis); + if (SessionClass::IsCampaign()) { - GET(BuildingClass* const, pThis, ESI); - // Preplaced structures are already managed before if (BuildingExt::ExtMap.Find(pThis)->IsCreatedFromMapFile) return SkipSetShouldRebuild; @@ -425,6 +429,11 @@ DEFINE_HOOK(0x445D87, BuildingClass_Limbo_DestroyableObstacle, 0x6) { GET(BuildingClass*, pThis, ESI); + auto const pTypeExt = BuildingTypeExt::ExtMap.Find(pThis->Type); + + if (pTypeExt->NewEvaVoice_Index.isset() && pTypeExt->NewEvaVoice_RecheckOnDeath) + SideExt::UpdateMainEvaVoice(pThis); + if (BuildingTypeExt::ExtMap.Find(pThis->Type)->IsDestroyableObstacle) RecalculateCells(pThis); diff --git a/src/Ext/BuildingType/Body.cpp b/src/Ext/BuildingType/Body.cpp index acad4d12e9..c19c07b3bb 100644 --- a/src/Ext/BuildingType/Body.cpp +++ b/src/Ext/BuildingType/Body.cpp @@ -195,6 +195,11 @@ void BuildingTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->BunkerWallsDownSound.Read(exINI, pSection, "BunkerWallsDownSound"); this->BuildingRepairedSound.Read(exINI, pSection, "BuildingRepairedSound"); + this->NewEvaVoice_Index.Read(exINI, pSection, "NewEVAVoice.Index"); + this->NewEvaVoice_Priority.Read(exINI, pSection, "NewEVAVoice.Priority"); + this->NewEvaVoice_RecheckOnDeath.Read(exINI, pSection, "NewEVAVoice.RecheckOnDeath"); + this->NewEvaVoice_InitialMessage.Read(exINI, pSection, "NewEVAVoice.InitialMessage"); + if (pThis->NumberOfDocks > 0) { this->AircraftDockingDirs.clear(); @@ -334,6 +339,10 @@ void BuildingTypeExt::ExtData::Serialize(T& Stm) .Process(this->BuildingRepairedSound) .Process(this->Refinery_UseNormalActiveAnim) .Process(this->HasPowerUpAnim) + .Process(this->NewEvaVoice_Index) + .Process(this->NewEvaVoice_Priority) + .Process(this->NewEvaVoice_RecheckOnDeath) + .Process(this->NewEvaVoice_InitialMessage) ; } diff --git a/src/Ext/BuildingType/Body.h b/src/Ext/BuildingType/Body.h index 9cf41a7af4..9916f61830 100644 --- a/src/Ext/BuildingType/Body.h +++ b/src/Ext/BuildingType/Body.h @@ -99,6 +99,11 @@ class BuildingTypeExt ValueableVector HasPowerUpAnim; + Nullable NewEvaVoice_Index; + Valueable NewEvaVoice_Priority; + Valueable NewEvaVoice_RecheckOnDeath; + NullableIdx NewEvaVoice_InitialMessage; + ExtData(BuildingTypeClass* OwnerObject) : Extension(OwnerObject) , PowersUp_Owner { AffectedHouse::Owner } , PowersUp_Buildings {} @@ -161,6 +166,10 @@ class BuildingTypeExt , BuildingRepairedSound {} , Refinery_UseNormalActiveAnim { false } , HasPowerUpAnim {} + , NewEvaVoice_Index {} + , NewEvaVoice_Priority { 0 } + , NewEvaVoice_RecheckOnDeath { false } + , NewEvaVoice_InitialMessage { } { } // Ares 0.A functions diff --git a/src/Ext/Rules/Body.cpp b/src/Ext/Rules/Body.cpp index fb860fb9b2..d33ab22b54 100644 --- a/src/Ext/Rules/Body.cpp +++ b/src/Ext/Rules/Body.cpp @@ -297,6 +297,9 @@ void RulesExt::ExtData::LoadBeforeTypeData(RulesClass* pThis, CCINIClass* pINI) this->DistributeTargetingFrame.Read(exINI, GameStrings::General, "DistributeTargetingFrame"); this->DistributeTargetingFrame_AIOnly.Read(exINI, GameStrings::General, "DistributeTargetingFrame.AIOnly"); + // Reading Ares section [EVATypes] at evamd.ini + LoadEvaVoices(); + // Section AITargetTypes int itemsCount = pINI->GetKeyCount("AITargetTypes"); for (int i = 0; i < itemsCount; ++i) @@ -548,6 +551,7 @@ void RulesExt::ExtData::Serialize(T& Stm) .Process(this->TintColorBerserk) .Process(this->AttackMove_IgnoreWeaponCheck) .Process(this->AttackMove_StopWhenTargetAcquired) + .Process(this->EVAIndexList) ; } @@ -590,6 +594,49 @@ void RulesExt::ExtData::ReplaceVoxelLightSources() Game::DestroyVoxelCaches(); } +void RulesExt::ExtData::LoadEvaVoices() +{ + CCFileClass pEvamdFile("evamd.ini"); + + if (pEvamdFile.Exists() && pEvamdFile.Open(FileAccessMode::Read)) + { + CCINIClass iniEva; + iniEva.ReadCCFile(&pEvamdFile, true); + iniEva.CurrentSection = nullptr; + iniEva.CurrentSectionName = nullptr; + const auto pEvaSection = "EVATypes"; + + if (iniEva.GetSection(pEvaSection)) + { + this->EVAIndexList.clear(); + + // Default EVA voices + this->EVAIndexList.emplace_back(GameStrings::Allied); + this->EVAIndexList.emplace_back(GameStrings::Russian); + this->EVAIndexList.emplace_back(GameStrings::Yuri); + + // New EVA voices due to a new Ares section in evamd.ini + const auto count = (std::size_t)iniEva.GetKeyCount(pEvaSection); + + for (std::size_t i = 0; i < count; i++) + { + const auto pEvaKey = iniEva.GetKeyName(pEvaSection, i); + + if (iniEva.ReadString(pEvaSection, pEvaKey, "", Phobos::readBuffer) > 0) + { + std::string buffer = Phobos::readBuffer; + bool found = std::find(this->EVAIndexList.begin(), this->EVAIndexList.end(), buffer) != this->EVAIndexList.end(); + + if (!found) + this->EVAIndexList.emplace_back(buffer); + } + } + } + } + + pEvamdFile.Close(); +} + // ============================= // container hooks diff --git a/src/Ext/Rules/Body.h b/src/Ext/Rules/Body.h index 27624f648c..cdd4c9f6e6 100644 --- a/src/Ext/Rules/Body.h +++ b/src/Ext/Rules/Body.h @@ -250,6 +250,8 @@ class RulesExt int TintColorForceShield; int TintColorBerserk; + std::vector EVAIndexList; + ExtData(RulesClass* OwnerObject) : Extension(OwnerObject) , Storage_TiberiumIndex { -1 } , HarvesterDumpAmount { 0.0f } @@ -443,6 +445,8 @@ class RulesExt , AttackMove_IgnoreWeaponCheck { false } , AttackMove_StopWhenTargetAcquired { } + + , EVAIndexList {} { } virtual ~ExtData() = default; @@ -460,6 +464,7 @@ class RulesExt virtual void SaveToStream(PhobosStreamWriter& Stm) override; void ReplaceVoxelLightSources(); + void LoadEvaVoices(); private: template diff --git a/src/Ext/Side/Body.cpp b/src/Ext/Side/Body.cpp index 9d59ab3cdb..845517c7f5 100644 --- a/src/Ext/Side/Body.cpp +++ b/src/Ext/Side/Body.cpp @@ -2,6 +2,9 @@ #include +#include +#include + SideExt::ExtContainer SideExt::ExtMap; void SideExt::ExtData::Initialize() @@ -45,6 +48,94 @@ void SideExt::ExtData::LoadFromINIFile(CCINIClass* pINI) this->SuperWeaponSidebar_TopPCX.Read(pINI, pSection, "SuperWeaponSidebar.TopPCX"); this->SuperWeaponSidebar_CenterPCX.Read(pINI, pSection, "SuperWeaponSidebar.CenterPCX"); this->SuperWeaponSidebar_BottomPCX.Read(pINI, pSection, "SuperWeaponSidebar.BottomPCX"); + + pINI->ReadString(pSection, "EVA.Tag", "", Phobos::readBuffer); + + if (std::strlen(Phobos::readBuffer) > 0) + this->EVA_Tag = _strdup(Phobos::readBuffer); +} + +void SideExt::UpdateMainEvaVoice(BuildingClass* pThis) +{ + const auto pTypeExt = BuildingTypeExt::ExtMap.Find(pThis->Type); + + if (!pTypeExt->NewEvaVoice_Index.isset()) + return; + + const auto pHouse = pThis->Owner; + + if (!pHouse->IsControlledByCurrentPlayer()) + return; + + int newPriority = -1; + int newEvaIndex = VoxClass::EVAIndex; + + for (const auto pBuilding : pHouse->Buildings) + { + const auto pBuildingTypeExt = BuildingTypeExt::ExtMap.Find(pBuilding->Type); + + // Special case that must be avoided here because can be the current EVA changer + if (!pBuildingTypeExt->NewEvaVoice_Index.isset() || pBuilding->CurrentMission == Mission::Selling) + continue; + + // The first highest priority takes precedence over lower ones + if (pBuildingTypeExt->NewEvaVoice_Priority > newPriority) + { + newPriority = pBuildingTypeExt->NewEvaVoice_Priority; + newEvaIndex = pBuildingTypeExt->NewEvaVoice_Index; + } + } + + if (pThis->CurrentMission != Mission::Selling && pTypeExt->NewEvaVoice_Priority > newPriority) + { + newPriority = pTypeExt->NewEvaVoice_Priority; + newEvaIndex = pTypeExt->NewEvaVoice_Index; + } + + if (newPriority > 0 && VoxClass::EVAIndex != newEvaIndex) + { + VoxClass::EVAIndex = newEvaIndex; + + // Greeting of the new EVA voice + int idxPlay = pTypeExt->NewEvaVoice_InitialMessage.Get(-1); + + if (idxPlay != -1) + VoxClass::PlayIndex(idxPlay); + } + else if (newPriority < 0) + { + // Restore the original EVA voice of the owner's side + const auto pSide = SideClass::Array.GetItem(pHouse->SideIndex); + const auto pSideExt = SideExt::ExtMap.Find(pSide); + newEvaIndex = 0; // By default is set the "Allies" EVA voice + + if (pSideExt->EVA_Tag.isset()) + { + const auto evaTag = pSideExt->EVA_Tag.Get(); + + for (size_t i = 0; i < RulesExt::Global()->EVAIndexList.size(); i++) + { + const auto item = RulesExt::Global()->EVAIndexList[i].c_str(); + + if (_strcmpi(item, evaTag) == 0) + { + newEvaIndex = i; + break; + } + } + } + else + { + if (pHouse->SideIndex == 0) + newEvaIndex = 0; // Allied + if (pHouse->SideIndex == 1) + newEvaIndex = 1; // Russian + else if (pHouse->SideIndex == 2) + newEvaIndex = 2; // Yuri + } + + VoxClass::EVAIndex = newEvaIndex; + } } // ============================= @@ -79,6 +170,7 @@ void SideExt::ExtData::Serialize(T& Stm) .Process(this->SuperWeaponSidebar_TopPCX) .Process(this->SuperWeaponSidebar_CenterPCX) .Process(this->SuperWeaponSidebar_BottomPCX) + .Process(this->EVA_Tag) ; } diff --git a/src/Ext/Side/Body.h b/src/Ext/Side/Body.h index e0f3362ef0..35eb40f1b3 100644 --- a/src/Ext/Side/Body.h +++ b/src/Ext/Side/Body.h @@ -41,6 +41,7 @@ class SideExt PhobosPCXFile SuperWeaponSidebar_TopPCX; PhobosPCXFile SuperWeaponSidebar_CenterPCX; PhobosPCXFile SuperWeaponSidebar_BottomPCX; + Nullable EVA_Tag; ExtData(SideClass* OwnerObject) : Extension(OwnerObject) , ArrayIndex { -1 } @@ -68,6 +69,7 @@ class SideExt , SuperWeaponSidebar_TopPCX {} , SuperWeaponSidebar_CenterPCX {} , SuperWeaponSidebar_BottomPCX {} + , EVA_Tag { } { } virtual ~ExtData() = default; @@ -92,6 +94,9 @@ class SideExt }; static ExtContainer ExtMap; + static bool LoadGlobals(PhobosStreamReader& Stm); static bool SaveGlobals(PhobosStreamWriter& Stm); + + static void UpdateMainEvaVoice(BuildingClass* pThis); };