From 862478c4bd444df3b4e7e2c64173b2f9d234e92e Mon Sep 17 00:00:00 2001 From: Causeless Date: Wed, 23 Jul 2025 20:02:55 +0100 Subject: [PATCH 01/15] testing pictorial loadouts, using icons instead of text --- Source/GUI/GUIListPanel.cpp | 2 +- Source/Menus/BuyMenuGUI.cpp | 73 +++++++++++++++++++++++-------------- 2 files changed, 47 insertions(+), 28 deletions(-) diff --git a/Source/GUI/GUIListPanel.cpp b/Source/GUI/GUIListPanel.cpp index 7bb1da89bc..e68e18695e 100644 --- a/Source/GUI/GUIListPanel.cpp +++ b/Source/GUI/GUIListPanel.cpp @@ -337,7 +337,7 @@ void GUIListPanel::BuildDrawBitmap() { I->m_pBitmap->DrawTrans(m_DrawBitmap, ((thirdWidth / 2) - (bitmapWidth / 2)) - itemX + 2, bitmapY, 0); } else { // No text, just bitmap, so give it more room - I->m_pBitmap->DrawTrans(m_DrawBitmap, ((thirdWidth / 2) - (bitmapWidth / 2)) - itemX + 4, bitmapY, 0); + I->m_pBitmap->DrawTrans(m_DrawBitmap, ((thirdWidth) - (bitmapWidth / 2)) - itemX + 4, bitmapY, 0); } } diff --git a/Source/Menus/BuyMenuGUI.cpp b/Source/Menus/BuyMenuGUI.cpp index 730f1fb0ae..c1c5861ca0 100644 --- a/Source/Menus/BuyMenuGUI.cpp +++ b/Source/Menus/BuyMenuGUI.cpp @@ -2159,7 +2159,7 @@ void BuyMenuGUI::AddObjectsToItemList(std::vector>& moduleLis void BuyMenuGUI::AddPresetsToItemList() { m_SelectedLoadoutIndex = -1; - GUIBitmap* pItemBitmap = 0; + AllegroBitmap* pItemBitmap = 0; std::string loadoutLabel; float loadoutCost; const Actor* pPassenger = 0; @@ -2172,37 +2172,56 @@ void BuyMenuGUI::AddPresetsToItemList() { pItemBitmap = 0; pPassenger = 0; - // Add preset name at the begining to differentiate loadouts from user-defined presets - if ((*lItr).GetPresetName() != "None") - loadoutLabel = (*lItr).GetPresetName() + ":\n"; - - // Go through the cargo setup of each loadout and encode a meaningful label for the list item - for (std::list::iterator cItr = (*lItr).GetCargoList()->begin(); cItr != (*lItr).GetCargoList()->end(); ++cItr) { - // If not the first one, add a comma separator to the label - if (cItr != (*lItr).GetCargoList()->begin()) - loadoutLabel += ", "; - // Append the name of the current cargo thing to the label - loadoutLabel += (*cItr)->GetPresetName(); - // Adjust price for foreignness of the items to this player - loadoutCost += (*cItr)->GetGoldValue(m_NativeTechModule, m_ForeignCostMult); - if (!pPassenger) - pPassenger = dynamic_cast(*cItr); + int bitmapHeight = 0; + int bitmapWidth = 0; + + int rowHeight = 0; + int rowWidth = 0; + for (const SceneObject* sceneObject : *(*lItr).GetCargoList()) { + if (dynamic_cast(sceneObject)) { + // start a new row + bitmapHeight += rowHeight; + bitmapWidth = std::max(bitmapWidth, rowWidth); + rowHeight = 0; + rowWidth = 0; + } + + rowHeight = std::max(rowHeight, sceneObject->GetGraphicalIcon()->h); + rowWidth += sceneObject->GetGraphicalIcon()->w; + } + + // and once more for the last row + bitmapHeight += rowHeight; + bitmapWidth = std::max(bitmapWidth, rowWidth); + + // Generate our bitmap of all the cargo items in the loadout + pItemBitmap = new AllegroBitmap(); + pItemBitmap->Create(bitmapWidth, bitmapHeight); + + // Now actually draw the stuff in the appropriate places + rowHeight = 0; + int heightOffset = 0; + int widthOffset = 0; + for (const SceneObject* sceneObject: *(*lItr).GetCargoList()) { + if (dynamic_cast(sceneObject)) { + // start a new row + heightOffset += rowHeight; + rowHeight = 0; + widthOffset = 0; + } + + draw_sprite_h_flip(pItemBitmap->GetBitmap(), sceneObject->GetGraphicalIcon(), widthOffset, heightOffset); + + rowHeight = std::max(rowHeight, sceneObject->GetGraphicalIcon()->h); + widthOffset += sceneObject->GetGraphicalIcon()->w; } - // Add the ship's cost, if there is one defined - if ((*lItr).GetDeliveryCraft()) { - loadoutLabel += " via " + (*lItr).GetDeliveryCraft()->GetPresetName(); - // Adjust price for foreignness of the ship to this player - loadoutCost += (*lItr).GetDeliveryCraft()->GetGoldValue(m_NativeTechModule, m_ForeignCostMult); + for (const SceneObject* sceneObject: *(*lItr).GetCargoList()) { + loadoutCost += sceneObject->GetGoldValue(m_NativeTechModule, m_ForeignCostMult); } - // Make the cost label - std::snprintf(costString, sizeof(costString), "%.0f", loadoutCost); - // Get a good icon and wrap it, while not passing ownership into the AllegroBitmap - // We're trying to pick the icon of the first passenger, or the first item if there's no passengers in the loadout - pItemBitmap = new AllegroBitmap(pPassenger ? const_cast(pPassenger)->GetGraphicalIcon() : const_cast((*lItr).GetCargoList()->front())->GetGraphicalIcon()); // Passing in ownership of the bitmap, but not of the pSpriteObj - m_pShopList->AddItem(loadoutLabel, costString, pItemBitmap, 0); + m_pShopList->AddItem("", costString, pItemBitmap); } } From 2f5893b68da51ce839d422d51205338528c14150 Mon Sep 17 00:00:00 2001 From: Causeless Date: Thu, 24 Jul 2025 07:55:37 +0100 Subject: [PATCH 02/15] Cleanup, and display cost again --- Source/Menus/BuyMenuGUI.cpp | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/Source/Menus/BuyMenuGUI.cpp b/Source/Menus/BuyMenuGUI.cpp index c1c5861ca0..0f660215cc 100644 --- a/Source/Menus/BuyMenuGUI.cpp +++ b/Source/Menus/BuyMenuGUI.cpp @@ -2158,19 +2158,11 @@ void BuyMenuGUI::AddObjectsToItemList(std::vector>& moduleLis void BuyMenuGUI::AddPresetsToItemList() { m_SelectedLoadoutIndex = -1; - - AllegroBitmap* pItemBitmap = 0; - std::string loadoutLabel; - float loadoutCost; - const Actor* pPassenger = 0; - char costString[256]; // Go through all the presets, making intelligible list items from then for the GUI item list for (std::vector::iterator lItr = m_Loadouts.begin(); lItr != m_Loadouts.end(); ++lItr) { - loadoutLabel.clear(); - loadoutCost = 0; - pItemBitmap = 0; - pPassenger = 0; + AllegroBitmap* pItemBitmap = nullptr; + float loadoutCost = 0; int bitmapHeight = 0; int bitmapWidth = 0; @@ -2220,8 +2212,8 @@ void BuyMenuGUI::AddPresetsToItemList() { loadoutCost += sceneObject->GetGoldValue(m_NativeTechModule, m_ForeignCostMult); } - // Passing in ownership of the bitmap, but not of the pSpriteObj - m_pShopList->AddItem("", costString, pItemBitmap); + // Passing in ownership of the bitmap + m_pShopList->AddItem("", std::to_string(loadoutCost), pItemBitmap, 0); } } From 08488aef3e525973ac31896bd54209021b8a389c Mon Sep 17 00:00:00 2001 From: Causeless Date: Thu, 24 Jul 2025 08:49:38 +0100 Subject: [PATCH 03/15] Show description, and improvements to rendering --- Source/Menus/BuyMenuGUI.cpp | 158 +++++++++++++++++++++++++----------- 1 file changed, 111 insertions(+), 47 deletions(-) diff --git a/Source/Menus/BuyMenuGUI.cpp b/Source/Menus/BuyMenuGUI.cpp index 0f660215cc..896e3d2cd4 100644 --- a/Source/Menus/BuyMenuGUI.cpp +++ b/Source/Menus/BuyMenuGUI.cpp @@ -1141,54 +1141,106 @@ void BuyMenuGUI::Update() { GUIListPanel::Item* pItem = m_pShopList->GetItem(m_ListItemIndex); std::string description = ""; - if (pItem && pItem->m_pEntity) { - description = ((pItem->m_pEntity->GetDescription().empty()) ? "-No Information Found-" : pItem->m_pEntity->GetDescription()) + "\n"; - const Entity* currentItem = pItem->m_pEntity; - const ACraft* itemAsCraft = dynamic_cast(currentItem); - if (itemAsCraft) { - int craftMaxPassengers = itemAsCraft->GetMaxPassengers(); - float craftMaxMass = itemAsCraft->GetMaxInventoryMass(); - if (craftMaxMass == 0) { - description += "\nNO CARGO SPACE!"; - } else if (craftMaxMass > 0) { - description += "\nMax Mass: " + RoundFloatToPrecision(craftMaxMass, craftMaxMass < 50.0F ? 1 : 0, 3) + " kg"; - } - if (craftMaxPassengers >= 0 && craftMaxMass != 0) { - description += (craftMaxPassengers == 0) ? "\nNO PASSENGER SPACE!" : "\nMax Passengers: " + std::to_string(craftMaxPassengers); - } - } else { - // Items in the BuyMenu always have any remainder rounded up in their masses. - const Actor* itemAsActor = dynamic_cast(currentItem); - if (itemAsActor) { - description += "\nMass: " + (itemAsActor->GetMass() < 0.1F ? "<0.1 kg" : RoundFloatToPrecision(itemAsActor->GetMass(), itemAsActor->GetMass() < 50.0F ? 1 : 0, 3) + " kg"); - int passengerSlotsTaken = itemAsActor->GetPassengerSlots(); - if (passengerSlotsTaken > 1) { - description += "\nPassenger Slots: " + std::to_string(passengerSlotsTaken); + if (pItem) { + if (pItem->m_pEntity) { + description = ((pItem->m_pEntity->GetDescription().empty()) ? "-No Information Found-" : pItem->m_pEntity->GetDescription()) + "\n"; + const Entity* currentItem = pItem->m_pEntity; + const ACraft* itemAsCraft = dynamic_cast(currentItem); + if (itemAsCraft) { + int craftMaxPassengers = itemAsCraft->GetMaxPassengers(); + float craftMaxMass = itemAsCraft->GetMaxInventoryMass(); + if (craftMaxMass == 0) { + description += "\nNO CARGO SPACE!"; + } else if (craftMaxMass > 0) { + description += "\nMax Mass: " + RoundFloatToPrecision(craftMaxMass, craftMaxMass < 50.0F ? 1 : 0, 3) + " kg"; + } + if (craftMaxPassengers >= 0 && craftMaxMass != 0) { + description += (craftMaxPassengers == 0) ? "\nNO PASSENGER SPACE!" : "\nMax Passengers: " + std::to_string(craftMaxPassengers); } } else { - const MovableObject* itemAsMO = dynamic_cast(currentItem); - if (itemAsMO) { - const MOSRotating* itemAsMOSRotating = dynamic_cast(currentItem); - float extraMass = 0; - if (itemAsMOSRotating) { - if (itemAsMOSRotating->NumberValueExists("Grenade Count")) { - description += "\nGrenade Count: " + RoundFloatToPrecision(itemAsMOSRotating->GetNumberValue("Grenade Count"), 0, 2); + // Items in the BuyMenu always have any remainder rounded up in their masses. + const Actor* itemAsActor = dynamic_cast(currentItem); + if (itemAsActor) { + description += "\nMass: " + (itemAsActor->GetMass() < 0.1F ? "<0.1 kg" : RoundFloatToPrecision(itemAsActor->GetMass(), itemAsActor->GetMass() < 50.0F ? 1 : 0, 3) + " kg"); + int passengerSlotsTaken = itemAsActor->GetPassengerSlots(); + if (passengerSlotsTaken > 1) { + description += "\nPassenger Slots: " + std::to_string(passengerSlotsTaken); + } + } else { + const MovableObject* itemAsMO = dynamic_cast(currentItem); + if (itemAsMO) { + const MOSRotating* itemAsMOSRotating = dynamic_cast(currentItem); + float extraMass = 0; + if (itemAsMOSRotating) { + if (itemAsMOSRotating->NumberValueExists("Grenade Count")) { + description += "\nGrenade Count: " + RoundFloatToPrecision(itemAsMOSRotating->GetNumberValue("Grenade Count"), 0, 2); + } + if (itemAsMOSRotating->NumberValueExists("Replenish Delay") && itemAsMOSRotating->GetNumberValue("Replenish Delay") > 0) { + description += "\nReplenish Delay: " + RoundFloatToPrecision(itemAsMOSRotating->GetNumberValue("Replenish Delay") / 1000.0F, 3, 2) + " seconds"; + } + if (itemAsMOSRotating->NumberValueExists("Belt Mass")) { + extraMass = itemAsMOSRotating->GetNumberValue("Belt Mass"); + } + } + description += "\nMass: " + (itemAsMO->GetMass() + extraMass < 0.1F ? "<0.1 kg" : RoundFloatToPrecision(itemAsMO->GetMass() + extraMass, itemAsMO->GetMass() + extraMass < 50.0F ? 1 : 0, 3) + " kg"); + } + } + } + } else if (pItem->m_ExtraIndex != -1) { + if (m_MenuCategory == PRESETS) { + // This is a loadout preset, so get the description from the preset + // Add preset name at the begining to differentiate loadouts from user-defined presets + Loadout& loadout = m_Loadouts[pItem->m_ExtraIndex]; + if (loadout.GetPresetName() != "None") { + description += loadout.GetPresetName() + ":\n"; + } + + // Go through the cargo setup of each loadout and encode a meaningful label for the list item + auto lastItr = loadout.GetCargoList()->end(); + --lastItr; + + auto nextItr = loadout.GetCargoList()->begin(); + + bool actorSeen = false; + for (std::list::iterator cItr = loadout.GetCargoList()->begin(); cItr != loadout.GetCargoList()->end(); ++cItr) { + ++nextItr; + + bool isActor = dynamic_cast(*cItr) != nullptr; + actorSeen = actorSeen || isActor; + + // Anything under an actor should be indented + if (!isActor && actorSeen) { + description += "\t\t"; + } + + // Append the name of the current cargo thing to the label + description += (*cItr)->GetPresetName(); + + // If not the last one, add a separator to the label + if (cItr != lastItr) { + bool nextIsActor = dynamic_cast(*nextItr) != nullptr; + if (isActor && !nextIsActor) { + description += ":"; } - if (itemAsMOSRotating->NumberValueExists("Replenish Delay") && itemAsMOSRotating->GetNumberValue("Replenish Delay") > 0) { - description += "\nReplenish Delay: " + RoundFloatToPrecision(itemAsMOSRotating->GetNumberValue("Replenish Delay") / 1000.0F, 3, 2) + " seconds"; + + if (actorSeen || nextIsActor) { + description += "\n"; + } else { + description += ", "; } - if (itemAsMOSRotating->NumberValueExists("Belt Mass")) { - extraMass = itemAsMOSRotating->GetNumberValue("Belt Mass"); + + if (nextIsActor) { + // Extra space between each actor + description += "\n"; } } - description += "\nMass: " + (itemAsMO->GetMass() + extraMass < 0.1F ? "<0.1 kg" : RoundFloatToPrecision(itemAsMO->GetMass() + extraMass, itemAsMO->GetMass() + extraMass < 50.0F ? 1 : 0, 3) + " kg"); } - } - } - } else if (pItem && pItem->m_ExtraIndex >= 0) { - const DataModule* pModule = g_PresetMan.GetDataModule(pItem->m_ExtraIndex); - if (pModule && !pModule->GetDescription().empty()) { - description = pModule->GetDescription(); + } else { + const DataModule* pModule = g_PresetMan.GetDataModule(pItem->m_ExtraIndex); + if (pModule && !pModule->GetDescription().empty()) { + description += pModule->GetDescription(); + } + } } } @@ -2160,16 +2212,20 @@ void BuyMenuGUI::AddPresetsToItemList() { m_SelectedLoadoutIndex = -1; // Go through all the presets, making intelligible list items from then for the GUI item list - for (std::vector::iterator lItr = m_Loadouts.begin(); lItr != m_Loadouts.end(); ++lItr) { + for (int i = 0; i < m_Loadouts.size(); ++i) { + Loadout& loadout = m_Loadouts[i]; + AllegroBitmap* pItemBitmap = nullptr; float loadoutCost = 0; + const int maxBitmapWidth = 100; + int bitmapHeight = 0; int bitmapWidth = 0; int rowHeight = 0; int rowWidth = 0; - for (const SceneObject* sceneObject : *(*lItr).GetCargoList()) { + for (const SceneObject* sceneObject: *loadout.GetCargoList()) { if (dynamic_cast(sceneObject)) { // start a new row bitmapHeight += rowHeight; @@ -2178,6 +2234,10 @@ void BuyMenuGUI::AddPresetsToItemList() { rowWidth = 0; } + if (rowWidth + sceneObject->GetGraphicalIcon()->w > maxBitmapWidth) { + continue; // don't draw anything that would overflow the bitmap + } + rowHeight = std::max(rowHeight, sceneObject->GetGraphicalIcon()->h); rowWidth += sceneObject->GetGraphicalIcon()->w; } @@ -2194,7 +2254,7 @@ void BuyMenuGUI::AddPresetsToItemList() { rowHeight = 0; int heightOffset = 0; int widthOffset = 0; - for (const SceneObject* sceneObject: *(*lItr).GetCargoList()) { + for (const SceneObject* sceneObject: *loadout.GetCargoList()) { if (dynamic_cast(sceneObject)) { // start a new row heightOffset += rowHeight; @@ -2202,18 +2262,22 @@ void BuyMenuGUI::AddPresetsToItemList() { widthOffset = 0; } - draw_sprite_h_flip(pItemBitmap->GetBitmap(), sceneObject->GetGraphicalIcon(), widthOffset, heightOffset); + if (widthOffset + sceneObject->GetGraphicalIcon()->w > maxBitmapWidth) { + continue; // don't draw anything that would overflow the bitmap + } + + draw_sprite(pItemBitmap->GetBitmap(), sceneObject->GetGraphicalIcon(), widthOffset, heightOffset); rowHeight = std::max(rowHeight, sceneObject->GetGraphicalIcon()->h); widthOffset += sceneObject->GetGraphicalIcon()->w; } - for (const SceneObject* sceneObject: *(*lItr).GetCargoList()) { + for (const SceneObject* sceneObject: *loadout.GetCargoList()) { loadoutCost += sceneObject->GetGoldValue(m_NativeTechModule, m_ForeignCostMult); } // Passing in ownership of the bitmap - m_pShopList->AddItem("", std::to_string(loadoutCost), pItemBitmap, 0); + m_pShopList->AddItem("", std::to_string((int)(loadoutCost + 0.5f)), pItemBitmap, nullptr, i); } } From e82e0ea3697a54fb288772471e2404ccdd031e1c Mon Sep 17 00:00:00 2001 From: Causeless Date: Thu, 24 Jul 2025 08:59:03 +0100 Subject: [PATCH 04/15] centre icons --- Source/Menus/BuyMenuGUI.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Source/Menus/BuyMenuGUI.cpp b/Source/Menus/BuyMenuGUI.cpp index 896e3d2cd4..5f3e70a884 100644 --- a/Source/Menus/BuyMenuGUI.cpp +++ b/Source/Menus/BuyMenuGUI.cpp @@ -2266,9 +2266,13 @@ void BuyMenuGUI::AddPresetsToItemList() { continue; // don't draw anything that would overflow the bitmap } - draw_sprite(pItemBitmap->GetBitmap(), sceneObject->GetGraphicalIcon(), widthOffset, heightOffset); - rowHeight = std::max(rowHeight, sceneObject->GetGraphicalIcon()->h); + + // TODO: make a smarter row structure so we can properly centre the icons if the actor isn't the tallest item in the row + // Vertically center the icon in the row + int yOffset = (rowHeight - sceneObject->GetGraphicalIcon()->h) / 2; + + draw_sprite(pItemBitmap->GetBitmap(), sceneObject->GetGraphicalIcon(), widthOffset, heightOffset + yOffset); widthOffset += sceneObject->GetGraphicalIcon()->w; } From f12035dcc646eace8b117182eb7c1b0d6a49f987 Mon Sep 17 00:00:00 2001 From: Causeless Date: Thu, 24 Jul 2025 13:53:40 +0100 Subject: [PATCH 05/15] UI tweaks --- Source/GUI/GUIListPanel.cpp | 21 ++++++++++++++------- Source/Menus/BuyMenuGUI.cpp | 6 +++--- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/Source/GUI/GUIListPanel.cpp b/Source/GUI/GUIListPanel.cpp index e68e18695e..4f9afc5472 100644 --- a/Source/GUI/GUIListPanel.cpp +++ b/Source/GUI/GUIListPanel.cpp @@ -329,15 +329,15 @@ void GUIListPanel::BuildDrawBitmap() { // Draw the associated bitmap if (I->m_pBitmap) { - if (bitmapWidth == thirdWidth) { + if (I->m_Name.empty()) { + // No text, just bitmap, so give it more room + I->m_pBitmap->DrawTrans(m_DrawBitmap, ((thirdWidth * 1.3f) - (bitmapWidth / 2)) - itemX + 4, bitmapY, 0); + } else if (bitmapWidth == thirdWidth) { // If it was deemed too large, draw it scaled I->m_pBitmap->DrawTransScaled(m_DrawBitmap, 3 - itemX, bitmapY, bitmapWidth, bitmapHeight); - } else if (!I->m_Name.empty()) { + } else { // There's text to compete for space with I->m_pBitmap->DrawTrans(m_DrawBitmap, ((thirdWidth / 2) - (bitmapWidth / 2)) - itemX + 2, bitmapY, 0); - } else { - // No text, just bitmap, so give it more room - I->m_pBitmap->DrawTrans(m_DrawBitmap, ((thirdWidth) - (bitmapWidth / 2)) - itemX + 4, bitmapY, 0); } } @@ -991,10 +991,15 @@ GUIListPanel::Item* GUIListPanel::GetItem(int Index) { if (Index >= 0 && Index < m_Items.size()) { return m_Items.at(Index); } - return 0; + return nullptr; } GUIListPanel::Item* GUIListPanel::GetItem(int X, int Y) { + // If outside of X bounds, return nothing + if (X < m_X || X >= m_X + m_Width) { + return nullptr; + } + int Height = m_Height; if (m_HorzScroll->_GetVisible()) { Height -= m_HorzScroll->GetHeight(); @@ -1004,6 +1009,7 @@ GUIListPanel::Item* GUIListPanel::GetItem(int X, int Y) { if (m_VertScroll->_GetVisible()) { y -= m_VertScroll->GetValue(); } + int Count = 0; for (std::vector::iterator it = m_Items.begin(); it != m_Items.end(); it++, Count++) { Item* pItem = *it; @@ -1019,7 +1025,8 @@ GUIListPanel::Item* GUIListPanel::GetItem(int X, int Y) { break; } } - return 0; + + return nullptr; } int GUIListPanel::GetItemHeight(Item* pItem) { diff --git a/Source/Menus/BuyMenuGUI.cpp b/Source/Menus/BuyMenuGUI.cpp index 5f3e70a884..31d762dae1 100644 --- a/Source/Menus/BuyMenuGUI.cpp +++ b/Source/Menus/BuyMenuGUI.cpp @@ -871,9 +871,9 @@ void BuyMenuGUI::Update() { } // Check if any direction has been held for the starting amount of time to get into repeat mode - if (m_RepeatStartTimer.IsPastRealMS(200)) { + if (m_RepeatStartTimer.IsPastRealMS(350)) { // Check for the repeat interval - if (m_RepeatTimer.IsPastRealMS(75)) { + if (m_RepeatTimer.IsPastRealMS(125)) { if (m_pController->IsState(MOVE_RIGHT)) { pressRight = true; } else if (m_pController->IsState(MOVE_LEFT)) { @@ -2218,7 +2218,7 @@ void BuyMenuGUI::AddPresetsToItemList() { AllegroBitmap* pItemBitmap = nullptr; float loadoutCost = 0; - const int maxBitmapWidth = 100; + const int maxBitmapWidth = 130; int bitmapHeight = 0; int bitmapWidth = 0; From e3a322ca645490d5e084d211472ad6abd2ef8506 Mon Sep 17 00:00:00 2001 From: Causeless Date: Thu, 24 Jul 2025 14:00:51 +0100 Subject: [PATCH 06/15] Fixed loadout selection not working --- Source/Menus/BuyMenuGUI.cpp | 25 ++++++------------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/Source/Menus/BuyMenuGUI.cpp b/Source/Menus/BuyMenuGUI.cpp index 31d762dae1..0d2dc1b2a1 100644 --- a/Source/Menus/BuyMenuGUI.cpp +++ b/Source/Menus/BuyMenuGUI.cpp @@ -1620,21 +1620,15 @@ void BuyMenuGUI::Update() { GUIListPanel::Item* pItem = m_pShopList->GetItem(mousePosX, mousePosY); + // If the player clicked on a loadout preset, deploy it if (pItem && m_MenuCategory == PRESETS) { - // The presets list must have a mouse-down event to select an item, whereas we implicitly select items on hover in other categories - m_LastHoveredMouseIndex = pItem->m_ID; - - // Play select sound if new index - if (m_ListItemIndex != pItem->m_ID) { - g_GUISound.SelectionChangeSound()->Play(m_pController->GetPlayer()); + // Beep if there's an error + if (!DeployLoadout(m_ListItemIndex)) { + g_GUISound.UserErrorSound()->Play(m_pController->GetPlayer()); } - - m_pShopList->SetSelectedIndex(m_CategoryItemIndex[m_MenuCategory] = m_ListItemIndex = pItem->m_ID); } - - // If a module group list item, toggle its expansion and update the list - if (pItem && pItem->m_ExtraIndex >= 0) { + else if (pItem && pItem->m_ExtraIndex >= 0) { // Make appropriate sound if (!m_aExpandedModules[pItem->m_ExtraIndex]) g_GUISound.ItemChangeSound()->Play(m_pController->GetPlayer()); @@ -1646,12 +1640,6 @@ void BuyMenuGUI::Update() { // Re-populate the item list with the new module expansion configuation CategoryChange(false); } - // Special case: user clicked on a loadout set, so load it into the menu - else if (pItem && m_MenuCategory == PRESETS) { - // Beep if there's an error - if (!DeployLoadout(m_ListItemIndex)) - g_GUISound.UserErrorSound()->Play(m_pController->GetPlayer()); - } // Normal: only add an item if there's an entity attached to the list item else if (pItem && pItem->m_pEntity) { m_CategoryItemIndex[m_MenuCategory] = m_ListItemIndex = m_pShopList->GetSelectedIndex(); @@ -1714,8 +1702,7 @@ void BuyMenuGUI::Update() { // See if it's hovering over any item GUIListPanel::Item* pItem = m_pShopList->GetItem(mousePosX, mousePosY); if (pItem) { - // On presets Menu, you must actively click to select an item. Anywhere else, an implicit hover will select - if (m_MenuCategory != PRESETS && m_LastHoveredMouseIndex != pItem->m_ID) { + if (m_LastHoveredMouseIndex != pItem->m_ID) { // Don't let mouse movement change the index if it's still hovering inside the same item. // This is to avoid erratic selection curosr if using both mouse and keyboard to work the menu m_LastHoveredMouseIndex = pItem->m_ID; From 78f0603fb74fa06fbab409716e6ee7761fd7d5f9 Mon Sep 17 00:00:00 2001 From: Causeless Date: Thu, 24 Jul 2025 15:10:16 +0100 Subject: [PATCH 07/15] Click-drag to reorder loadouts Renamed PRESETS -> Loadouts Renamed Clear -> Delete --- Data/Base.rte/GUIs/BuyMenuGUI.ini | 12 +-- Source/Menus/BuyMenuGUI.cpp | 124 +++++++++++++++++++++++------- Source/Menus/BuyMenuGUI.h | 4 +- 3 files changed, 104 insertions(+), 36 deletions(-) diff --git a/Data/Base.rte/GUIs/BuyMenuGUI.ini b/Data/Base.rte/GUIs/BuyMenuGUI.ini index ef941d64cc..afc9851768 100644 --- a/Data/Base.rte/GUIs/BuyMenuGUI.ini +++ b/Data/Base.rte/GUIs/BuyMenuGUI.ini @@ -211,7 +211,7 @@ Anchor = Left, Top Text = Shields Checked = False -[SetsTab] +[LoadoutsTab] ControlType = TAB Parent = BuyGUIBox X = 2 @@ -220,9 +220,9 @@ Width = 57 Height = 17 Visible = True Enabled = True -Name = SetsTab +Name = LoadoutsTab Anchor = Left, Top -Text = Presets +Text = Loadouts Checked = False [CargoLabel] @@ -439,7 +439,7 @@ Name = SaveButton Anchor = Left, Top Text = Save -[ClearButton] +[DeleteButton] ControlType = BUTTON Parent = BuyGUIBox X = 4 @@ -448,6 +448,6 @@ Width = 48 Height = 18 Visible = True Enabled = True -Name = ClearButton +Name = DeleteButton Anchor = Left, Top -Text = Clear +Text = Delete diff --git a/Source/Menus/BuyMenuGUI.cpp b/Source/Menus/BuyMenuGUI.cpp index 0d2dc1b2a1..eaa1426ba5 100644 --- a/Source/Menus/BuyMenuGUI.cpp +++ b/Source/Menus/BuyMenuGUI.cpp @@ -100,7 +100,7 @@ void BuyMenuGUI::Clear() { m_pBuyButton = 0; m_ClearOrderButton = nullptr; m_pSaveButton = 0; - m_pClearButton = 0; + m_pDeleteButton = 0; m_Loadouts.clear(); m_SelectedLoadoutIndex = -1; m_PurchaseMade = false; @@ -119,7 +119,7 @@ void BuyMenuGUI::Clear() { m_LastEquipmentScrollPosition = -1; m_LastMainScrollPosition = -1; m_FirstMainTab = CRAFT; - m_LastMainTab = PRESETS; + m_LastMainTab = LOADOUTS; m_FirstEquipmentTab = TOOLS; m_LastEquipmentTab = SHIELDS; } @@ -184,7 +184,7 @@ int BuyMenuGUI::Create(Controller* pController) { m_pCategoryTabs[GUNS] = dynamic_cast(m_pGUIController->GetControl("GunsTab")); m_pCategoryTabs[BOMBS] = dynamic_cast(m_pGUIController->GetControl("BombsTab")); m_pCategoryTabs[SHIELDS] = dynamic_cast(m_pGUIController->GetControl("ShieldsTab")); - m_pCategoryTabs[PRESETS] = dynamic_cast(m_pGUIController->GetControl("SetsTab")); + m_pCategoryTabs[LOADOUTS] = dynamic_cast(m_pGUIController->GetControl("LoadoutsTab")); RefreshTabDisabledStates(); m_pShopList = dynamic_cast(m_pGUIController->GetControl("CatalogLB")); @@ -204,9 +204,9 @@ int BuyMenuGUI::Create(Controller* pController) { m_pBuyButton = dynamic_cast(m_pGUIController->GetControl("BuyButton")); m_ClearOrderButton = dynamic_cast(m_pGUIController->GetControl("OrderClearButton")); m_pSaveButton = dynamic_cast(m_pGUIController->GetControl("SaveButton")); - m_pClearButton = dynamic_cast(m_pGUIController->GetControl("ClearButton")); + m_pDeleteButton = dynamic_cast(m_pGUIController->GetControl("DeleteButton")); m_pSaveButton->SetVisible(false); - m_pClearButton->SetVisible(false); + m_pDeleteButton->SetVisible(false); // If we're not split screen horizontally, then stretch out the layout for all the relevant controls int stretchAmount = g_WindowMan.GetResY() / 2; @@ -726,7 +726,7 @@ void BuyMenuGUI::RefreshTabDisabledStates() { m_pCategoryTabs[GUNS]->SetEnabled(smartBuyMenuNavigationDisabled || m_SelectingEquipment); m_pCategoryTabs[BOMBS]->SetEnabled(smartBuyMenuNavigationDisabled || m_SelectingEquipment); m_pCategoryTabs[SHIELDS]->SetEnabled(smartBuyMenuNavigationDisabled || m_SelectingEquipment); - m_pCategoryTabs[PRESETS]->SetEnabled(smartBuyMenuNavigationDisabled || !m_SelectingEquipment); + m_pCategoryTabs[LOADOUTS]->SetEnabled(smartBuyMenuNavigationDisabled || !m_SelectingEquipment); } void BuyMenuGUI::Update() { @@ -1022,12 +1022,12 @@ void BuyMenuGUI::Update() { } ///////////////////////////////////////// - // PRESETS BUTTONS focus + // LOADOUTS BUTTONS focus if (m_MenuFocus == SETBUTTONS) { if (m_FocusChange) { // Set the correct special Sets category so the sets buttons show up - m_MenuCategory = PRESETS; + m_MenuCategory = LOADOUTS; CategoryChange(); m_pSaveButton->SetFocus(); m_FocusChange = 0; @@ -1036,12 +1036,12 @@ void BuyMenuGUI::Update() { if (m_pController->IsState(PRESS_FACEBUTTON)) { if (m_pSaveButton->HasFocus()) SaveCurrentLoadout(); - else if (m_pClearButton->HasFocus() && m_Loadouts.size() != 0 && m_SelectedLoadoutIndex != -1) { + else if (m_pDeleteButton->HasFocus() && m_Loadouts.size() != 0 && m_SelectedLoadoutIndex != -1) { m_Loadouts.erase(m_Loadouts.begin() + m_SelectedLoadoutIndex); // Update the list of loadout presets so the removal shows up CategoryChange(); // Set focus back on the save button (CatChange changed it) - m_pClearButton->SetFocus(); + m_pDeleteButton->SetFocus(); m_SelectedLoadoutIndex = -1; } g_GUISound.ItemChangeSound()->Play(m_pController->GetPlayer()); @@ -1052,15 +1052,15 @@ void BuyMenuGUI::Update() { if (m_pSaveButton->HasFocus()) { m_MenuFocus = CATEGORIES; m_FocusChange = 1; - } else if (m_pClearButton->HasFocus()) { + } else if (m_pDeleteButton->HasFocus()) { m_pSaveButton->SetFocus(); g_GUISound.SelectionChangeSound()->Play(m_pController->GetPlayer()); } } else if (pressDown) { if (m_pSaveButton->HasFocus()) { - m_pClearButton->SetFocus(); + m_pDeleteButton->SetFocus(); g_GUISound.SelectionChangeSound()->Play(m_pController->GetPlayer()); - } else if (m_pClearButton->HasFocus()) + } else if (m_pDeleteButton->HasFocus()) g_GUISound.UserErrorSound()->Play(m_pController->GetPlayer()); } } @@ -1115,7 +1115,25 @@ void BuyMenuGUI::Update() { } int listSize = m_pShopList->GetItemList()->size(); - if (pressDown) { + if (m_MenuCategory == LOADOUTS && m_DraggedItemIndex != -1) { + if (pressDown && m_DraggedItemIndex < listSize - 1) { + m_IsDragging = true; + std::swap((*m_pShopList->GetItemList())[m_DraggedItemIndex], (*m_pShopList->GetItemList())[m_DraggedItemIndex + 1]); + std::swap((*m_pShopList->GetItemList())[m_DraggedItemIndex + 1]->m_ID, (*m_pShopList->GetItemList())[m_DraggedItemIndex]->m_ID); + m_ListItemIndex = ++m_DraggedItemIndex; + m_SelectedLoadoutIndex = -1; + m_pShopList->SetSelectedIndex(m_ListItemIndex); + g_GUISound.SelectionChangeSound()->Play(m_pController->GetPlayer()); + } else if (pressUp && m_DraggedItemIndex > 0) { + m_IsDragging = true; + std::swap((*m_pShopList->GetItemList())[m_DraggedItemIndex], (*m_pShopList->GetItemList())[m_DraggedItemIndex - 1]); + std::swap((*m_pShopList->GetItemList())[m_DraggedItemIndex - 1]->m_ID, (*m_pShopList->GetItemList())[m_DraggedItemIndex]->m_ID); + m_ListItemIndex = --m_DraggedItemIndex; + m_SelectedLoadoutIndex = -1; + m_pShopList->SetSelectedIndex(m_ListItemIndex); + g_GUISound.SelectionChangeSound()->Play(m_pController->GetPlayer()); + } + } else if (pressDown) { m_ListItemIndex++; // Loop around if (m_ListItemIndex >= listSize) @@ -1187,7 +1205,7 @@ void BuyMenuGUI::Update() { } } } else if (pItem->m_ExtraIndex != -1) { - if (m_MenuCategory == PRESETS) { + if (m_MenuCategory == LOADOUTS) { // This is a loadout preset, so get the description from the preset // Add preset name at the begining to differentiate loadouts from user-defined presets Loadout& loadout = m_Loadouts[pItem->m_ExtraIndex]; @@ -1261,7 +1279,7 @@ void BuyMenuGUI::Update() { } // User selected to add an item to cart list! - if (m_pController->IsState(PRESS_FACEBUTTON)) { + if (m_pController->IsState(PRESS_FACEBUTTON) && !m_IsDragging) { // User pressed on a module group item; toggle its expansion! if (pItem && pItem->m_ExtraIndex >= 0) { // Make appropriate sound @@ -1277,7 +1295,7 @@ void BuyMenuGUI::Update() { CategoryChange(false); } // User pressed on a loadout set, so load it into the menu - else if (pItem && m_MenuCategory == PRESETS) { + else if (pItem && m_MenuCategory == LOADOUTS) { // Beep if there's an error if (!DeployLoadout(m_ListItemIndex)) g_GUISound.UserErrorSound()->Play(m_pController->GetPlayer()); @@ -1314,6 +1332,19 @@ void BuyMenuGUI::Update() { UpdateTotalPassengersLabel(dynamic_cast(m_pSelectedCraft), m_pCraftPassengersLabel); UpdateTotalMassLabel(dynamic_cast(m_pSelectedCraft), m_pCraftMassLabel); } + + if (m_MenuCategory == LOADOUTS) { + bool isKeyboardControlled = !m_pController->IsMouseControlled() && !m_pController->IsGamepadControlled(); + if (isKeyboardControlled ? m_pController->IsState(AIM_SHARP) : m_pController->IsState(PRESS_FACEBUTTON)) { + m_DraggedItemIndex = m_pCartList->GetSelectedIndex(); + } else if (m_pController->IsState(RELEASE_FACEBUTTON)) { + m_DraggedItemIndex = -1; + m_IsDragging = false; + } + } else { + m_DraggedItemIndex = -1; + m_IsDragging = false; + } } ///////////////////////////////////////// @@ -1559,8 +1590,8 @@ void BuyMenuGUI::Update() { } // CLEAR button clicks - if (anEvent.GetControl() == m_pClearButton) { - m_pClearButton->SetFocus(); + if (anEvent.GetControl() == m_pDeleteButton) { + m_pDeleteButton->SetFocus(); if (m_SelectedLoadoutIndex != -1) { m_Loadouts.erase(m_Loadouts.begin() + m_SelectedLoadoutIndex); // Update the list of loadout presets so the removal shows up @@ -1570,7 +1601,7 @@ void BuyMenuGUI::Update() { m_SelectedLoadoutIndex = -1; } // Set focus back on the clear button (CatChange changed it) - m_pClearButton->SetFocus(); + m_pDeleteButton->SetFocus(); m_MenuFocus = SETBUTTONS; // m_FocusChange = -1; g_GUISound.ItemChangeSound()->Play(m_pController->GetPlayer()); @@ -1614,14 +1645,14 @@ void BuyMenuGUI::Update() { // Events on the Shop List if (anEvent.GetControl() == m_pShopList) { - if (anEvent.GetMsg() == GUIListBox::MouseDown && (anEvent.GetData() & GUIListBox::MOUSE_LEFT)) { + if (anEvent.GetMsg() == GUIListBox::MouseUp && (anEvent.GetData() & GUIListBox::MOUSE_LEFT) && !m_IsDragging) { m_pShopList->SetFocus(); m_MenuFocus = ITEMS; GUIListPanel::Item* pItem = m_pShopList->GetItem(mousePosX, mousePosY); // If the player clicked on a loadout preset, deploy it - if (pItem && m_MenuCategory == PRESETS) { + if (pItem && m_MenuCategory == LOADOUTS) { // Beep if there's an error if (!DeployLoadout(m_ListItemIndex)) { g_GUISound.UserErrorSound()->Play(m_pController->GetPlayer()); @@ -1703,6 +1734,24 @@ void BuyMenuGUI::Update() { GUIListPanel::Item* pItem = m_pShopList->GetItem(mousePosX, mousePosY); if (pItem) { if (m_LastHoveredMouseIndex != pItem->m_ID) { + if (m_MenuCategory == LOADOUTS && m_DraggedItemIndex != -1 && m_DraggedItemIndex != pItem->m_ID) { + m_IsDragging = true; + int start = std::min(m_DraggedItemIndex, pItem->m_ID); + int end = std::max(m_DraggedItemIndex, pItem->m_ID); + int direction = pItem->m_ID > m_DraggedItemIndex ? 1 : -1; + for (int i = start; i < end; i++) { + int oldIndex = m_DraggedItemIndex; + if (oldIndex + direction < 0 || oldIndex + direction >= m_pShopList->GetItemList()->size()) { + break; + } + + m_DraggedItemIndex = oldIndex + direction; + m_SelectedLoadoutIndex = m_DraggedItemIndex; + std::swap((*m_pShopList->GetItemList())[oldIndex], (*m_pShopList->GetItemList())[oldIndex + direction]); + std::swap((*m_pShopList->GetItemList())[oldIndex + direction]->m_ID, (*m_pShopList->GetItemList())[oldIndex]->m_ID); + } + } + // Don't let mouse movement change the index if it's still hovering inside the same item. // This is to avoid erratic selection curosr if using both mouse and keyboard to work the menu m_LastHoveredMouseIndex = pItem->m_ID; @@ -1716,6 +1765,17 @@ void BuyMenuGUI::Update() { } } } + + if (anEvent.GetMsg() == GUIListBox::MouseDown) { + m_pShopList->SetFocus(); + m_MenuFocus = ITEMS; + m_ListItemIndex = m_pShopList->GetSelectedIndex(); + m_pShopList->ScrollToSelected(); + if (m_MenuCategory == LOADOUTS && anEvent.GetData() & GUIListBox::MOUSE_LEFT) { + m_DraggedItemIndex = m_pShopList->GetSelectedIndex(); + m_SelectedLoadoutIndex = m_ListItemIndex; + } + } } /////////////////////////////////////////////// @@ -1821,17 +1881,25 @@ void BuyMenuGUI::Update() { } } - // We do this down here, outside the m_pCartList control, because if we have a mouse-up event even outside the cart, we should stop dragging. We also check UInputMan in case the mouse is released entirely outside of the buy menu. + // We do this down here, because if we have a mouse-up event even outside the cart, we should stop dragging. We also check UInputMan in case the mouse is released entirely outside of the buy menu. if ((anEvent.GetMsg() == GUIListBox::MouseUp && (anEvent.GetData() & GUIListBox::MOUSE_LEFT)) || g_UInputMan.MouseButtonReleased(MouseButtons::MOUSE_LEFT, m_pController->GetPlayer())) { + if (m_MenuCategory == LOADOUTS) { + // Might've reordered the loadout list, so we need to save the new order + SaveAllLoadoutsToFile(); + } m_DraggedItemIndex = -1; m_IsDragging = false; } } } - if (m_MenuCategory == PRESETS) { + if (m_MenuCategory == LOADOUTS) { m_pSaveButton->SetEnabled(!m_pCartList->GetItemList()->empty()); - m_pClearButton->SetEnabled(m_SelectedLoadoutIndex != -1); + m_pDeleteButton->SetEnabled(m_SelectedLoadoutIndex != -1); + if (m_SelectedLoadoutIndex != -1) { + // Always highlight the currently selected loadout as if it's focused + m_pShopList->SetSelectedIndex(m_SelectedLoadoutIndex); + } } } @@ -1901,10 +1969,10 @@ void BuyMenuGUI::CategoryChange(bool focusOnCategoryTabs) { m_pShopList->ClearList(); // Hide/show the logo and special sets category buttons, and add all current presets to the list, and we're done. - if (m_MenuCategory == PRESETS) { + if (m_MenuCategory == LOADOUTS) { m_Logo->SetVisible(false); m_pSaveButton->SetVisible(true); - m_pClearButton->SetVisible(true); + m_pDeleteButton->SetVisible(true); m_pShopList->SetHighlightAsIfAlwaysFocused(true); // Add and done! AddPresetsToItemList(); @@ -1914,7 +1982,7 @@ void BuyMenuGUI::CategoryChange(bool focusOnCategoryTabs) { else { m_Logo->SetVisible(true); m_pSaveButton->SetVisible(false); - m_pClearButton->SetVisible(false); + m_pDeleteButton->SetVisible(false); m_pShopList->SetHighlightAsIfAlwaysFocused(false); } diff --git a/Source/Menus/BuyMenuGUI.h b/Source/Menus/BuyMenuGUI.h index ac72cdd653..72aebf91e5 100644 --- a/Source/Menus/BuyMenuGUI.h +++ b/Source/Menus/BuyMenuGUI.h @@ -364,7 +364,7 @@ namespace RTE { GUNS, BOMBS, SHIELDS, - PRESETS, + LOADOUTS, CATEGORYCOUNT }; @@ -482,7 +482,7 @@ namespace RTE { // The save set button GUIButton* m_pSaveButton; // The clear set button - GUIButton* m_pClearButton; + GUIButton* m_pDeleteButton; // Sets of user-defined loadouts that can be selected quickly. std::vector m_Loadouts; // The selected loadout index, -1 if no loadout is selected From 9707ae425917a71b2f5a62e287bf14c1ea62c21d Mon Sep 17 00:00:00 2001 From: Causeless Date: Thu, 24 Jul 2025 15:25:54 +0100 Subject: [PATCH 08/15] More fixes --- Source/Menus/BuyMenuGUI.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/Source/Menus/BuyMenuGUI.cpp b/Source/Menus/BuyMenuGUI.cpp index eaa1426ba5..123ca2f2b5 100644 --- a/Source/Menus/BuyMenuGUI.cpp +++ b/Source/Menus/BuyMenuGUI.cpp @@ -1119,7 +1119,8 @@ void BuyMenuGUI::Update() { if (pressDown && m_DraggedItemIndex < listSize - 1) { m_IsDragging = true; std::swap((*m_pShopList->GetItemList())[m_DraggedItemIndex], (*m_pShopList->GetItemList())[m_DraggedItemIndex + 1]); - std::swap((*m_pShopList->GetItemList())[m_DraggedItemIndex + 1]->m_ID, (*m_pShopList->GetItemList())[m_DraggedItemIndex]->m_ID); + std::swap((*m_pShopList->GetItemList())[m_DraggedItemIndex]->m_ID, (*m_pShopList->GetItemList())[m_DraggedItemIndex + 1]->m_ID); + std::swap(m_Loadouts[m_DraggedItemIndex], m_Loadouts[m_DraggedItemIndex + 1]); m_ListItemIndex = ++m_DraggedItemIndex; m_SelectedLoadoutIndex = -1; m_pShopList->SetSelectedIndex(m_ListItemIndex); @@ -1127,7 +1128,8 @@ void BuyMenuGUI::Update() { } else if (pressUp && m_DraggedItemIndex > 0) { m_IsDragging = true; std::swap((*m_pShopList->GetItemList())[m_DraggedItemIndex], (*m_pShopList->GetItemList())[m_DraggedItemIndex - 1]); - std::swap((*m_pShopList->GetItemList())[m_DraggedItemIndex - 1]->m_ID, (*m_pShopList->GetItemList())[m_DraggedItemIndex]->m_ID); + std::swap((*m_pShopList->GetItemList())[m_DraggedItemIndex]->m_ID, (*m_pShopList->GetItemList())[m_DraggedItemIndex - 1]->m_ID); + std::swap(m_Loadouts[m_DraggedItemIndex], m_Loadouts[m_DraggedItemIndex - 1]); m_ListItemIndex = --m_DraggedItemIndex; m_SelectedLoadoutIndex = -1; m_pShopList->SetSelectedIndex(m_ListItemIndex); @@ -1373,7 +1375,7 @@ void BuyMenuGUI::Update() { m_IsDragging = true; itemsChanged = true; std::swap((*m_pCartList->GetItemList())[m_DraggedItemIndex], (*m_pCartList->GetItemList())[m_DraggedItemIndex + 1]); - std::swap((*m_pCartList->GetItemList())[m_DraggedItemIndex + 1]->m_ID, (*m_pCartList->GetItemList())[m_DraggedItemIndex]->m_ID); + std::swap((*m_pCartList->GetItemList())[m_DraggedItemIndex]->m_ID, (*m_pCartList->GetItemList())[m_DraggedItemIndex + 1]->m_ID); m_ListItemIndex = ++m_DraggedItemIndex; m_pCartList->SetSelectedIndex(m_ListItemIndex); g_GUISound.SelectionChangeSound()->Play(m_pController->GetPlayer()); @@ -1381,7 +1383,7 @@ void BuyMenuGUI::Update() { m_IsDragging = true; itemsChanged = true; std::swap((*m_pCartList->GetItemList())[m_DraggedItemIndex], (*m_pCartList->GetItemList())[m_DraggedItemIndex - 1]); - std::swap((*m_pCartList->GetItemList())[m_DraggedItemIndex - 1]->m_ID, (*m_pCartList->GetItemList())[m_DraggedItemIndex]->m_ID); + std::swap((*m_pCartList->GetItemList())[m_DraggedItemIndex]->m_ID, (*m_pCartList->GetItemList())[m_DraggedItemIndex + 1]->m_ID); m_ListItemIndex = --m_DraggedItemIndex; m_pCartList->SetSelectedIndex(m_ListItemIndex); g_GUISound.SelectionChangeSound()->Play(m_pController->GetPlayer()); @@ -1748,7 +1750,8 @@ void BuyMenuGUI::Update() { m_DraggedItemIndex = oldIndex + direction; m_SelectedLoadoutIndex = m_DraggedItemIndex; std::swap((*m_pShopList->GetItemList())[oldIndex], (*m_pShopList->GetItemList())[oldIndex + direction]); - std::swap((*m_pShopList->GetItemList())[oldIndex + direction]->m_ID, (*m_pShopList->GetItemList())[oldIndex]->m_ID); + std::swap((*m_pShopList->GetItemList())[oldIndex]->m_ID, (*m_pShopList->GetItemList())[oldIndex + direction]->m_ID); + std::swap(m_Loadouts[oldIndex], m_Loadouts[oldIndex + direction]); } } @@ -1853,7 +1856,7 @@ void BuyMenuGUI::Update() { m_DraggedItemIndex = oldIndex + direction; std::swap((*m_pCartList->GetItemList())[oldIndex], (*m_pCartList->GetItemList())[oldIndex + direction]); - std::swap((*m_pCartList->GetItemList())[oldIndex + direction]->m_ID, (*m_pCartList->GetItemList())[oldIndex]->m_ID); + std::swap((*m_pCartList->GetItemList())[oldIndex]->m_ID, (*m_pCartList->GetItemList())[oldIndex + direction]->m_ID); } UpdateItemNestingLevels(); } From 6105132de65883b570ff50df0e87c68dc70b1871 Mon Sep 17 00:00:00 2001 From: Causeless Date: Thu, 24 Jul 2025 16:23:09 +0100 Subject: [PATCH 09/15] Fixed swapped +/- --- Source/Menus/BuyMenuGUI.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Menus/BuyMenuGUI.cpp b/Source/Menus/BuyMenuGUI.cpp index 123ca2f2b5..e27da71cd0 100644 --- a/Source/Menus/BuyMenuGUI.cpp +++ b/Source/Menus/BuyMenuGUI.cpp @@ -1383,7 +1383,7 @@ void BuyMenuGUI::Update() { m_IsDragging = true; itemsChanged = true; std::swap((*m_pCartList->GetItemList())[m_DraggedItemIndex], (*m_pCartList->GetItemList())[m_DraggedItemIndex - 1]); - std::swap((*m_pCartList->GetItemList())[m_DraggedItemIndex]->m_ID, (*m_pCartList->GetItemList())[m_DraggedItemIndex + 1]->m_ID); + std::swap((*m_pCartList->GetItemList())[m_DraggedItemIndex]->m_ID, (*m_pCartList->GetItemList())[m_DraggedItemIndex - 1]->m_ID); m_ListItemIndex = --m_DraggedItemIndex; m_pCartList->SetSelectedIndex(m_ListItemIndex); g_GUISound.SelectionChangeSound()->Play(m_pController->GetPlayer()); From 5f37234a7745745e3ab44c428ed2b62183bdce1d Mon Sep 17 00:00:00 2001 From: Causeless Date: Thu, 24 Jul 2025 16:35:39 +0100 Subject: [PATCH 10/15] Fixed issue that disallowed selection on keyboard --- Source/Menus/BuyMenuGUI.cpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Source/Menus/BuyMenuGUI.cpp b/Source/Menus/BuyMenuGUI.cpp index e27da71cd0..d0bde0fdee 100644 --- a/Source/Menus/BuyMenuGUI.cpp +++ b/Source/Menus/BuyMenuGUI.cpp @@ -1281,9 +1281,16 @@ void BuyMenuGUI::Update() { } // User selected to add an item to cart list! - if (m_pController->IsState(PRESS_FACEBUTTON) && !m_IsDragging) { + if (m_pController->IsState(RELEASE_FACEBUTTON) && !m_IsDragging) { + // User pressed on a loadout set, so load it into the menu + if (pItem && m_MenuCategory == LOADOUTS) { + // Beep if there's an error + if (!DeployLoadout(m_ListItemIndex)) { + g_GUISound.UserErrorSound()->Play(m_pController->GetPlayer()); + } + } // User pressed on a module group item; toggle its expansion! - if (pItem && pItem->m_ExtraIndex >= 0) { + else if (pItem && pItem->m_ExtraIndex >= 0) { // Make appropriate sound if (!m_aExpandedModules[pItem->m_ExtraIndex]) { g_GUISound.ItemChangeSound()->Play(m_pController->GetPlayer()); @@ -1296,12 +1303,6 @@ void BuyMenuGUI::Update() { // Re-populate the item list with the new module expansion configuation CategoryChange(false); } - // User pressed on a loadout set, so load it into the menu - else if (pItem && m_MenuCategory == LOADOUTS) { - // Beep if there's an error - if (!DeployLoadout(m_ListItemIndex)) - g_GUISound.UserErrorSound()->Play(m_pController->GetPlayer()); - } // User mashed button on a regular shop item, add it to cargo, or select craft else if (pItem && pItem->m_pEntity) { // Select the craft From 6690c81d87447c6187ca7cdde4020c8cdb24b700 Mon Sep 17 00:00:00 2001 From: Causeless Date: Thu, 24 Jul 2025 17:15:15 +0100 Subject: [PATCH 11/15] Fixed to make keyboard work properly --- Source/Menus/BuyMenuGUI.cpp | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/Source/Menus/BuyMenuGUI.cpp b/Source/Menus/BuyMenuGUI.cpp index d0bde0fdee..c56f7f3318 100644 --- a/Source/Menus/BuyMenuGUI.cpp +++ b/Source/Menus/BuyMenuGUI.cpp @@ -854,6 +854,7 @@ void BuyMenuGUI::Update() { ///////////////////////////////////////////// // Repeating input logic + bool isKeyboardControlled = !m_pController->IsMouseControlled() && !m_pController->IsGamepadControlled(); bool pressLeft = m_pController->IsState(PRESS_LEFT); bool pressRight = m_pController->IsState(PRESS_RIGHT); bool pressUp = m_pController->IsState(PRESS_UP); @@ -1028,7 +1029,6 @@ void BuyMenuGUI::Update() { if (m_FocusChange) { // Set the correct special Sets category so the sets buttons show up m_MenuCategory = LOADOUTS; - CategoryChange(); m_pSaveButton->SetFocus(); m_FocusChange = 0; } @@ -1120,6 +1120,7 @@ void BuyMenuGUI::Update() { m_IsDragging = true; std::swap((*m_pShopList->GetItemList())[m_DraggedItemIndex], (*m_pShopList->GetItemList())[m_DraggedItemIndex + 1]); std::swap((*m_pShopList->GetItemList())[m_DraggedItemIndex]->m_ID, (*m_pShopList->GetItemList())[m_DraggedItemIndex + 1]->m_ID); + std::swap((*m_pShopList->GetItemList())[m_DraggedItemIndex]->m_ExtraIndex, (*m_pShopList->GetItemList())[m_DraggedItemIndex + 1]->m_ExtraIndex); std::swap(m_Loadouts[m_DraggedItemIndex], m_Loadouts[m_DraggedItemIndex + 1]); m_ListItemIndex = ++m_DraggedItemIndex; m_SelectedLoadoutIndex = -1; @@ -1129,6 +1130,7 @@ void BuyMenuGUI::Update() { m_IsDragging = true; std::swap((*m_pShopList->GetItemList())[m_DraggedItemIndex], (*m_pShopList->GetItemList())[m_DraggedItemIndex - 1]); std::swap((*m_pShopList->GetItemList())[m_DraggedItemIndex]->m_ID, (*m_pShopList->GetItemList())[m_DraggedItemIndex - 1]->m_ID); + std::swap((*m_pShopList->GetItemList())[m_DraggedItemIndex]->m_ExtraIndex, (*m_pShopList->GetItemList())[m_DraggedItemIndex - 1]->m_ExtraIndex); std::swap(m_Loadouts[m_DraggedItemIndex], m_Loadouts[m_DraggedItemIndex - 1]); m_ListItemIndex = --m_DraggedItemIndex; m_SelectedLoadoutIndex = -1; @@ -1281,12 +1283,14 @@ void BuyMenuGUI::Update() { } // User selected to add an item to cart list! - if (m_pController->IsState(RELEASE_FACEBUTTON) && !m_IsDragging) { + if (isKeyboardControlled ? (m_pController->IsState(PRESS_FACEBUTTON) && !m_pController->IsState(AIM_SHARP)) : (m_pController->IsState(RELEASE_FACEBUTTON) && !m_IsDragging)) { // User pressed on a loadout set, so load it into the menu if (pItem && m_MenuCategory == LOADOUTS) { // Beep if there's an error if (!DeployLoadout(m_ListItemIndex)) { g_GUISound.UserErrorSound()->Play(m_pController->GetPlayer()); + } else { + g_GUISound.ItemChangeSound()->Play(m_pController->GetPlayer()); } } // User pressed on a module group item; toggle its expansion! @@ -1336,15 +1340,13 @@ void BuyMenuGUI::Update() { UpdateTotalMassLabel(dynamic_cast(m_pSelectedCraft), m_pCraftMassLabel); } - if (m_MenuCategory == LOADOUTS) { - bool isKeyboardControlled = !m_pController->IsMouseControlled() && !m_pController->IsGamepadControlled(); - if (isKeyboardControlled ? m_pController->IsState(AIM_SHARP) : m_pController->IsState(PRESS_FACEBUTTON)) { - m_DraggedItemIndex = m_pCartList->GetSelectedIndex(); - } else if (m_pController->IsState(RELEASE_FACEBUTTON)) { - m_DraggedItemIndex = -1; - m_IsDragging = false; + if (isKeyboardControlled ? m_pController->IsState(AIM_SHARP) : m_pController->IsState(PRESS_FACEBUTTON)) { + m_DraggedItemIndex = m_pShopList->GetSelectedIndex(); + } else if (m_pController->IsState(RELEASE_FACEBUTTON)) { + if (m_MenuCategory == LOADOUTS) { + // Might've reordered the loadout list, so we need to save the new order + SaveAllLoadoutsToFile(); } - } else { m_DraggedItemIndex = -1; m_IsDragging = false; } @@ -1456,7 +1458,6 @@ void BuyMenuGUI::Update() { } // Fire button removes items from the order list, including equipment on AHumans - bool isKeyboardControlled = !m_pController->IsMouseControlled() && !m_pController->IsGamepadControlled(); if (isKeyboardControlled ? (m_pController->IsState(PRESS_FACEBUTTON) && !m_pController->IsState(AIM_SHARP)) : (m_pController->IsState(RELEASE_FACEBUTTON) && !m_IsDragging)) { if (g_UInputMan.FlagShiftState()) { ClearCartList(); @@ -1902,7 +1903,8 @@ void BuyMenuGUI::Update() { m_pDeleteButton->SetEnabled(m_SelectedLoadoutIndex != -1); if (m_SelectedLoadoutIndex != -1) { // Always highlight the currently selected loadout as if it's focused - m_pShopList->SetSelectedIndex(m_SelectedLoadoutIndex); + // TODO- need something better than this, like a dedicated highlight control + //m_pShopList->SetSelectedIndex(m_SelectedLoadoutIndex); } } } @@ -2271,6 +2273,7 @@ void BuyMenuGUI::AddPresetsToItemList() { m_SelectedLoadoutIndex = -1; // Go through all the presets, making intelligible list items from then for the GUI item list + m_pShopList->ClearList(); for (int i = 0; i < m_Loadouts.size(); ++i) { Loadout& loadout = m_Loadouts[i]; From fe5cd083884da5b6a24c4372113be131550422f8 Mon Sep 17 00:00:00 2001 From: Causeless Date: Thu, 24 Jul 2025 17:16:49 +0100 Subject: [PATCH 12/15] fixed description tooltip being mismatched when using mouse dragging --- Source/Menus/BuyMenuGUI.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/Menus/BuyMenuGUI.cpp b/Source/Menus/BuyMenuGUI.cpp index c56f7f3318..8a27b53aa9 100644 --- a/Source/Menus/BuyMenuGUI.cpp +++ b/Source/Menus/BuyMenuGUI.cpp @@ -1753,6 +1753,7 @@ void BuyMenuGUI::Update() { m_SelectedLoadoutIndex = m_DraggedItemIndex; std::swap((*m_pShopList->GetItemList())[oldIndex], (*m_pShopList->GetItemList())[oldIndex + direction]); std::swap((*m_pShopList->GetItemList())[oldIndex]->m_ID, (*m_pShopList->GetItemList())[oldIndex + direction]->m_ID); + std::swap((*m_pShopList->GetItemList())[oldIndex]->m_ExtraIndex, (*m_pShopList->GetItemList())[oldIndex + direction]->m_ExtraIndex); std::swap(m_Loadouts[oldIndex], m_Loadouts[oldIndex + direction]); } } From 6af2121b2bd393c5ba3b9c421975e3d5a670dc1d Mon Sep 17 00:00:00 2001 From: Causeless Date: Fri, 25 Jul 2025 18:47:15 +0100 Subject: [PATCH 13/15] added margin --- Source/Menus/BuyMenuGUI.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Source/Menus/BuyMenuGUI.cpp b/Source/Menus/BuyMenuGUI.cpp index 8a27b53aa9..0064909ecb 100644 --- a/Source/Menus/BuyMenuGUI.cpp +++ b/Source/Menus/BuyMenuGUI.cpp @@ -2282,6 +2282,7 @@ void BuyMenuGUI::AddPresetsToItemList() { float loadoutCost = 0; const int maxBitmapWidth = 130; + const int margin = 2; int bitmapHeight = 0; int bitmapWidth = 0; @@ -2291,7 +2292,7 @@ void BuyMenuGUI::AddPresetsToItemList() { for (const SceneObject* sceneObject: *loadout.GetCargoList()) { if (dynamic_cast(sceneObject)) { // start a new row - bitmapHeight += rowHeight; + bitmapHeight += rowHeight + margin; bitmapWidth = std::max(bitmapWidth, rowWidth); rowHeight = 0; rowWidth = 0; @@ -2302,7 +2303,7 @@ void BuyMenuGUI::AddPresetsToItemList() { } rowHeight = std::max(rowHeight, sceneObject->GetGraphicalIcon()->h); - rowWidth += sceneObject->GetGraphicalIcon()->w; + rowWidth += sceneObject->GetGraphicalIcon()->w + margin; } // and once more for the last row @@ -2320,7 +2321,7 @@ void BuyMenuGUI::AddPresetsToItemList() { for (const SceneObject* sceneObject: *loadout.GetCargoList()) { if (dynamic_cast(sceneObject)) { // start a new row - heightOffset += rowHeight; + heightOffset += rowHeight + margin; rowHeight = 0; widthOffset = 0; } @@ -2336,7 +2337,7 @@ void BuyMenuGUI::AddPresetsToItemList() { int yOffset = (rowHeight - sceneObject->GetGraphicalIcon()->h) / 2; draw_sprite(pItemBitmap->GetBitmap(), sceneObject->GetGraphicalIcon(), widthOffset, heightOffset + yOffset); - widthOffset += sceneObject->GetGraphicalIcon()->w; + widthOffset += sceneObject->GetGraphicalIcon()->w + margin; } for (const SceneObject* sceneObject: *loadout.GetCargoList()) { From 445110d0d9dc2cb8d5763bd1edf91e88695b38b5 Mon Sep 17 00:00:00 2001 From: Causeless Date: Fri, 25 Jul 2025 18:55:32 +0100 Subject: [PATCH 14/15] fixed minor issue with spacing consistency --- Source/Menus/BuyMenuGUI.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Menus/BuyMenuGUI.cpp b/Source/Menus/BuyMenuGUI.cpp index 0064909ecb..a72ae71c75 100644 --- a/Source/Menus/BuyMenuGUI.cpp +++ b/Source/Menus/BuyMenuGUI.cpp @@ -2287,7 +2287,7 @@ void BuyMenuGUI::AddPresetsToItemList() { int bitmapHeight = 0; int bitmapWidth = 0; - int rowHeight = 0; + int rowHeight = -margin; int rowWidth = 0; for (const SceneObject* sceneObject: *loadout.GetCargoList()) { if (dynamic_cast(sceneObject)) { @@ -2315,7 +2315,7 @@ void BuyMenuGUI::AddPresetsToItemList() { pItemBitmap->Create(bitmapWidth, bitmapHeight); // Now actually draw the stuff in the appropriate places - rowHeight = 0; + rowHeight = -margin; int heightOffset = 0; int widthOffset = 0; for (const SceneObject* sceneObject: *loadout.GetCargoList()) { From 6242a4dab008b8569f8593478e420c708abbbe59 Mon Sep 17 00:00:00 2001 From: Causeless Date: Fri, 25 Jul 2025 19:33:43 +0100 Subject: [PATCH 15/15] feels really messy now, but it's more accurate --- Source/Menus/BuyMenuGUI.cpp | 64 +++++++++++++++++++++---------------- 1 file changed, 36 insertions(+), 28 deletions(-) diff --git a/Source/Menus/BuyMenuGUI.cpp b/Source/Menus/BuyMenuGUI.cpp index a72ae71c75..d9b4176f87 100644 --- a/Source/Menus/BuyMenuGUI.cpp +++ b/Source/Menus/BuyMenuGUI.cpp @@ -2281,63 +2281,71 @@ void BuyMenuGUI::AddPresetsToItemList() { AllegroBitmap* pItemBitmap = nullptr; float loadoutCost = 0; + std::vector> rowBounds; + rowBounds.resize(loadout.GetCargoList()->size()); // pessimistic but eh + const int maxBitmapWidth = 130; - const int margin = 2; + const int horizontalMargin = 2; + const int verticalMargin = 3; - int bitmapHeight = 0; int bitmapWidth = 0; + int bitmapHeight = 0; - int rowHeight = -margin; - int rowWidth = 0; - for (const SceneObject* sceneObject: *loadout.GetCargoList()) { - if (dynamic_cast(sceneObject)) { - // start a new row - bitmapHeight += rowHeight + margin; - bitmapWidth = std::max(bitmapWidth, rowWidth); - rowHeight = 0; - rowWidth = 0; + int row = -1; + int numRows = 0; + for (auto itr = loadout.GetCargoList()->begin(), itr_end = loadout.GetCargoList()->end(); itr != itr_end; ++itr) { + const SceneObject* sceneObject = *itr; + if (itr == loadout.GetCargoList()->begin() || dynamic_cast(sceneObject)) { + ++row; + ++numRows; } - if (rowWidth + sceneObject->GetGraphicalIcon()->w > maxBitmapWidth) { - continue; // don't draw anything that would overflow the bitmap - } + rowBounds[row].first += sceneObject->GetGraphicalIcon()->w + horizontalMargin; + rowBounds[row].second = std::max(rowBounds[row].second, sceneObject->GetGraphicalIcon()->h + verticalMargin); + } + + if (numRows == 0) { + // this should never happen, but just in case + continue; + } - rowHeight = std::max(rowHeight, sceneObject->GetGraphicalIcon()->h); - rowWidth += sceneObject->GetGraphicalIcon()->w + margin; + for (int i = 0; i < numRows; ++i) { + bitmapWidth = std::max(bitmapWidth, rowBounds[i].first); + bitmapHeight += rowBounds[i].second; } - // and once more for the last row - bitmapHeight += rowHeight; - bitmapWidth = std::max(bitmapWidth, rowWidth); + bitmapWidth = std::min(bitmapWidth, maxBitmapWidth); // Generate our bitmap of all the cargo items in the loadout pItemBitmap = new AllegroBitmap(); pItemBitmap->Create(bitmapWidth, bitmapHeight); // Now actually draw the stuff in the appropriate places - rowHeight = -margin; int heightOffset = 0; int widthOffset = 0; - for (const SceneObject* sceneObject: *loadout.GetCargoList()) { - if (dynamic_cast(sceneObject)) { - // start a new row - heightOffset += rowHeight + margin; - rowHeight = 0; + row = 0; + for (auto itr = loadout.GetCargoList()->begin(), itr_end = loadout.GetCargoList()->end(); itr != itr_end; ++itr) { + const SceneObject* sceneObject = *itr; + int rowWidth = rowBounds[row].first; + int rowHeight = rowBounds[row].second; + if (itr != loadout.GetCargoList()->begin() && dynamic_cast(sceneObject)) { + heightOffset += rowHeight; widthOffset = 0; + ++row; + rowWidth = rowBounds[row].first; + rowHeight = rowBounds[row].second; } if (widthOffset + sceneObject->GetGraphicalIcon()->w > maxBitmapWidth) { continue; // don't draw anything that would overflow the bitmap } - rowHeight = std::max(rowHeight, sceneObject->GetGraphicalIcon()->h); - // TODO: make a smarter row structure so we can properly centre the icons if the actor isn't the tallest item in the row // Vertically center the icon in the row int yOffset = (rowHeight - sceneObject->GetGraphicalIcon()->h) / 2; draw_sprite(pItemBitmap->GetBitmap(), sceneObject->GetGraphicalIcon(), widthOffset, heightOffset + yOffset); - widthOffset += sceneObject->GetGraphicalIcon()->w + margin; + widthOffset += sceneObject->GetGraphicalIcon()->w + horizontalMargin; } for (const SceneObject* sceneObject: *loadout.GetCargoList()) {