Index: binaries/data/mods/public/gui/session/selection_panels.js =================================================================== --- binaries/data/mods/public/gui/session/selection_panels.js +++ binaries/data/mods/public/gui/session/selection_panels.js @@ -348,7 +348,7 @@ for (let state of unitEntStates) if (state.garrisonHolder) - groups.add(state.garrisonHolder.entities); + groups.add(state.garrisonHolder.hiddenEntities); return groups.getEntsGrouped(); }, @@ -1018,6 +1018,76 @@ } }; +g_SelectionPanels.Turrets = { + "getMaxNumberOfItems": function() + { + return 12 - getNumberOfRightPanelButtons(); + }, + "getItems": function(unitEntStates) + { + if (unitEntStates.every(state => !state.garrisonHolder || !state.garrisonHolder.visibleGarrisonPoints.length)) + return []; + + let visibleGarrisonPoints = []; + + for (let state of unitEntStates) + if (state.garrisonHolder && state.garrisonHolder.visibleGarrisonPoints.length) + for (let point of state.garrisonHolder.visibleGarrisonPoints) + visibleGarrisonPoints.push(point); + + return visibleGarrisonPoints; + }, + "setupButton": function(data) + { + let entState; + let template; + let occupied = false; + if (data.item.entity) + { + entState = GetEntityState(data.item.entity); + template = GetTemplateData(entState.template); + occupied = true; + } + + data.button.onPress = function() { + if (occupied) + unloadEntities([data.item.entity], data.unitEntStates[0].id); + else + warn("Empty?"); + }; + + data.button.onPressRight = function() { + if (occupied) + warn("Right-clicked occupied!"); + else + warn("Empty, right?"); + }; + + data.button.onPressMiddle = function() { + g_Selection.setHighlightList([data.item.entity]); + }; + + data.button.enabled = occupied && controlsPlayer(g_ViewedPlayer); + + data.button.tooltip = occupied ? + translate("Single-click to eject this turret. Middle-click to highlight the entity.") : ""; + + data.guiSelection.sprite = "color:" + g_DiplomacyColors.getPlayerColor(occupied ? entState.player : data.player, 160); + data.button.sprite_disabled = data.button.sprite; + + // Selection panel buttons only appear disabled if they + // also appear disabled to the owner of the building. + data.icon.sprite = + (occupied || g_IsObserver ? "" : "grayscale:") + + (!!template ? "stretched:session/portraits/" + template.icon : + "stretched:session/icons/groups.png"); + + setPanelObjectPosition(data.button, data.i + getNumberOfRightPanelButtons(), data.rowLength); + + return true; + } +}; + g_SelectionPanels.Upgrade = { "getMaxNumberOfItems": function() { @@ -1176,6 +1246,7 @@ "Gate", // Must always be shown on gates "Pack", // Must always be shown on packable entities "Upgrade", // Must always be shown on upgradable entities + "Turrets", "Training", "Construction", "Research", // Normal together with training Index: binaries/data/mods/public/gui/session/selection_panels_helpers.js =================================================================== --- binaries/data/mods/public/gui/session/selection_panels_helpers.js +++ binaries/data/mods/public/gui/session/selection_panels_helpers.js @@ -395,6 +395,21 @@ }); } +/** + * Unloads entities from the parent + * + * @param {number[]} entities - The entity IDs to eject. + * @param {number} parent - The entity ID of the garrisonHolder. + */ +function unloadEntities(entities, parent) +{ + Engine.PostNetworkCommand({ + "type": "unload", + "entities": entities, + "garrisonHolder": parent + }); +} + function unloadAll() { let garrisonHolders = g_Selection.toList().filter(e => { Index: binaries/data/mods/public/gui/session/selection_panels_right/turrets_panel.xml =================================================================== --- /dev/null +++ binaries/data/mods/public/gui/session/selection_panels_right/turrets_panel.xml @@ -0,0 +1,13 @@ + + + + + Index: binaries/data/mods/public/gui/session/unit_commands.js =================================================================== --- binaries/data/mods/public/gui/session/unit_commands.js +++ binaries/data/mods/public/gui/session/unit_commands.js @@ -14,7 +14,8 @@ "Stance": 0, "Gate": 0, "Pack": 0, - "Upgrade": 0 + "Upgrade": 0, + "Turrets": 0 }; /** @@ -220,9 +221,9 @@ function getNumberOfRightPanelButtons() { - var sum = 0; + let sum = 0; - for (let prop of ["Construction", "Training", "Pack", "Gate", "Upgrade"]) + for (let prop of ["Construction", "Training", "Pack", "Gate", "Upgrade", "Turrets"]) if (g_SelectionPanels[prop].used) sum += g_unitPanelButtons[prop]; Index: binaries/data/mods/public/simulation/components/GarrisonHolder.js =================================================================== --- binaries/data/mods/public/simulation/components/GarrisonHolder.js +++ binaries/data/mods/public/simulation/components/GarrisonHolder.js @@ -106,6 +106,39 @@ }; /** + * Get the entity IDs of the entities which are not visibly garrisoned. + * Used in the GUI to display in the garrison panel. + * + * @return {number[]} - An array containing entity IDs. + */ +GarrisonHolder.prototype.GetHiddenGarrisonedEntities = function() +{ + return this.entities.filter(entity => !this.IsVisiblyGarrisoned(entity)); +}; + +/** + * Test whether the entity is visibly garrisoned. + * + * @param {number} entity - The entity ID of the entity to check. + * + * @return {boolean} - Whether the entity is visibly garrisoned. + */ +GarrisonHolder.prototype.IsVisiblyGarrisoned = function(entity) +{ + return this.visibleGarrisonPoints.some(vgp => vgp.entity == entity); +}; + +/** + * Get the visible garrison points of this entity. + * + * @return {Object[]} - An array containing visibly garrison points. + */ +GarrisonHolder.prototype.GetVisibleGarrisonPoints = function() +{ + return this.visibleGarrisonPoints; +}; + +/** * @return {Array} unit classes which can be garrisoned inside this * particular entity. Obtained from the entity's template. */ Index: binaries/data/mods/public/simulation/components/GuiInterface.js =================================================================== --- binaries/data/mods/public/simulation/components/GuiInterface.js +++ binaries/data/mods/public/simulation/components/GuiInterface.js @@ -357,10 +357,12 @@ if (cmpGarrisonHolder) ret.garrisonHolder = { "entities": cmpGarrisonHolder.GetEntities(), + "hiddenEntities": cmpGarrisonHolder.GetHiddenGarrisonedEntities(), "buffHeal": cmpGarrisonHolder.GetHealRate(), "allowedClasses": cmpGarrisonHolder.GetAllowedClasses(), "capacity": cmpGarrisonHolder.GetCapacity(), - "garrisonedEntitiesCount": cmpGarrisonHolder.GetGarrisonedEntitiesCount() + "garrisonedEntitiesCount": cmpGarrisonHolder.GetGarrisonedEntitiesCount(), + "visibleGarrisonPoints": cmpGarrisonHolder.GetVisibleGarrisonPoints() }; ret.canGarrison = !!Engine.QueryInterface(ent, IID_Garrisonable); Index: source/gui/CGUI.cpp =================================================================== --- source/gui/CGUI.cpp +++ source/gui/CGUI.cpp @@ -156,6 +156,11 @@ ret = pNearest->SendEvent(GUIM_MOUSE_PRESS_RIGHT, "mouserightpress"); break; + case SDL_BUTTON_MIDDLE: + if (pNearest) + ret = pNearest->SendEvent(GUIM_MOUSE_PRESS_MIDDLE, "mousemiddlepress"); + break; + default: break; } Index: source/gui/ObjectBases/IGUIButtonBehavior.h =================================================================== --- source/gui/ObjectBases/IGUIButtonBehavior.h +++ source/gui/ObjectBases/IGUIButtonBehavior.h @@ -74,6 +74,7 @@ */ bool m_Pressed; bool m_PressedRight; + bool m_PressedMiddle; // Settings CStrW m_SoundDisabled; Index: source/gui/ObjectBases/IGUIButtonBehavior.cpp =================================================================== --- source/gui/ObjectBases/IGUIButtonBehavior.cpp +++ source/gui/ObjectBases/IGUIButtonBehavior.cpp @@ -25,6 +25,7 @@ : m_pObject(pObject), m_Pressed(), m_PressedRight(), + m_PressedMiddle(), m_SoundDisabled(), m_SoundEnter(), m_SoundLeave(), @@ -46,6 +47,7 @@ { m_Pressed = false; m_PressedRight = false; + m_PressedMiddle = false; } void IGUIButtonBehavior::HandleMessage(SGUIMessage& Message) @@ -128,6 +130,29 @@ } break; + case GUIM_MOUSE_PRESS_MIDDLE: + if (!m_pObject.IsEnabled()) + { + m_pObject.PlaySound(m_SoundDisabled); + break; + } + + m_pObject.PlaySound(m_SoundPressed); + m_pObject.SendEvent(GUIM_PRESSED_MOUSE_MIDDLE, "pressmiddle"); + m_PressedMiddle = true; + break; + + case GUIM_MOUSE_RELEASE_MIDDLE: + if (!m_pObject.IsEnabled()) + break; + + if (m_PressedMiddle) + { + m_PressedMiddle = false; + m_pObject.PlaySound(m_SoundReleased); + } + break; + default: break; } Index: source/gui/SGUIMessage.h =================================================================== --- source/gui/SGUIMessage.h +++ source/gui/SGUIMessage.h @@ -32,6 +32,7 @@ GUIM_MOUSE_PRESS_LEFT, GUIM_MOUSE_PRESS_LEFT_ITEM, GUIM_MOUSE_PRESS_RIGHT, + GUIM_MOUSE_PRESS_MIDDLE, GUIM_MOUSE_DOWN_LEFT, GUIM_MOUSE_DOWN_RIGHT, GUIM_MOUSE_DBLCLICK_LEFT, @@ -39,6 +40,7 @@ GUIM_MOUSE_DBLCLICK_RIGHT, GUIM_MOUSE_RELEASE_LEFT, GUIM_MOUSE_RELEASE_RIGHT, + GUIM_MOUSE_RELEASE_MIDDLE, GUIM_MOUSE_WHEEL_UP, GUIM_MOUSE_WHEEL_DOWN, GUIM_SETTINGS_UPDATED, // SGUIMessage.m_Value = name of setting @@ -51,6 +53,7 @@ GUIM_LOST_FOCUS, GUIM_PRESSED_MOUSE_RIGHT, GUIM_DOUBLE_PRESSED_MOUSE_RIGHT, + GUIM_PRESSED_MOUSE_MIDDLE, GUIM_TAB, // Used by CInput GUIM_TEXTEDIT };