diff --git a/CREDITS.md b/CREDITS.md index 13ec35cbe6..37a6598a35 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -878,3 +878,4 @@ This page lists all the individual contributions to the project by their author. - **Chang_zhi**: - Interop export interface for accessing scenario local/global variables - Add `ClampToScreen` tag for `BannerType` to control whether banner position is clamped to the visible area +- **ABCZ (CZHczhABCZ)** - Passengers Cameo Display diff --git a/Phobos.vcxproj b/Phobos.vcxproj index f5437cd6ff..df818f4921 100644 --- a/Phobos.vcxproj +++ b/Phobos.vcxproj @@ -47,6 +47,7 @@ + @@ -310,6 +311,7 @@ + diff --git a/YRpp b/YRpp index 85b5b5e185..f304f4fa13 160000 --- a/YRpp +++ b/YRpp @@ -1 +1 @@ -Subproject commit 85b5b5e1857a6e06e1341dc87d01c38b1104272e +Subproject commit f304f4fa13bdaab0495a6c847a91bf8af2b9d932 diff --git a/docs/User-Interface.md b/docs/User-Interface.md index 26fc2b8418..f37ad94ab3 100644 --- a/docs/User-Interface.md +++ b/docs/User-Interface.md @@ -526,6 +526,59 @@ In `RA2MD.INI`: ShowPowerPlantEnhancerRange=false ; boolean ``` +### Show Cameo + +- You can now configure the game to display cameo icons and their counts above a selected vehicle or building, showing both passengers (`Passengers=`) and garrisoned occupants (`Occupier=`). Only shown when a single unit is selected (not box-selection). +- Two activation modes are available: + - **Permanent mode**: Set `ShowCameo=true` (globally or per-unit) to always display cameos. Per-unit setting overrides the global setting. If a per-unit `ShowCameo` is explicitly set to `false`, cameos are never shown for that unit. + - **Toggleable mode**: Set `ShowCameo=false` (global) and `ShowCameo.Toggleable=true` (global), then use the [Toggle Cameos](#toggle-cameos) hotkey to switch the display on/off. The master switch `ShowCameo.Enable` in `RA2MD.INI` controls the toggle state. +- Cameos are arranged in horizontal rows, centered above the selection bracket. When the number of cameo types exceeds `ShowCameo.PerRow`, additional rows are stacked above the bottom row. The first-entered unit is displayed at the top. +- Translucency can be set via `ShowCameo.Translucency` (0/25/50/75, default 0). SHP cameos are affected; PCX cameos are not. +- `ShowCameo.OverlapXY` and `ShowCameo.OffsetXY` control overlap between adjacent cameos. `ShowCameo.OverlapPrimary` (default true) determines whether the earlier cameo covers the later one. On the same row, only X overlap applies; when wrapping to a new row, only Y overlap applies. +- A custom cameo shape (SHP) can be specified per passenger/occupant type via `ShowCameo.CustomShape` in `artmd.ini`. Animated SHPs are supported. A custom PCX can be specified via `ShowCameo.CustomPCX`. A custom palette can be specified via `ShowCameo.CustomPalette`; if not set, the palette falls back to `CameoPalette` or the default `cameo.pal`. + +```{hint} +For example, with `ShowCameo.PerRow=1` (vertical stacking), `ShowCameo.OverlapPrimary=true` means the bottom cameo covers the one above it, preserving the bottom cameo's text label from being hidden. +``` + +- When toggled via the hotkey, messages are shown from CSF labels `MSG:ShowCameoEnabled` (fallback: "Cameos Display: Enabled") and `MSG:ShowCameoDisabled` (fallback: "Cameos Display: Disabled"). + +In `RA2MD.INI`: +```ini +[Phobos] +ShowCameo.Enable=true ; boolean +``` + +In `rulesmd.ini`: +```ini +[AudioVisual] +ShowCameo=false ; boolean +ShowCameo.Toggleable=false ; boolean +ShowCameo.PerRow=5 ; integer +ShowCameo.BottomOffset=0,0 ; X,Y +ShowCameo.Translucency=0 ; translucency level (0/25/50/75) +ShowCameo.OverlapXY=0,0 ; X,Y overlap percentage +ShowCameo.OffsetXY=0,0 ; X,Y pixel offset +ShowCameo.OverlapPrimary=true ; boolean + +[SOMETECHNO] ; TechnoType +ShowCameo= ; boolean, default true +ShowCameo.PerRow= ; integer, defaults to [AudioVisual] +ShowCameo.BottomOffset= ; X,Y, defaults to [AudioVisual] +ShowCameo.Translucency= ; translucency level (0/25/50/75), defaults to [AudioVisual] +ShowCameo.OverlapXY= ; X,Y, defaults to [AudioVisual] +ShowCameo.OffsetXY= ; X,Y, defaults to [AudioVisual] +ShowCameo.OverlapPrimary= ; boolean, defaults to [AudioVisual] +``` + +In `artmd.ini`: +```ini +[SOMETECHNO] ; TechnoType +ShowCameo.CustomShape= ; SHP filename +ShowCameo.CustomPCX= ; PCX filename +ShowCameo.CustomPalette= ; PAL filename +``` + ## Hotkey Commands ### `[ ]` Display Damage Numbers @@ -585,6 +638,11 @@ For this command to work in multiplayer - you need to use a version of [YRpp spa - Deselect 1 or 5 object(s) from current selected objects. - For localization add `TXT_DESELECT`, `TXT_DESELECT_DESC`, `TXT_DESELECT5` and `TXT_DESELECT5_DESC` into your `.csf` file. +### `[ ]` Toggle Cameos + +- Switches on/off [cameo display](#show-cameo) in toggleable mode. Has no effect in permanent mode. +- For localization add `TXT_SHOWCAMEO` and `TXT_SHOWCAMEO_DESC` into your `.csf` file. + ## Loading screen - PCX files can now be used as loadscreen images. diff --git a/docs/Whats-New.md b/docs/Whats-New.md index 057e57276d..ec386108c0 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -607,6 +607,7 @@ HideShakeEffects=false ; boolean - [Customize the step limit of the credits indicator](User-Interface.md#customize-the-step-limit-of-the-credits-indicator) (by Noble_Fish) - [Disable the credits indicator smooth transition](User-Interface.md#disable-the-credits-indicator-smooth-transition) (by Noble_Fish) - Add `selling`, `undeploying` and `harvesting` conditions to `DiscardOn` (by Noble_Fish) +- [Show Cameo feature to display passenger/occupant cameos above selected vehicles/buildings](User-Interface.md#show-cameo) (by ABCZ) #### 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/Commands/Commands.cpp b/src/Commands/Commands.cpp index 14b9044f56..ef90486e81 100644 --- a/src/Commands/Commands.cpp +++ b/src/Commands/Commands.cpp @@ -14,6 +14,7 @@ #include "ToggleMessageList.h" #include "DeselectObject.h" #include "DeselectObject5.h" +#include "ToggleCameos.h" #include @@ -32,6 +33,7 @@ DEFINE_HOOK(0x533066, CommandClassCallback_Register, 0x6) MakeCommand(); MakeCommand(); MakeCommand(); + MakeCommand(); if (Phobos::Config::SuperWeaponSidebarCommands) { diff --git a/src/Commands/ToggleCameos.cpp b/src/Commands/ToggleCameos.cpp new file mode 100644 index 0000000000..5c9ad1355c --- /dev/null +++ b/src/Commands/ToggleCameos.cpp @@ -0,0 +1,54 @@ +#include "ToggleCameos.h" + +#include +#include +#include +#include + +const char* ToggleCameosCommandClass::GetName() const +{ + return "Toggle Cameos"; +} + +const wchar_t* ToggleCameosCommandClass::GetUIName() const +{ + return GeneralUtils::LoadStringUnlessMissing("TXT_SHOWCAMEO", L"Toggle Cameos"); +} + +const wchar_t* ToggleCameosCommandClass::GetUICategory() const +{ + return CATEGORY_INTERFACE; +} + +const wchar_t* ToggleCameosCommandClass::GetUIDescription() const +{ + return GeneralUtils::LoadStringUnlessMissing("TXT_SHOWCAMEO_DESC", L"Show/hide cameo display."); +} + +void ToggleCameosCommandClass::Execute(WWKey eInput) const +{ + // Ignore hotkey in permanent mode + if (RulesExt::Global()->ShowCameo) + return; + + // Ignore hotkey if toggleable is off + if (!RulesExt::Global()->ShowCameo_Toggleable) + return; + + Phobos::Config::ShowCameo_Enable = !Phobos::Config::ShowCameo_Enable; + + auto PrintMessage = [](const wchar_t* pMessage) + { + MessageListClass::Instance.PrintMessage( + pMessage, + RulesClass::Instance->MessageDelay, + HouseClass::CurrentPlayer->ColorSchemeIndex, + true + ); + }; + + if (Phobos::Config::ShowCameo_Enable) + PrintMessage(GeneralUtils::LoadStringUnlessMissing("MSG:ShowCameoEnabled", L"Cameos Display: Enabled")); + else + PrintMessage(GeneralUtils::LoadStringUnlessMissing("MSG:ShowCameoDisabled", L"Cameos Display: Disabled")); +} \ No newline at end of file diff --git a/src/Commands/ToggleCameos.h b/src/Commands/ToggleCameos.h new file mode 100644 index 0000000000..d77beb37f7 --- /dev/null +++ b/src/Commands/ToggleCameos.h @@ -0,0 +1,13 @@ +#pragma once + +#include "Commands.h" + +class ToggleCameosCommandClass : public CommandClass +{ +public: + virtual const char* GetName() const override; + virtual const wchar_t* GetUIName() const override; + virtual const wchar_t* GetUICategory() const override; + virtual const wchar_t* GetUIDescription() const override; + virtual void Execute(WWKey eInput) const override; +}; \ No newline at end of file diff --git a/src/Ext/Rules/Body.cpp b/src/Ext/Rules/Body.cpp index be0e1d88d4..5cc0f735c8 100644 --- a/src/Ext/Rules/Body.cpp +++ b/src/Ext/Rules/Body.cpp @@ -453,6 +453,15 @@ void RulesExt::ExtData::LoadBeforeTypeData(RulesClass* pThis, CCINIClass* pINI) this->AutoRemoveEarliestBeacon.Read(exINI, GameStrings::General, "AutoRemoveEarliestBeacon"); this->AllowBeaconHotKeyInSinglePlayer.Read(exINI, GameStrings::General, "AllowBeaconHotKeyInSinglePlayer"); + this->ShowCameo.Read(exINI, GameStrings::AudioVisual, "ShowCameo"); + this->ShowCameo_Toggleable.Read(exINI, GameStrings::AudioVisual, "ShowCameo.Toggleable"); + this->ShowCameo_PerRow.Read(exINI, GameStrings::AudioVisual, "ShowCameo.PerRow"); + this->ShowCameo_BottomOffset.Read(exINI, GameStrings::AudioVisual, "ShowCameo.BottomOffset"); + this->ShowCameo_Translucency.Read(exINI, GameStrings::AudioVisual, "ShowCameo.Translucency"); + this->ShowCameo_OverlapXY.Read(exINI, GameStrings::AudioVisual, "ShowCameo.OverlapXY"); + this->ShowCameo_OffsetXY.Read(exINI, GameStrings::AudioVisual, "ShowCameo.OffsetXY"); + this->ShowCameo_OverlapPrimary.Read(exINI, GameStrings::AudioVisual, "ShowCameo.OverlapPrimary"); + // Section AITargetTypes int itemsCount = pINI->GetKeyCount("AITargetTypes"); for (int i = 0; i < itemsCount; ++i) @@ -804,6 +813,14 @@ void RulesExt::ExtData::Serialize(T& Stm) .Process(this->SecondaryFireSequenceLandOnly) .Process(this->AutoRemoveEarliestBeacon) .Process(this->AllowBeaconHotKeyInSinglePlayer) + .Process(this->ShowCameo) + .Process(this->ShowCameo_Toggleable) + .Process(this->ShowCameo_PerRow) + .Process(this->ShowCameo_BottomOffset) + .Process(this->ShowCameo_Translucency) + .Process(this->ShowCameo_OverlapXY) + .Process(this->ShowCameo_OffsetXY) + .Process(this->ShowCameo_OverlapPrimary) ; } diff --git a/src/Ext/Rules/Body.h b/src/Ext/Rules/Body.h index a00603d273..d793c90f2e 100644 --- a/src/Ext/Rules/Body.h +++ b/src/Ext/Rules/Body.h @@ -384,6 +384,15 @@ class RulesExt Valueable AutoRemoveEarliestBeacon; Valueable AllowBeaconHotKeyInSinglePlayer; + Valueable ShowCameo; + Valueable ShowCameo_Toggleable; + Valueable ShowCameo_PerRow; + Valueable ShowCameo_BottomOffset; + TranslucencyLevel ShowCameo_Translucency; + Valueable ShowCameo_OverlapXY; + Valueable ShowCameo_OffsetXY; + Valueable ShowCameo_OverlapPrimary; + ExtData(RulesClass* OwnerObject) : Extension(OwnerObject) , Storage_TiberiumIndex { -1 } , HarvesterDumpAmount { 0.0f } @@ -708,6 +717,15 @@ class RulesExt , AutoRemoveEarliestBeacon { false } , AllowBeaconHotKeyInSinglePlayer { false } + + , ShowCameo { false } + , ShowCameo_Toggleable { false } + , ShowCameo_PerRow { 5 } + , ShowCameo_BottomOffset { { 0, 0 } } + , ShowCameo_Translucency { 0 } + , ShowCameo_OverlapXY { { 0, 0 } } + , ShowCameo_OffsetXY { { 0, 0 } } + , ShowCameo_OverlapPrimary { true } { } virtual ~ExtData() = default; diff --git a/src/Ext/Techno/Body.Visuals.cpp b/src/Ext/Techno/Body.Visuals.cpp index ff47accb46..838d08f58e 100644 --- a/src/Ext/Techno/Body.Visuals.cpp +++ b/src/Ext/Techno/Body.Visuals.cpp @@ -3,6 +3,8 @@ #include #include #include +#include +#include void TechnoExt::DrawSelfHealPips(TechnoClass* pThis, Point2D* pLocation, RectangleStruct* pBounds) { @@ -873,3 +875,345 @@ void TechnoExt::ShowPromoteAnim(TechnoClass* pThis) else if (!eliteAnims.empty()) AnimExt::CreateRandomAnim(eliteAnims, pThis->GetCenterCoords(), pThis, pThis->Owner, true, true); } + +void TechnoExt::DrawCameos(TechnoClass* pThis) +{ + if (ObjectClass::CurrentObjects.Count != 1) + return; + + if (ObjectClass::CurrentObjects.GetItem(0) != pThis) + return; + + const auto whatAmI = pThis->WhatAmI(); + + const auto pType = pThis->GetTechnoType(); + const auto pTypeExt = TechnoTypeExt::ExtMap.Find(pType); + const auto pRulesExt = RulesExt::Global(); + + // Per-unit explicitly disabled - skip entirely + if (pTypeExt->ShowCameo.isset() && !pTypeExt->ShowCameo.Get()) + return; + + // Determine if permanent mode is on (permanent mode bypasses master switch and toggleable) + const bool permanentMode = pTypeExt->ShowCameo.isset() + ? pTypeExt->ShowCameo.Get() + : pRulesExt->ShowCameo; + + // Master switch check (skipped for permanent mode) + if (!permanentMode && !Phobos::Config::ShowCameo_Enable) + return; + + // Toggleable check (skipped for permanent mode) + if (!permanentMode && !pRulesExt->ShowCameo_Toggleable) + return; + + // Collect cameos: passengers + occupants + // Passengers are stored in LIFO linked list; for buildings we reverse to FIFO + // so that Y flip produces correct top-to-bottom layout. + // Occupants are stored in FIFO DynamicVectorClass and do NOT need reversing. + std::vector> cameoCounts; + + // Step 1: Collect passengers into a temp vector (LIFO order) + std::vector> passengerCounts; + for (auto pUnit = pThis->Passengers.GetFirstPassenger(); pUnit; pUnit = abstract_cast(pUnit->NextObject)) + { + const auto pUnitType = pUnit->GetTechnoType(); + auto it = std::find_if(passengerCounts.begin(), passengerCounts.end(), + [pUnitType](const auto& pair) { return pair.first == pUnitType; }); + if (it != passengerCounts.end()) + it->second++; + else + passengerCounts.emplace_back(pUnitType, 1); + } + + // For buildings, reverse passenger order: LIFO -> FIFO + if (whatAmI == AbstractType::Building) + std::reverse(passengerCounts.begin(), passengerCounts.end()); + + // Merge passengers into cameoCounts + for (const auto& item : passengerCounts) + cameoCounts.emplace_back(item.first, item.second); + + // Step 2: Collect occupants (FIFO order, no reverse needed) + if (whatAmI == AbstractType::Building) + { + const auto pBuilding = static_cast(pThis); + for (int i = 0; i < pBuilding->Occupants.Count; i++) + { + const auto pOccupant = pBuilding->Occupants.GetItem(i); + const auto pOccupantType = pOccupant->GetTechnoType(); + auto it = std::find_if(cameoCounts.begin(), cameoCounts.end(), + [pOccupantType](const auto& pair) { return pair.first == pOccupantType; }); + if (it != cameoCounts.end()) + it->second++; + else + cameoCounts.emplace_back(pOccupantType, 1); + } + } + + if (cameoCounts.empty()) + return; + + const auto bracketPos = whatAmI == AbstractType::Building + ? GetBuildingSelectBracketPosition(pThis, BuildingSelectBracketPosition::Top) + : GetFootSelectBracketPosition(pThis, Anchor(HorizontalPosition::Center, VerticalPosition::Top)); + Point2D offset = pTypeExt->ShowCameo_BottomOffset.isset() + ? pTypeExt->ShowCameo_BottomOffset.Get() + : pRulesExt->ShowCameo_BottomOffset.Get(); + Point2D basePos = bracketPos; + basePos.X += offset.X; + basePos.Y += offset.Y + pType->PixelSelectionBracketDelta; + + const int perRow = pTypeExt->ShowCameo_PerRow.isset() + ? pTypeExt->ShowCameo_PerRow.Get() + : pRulesExt->ShowCameo_PerRow.Get(); + const int count = static_cast(cameoCounts.size()); + const int rows = (count + perRow - 1) / perRow; + + // Get overlap/offset/overlapPrimary settings + const Point2D overlapXY = pTypeExt->ShowCameo_OverlapXY.isset() + ? pTypeExt->ShowCameo_OverlapXY.Get() + : pRulesExt->ShowCameo_OverlapXY.Get(); + const Point2D cameoOffsetXY = pTypeExt->ShowCameo_OffsetXY.isset() + ? pTypeExt->ShowCameo_OffsetXY.Get() + : pRulesExt->ShowCameo_OffsetXY.Get(); + const bool overlapPrimary = pTypeExt->ShowCameo_OverlapPrimary.isset() + ? pTypeExt->ShowCameo_OverlapPrimary.Get() + : pRulesExt->ShowCameo_OverlapPrimary.Get(); + // overlapPrimary=true: primary (earlier) covers secondary (later) + // overlapPrimary=false: secondary (later) covers primary (earlier) + + // Pre-compute canvas sizes for each cameo (standard 60x48, or custom SHP dimensions) + struct CameoCanvas { int width; int height; }; + std::vector canvases; + canvases.reserve(count); + for (const auto& item : cameoCounts) + { + const auto pCameoType = item.first; + const auto pCameoTypeExt = TechnoTypeExt::ExtMap.Find(pCameoType); + int w = 60, h = 48; + if (SHPStruct* pCustomCameo = pCameoTypeExt->ShowCameo_CustomShape) + { + w = pCustomCameo->Width; + h = pCustomCameo->Height; + } + else if (auto pCustomPCX = pCameoTypeExt->ShowCameo_CustomPCX.GetSurface()) + { + w = pCustomPCX->GetWidth(); + h = pCustomPCX->GetHeight(); + } + canvases.push_back({ w, h }); + } + + // Step 1-2-3: Calculate positions sequentially (row layout + OverlapXY + OffsetXY) + // Overlap formula: overlap = coveredCanvasSize * (OverlapXY% / 100) + OffsetXY + // OverlapPrimary=true: overlap based on secondary cameo's size (secondary is covered) + // OverlapPrimary=false: overlap based on primary cameo's size (primary is covered) + // Base gap between adjacent icons: 2 pixels (hGap / vGap) + const int hGap = 2; + const int vGap = 2; + // Iterate from bottom row to top row, so that each row can reference the row below it + std::vector cameoPositions(count); + for (int row = 0; row < rows; row++) + { + const int firstIdx = row * perRow; + const int lastIdx = Math::min(firstIdx + perRow - 1, count - 1); + for (int idx = firstIdx; idx <= lastIdx; idx++) + { + const int col = idx - firstIdx; + const int curW = canvases[idx].width; + const int curH = canvases[idx].height; + + if (row == 0) + { + // Bottom row: anchor at base position (bottom edge fixed at basePos.Y) + if (col == 0) + { + cameoPositions[idx].X = basePos.X; + cameoPositions[idx].Y = basePos.Y - curH; + } + else + { + // Same row: position to the right of the cameo directly to the left + const int prevIdx = idx - 1; + const int prevX = cameoPositions[prevIdx].X; + const int prevW = canvases[prevIdx].width; + + const int overlapX = overlapPrimary + ? (curW * overlapXY.X / 100 + cameoOffsetXY.X) + : (prevW * overlapXY.X / 100 + cameoOffsetXY.X); + + cameoPositions[idx].X = prevX + prevW + hGap - overlapX; + cameoPositions[idx].Y = cameoPositions[prevIdx].Y; + } + } + else + { + if (col == 0) + { + // First in row: position above the cameo directly below + const int belowIdx = (row - 1) * perRow; + const int belowY = cameoPositions[belowIdx].Y; + const int belowH = canvases[belowIdx].height; + + const int overlapY = overlapPrimary + ? (curH * overlapXY.Y / 100 + cameoOffsetXY.Y) + : (belowH * overlapXY.Y / 100 + cameoOffsetXY.Y); + + cameoPositions[idx].X = basePos.X; + cameoPositions[idx].Y = belowY - curH - vGap + overlapY; + } + else + { + // Same row: position to the right of the cameo directly to the left + const int prevIdx = idx - 1; + const int prevX = cameoPositions[prevIdx].X; + const int prevW = canvases[prevIdx].width; + + const int overlapX = overlapPrimary + ? (curW * overlapXY.X / 100 + cameoOffsetXY.X) + : (prevW * overlapXY.X / 100 + cameoOffsetXY.X); + + cameoPositions[idx].X = prevX + prevW + hGap - overlapX; + cameoPositions[idx].Y = cameoPositions[prevIdx].Y; + } + } + } + } + + // Step 4: Center each row horizontally + for (int row = 0; row < rows; row++) + { + const int firstIdx = row * perRow; + const int lastIdx = Math::min(firstIdx + perRow - 1, count - 1); + const int rowWidth = cameoPositions[lastIdx].X + canvases[lastIdx].width - cameoPositions[firstIdx].X; + const int centerOffset = basePos.X - rowWidth / 2 - cameoPositions[firstIdx].X; + for (int i = firstIdx; i <= lastIdx; i++) + cameoPositions[i].X += centerOffset; + } + + // For buildings, flip Y axis: first-entered goes to top, last-entered to bottom + if (whatAmI == AbstractType::Building) + { + int topY = INT_MAX; + for (int i = 0; i < count; i++) + topY = Math::min(topY, cameoPositions[i].Y); + + for (int i = 0; i < count; i++) + cameoPositions[i].Y = basePos.Y - canvases[i].height - (cameoPositions[i].Y - topY); + } + + const auto pSurface = DSurface::Composite; + RectangleStruct bounds = DSurface::Composite->GetRect(); + + // Determine BlitterFlags for translucency + const TranslucencyLevel translucency = pTypeExt->ShowCameo_Translucency.isset() + ? pTypeExt->ShowCameo_Translucency.Get() + : pRulesExt->ShowCameo_Translucency; + const BlitterFlags translucencyFlag = translucency.GetBlitterFlags(); + + // For buildings, draw in reverse so that visual top (earlier-entered) covers visual bottom. + // This is needed because Y flip reverses the visual order but overlap calculation + // still follows the original cameoCounts (bottom-to-top) order. + int idx = (whatAmI == AbstractType::Building) ? count - 1 : 0; + const int idxEnd = (whatAmI == AbstractType::Building) ? -1 : count; + const int idxStep = (whatAmI == AbstractType::Building) ? -1 : 1; + for (; idx != idxEnd; idx += idxStep) + { + const auto& item = cameoCounts[idx]; + const auto pCameoType = item.first; + const int cameoCount = item.second; + const int curW = canvases[idx].width; + const int curH = canvases[idx].height; + + Point2D iconPos = cameoPositions[idx]; + + // Skip icons whose any part exceeds the screen top boundary (Y < 0) to prevent crash + // Only the top boundary matters because cameos are always above the selection bracket + if (iconPos.Y < 0) + continue; + + bool drawn = false; + + const auto pCameoTypeExt = TechnoTypeExt::ExtMap.Find(pCameoType); + + // Use custom ShowCameo SHP if specified + if (SHPStruct* pCustomCameo = pCameoTypeExt->ShowCameo_CustomShape) + { + const auto pCameoConvert = pCameoTypeExt->CameoPalette.GetOrDefaultConvert(FileSystem::CAMEO_PAL); + const auto pConvert = pCameoTypeExt->ShowCameo_CustomPalette.GetOrDefaultConvert(pCameoConvert); + const int frameCount = pCustomCameo->Frames > 0 ? pCustomCameo->Frames : 1; + const int frame = (Unsorted::CurrentFrame % frameCount); + pSurface->DrawSHP( + pConvert, + pCustomCameo, + frame, + &iconPos, + &bounds, + BlitterFlags::bf_400 | BlitterFlags::Alpha | translucencyFlag, + 0, 0, + ZGradient::Ground, + 1000, 0, nullptr, 0, 0, 0 + ); + drawn = true; + } + + if (!drawn) + { + if (const auto pCustomPCX = pCameoTypeExt->ShowCameo_CustomPCX.GetSurface()) + { + RectangleStruct pcxBounds = { iconPos.X, iconPos.Y, curW, curH }; + PCX::Instance.BlitToSurface(&pcxBounds, pSurface, pCustomPCX); + drawn = true; + } + } + + if (!drawn) + { + if (const auto pCameoPCX = pCameoTypeExt->CameoPCX.GetSurface()) + { + RectangleStruct pcxBounds = { iconPos.X, iconPos.Y, curW, curH }; + PCX::Instance.BlitToSurface(&pcxBounds, pSurface, pCameoPCX); + drawn = true; + } + } + + if (!drawn) + { + SHPStruct* pCameo = pCameoType->GetCameo(); + if (pCameo) + { + const auto pConvert = pCameoTypeExt->CameoPalette.GetOrDefaultConvert(FileSystem::CAMEO_PAL); + pSurface->DrawSHP( + pConvert, + pCameo, + 0, + &iconPos, + &bounds, + BlitterFlags::bf_400 | BlitterFlags::Alpha | translucencyFlag, + 0, 0, + ZGradient::Ground, + 1000, 0, nullptr, 0, 0, 0 + ); + drawn = true; + } + } + + if (drawn) + { + wchar_t countText[16]; + _snwprintf_s(countText, _countof(countText), _TRUNCATE, L"%d", cameoCount); + + Point2D textPos = { iconPos.X + curW - 4, iconPos.Y + 2 }; + + pSurface->DrawTextA( + countText, + &bounds, + &textPos, + Drawing::RGB_To_Int({ 0, 255, 0 }), + 0, + TextPrintType::Center | TextPrintType::FullShadow + ); + } + } +} diff --git a/src/Ext/Techno/Body.h b/src/Ext/Techno/Body.h index bb31ec98a5..9651713f25 100644 --- a/src/Ext/Techno/Body.h +++ b/src/Ext/Techno/Body.h @@ -321,6 +321,8 @@ class TechnoExt static bool EjectSurvivor(FootClass* pSurvivor, CoordStruct coords, bool select); static bool __fastcall ApplyKillDriver(TechnoClass** pData, void*, HouseClass* pToHouse, TechnoClass* pKiller, bool resetVeterancy); + static void DrawCameos(TechnoClass* pThis); + // WeaponHelpers.cpp static int PickWeaponIndex(TechnoClass* pThis, TechnoClass* pTargetTechno, AbstractClass* pTarget, int weaponIndexOne, int weaponIndexTwo, bool allowFallback = true, bool allowAAFallback = true); static void FireWeaponAtSelf(TechnoClass* pThis, WeaponTypeClass* pWeaponType); diff --git a/src/Ext/Techno/Hooks.Pips.cpp b/src/Ext/Techno/Hooks.Pips.cpp index 075299f870..7d8b842aa4 100644 --- a/src/Ext/Techno/Hooks.Pips.cpp +++ b/src/Ext/Techno/Hooks.Pips.cpp @@ -94,6 +94,8 @@ DEFINE_HOOK(0x6F65D1, TechnoClass_DrawHealthBar_Buildings, 0x6) TechnoExt::ProcessDigitalDisplays(pThis); + TechnoExt::DrawCameos(pThis); + return 0; } @@ -124,6 +126,8 @@ DEFINE_HOOK(0x6F683C, TechnoClass_DrawHealthBar_Units, 0x7) TechnoExt::ProcessDigitalDisplays(pThis); + TechnoExt::DrawCameos(pThis); + if (pExt->TypeExtData->HealthBar_HidePips) { R->EDI(pLocation); diff --git a/src/Ext/TechnoType/Body.cpp b/src/Ext/TechnoType/Body.cpp index ecdfdd15c7..3ebe2eb2b6 100644 --- a/src/Ext/TechnoType/Body.cpp +++ b/src/Ext/TechnoType/Body.cpp @@ -1343,7 +1343,11 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->ShadowIndex_Frame.Read(exArtINI, pArtSection, "ShadowIndex.Frame"); + this->CameoPCX.Read(pArtINI, pArtSection, "CameoPCX"); this->AltCameoPCX.Read(pArtINI, pArtSection, "AltCameoPCX"); + this->ShowCameo_CustomPalette.LoadFromINI(pArtINI, pArtSection, "ShowCameo.CustomPalette"); + this->ShowCameo_CustomShape.Read(exArtINI, pArtSection, "ShowCameo.CustomShape"); + this->ShowCameo_CustomPCX.Read(pArtINI, pArtSection, "ShowCameo.CustomPCX"); this->LaserTrailData.clear(); for (size_t i = 0; ; ++i) @@ -1463,6 +1467,15 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) // VoiceIFVRepair from Ares 0.2 this->VoiceIFVRepair.Read(exINI, pSection, "VoiceIFVRepair"); this->ParseVoiceWeaponAttacks(exINI, pSection, this->VoiceWeaponAttacks, this->VoiceEliteWeaponAttacks); + + this->ShowCameo.Read(exINI, pSection, "ShowCameo"); + this->ShowCameo_PerRow.Read(exINI, pSection, "ShowCameo.PerRow"); + this->ShowCameo_BottomOffset.Read(exINI, pSection, "ShowCameo.BottomOffset"); + this->ShowCameo_Translucency.Read(exINI, pSection, "ShowCameo.Translucency"); + this->ShowCameo_OverlapXY.Read(exINI, pSection, "ShowCameo.OverlapXY"); + this->ShowCameo_OffsetXY.Read(exINI, pSection, "ShowCameo.OffsetXY"); + this->ShowCameo_OverlapPrimary.Read(exINI, pSection, "ShowCameo.OverlapPrimary"); + this->CameoPalette.LoadFromINI(pArtINI, pArtSection, "CameoPalette"); } void TechnoTypeExt::ExtData::LoadFromINIByWhatAmI(INI_EX& exINI, const char* pSection, INI_EX& exArtINI, const char* pArtSection) @@ -1532,7 +1545,11 @@ void TechnoTypeExt::ExtData::Serialize(T& Stm) .Process(this->ImmuneToCrit) .Process(this->MultiMindControl_ReleaseVictim) .Process(this->CameoPriority) + .Process(this->CameoPCX) .Process(this->AltCameoPCX) + .Process(this->ShowCameo_CustomPalette) + .Process(this->ShowCameo_CustomShape) + .Process(this->ShowCameo_CustomPCX) .Process(this->NoManualMove) .Process(this->InitialStrength) .Process(this->ReloadInTransport) @@ -1973,6 +1990,18 @@ void TechnoTypeExt::ExtData::Serialize(T& Stm) .Process(this->Missile_Cruise) .Process(this->Missile_TakeOffAnim) .Process(this->Missile_TakeOffSeparation) + + .Process(this->ShowCameo) + .Process(this->ShowCameo_PerRow) + .Process(this->ShowCameo_BottomOffset) + .Process(this->ShowCameo_Translucency) + .Process(this->ShowCameo_OverlapXY) + .Process(this->ShowCameo_OffsetXY) + .Process(this->ShowCameo_OverlapPrimary) + .Process(this->CameoPalette) + .Process(this->ShowCameo_CustomPalette) + .Process(this->ShowCameo_CustomShape) + .Process(this->ShowCameo_CustomPCX) ; } void TechnoTypeExt::ExtData::LoadFromStream(PhobosStreamReader& Stm) diff --git a/src/Ext/TechnoType/Body.h b/src/Ext/TechnoType/Body.h index 66b264dc26..c0616dec48 100644 --- a/src/Ext/TechnoType/Body.h +++ b/src/Ext/TechnoType/Body.h @@ -66,6 +66,7 @@ class TechnoTypeExt Valueable ImmuneToCrit; Valueable MultiMindControl_ReleaseVictim; Valueable CameoPriority; + PhobosPCXFile CameoPCX; PhobosPCXFile AltCameoPCX; Valueable NoManualMove; Nullable InitialStrength; @@ -523,6 +524,18 @@ class TechnoTypeExt Valueable Missile_TakeOffAnim; Valueable Missile_TakeOffSeparation; + Nullable ShowCameo; + Nullable ShowCameo_PerRow; + Nullable ShowCameo_BottomOffset; + Nullable ShowCameo_Translucency; + Nullable ShowCameo_OverlapXY; + Nullable ShowCameo_OffsetXY; + Nullable ShowCameo_OverlapPrimary; + CustomPalette CameoPalette; + CustomPalette ShowCameo_CustomPalette; + TheaterSpecificSHP ShowCameo_CustomShape; + PhobosPCXFile ShowCameo_CustomPCX; + ExtData(TechnoTypeClass* OwnerObject) : Extension(OwnerObject) , HealthBar_Hide { false } , HealthBar_HidePips { false } @@ -564,6 +577,7 @@ class TechnoTypeExt , ImmuneToCrit { false } , MultiMindControl_ReleaseVictim { false } , CameoPriority { 0 } + , CameoPCX {} , AltCameoPCX {} , NoManualMove { false } , InitialStrength {} @@ -998,6 +1012,18 @@ class TechnoTypeExt , Missile_Cruise { false } , Missile_TakeOffAnim { nullptr } , Missile_TakeOffSeparation { 24 } + + , ShowCameo {} + , ShowCameo_PerRow {} + , ShowCameo_BottomOffset {} + , ShowCameo_Translucency {} + , ShowCameo_OverlapXY {} + , ShowCameo_OffsetXY {} + , ShowCameo_OverlapPrimary {} + , CameoPalette {} + , ShowCameo_CustomPalette {} + , ShowCameo_CustomShape {} + , ShowCameo_CustomPCX {} { } virtual ~ExtData() = default; diff --git a/src/Phobos.INI.cpp b/src/Phobos.INI.cpp index ac9f7649b2..2fbea55fb6 100644 --- a/src/Phobos.INI.cpp +++ b/src/Phobos.INI.cpp @@ -80,6 +80,7 @@ bool Phobos::Config::HideShakeEffects = true; bool Phobos::Config::ShowFlashOnSelecting = false; bool Phobos::Config::UnitPowerDrain = false; int Phobos::Config::SuperWeaponSidebar_RequiredSignificance = 0; +bool Phobos::Config::ShowCameo_Enable = true; bool Phobos::Misc::CustomGS = false; int Phobos::Misc::CustomGS_ChangeInterval[7] = { -1, -1, -1, -1, -1, -1, -1 }; @@ -115,6 +116,7 @@ DEFINE_HOOK(0x5FACDF, OptionsClass_LoadSettings_LoadPhobosSettings, 0x5) Phobos::Config::HideShakeEffects = CCINIClass::INI_RA2MD.ReadBool(phobosSection, "HideShakeEffects", false); Phobos::Config::ShowFlashOnSelecting = CCINIClass::INI_RA2MD.ReadBool(phobosSection, "ShowFlashOnSelecting", false); Phobos::Config::SuperWeaponSidebar_RequiredSignificance = CCINIClass::INI_RA2MD.ReadInteger(phobosSection, "SuperWeaponSidebar.RequiredSignificance", 0); + Phobos::Config::ShowCameo_Enable = CCINIClass::INI_RA2MD.ReadBool(phobosSection, "ShowCameo.Enable", true); // Custom game speeds, 6 - i so that GS6 is index 0, just like in the engine Phobos::Config::CampaignDefaultGameSpeed = 6 - CCINIClass::INI_RA2MD.ReadInteger(phobosSection, "CampaignDefaultGameSpeed", 4); diff --git a/src/Phobos.h b/src/Phobos.h index 6bdb4073ec..fc76be3878 100644 --- a/src/Phobos.h +++ b/src/Phobos.h @@ -116,6 +116,7 @@ class Phobos static bool ShowFlashOnSelecting; static bool UnitPowerDrain; static int SuperWeaponSidebar_RequiredSignificance; + static bool ShowCameo_Enable; }; class Misc diff --git a/src/Utilities/Enum.h b/src/Utilities/Enum.h index 2e9d9645f4..922b95c87f 100644 --- a/src/Utilities/Enum.h +++ b/src/Utilities/Enum.h @@ -275,6 +275,7 @@ enum class VerticalPosition : BYTE Center = 1, Bottom = 2 }; + //hexagon enum class BuildingSelectBracketPosition :BYTE {