From 2b981d8318ca66b3b5bac488d8eb70a47eb20444 Mon Sep 17 00:00:00 2001 From: Daniel Harvey Date: Fri, 19 Jun 2026 23:28:31 -0600 Subject: [PATCH] Sharing 4/4: Sharing Tab UI --- .../gui_advplayerlist/api_extensions.lua | 78 ++ .../gui_advplayerlist/helpers.lua | 18 + .../gui_advplayerlist/policy.lua | 163 ++++ .../gui_advplayerlist/resource.lua | 34 + .../team_transfer/team_transfer_unsynced.lua | 11 + .../team_transfer/unit_transfer_unsynced.lua | 17 + language/en/interface.json | 84 +- language/en/tips.json | 2 + luaui/Shaders/DrawPrimitiveAtUnit.frag.glsl | 1 + luaui/Shaders/DrawPrimitiveAtUnit.geom.glsl | 2 + .../DrawPrimitiveAtUnit_nogs.vert.glsl | 3 + luaui/Widgets/cmd_share_unit.lua | 37 +- luaui/Widgets/cmd_take.lua | 18 + luaui/Widgets/gui_advplayerslist.lua | 855 +++++++----------- luaui/Widgets/gui_advplayerslist_modules.lua | 213 +++++ luaui/Widgets/gui_chat.lua | 58 +- luaui/Widgets/gui_selectedunits_gl4.lua | 68 +- luaui/Widgets/gui_tax_buildspeed_debuff.lua | 3 +- luaui/Widgets/gui_teamstats.lua | 9 + luaui/Widgets/gui_top_bar.lua | 158 ++-- luaui/barwidgets.lua | 9 + modoptions.lua | 89 +- 22 files changed, 1253 insertions(+), 677 deletions(-) create mode 100644 common/luaUtilities/team_transfer/gui_advplayerlist/api_extensions.lua create mode 100644 common/luaUtilities/team_transfer/gui_advplayerlist/helpers.lua create mode 100644 common/luaUtilities/team_transfer/gui_advplayerlist/policy.lua create mode 100644 common/luaUtilities/team_transfer/gui_advplayerlist/resource.lua create mode 100644 common/luaUtilities/team_transfer/team_transfer_unsynced.lua create mode 100644 common/luaUtilities/team_transfer/unit_transfer_unsynced.lua create mode 100644 luaui/Widgets/cmd_take.lua create mode 100644 luaui/Widgets/gui_advplayerslist_modules.lua diff --git a/common/luaUtilities/team_transfer/gui_advplayerlist/api_extensions.lua b/common/luaUtilities/team_transfer/gui_advplayerlist/api_extensions.lua new file mode 100644 index 00000000000..247083687ff --- /dev/null +++ b/common/luaUtilities/team_transfer/gui_advplayerlist/api_extensions.lua @@ -0,0 +1,78 @@ +local UnitShared = VFS.Include("common/luaUtilities/team_transfer/unit_transfer_shared.lua") + +local API = {} + +local hoverChangeListeners = {} + +---@param listener function +function API.AddHoverChangeListener(listener) + table.insert(hoverChangeListeners, listener) +end + +---@param listener function +function API.RemoveHoverChangeListener(listener) + for i, existingListener in ipairs(hoverChangeListeners) do + if existingListener == listener then + table.remove(hoverChangeListeners, i) + break + end + end +end + +---Handle hover changes and notify about invalid units for the hovered player +---@param myTeamID number +---@param selectedUnits number[] +---@param newHoverTeamID number | nil +---@param newHoverPlayerID number | nil +function API.HandleHoverChange(myTeamID, selectedUnits, newHoverTeamID, newHoverPlayerID) + API.NotifyHoverChangeListeners(newHoverTeamID, newHoverPlayerID) + + if newHoverTeamID and selectedUnits and #selectedUnits > 0 then + local policyResult = UnitShared.GetCachedPolicyResult(myTeamID, newHoverTeamID, Spring) + local validationResult = UnitShared.ValidateUnits(policyResult, selectedUnits, Spring) + if #validationResult.invalidUnitIds > 0 then + API.NotifyHoverSelectedUnitsInvalid(newHoverTeamID, newHoverPlayerID, validationResult.invalidUnitIds) + else + -- empty notify clears any previous invalid state + API.NotifyHoverSelectedUnitsInvalid(newHoverTeamID, newHoverPlayerID, {}) + end + else + API.NotifyHoverSelectedUnitsInvalid(newHoverTeamID, newHoverPlayerID, {}) + end +end + +---@param newHoverTeamID number | nil +---@param newHoverPlayerID number | nil +function API.NotifyHoverChangeListeners(newHoverTeamID, newHoverPlayerID) + for _, listener in ipairs(hoverChangeListeners) do + listener(newHoverTeamID, newHoverPlayerID) + end +end + +local hoverInvalidUnitsListeners = {} + +---@param listener function +function API.AddHoverInvalidUnitsListener(listener) + table.insert(hoverInvalidUnitsListeners, listener) +end + +---@param listener function +function API.RemoveHoverInvalidUnitsListener(listener) + for i, existingListener in ipairs(hoverInvalidUnitsListeners) do + if existingListener == listener then + table.remove(hoverInvalidUnitsListeners, i) + break + end + end +end + +---@param newHoverTeamID number | nil +---@param newHoverPlayerID number | nil +---@param invalidUnitIds number[] +function API.NotifyHoverSelectedUnitsInvalid(newHoverTeamID, newHoverPlayerID, invalidUnitIds) + for _, listener in ipairs(hoverInvalidUnitsListeners) do + listener(newHoverTeamID, newHoverPlayerID, invalidUnitIds) + end +end + +return API diff --git a/common/luaUtilities/team_transfer/gui_advplayerlist/helpers.lua b/common/luaUtilities/team_transfer/gui_advplayerlist/helpers.lua new file mode 100644 index 00000000000..4d19f6d0e65 --- /dev/null +++ b/common/luaUtilities/team_transfer/gui_advplayerlist/helpers.lua @@ -0,0 +1,18 @@ +--- Aggregates sub-helper modules so gui_advplayerslist.lua avoids the Lua local cap +local PolicyHelpers = VFS.Include("common/luaUtilities/team_transfer/gui_advplayerlist/policy.lua") +local ResourceHelpersFactory = VFS.Include("common/luaUtilities/team_transfer/gui_advplayerlist/resource.lua") +local UnitValidationHelpers = VFS.Include("common/luaUtilities/team_transfer/gui_advplayerlist/validation.lua") + +local Helpers = {} + +local function extend(source) + for key, value in pairs(source) do + Helpers[key] = value + end +end + +extend(PolicyHelpers) +extend(ResourceHelpersFactory(PolicyHelpers)) +extend(UnitValidationHelpers) + +return Helpers diff --git a/common/luaUtilities/team_transfer/gui_advplayerlist/policy.lua b/common/luaUtilities/team_transfer/gui_advplayerlist/policy.lua new file mode 100644 index 00000000000..8290040eb7e --- /dev/null +++ b/common/luaUtilities/team_transfer/gui_advplayerlist/policy.lua @@ -0,0 +1,163 @@ +--- Policy helpers that keep gui_advplayerslist.lua under the Lua local closure cap +local TransferEnums = VFS.Include("common/luaUtilities/team_transfer/transfer_enums.lua") +local UnitShared = VFS.Include("common/luaUtilities/team_transfer/unit_transfer_shared.lua") +local ResourceShared = VFS.Include("common/luaUtilities/team_transfer/resource_transfer_shared.lua") + +local METAL_POLICY_PREFIX = "metal_" +local ENERGY_POLICY_PREFIX = "energy_" +local UNIT_POLICY_PREFIX = "unit_" + +local metalPlayerScratch = {} +local energyPlayerScratch = {} +local unitPlayerScratch = {} + +local PolicyHelpers = {} + +---@param player table +---@param resourceType string +---@param senderTeamId number +---@return ResourcePolicyResult policyResult, string pascalResourceType +function PolicyHelpers.GetPlayerResourcePolicy(player, resourceType, senderTeamId) + local transferCategory = resourceType == "metal" and TransferEnums.TransferCategory.MetalTransfer or + TransferEnums.TransferCategory.EnergyTransfer + local policyResult = PolicyHelpers.UnpackPolicyResult(transferCategory, player, senderTeamId, player.team) + local pascalResourceType = resourceType == TransferEnums.ResourceType.METAL and "Metal" or "Energy" + return policyResult, pascalResourceType +end + +---@param playerData table +---@param myTeamID number +---@param playerTeamID number +function PolicyHelpers.PackAllPoliciesForPlayer(playerData, myTeamID, playerTeamID) + PolicyHelpers.PackMetalPolicyResult(playerTeamID, myTeamID, playerData) + PolicyHelpers.PackEnergyPolicyResult(playerTeamID, myTeamID, playerData) + PolicyHelpers.PackUnitPolicyResult(playerTeamID, myTeamID, playerData) +end + +---@param playerData table +---@param myTeamID number +---@param team number +---@return table, table, table +function PolicyHelpers.UnpackAllPolicies(playerData, myTeamID, team) + local metalPolicy = PolicyHelpers.UnpackPolicyResult(TransferEnums.TransferCategory.MetalTransfer, playerData, myTeamID, + team) + local energyPolicy = PolicyHelpers.UnpackPolicyResult(TransferEnums.TransferCategory.EnergyTransfer, playerData, myTeamID, + team) + local unitPolicy = PolicyHelpers.UnpackUnitPolicyResult(playerData, myTeamID, team) + return metalPolicy, energyPolicy, unitPolicy +end + +---@param transferCategory string TransferEnums.TransferCategory +---@param playerData table +---@param senderTeamId number +---@param receiverTeamId number +---@return table +function PolicyHelpers.UnpackPolicyResult(transferCategory, playerData, senderTeamId, receiverTeamId) + local fields, prefix, scratch + if transferCategory == TransferEnums.TransferCategory.MetalTransfer then + fields = ResourceShared.ResourcePolicyFields + prefix = METAL_POLICY_PREFIX + scratch = metalPlayerScratch + elseif transferCategory == TransferEnums.TransferCategory.EnergyTransfer then + fields = ResourceShared.ResourcePolicyFields + prefix = ENERGY_POLICY_PREFIX + scratch = energyPlayerScratch + elseif transferCategory == TransferEnums.TransferCategory.UnitTransfer then + fields = UnitShared.UnitPolicyFields + prefix = UNIT_POLICY_PREFIX + scratch = unitPlayerScratch + else + error("Invalid transfer category: " .. transferCategory) + end + + scratch.senderTeamId = senderTeamId + scratch.receiverTeamId = receiverTeamId + + for field, _ in pairs(fields) do + scratch[field] = playerData[prefix .. field] + end + return scratch +end + +---@param playerData table +---@param senderTeamId number +---@param receiverTeamId number +---@return UnitPolicyResult +function PolicyHelpers.UnpackUnitPolicyResult(playerData, senderTeamId, receiverTeamId) + return PolicyHelpers.UnpackPolicyResult(TransferEnums.TransferCategory.UnitTransfer, playerData, senderTeamId, + receiverTeamId) +end + +---@param transferCategory string TransferEnums.TransferCategory +---@param policy table +---@param playerData table +function PolicyHelpers.PackPolicyResult(transferCategory, policy, playerData) + local fields, prefix + if transferCategory == TransferEnums.TransferCategory.MetalTransfer then + fields = ResourceShared.ResourcePolicyFields + prefix = METAL_POLICY_PREFIX + elseif transferCategory == TransferEnums.TransferCategory.EnergyTransfer then + fields = ResourceShared.ResourcePolicyFields + prefix = ENERGY_POLICY_PREFIX + elseif transferCategory == TransferEnums.TransferCategory.UnitTransfer then + fields = UnitShared.UnitPolicyFields + prefix = UNIT_POLICY_PREFIX + else + error("Invalid transfer category: " .. transferCategory) + end + for field, _ in pairs(fields) do + playerData[prefix .. field] = policy[field] + end +end + +---@param team number +---@param myTeamID number +---@param player table +function PolicyHelpers.PackMetalPolicyResult(team, myTeamID, player) + local policyResult = ResourceShared.GetCachedPolicyResult(myTeamID, team, TransferEnums.ResourceType.METAL) + PolicyHelpers.PackPolicyResult(TransferEnums.TransferCategory.MetalTransfer, policyResult, player) +end + +---@param team number +---@param myTeamID number +---@param player table +function PolicyHelpers.PackEnergyPolicyResult(team, myTeamID, player) + local policyResult = ResourceShared.GetCachedPolicyResult(myTeamID, team, TransferEnums.ResourceType.ENERGY) + PolicyHelpers.PackPolicyResult(TransferEnums.TransferCategory.EnergyTransfer, policyResult, player) +end + +---@param team number +---@param myTeamID number +---@param player table +function PolicyHelpers.PackUnitPolicyResult(team, myTeamID, player) + local policyResult = UnitShared.GetCachedPolicyResult(myTeamID, team, Spring) + PolicyHelpers.PackPolicyResult(TransferEnums.TransferCategory.UnitTransfer, policyResult, player) +end + +local packByDomain = { + unit = PolicyHelpers.PackUnitPolicyResult, + metal = PolicyHelpers.PackMetalPolicyResult, + energy = PolicyHelpers.PackEnergyPolicyResult, +} + +---Re-pack policy for rows affected by a SharePolicyChanged event: all rows when our own +---team (the sender) changed, otherwise just the changed team's row. +---@param player table all player rows +---@param myTeamID number +---@param changedTeamID number team whose policy changed +---@param domain string "unit" | "metal" | "energy" +function PolicyHelpers.RepackPolicy(player, myTeamID, changedTeamID, domain) + local packFn = packByDomain[domain] + if not packFn then return end + + for _, playerData in pairs(player) do + if playerData.team and playerData.team ~= myTeamID then + if changedTeamID == myTeamID or playerData.team == changedTeamID then + packFn(playerData.team, myTeamID, playerData) + end + end + end +end + +return PolicyHelpers + diff --git a/common/luaUtilities/team_transfer/gui_advplayerlist/resource.lua b/common/luaUtilities/team_transfer/gui_advplayerlist/resource.lua new file mode 100644 index 00000000000..330cb8aeb62 --- /dev/null +++ b/common/luaUtilities/team_transfer/gui_advplayerlist/resource.lua @@ -0,0 +1,34 @@ +--- Resource transfer helpers kept separate to reduce gui_advplayerslist.lua locals +local TransferEnums = VFS.Include("common/luaUtilities/team_transfer/transfer_enums.lua") +local ResourceShared = VFS.Include("common/luaUtilities/team_transfer/resource_transfer_shared.lua") +local LuaRulesMsg = VFS.Include("common/luaUtilities/lua_rules_msg.lua") + +---@param policyHelpers table +---@return table +return function(policyHelpers) + local ResourceHelpers = {} + + ---Handle resource transfer logic for a players list entry + ---@param targetPlayer table + ---@param resourceType string + ---@param shareAmount number + ---@param senderTeamId number + function ResourceHelpers.HandleResourceTransfer(targetPlayer, resourceType, shareAmount, senderTeamId) + local policyResult, pascalResourceType = policyHelpers.GetPlayerResourcePolicy(targetPlayer, resourceType, senderTeamId) + + local case = ResourceShared.DecideCommunicationCase(policyResult) + + if case == TransferEnums.ResourceCommunicationCase.OnSelf then + if shareAmount > 0 then + Spring.SendLuaRulesMsg('msg:ui.playersList.chat.need' .. pascalResourceType .. 'Amount:amount:' .. shareAmount) + elseif policyResult.amountReceivable > 0 then + Spring.SendLuaRulesMsg('msg:ui.playersList.chat.need' .. pascalResourceType) + end + elseif shareAmount and shareAmount > 0 then + local msg = LuaRulesMsg.SerializeResourceShare(senderTeamId, targetPlayer.team, resourceType, shareAmount) + Spring.SendLuaRulesMsg(msg) + end + end + + return ResourceHelpers +end diff --git a/common/luaUtilities/team_transfer/team_transfer_unsynced.lua b/common/luaUtilities/team_transfer/team_transfer_unsynced.lua new file mode 100644 index 00000000000..615141fef95 --- /dev/null +++ b/common/luaUtilities/team_transfer/team_transfer_unsynced.lua @@ -0,0 +1,11 @@ +local ResourceShared = VFS.Include("common/luaUtilities/team_transfer/resource_transfer_shared.lua") +local UnitShared = VFS.Include("common/luaUtilities/team_transfer/unit_transfer_shared.lua") +local UnitUnsynced = VFS.Include("common/luaUtilities/team_transfer/unit_transfer_unsynced.lua") + +local TeamTransfer = {} + +TeamTransfer.Resources = ResourceShared +TeamTransfer.Units = UnitShared +TeamTransfer.Units.ShareUnits = UnitUnsynced.ShareUnits + +return TeamTransfer diff --git a/common/luaUtilities/team_transfer/unit_transfer_unsynced.lua b/common/luaUtilities/team_transfer/unit_transfer_unsynced.lua new file mode 100644 index 00000000000..82e0e18f2a2 --- /dev/null +++ b/common/luaUtilities/team_transfer/unit_transfer_unsynced.lua @@ -0,0 +1,17 @@ +local LuaRulesMsg = VFS.Include("common/luaUtilities/lua_rules_msg.lua") + +local Unsynced = {} + +---Share currently selected units to target team (mirrors Spring.ShareResources behavior) +---@param targetTeamID number +function Unsynced.ShareUnits(targetTeamID) + local unitIDs = Spring.GetSelectedUnits() + if #unitIDs == 0 then + return + end + local msg = LuaRulesMsg.SerializeUnitTransfer(targetTeamID, unitIDs) + Spring.SendLuaRulesMsg(msg) +end + +return Unsynced + diff --git a/language/en/interface.json b/language/en/interface.json index 9c756738905..52320e4f93a 100644 --- a/language/en/interface.json +++ b/language/en/interface.json @@ -90,9 +90,58 @@ "requestSupport": "Double-click to ask for support", "requestMetal": "Click-and-drag to ask for metal", "requestEnergy": "Click-and-drag to ask for energy", - "shareUnits": "Double-click to share units", - "shareMetal": "Click-and-drag to share metal", - "shareEnergy": "Click-and-drag to share energy", + "shareUnits": { + "base": { + "default": "Double-click to share units", + "disabled": "Unit sharing not allowed (mode is %{unitSharingMode})", + "allInvalid": "Sharing these units is not allowed (mode is %{unitSharingMode})", + "buildDelay": { + "one": "%{count} shared constructor will be paused for %{buildDelaySeconds}s", + "other": "%{count} shared constructors will be paused for %{buildDelaySeconds}s" + }, + "stunDelay": { + "one": "%{count} shared %{stunCategory} unit will be stunned for %{stunSeconds}s", + "other": "%{count} shared %{stunCategory} units will be stunned for %{stunSeconds}s" + }, + "invalid": { + "one": "Double-click to share units (mode is %{unitSharingMode} - %{firstInvalidUnitName} cannot be shared)", + "other": "Double-click to share units (mode is %{unitSharingMode} - %{count} units cannot be shared, including %{firstInvalidUnitName})" + } + }, + "tech": { + "disabled": "Unit sharing unlocks at Tech %{nextTechLevel} (%{currentKeystones}/%{requiredKeystones} Keystones)", + "noUnlock": "These units cannot be shared", + "allInvalid": "These units cannot be shared — %{nextUnitSharingMode} unlocks at Tech %{nextTechLevel} (%{currentKeystones}/%{requiredKeystones} Keystones)", + "invalid": { + "one": "Double-click to share units (%{firstInvalidUnitName} blocked — %{nextUnitSharingMode} unlocks at Tech %{nextTechLevel}, %{currentKeystones}/%{requiredKeystones} Keystones)", + "other": "Double-click to share units (%{count} units blocked, including %{firstInvalidUnitName} — %{nextUnitSharingMode} unlocks at Tech %{nextTechLevel}, %{currentKeystones}/%{requiredKeystones} Keystones)" + } + } + }, + "shareMetal": { + "base": { + "default": "Click-and-drag to share metal", + "disabled": "Metal sharing not allowed", + "taxed": "Click-and-drag to share metal, receivable: %{amountReceivable}m, sendable: %{amountSendable}m at %{taxRatePercentage}%%" + }, + "tech": { + "disabled": "%{resourceType} sharing unlocks at Tech %{nextTechLevel} (%{currentKeystones}/%{requiredKeystones} Keystones)", + "default": "Click-and-drag to share metal (tax-free now, %{nextRate}%% tax at Tech %{nextTechLevel} — %{currentKeystones}/%{requiredKeystones} Keystones)", + "taxed": "Click-and-drag to share metal, receivable: %{amountReceivable}m, sendable: %{amountSendable}m at %{taxRatePercentage}%% (reduces to %{nextRate}%% at Tech %{nextTechLevel} — %{currentKeystones}/%{requiredKeystones} Keystones)" + } + }, + "shareEnergy": { + "base": { + "default": "Click-and-drag to share energy", + "disabled": "Energy sharing not allowed", + "taxed": "Click-and-drag to share energy, receivable: %{amountReceivable}e, sendable: %{amountSendable}e at %{taxRatePercentage}%%" + }, + "tech": { + "disabled": "%{resourceType} sharing unlocks at Tech %{nextTechLevel} (%{currentKeystones}/%{requiredKeystones} Keystones)", + "default": "Click-and-drag to share energy (tax-free now, %{nextRate}%% tax at Tech %{nextTechLevel} — %{currentKeystones}/%{requiredKeystones} Keystones)", + "taxed": "Click-and-drag to share energy, receivable: %{amountReceivable}e, sendable: %{amountSendable}e at %{taxRatePercentage}%% (reduces to %{nextRate}%% at Tech %{nextTechLevel} — %{currentKeystones}/%{requiredKeystones} Keystones)" + } + }, "becomeEnemy": "Click to become enemy", "becomeAlly": "Click to become ally", "thousands": "%{number}k", @@ -116,8 +165,10 @@ "needEnergyAmount": "I need %{amount} energy!", "giveMetal": "I sent %{amount} metal to %{name}", "giveEnergy": "I sent %{amount} energy to %{name}", - "giveMetal_taxed": "I sent %{amount} metal to %{name} and they received %{amount2} metal", - "giveEnergy_taxed": "I sent %{amount} energy to %{name} and they received %{amount2} energy", + "sentMetal": "Sent %{receivedAmount}m", + "sentMetalTaxed": "Sent %{receivedAmount}m, spent %{sentAmount}m at %{taxRatePercentage}%% overhead", + "sentEnergy": "Sent %{receivedAmount}e", + "sentEnergyTaxed": "Sent %{receivedAmount}e, spent %{sentAmount}e at %{taxRatePercentage}%% overhead", "takeTeam": "I took %{name}.", "takeTeamAmount": "I took %{name}: %{units} units, %{energy} energy and %{metal} metal." } @@ -1950,11 +2001,30 @@ }, "techBlocking": { "techLevel": "TECH", + "gameStart": "Tech Core active: build Keystones to unlock T2 (%{t2Threshold} needed) and T3 (%{t3Threshold} needed)", + "techUp": "Tech %{level} unlocked! %{unlockDescription}", + "milestone": { + "t2": "T2 units and %{unitSharingMode} unit sharing unlocked", + "t3": "T3 units and full sharing unlocked" + }, + "tooltip": { + "currentLevel": "Tech %{level} — %{points}/%{threshold} Keystones", + "nextUnlock": "Next: Tech %{nextLevel} unlocks %{unitSharingMode} sharing at %{threshold} Keystones" + }, "techPopup": { "level1": "Tech Level 1", - "level2": "Tech Level 2", - "level3": "Tech Level 3" + "level2": "Tech Level 2 Unlocked!", + "level3": "Tech Level 3 Unlocked!" } + }, + "unitSharingMode": { + "none": "Disabled", + "all": "All", + "combat": "Combat", + "buildings": "Buildings", + "constructors": "Constructors", + "resource": "Resource", + "non_combat": "Non-combat" } } } diff --git a/language/en/tips.json b/language/en/tips.json index fa3a416d9bc..bf6bd1baa01 100644 --- a/language/en/tips.json +++ b/language/en/tips.json @@ -119,6 +119,8 @@ "tech2TeamReached": "Your Team Reached Tech 2", "tech3TeamReached": "Your Team Reached Tech 3", "tech4TeamReached": "Your Team Reached Tech 4", + "tech2TeamLost": "Your Team Lost Tech 2", + "tech3TeamLost": "Your Team Lost Tech 3", "welcome": "Welcome, Commander. I'm your personal voice assistant. I will provide you with battlefield analysis and resource management insights during battles. Let's get to work and good luck!", "welcomeShort": "Welcome, Commander.", "buildMetal": "Choose the metal extractor and build it on a metal spot, indicated by the rotating circles on the map.", diff --git a/luaui/Shaders/DrawPrimitiveAtUnit.frag.glsl b/luaui/Shaders/DrawPrimitiveAtUnit.frag.glsl index c9eb49a714e..cf85abf878e 100644 --- a/luaui/Shaders/DrawPrimitiveAtUnit.frag.glsl +++ b/luaui/Shaders/DrawPrimitiveAtUnit.frag.glsl @@ -15,6 +15,7 @@ uniform float iconDistance = 20000.0; in DataGS { vec4 g_color; vec4 g_uv; + float g_invalid; }; uniform sampler2D DrawPrimitiveAtUnitTexture; diff --git a/luaui/Shaders/DrawPrimitiveAtUnit.geom.glsl b/luaui/Shaders/DrawPrimitiveAtUnit.geom.glsl index 946c142e663..97f0839a8bd 100644 --- a/luaui/Shaders/DrawPrimitiveAtUnit.geom.glsl +++ b/luaui/Shaders/DrawPrimitiveAtUnit.geom.glsl @@ -31,6 +31,7 @@ in DataVS { out DataGS { vec4 g_color; vec4 g_uv; + float g_invalid; }; mat3 rotY; @@ -58,6 +59,7 @@ void offsetVertex4(float x, float y, float z, float u, float v, float addRadiusC gl_Position.z = (gl_Position.z) - ZPULL / (gl_Position.w); // send 16 elmos forward in depth buffer #endif g_uv.zw = dataIn[0].v_parameters.zw; + g_invalid = dataIn[0].v_parameters.y; POST_GEOMETRY EmitVertex(); } diff --git a/luaui/Shaders/DrawPrimitiveAtUnit_nogs.vert.glsl b/luaui/Shaders/DrawPrimitiveAtUnit_nogs.vert.glsl index 7e65be42a9d..c096cd43fdf 100644 --- a/luaui/Shaders/DrawPrimitiveAtUnit_nogs.vert.glsl +++ b/luaui/Shaders/DrawPrimitiveAtUnit_nogs.vert.glsl @@ -59,6 +59,7 @@ uniform float iconDistance = 20000.0; out DataGS { vec4 g_color; vec4 g_uv; + float g_invalid; }; #if USEQUATERNIONS == 0 @@ -201,6 +202,7 @@ void main() gl_Position = vec4(2.0, 2.0, 2.0, 1.0); g_color = vec4(0.0); g_uv = vec4(0.0); + g_invalid = 0.0; return; } @@ -257,5 +259,6 @@ void main() gl_Position.z = (gl_Position.z) - ZPULL / (gl_Position.w); #endif g_uv.zw = v_parameters.zw; + g_invalid = v_parameters.y; POST_GEOMETRY } diff --git a/luaui/Widgets/cmd_share_unit.lua b/luaui/Widgets/cmd_share_unit.lua index 789d4a870fe..c05d7cf0773 100644 --- a/luaui/Widgets/cmd_share_unit.lua +++ b/luaui/Widgets/cmd_share_unit.lua @@ -14,6 +14,8 @@ function widget:GetInfo() } end +local TeamTransfer = VFS.Include("common/luaUtilities/team_transfer/team_transfer_unsynced.lua") + -------------------------------------------------------------------------------- --vars -------------------------------------------------------------------------------- @@ -33,12 +35,10 @@ local GetMyTeamID = Spring.GetMyTeamID local GetUnitTeam = Spring.GetUnitTeam local GetSelectedUnits = Spring.GetSelectedUnits local GetTeamAllyTeamID = Spring.GetTeamAllyTeamID -local ShareResources = Spring.ShareResources local I18N = Spring.I18N local GetSpectatingState = Spring.GetSpectatingState local WorldToScreenCoords = Spring.WorldToScreenCoords local PlaySoundFile = Spring.PlaySoundFile -local GetTeamColor = Spring.GetTeamColor local GetActiveCommand = Spring.GetActiveCommand local GetCameraPosition = Spring.GetCameraPosition local GetMouseState = Spring.GetMouseState @@ -46,7 +46,6 @@ local TraceScreenRay = Spring.TraceScreenRay local GetPlayerList = Spring.GetPlayerList local GetPlayerInfo = Spring.GetPlayerInfo local GetGameRulesParam = Spring.GetGameRulesParam -local GetViewGeometry = Spring.GetViewGeometry local glBeginEnd = gl.BeginEnd local glCallList = gl.CallList @@ -154,13 +153,7 @@ local function getMouseDistance() return sqrt(dx * dx + dy * dy + dz * dz) end -local function getTeamColorWithAlpha(teamId) - local tred, tgreen, tblue = GetTeamColor(teamId) - return { tred, tgreen, tblue, 1 } -end - -local function drawAoE(tx, ty, tz, selectedTeam) - --local color = selectedTeam and getTeamColorWithAlpha(selectedTeam) or defaultColor +local function drawAoE(tx, ty, tz) local color = defaultColor mouseDistance = getMouseDistance() or 1000 @@ -302,13 +295,13 @@ local function getSelectedTeam() end function widget:DrawWorld() - local targetX, targetY, targetZ, selectedTeam = getSelectedTeam() + local targetX, targetY, targetZ = getSelectedTeam() if (not targetX) then return end - drawAoE(targetX, targetY+10, targetZ, selectedTeam) + drawAoE(targetX, targetY+10, targetZ) end function widget:DrawScreen() @@ -336,17 +329,29 @@ function widget:CommandNotify(cmdID, cmdParams, _) targetTeamID = findTeamInArea(mouseX, mouseY) end - if targetTeamID == nil or targetTeamID == myTeamID or GetTeamAllyTeamID(targetTeamID) ~= myAllyTeamID then - -- invalid target, don't do anything + local policyResult = TeamTransfer.Units.GetCachedPolicyResult(myTeamID, targetTeamID) + if not policyResult or not policyResult.canShare then return true end - ShareResources(targetTeamID, "units") - PlaySoundFile("beep4", 1, 'ui') + local selectedUnits = GetSelectedUnits() + if #selectedUnits > 0 then + local msg = "share:units:" .. targetTeamID .. ":" .. table.concat(selectedUnits, ",") + Spring.SendLuaRulesMsg(msg) + end return false end end +function widget:RecvLuaMsg(msg, playerID) + if msg:find("^unit_transfer:success:") then + local senderTeamID = tonumber(msg:match("^unit_transfer:success:(%d+)")) + if senderTeamID == GetMyTeamID() then + PlaySoundFile("beep4", 1, 'ui') + end + end +end + function widget:CommandsChanged() if GetSpectatingState() then return diff --git a/luaui/Widgets/cmd_take.lua b/luaui/Widgets/cmd_take.lua new file mode 100644 index 00000000000..6cad8e71d95 --- /dev/null +++ b/luaui/Widgets/cmd_take.lua @@ -0,0 +1,18 @@ +function widget:GetInfo() + return { + name = "Take Command", + desc = "Catches /take and forwards to synced gadget", + author = "Antigravity", + date = "2024", + license = "GPL-v2", + layer = 0, + enabled = true, + } +end + +function widget:TextCommand(command) + if command:lower() == "take" then + Spring.SendLuaRulesMsg("take_cmd") + return true + end +end diff --git a/luaui/Widgets/gui_advplayerslist.lua b/luaui/Widgets/gui_advplayerslist.lua index 9f3c3d589c3..4044212acca 100644 --- a/luaui/Widgets/gui_advplayerslist.lua +++ b/luaui/Widgets/gui_advplayerslist.lua @@ -74,8 +74,15 @@ local spGetSpectatingState = Spring.GetSpectatingState v44 (Floris): added rendertotexture draw method v45 (Floris): support PvE team ranking leaderboard style v46 (Floris): support alternative (historic) playernames based on accountID's + v47 (Attean): introduce a policy cache and TeamTransfer module, removing a lot of economic and unit specific code from this file and enabling unit_sharing_mode modoptions ]]-- +local TransferEnums = VFS.Include('common/luaUtilities/team_transfer/transfer_enums.lua') +local TeamTransfer = VFS.Include('common/luaUtilities/team_transfer/team_transfer_unsynced.lua') +local ResourceTransfer = TeamTransfer.Resources +local ApiExtensions = VFS.Include('common/luaUtilities/team_transfer/gui_advplayerlist/api_extensions.lua') +local Helpers = VFS.Include('common/luaUtilities/team_transfer/gui_advplayerlist/helpers.lua') + -------------------------------------------------------------------------------- -- Config -------------------------------------------------------------------------------- @@ -146,54 +153,53 @@ local specOffset = 256 -------------------------------------------------------------------------------- -- IMAGES -------------------------------------------------------------------------------- -local images = {} -images['imgDir'] = LUAUI_DIRNAME .. "Images/advplayerslist/" -images['imageDirectory'] = ":lc:" .. images['imgDir'] -images['flagsExt'] = '.png' -images['flagHeight'] = 10 +local imgDir = LUAUI_DIRNAME .. "Images/advplayerslist/" +local imageDirectory = ":lc:" .. imgDir +local flagsExt = '.png' +local flagHeight = 10 local pics = { - currentPic = images['imageDirectory'] .. "indicator.dds", - unitsPic = images['imageDirectory'] .. "units.dds", - energyPic = images['imageDirectory'] .. "energy.dds", - metalPic = images['imageDirectory'] .. "metal.dds", - notFirstPic = images['imageDirectory'] .. "notfirst.dds", - pingPic = images['imageDirectory'] .. "ping.dds", - cpuPic = images['imageDirectory'] .. "cpu.dds", - barPic = images['imageDirectory'] .. "bar.png", - pointPic = images['imageDirectory'] .. "point.dds", - pencilPic = images['imageDirectory'] .. "pencil.dds", - eraserPic = images['imageDirectory'] .. "eraser.dds", - lowPic = images['imageDirectory'] .. "low.dds", - arrowPic = images['imageDirectory'] .. "arrow.dds", - takePic = images['imageDirectory'] .. "take.dds", - indentPic = images['imageDirectory'] .. "indent.png", - cameraPic = images['imageDirectory'] .. "camera.dds", - countryPic = images['imageDirectory'] .. "country.dds", - readyTexture = images['imageDirectory'] .. "indicator.dds", - allyPic = images['imageDirectory'] .. "ally.dds", - unallyPic = images['imageDirectory'] .. "unally.dds", - resourcesPic = images['imageDirectory'] .. "res.png", - resbarPic = images['imageDirectory'] .. "resbar.png", - resbarBgPic = images['imageDirectory'] .. "resbarBg.png", - incomePic = images['imageDirectory'] .. "res.png", - barGlowCenterPic = images['imageDirectory'] .. "barglow-center.png", - barGlowEdgePic = images['imageDirectory'] .. "barglow-edge.png", - - chatPic = images['imageDirectory'] .. "chat.dds", - sidePic = images['imageDirectory'] .. "side.dds", - sharePic = images['imageDirectory'] .. "share.dds", - idPic = images['imageDirectory'] .. "id.dds", - tsPic = images['imageDirectory'] .. "ts.dds", - - rank0 = images['imageDirectory'] .. "ranks/1.png", - rank1 = images['imageDirectory'] .. "ranks/2.png", - rank2 = images['imageDirectory'] .. "ranks/3.png", - rank3 = images['imageDirectory'] .. "ranks/4.png", - rank4 = images['imageDirectory'] .. "ranks/5.png", - rank5 = images['imageDirectory'] .. "ranks/6.png", - rank6 = images['imageDirectory'] .. "ranks/7.png", - rank7 = images['imageDirectory'] .. "ranks/8.png", + currentPic = imageDirectory .. "indicator.dds", + unitsPic = imageDirectory .. "units.dds", + energyPic = imageDirectory .. "energy.dds", + metalPic = imageDirectory .. "metal.dds", + notFirstPic = imageDirectory .. "notfirst.dds", + pingPic = imageDirectory .. "ping.dds", + cpuPic = imageDirectory .. "cpu.dds", + barPic = imageDirectory .. "bar.png", + pointPic = imageDirectory .. "point.dds", + pencilPic = imageDirectory .. "pencil.dds", + eraserPic = imageDirectory .. "eraser.dds", + lowPic = imageDirectory .. "low.dds", + arrowPic = imageDirectory .. "arrow.dds", + takePic = imageDirectory .. "take.dds", + indentPic = imageDirectory .. "indent.png", + cameraPic = imageDirectory .. "camera.dds", + countryPic = imageDirectory .. "country.dds", + readyTexture = imageDirectory .. "indicator.dds", + allyPic = imageDirectory .. "ally.dds", + unallyPic = imageDirectory .. "unally.dds", + resourcesPic = imageDirectory .. "res.png", + resbarPic = imageDirectory .. "resbar.png", + resbarBgPic = imageDirectory .. "resbarBg.png", + incomePic = imageDirectory .. "res.png", + barGlowCenterPic = imageDirectory .. "barglow-center.png", + barGlowEdgePic = imageDirectory .. "barglow-edge.png", + + chatPic = imageDirectory .. "chat.dds", + sidePic = imageDirectory .. "side.dds", + sharePic = imageDirectory .. "share.dds", + idPic = imageDirectory .. "id.dds", + tsPic = imageDirectory .. "ts.dds", + + rank0 = imageDirectory .. "ranks/1.png", + rank1 = imageDirectory .. "ranks/2.png", + rank2 = imageDirectory .. "ranks/3.png", + rank3 = imageDirectory .. "ranks/4.png", + rank4 = imageDirectory .. "ranks/5.png", + rank5 = imageDirectory .. "ranks/6.png", + rank6 = imageDirectory .. "ranks/7.png", + rank7 = imageDirectory .. "ranks/8.png", } local rankPics = { @@ -211,11 +217,6 @@ local apiAbsPosition = { 0, 0, 0, 0, 1, 1, false } local anonymousMode = Spring.GetModOptions().teamcolors_anonymous_mode local anonymousTeamColor = {Spring.GetConfigInt("anonymousColorR", 255)/255, Spring.GetConfigInt("anonymousColorG", 0)/255, Spring.GetConfigInt("anonymousColorB", 0)/255} -local sharingTax = Spring.GetModOptions().tax_resource_sharing_amount or 0 -if Spring.GetModOptions().easytax then - sharingTax = 0.3 -- 30% tax for easytax modoption -end - -------------------------------------------------------------------------------- -- Colors -------------------------------------------------------------------------------- @@ -257,8 +258,10 @@ local guishaderWasActive = false --local specJoinedOnce, scheduledSpecFullView --local prevClickedPlayer, clickedPlayerTime, clickedPlayerID local lockPlayerID --leftPosX, lastSliderSound, release +local hoveredPlayerID = nil local MainList, MainList2, MainList3, drawListOffset + local deadPlayerHeightReduction = 8 local reportTake = false @@ -330,7 +333,6 @@ end local energyPlayer -- player to share energy with (nil when no energy sharing) local metalPlayer -- player to share metal with(nil when no metal sharing) local shareAmount = 0 -- amount of metal/energy to share/ask -local maxShareAmount -- max amount of metal/energy to share/ask local sliderPosition -- slider position in metal and energy sharing local shareSliderHeight = 80 local sliderOrigin -- position of the cursor before dragging the widget @@ -382,238 +384,9 @@ local forceMainListRefresh = true -- Modules -------------------------------------------------- -local modules = {} -local m_indent, m_rank, m_side, m_allyID, m_playerID, m_ID, m_name, m_share, m_chat, m_cpuping, m_country, m_alliance, m_skill, m_resources, m_income - --- these are not considered as normal module since they dont take any place and wont affect other's position --- (they have no module.width and are not part of modules) -local m_point, m_take - -local position = 1 -m_indent = { - name = "indent", - spec = true, --display for specs? - play = true, --display for players? - active = true, --display? (overrides above) - default = true, --display by default? - width = 9, - position = position, - posX = 0, - pic = pics["indentPic"], - noPic = true, -} -position = position + 1 - -m_allyID = { - name = "allyid", - spec = true, - play = true, - active = false, - width = 17, - position = position, - posX = 0, - pic = pics["idPic"], -} -position = position + 1 - -m_ID = { - name = "id", - spec = true, - play = true, - active = false, - width = 17, - position = position, - posX = 0, - pic = pics["idPic"], -} -position = position + 1 - -m_playerID = { - name = "playerid", - spec = true, - play = true, - active = false, - width = 17, - position = position, - posX = 0, - pic = pics["idPic"], -} -position = position + 1 - -m_rank = { - name = "rank", - spec = true, --display for specs? - play = true, --display for players? - active = true, --display? (overrides above) - default = false, --display by default? - width = 18, - position = position, - posX = 0, - pic = pics["rank6"], -} -position = position + 1 - -m_country = { - name = "country", - spec = true, - play = true, - active = true, - default = true, - width = 20, - position = position, - posX = 0, - pic = pics["countryPic"], -} -position = position + 1 - -m_side = { - name = "side", - spec = true, - play = true, - active = false, - width = 18, - position = position, - posX = 0, - pic = pics["sidePic"], -} -position = position + 1 - -m_skill = { - name = "skill", - spec = true, - play = true, - active = false, - width = 18, - position = position, - posX = 0, - pic = pics["tsPic"], -} -position = position + 1 - -m_name = { - name = "name", - spec = true, - play = true, - active = true, - alwaysActive = true, - width = 10, - position = position, - posX = 0, - noPic = true, - picGap = 7, -} -position = position + 1 - -m_cpuping = { - name = "cpuping", - spec = true, - play = true, - active = true, - width = 24, - position = position, - posX = 0, - pic = pics["cpuPic"], -} -position = position + 1 - -m_resources = { - name = "resources", - spec = true, - play = true, - active = true, - width = 28, - position = position, - posX = 0, - pic = pics["resourcesPic"], - picGap = 7, -} -position = position + 1 - -m_income = { - name = "income", - spec = true, - play = true, - active = false, - width = 28, - position = position, - posX = 0, - pic = pics["incomePic"], - picGap = 7, -} -position = position + 1 - -m_share = { - name = "share", - spec = false, - play = true, - active = true, - width = 50, - position = position, - posX = 0, - pic = pics["sharePic"], -} -position = position + 1 - -m_chat = { - name = "chat", - spec = false, - play = true, - active = false, - width = 18, - position = position, - posX = 0, - pic = pics["chatPic"], -} -position = position + 1 - local drawAllyButton = not Spring.GetModOptions().fixedallies - -m_alliance = { - name = "ally", - spec = false, - play = true, - active = true, - width = 16, - position = position, - posX = 0, - pic = pics["allyPic"], - noPic = false, -} - -if not drawAllyButton then - m_alliance.width = 0 -end - -position = position + 1 - -modules = { - m_indent, - m_rank, - m_country, - m_allyID, - m_ID, - m_playerID, - --m_side, - m_name, - m_skill, - m_resources, - m_income, - m_cpuping, - m_alliance, - m_share, - m_chat, -} - -m_point = { - active = true, - default = true, -} - -m_take = { - active = true, - default = true, - pic = pics["takePic"], -} +local ModulesFactory = VFS.Include('luaui/Widgets/gui_advplayerslist_modules.lua') +local modules, ModuleRefs, m_point, m_take = ModulesFactory(pics, drawAllyButton) local specsLabelOffset = 0 local enemyLabelOffset = 0 @@ -633,7 +406,7 @@ local isPvE = Spring.Utilities.Gametype.IsPvE() --------------------------------------------------------------------------------------------------- function SetModulesPositionX() - m_name.width = SetMaxPlayerNameWidth() + ModuleRefs.name.width = SetMaxPlayerNameWidth() tableSort(modules, function(v1, v2) return v1.position < v2.position end) @@ -819,6 +592,18 @@ function ActivityEvent(playerID) lastActivity[playerID] = osClock() end +function widget:SelectionChanged(selectedUnits) + Helpers.UpdatePlayerUnitValidations(player, myTeamID, selectedUnits) +end + +function widget:SharePolicyChanged(teamID, domain) + Helpers.RepackPolicy(player, myTeamID, teamID, domain) + if domain == "unit" and teamID == myTeamID then + Helpers.UpdatePlayerUnitValidations(player, myTeamID, Spring.GetSelectedUnits()) + end +end + + --------------------------------------------------------------------------------------------------- -- Init/GameStart (creating players) --------------------------------------------------------------------------------------------------- @@ -1050,6 +835,11 @@ function widget:Initialize() end end + WG['advplayerlist_api'].AddHoverChangeListener = ApiExtensions.AddHoverChangeListener + WG['advplayerlist_api'].RemoveHoverChangeListener = ApiExtensions.RemoveHoverChangeListener + WG['advplayerlist_api'].AddHoverInvalidUnitsListener = ApiExtensions.AddHoverInvalidUnitsListener + WG['advplayerlist_api'].RemoveHoverInvalidUnitsListener = ApiExtensions.RemoveHoverInvalidUnitsListener + widgetHandler:AddAction("speclist", speclistCmd, nil, 't') end @@ -1156,9 +946,9 @@ function SetSidePics() end if teamSide then - sidePics[team] = images['imageDirectory'] .. teamSide .. "_default.png" + sidePics[team] = imageDirectory .. teamSide .. "_default.png" else - sidePics[team] = images['imageDirectory'] .. "default.png" + sidePics[team] = imageDirectory .. "default.png" end end end @@ -1475,7 +1265,7 @@ function CreatePlayerFromTeam(teamID) metal = metal, metalStorage = metalStorage, metalShare = metalShare, - incomeMultiplier = tincomeMultiplier, + incomeMultiplier = tincomeMultiplier } end @@ -1530,6 +1320,9 @@ function UpdatePlayerResources() end end end + if player[playerID].team then + Helpers.PackAllPoliciesForPlayer(player[playerID], myTeamID, player[playerID].team) + end end updateRateMult = math.clamp(displayedPlayers*0.05, 1, 2) @@ -1947,7 +1740,7 @@ function doCreateLists(onlyMainList, onlyMainList2, onlyMainList3) if not onlyMainList and not onlyMainList2 and not onlyMainList3 then onlyMainList = true onlyMainList2 = true - --if m_resources.active or m_income.active then + --if ModuleRefs.resources.active or ModuleRefs.income.active then onlyMainList3 = true --end end @@ -1972,7 +1765,7 @@ function doCreateLists(onlyMainList, onlyMainList2, onlyMainList3) GetAliveAllyTeams() end if onlyMainList2 or onlyMainList3 then - if m_resources.active or m_income.active then + if ModuleRefs.resources.active or ModuleRefs.income.active then UpdateResources() UpdatePlayerResources() end @@ -2069,37 +1862,25 @@ end -- Main (player) gllist --------------------------------------------------------------------------------------------------- +local function updateShareAmount(player, resourceType) + local policyResult = Helpers.GetPlayerResourcePolicy(player, resourceType, myTeamID) + if not policyResult.amountSendable then + shareAmount = 0 + return + end + shareAmount = policyResult.amountSendable * sliderPosition / shareSliderHeight + shareAmount = shareAmount - (shareAmount % 1) +end + function UpdateResources() if sliderPosition then if energyPlayer ~= nil then - if energyPlayer.team == myTeamID then - local current, storage = sp.GetTeamResources(myTeamID, "energy") - maxShareAmount = storage - current - shareAmount = maxShareAmount * sliderPosition / shareSliderHeight - shareAmount = shareAmount - (shareAmount % 1) - else - maxShareAmount = sp.GetTeamResources(myTeamID, "energy") - local energy, energyStorage, _, _, _, shareSliderPos = sp.GetTeamResources(energyPlayer.team, "energy") - maxShareAmount = mathMin(maxShareAmount, ((energyStorage*shareSliderPos) - energy)) - shareAmount = maxShareAmount * sliderPosition / shareSliderHeight - shareAmount = shareAmount - (shareAmount % 1) - end + updateShareAmount(energyPlayer, "energy") end - if metalPlayer ~= nil then - if metalPlayer.team == myTeamID then - local current, storage = sp.GetTeamResources(myTeamID, "metal") - maxShareAmount = storage - current - shareAmount = maxShareAmount * sliderPosition / shareSliderHeight - shareAmount = shareAmount - (shareAmount % 1) - else - maxShareAmount = sp.GetTeamResources(myTeamID, "metal") - local metal, metalStorage, _, _, _, shareSliderPos = sp.GetTeamResources(metalPlayer.team, "metal") - maxShareAmount = mathMin(maxShareAmount, ((metalStorage*shareSliderPos) - metal)) - shareAmount = maxShareAmount * sliderPosition / shareSliderHeight - shareAmount = shareAmount - (shareAmount % 1) - end - end + if metalPlayer ~= nil then + updateShareAmount(metalPlayer, "metal") + end end end @@ -2404,7 +2185,11 @@ function DrawPlayer(playerID, leader, vOffset, mouseX, mouseY, onlyMainList, onl local neede = p.neede local dead = p.dead local ai = p.ai - local alliances = p.alliances + local metalPolicy, energyPolicy, unitPolicy, unitValidationResult + if not spec then + metalPolicy, energyPolicy, unitPolicy = Helpers.UnpackAllPolicies(p, myTeamID, team) + unitValidationResult = Helpers.UnpackSelectedUnitsValidation(p) + end local posY = widgetPosY + widgetHeight - vOffset -- Center elements vertically in the shorter absent (non-AI ghost) row if playerID >= specOffset and not ai then @@ -2431,8 +2216,15 @@ function DrawPlayer(playerID, leader, vOffset, mouseX, mouseY, onlyMainList, onl alpha = alphaActivity end end - if mouseY >= tipPosY and mouseY <= tipPosY + (16 * widgetScale * playerScale) then + -- full row bounds so hover-leave is detectable + local rowTop = posY + local rowBottom = posY + (playerOffset*playerScale) + if IsOnRect(mouseX, mouseY, widgetPosX, rowTop, widgetPosX + widgetWidth, rowBottom) then tipY = true + if hoveredPlayerID ~= playerID and player[playerID] and player[playerID].team then + local selectedUnits = Spring.GetSelectedUnits() + ApiExtensions.HandleHoverChange(myTeamID, selectedUnits, player[playerID].team, playerID) + end end if onlyMainList and lockPlayerID and lockPlayerID == playerID then @@ -2440,10 +2232,10 @@ function DrawPlayer(playerID, leader, vOffset, mouseX, mouseY, onlyMainList, onl end if onlyMainList then - if m_allyID.active and not spec then + if ModuleRefs.allyID.active and not spec then DrawAllyID(allyteam, posY, dark, dead) end - if m_playerID.active and not ai and playerID < 255 then + if ModuleRefs.playerID.active and not ai and playerID < 255 then DrawPlayerID(playerID, posY, dark, spec) end end @@ -2468,10 +2260,10 @@ function DrawPlayer(playerID, leader, vOffset, mouseX, mouseY, onlyMainList, onl end end end - if m_share.active and not dead and not hideShareIcons then - DrawShareButtons(posY, needm, neede) + if ModuleRefs.share.active and not dead and not hideShareIcons and (unitPolicy and metalPolicy and energyPolicy) then + DrawShareButtons(posY, unitPolicy, metalPolicy, energyPolicy, unitValidationResult) if tipY then - ShareTip(mouseX, playerID) + ShareTip(mouseX, unitPolicy, metalPolicy, energyPolicy, unitValidationResult) end end end @@ -2482,15 +2274,15 @@ function DrawPlayer(playerID, leader, vOffset, mouseX, mouseY, onlyMainList, onl end end else - if onlyMainList and m_indent.active and sp.GetMyTeamID() == team then + if onlyMainList and ModuleRefs.indent.active and sp.GetMyTeamID() == team then DrawDot(posY) end end if onlyMainList then - if m_ID.active and not dead then + if ModuleRefs.ID.active and not dead then DrawID(team, posY, dark, dead) end - if m_skill.active then + if ModuleRefs.skill.active then DrawSkill(skill, posY, dark) end @@ -2498,25 +2290,25 @@ function DrawPlayer(playerID, leader, vOffset, mouseX, mouseY, onlyMainList, onl end if onlyMainList then - if m_rank.active then + if ModuleRefs.rank.active then DrawRank(tonumber(rank), posY) end - if m_country.active and country ~= "" then + if ModuleRefs.country.active and country ~= "" then DrawCountry(country, posY) end - if name ~= absentName and m_side.active then + if name ~= absentName and ModuleRefs.side.active then DrawSidePic(team, playerID, posY, leader, dark, ai) end - if m_name.active then + if ModuleRefs.name.active then DrawName(name, nameIsAlias, team, posY, dark, playerID, accountID, desynced) end end - if onlyMainList2 and m_alliance.active and drawAllyButton and not mySpecStatus and not dead and team ~= myTeamID then + if onlyMainList2 and ModuleRefs.alliance.active and drawAllyButton and not mySpecStatus and not dead and team ~= myTeamID then DrawAlly(posY, p.team) end - if (onlyMainList2 or onlyMainList3) and not isSingle and (m_resources.active or m_income.active) and aliveAllyTeams[allyteam] ~= nil and p.energy ~= nil then + if (onlyMainList2 or onlyMainList3) and not isSingle and (ModuleRefs.resources.active or ModuleRefs.income.active) and aliveAllyTeams[allyteam] ~= nil and p.energy ~= nil then if (mySpecStatus and enemyListShow) or myAllyTeamID == allyteam then local e = p.energy local es = p.energyStorage @@ -2528,13 +2320,13 @@ function DrawPlayer(playerID, leader, vOffset, mouseX, mouseY, onlyMainList, onl local mi = p.metalIncome local msh = p.metalShare if es and es > 0 then - if onlyMainList3 and m_resources.active and e and (not dead or (e > 0 or m > 0)) then + if onlyMainList3 and ModuleRefs.resources.active and e and (not dead or (e > 0 or m > 0)) then DrawResources(e, es, esh, ec, m, ms, msh, posY, dead, (absoluteResbarValues and (allyTeamMaxStorage[allyteam] and allyTeamMaxStorage[allyteam][1])), (absoluteResbarValues and (allyTeamMaxStorage[allyteam] and allyTeamMaxStorage[allyteam][2]))) if tipY then ResourcesTip(mouseX, e, es, ei, m, ms, mi, name, team) end end - if onlyMainList2 and m_income.active and ei and playerScale >= 0.7 then + if onlyMainList2 and ModuleRefs.income.active and ei and playerScale >= 0.7 then DrawIncome(ei, mi, posY, dead) if tipY then IncomeTip(mouseX, ei, mi, name, team) @@ -2545,12 +2337,12 @@ function DrawPlayer(playerID, leader, vOffset, mouseX, mouseY, onlyMainList, onl end else -- spectator - if onlyMainList and specListShow and m_name.active then + if onlyMainList and specListShow and ModuleRefs.name.active then DrawSmallName(name, nameIsAlias, team, posY, false, playerID, accountID, alpha) end end - if onlyMainList2 and m_cpuping.active and not isSinglePlayer then + if onlyMainList2 and ModuleRefs.cpuping.active and not isSinglePlayer then if cpuLvl ~= nil then -- draws CPU usage and ping icons (except AI and ghost teams) DrawPingCpu(pingLvl, cpuLvl, posY, spec, cpu, lastFpsData[playerID]) @@ -2562,11 +2354,32 @@ function DrawPlayer(playerID, leader, vOffset, mouseX, mouseY, onlyMainList, onl if playerID < specOffset then if onlyMainList then - if m_chat.active and mySpecStatus == false and spec == false then + if ModuleRefs.chat.active and mySpecStatus == false and spec == false then if playerID ~= myPlayerID then DrawChatButton(posY) end end + else + if m_point.active then + if player[playerID].pointTime ~= nil then + if player[playerID].allyteam == myAllyTeamID or mySpecStatus then + DrawPoint(posY, player[playerID].pointTime - now) + if tipY then + PointTip(mouseX) + end + end + end + if player[playerID].pencilTime ~= nil then + if player[playerID].allyteam == myAllyTeamID or mySpecStatus then + DrawPencil(posY, player[playerID].pencilTime - now) + end + end + if player[playerID].eraserTime ~= nil then + if player[playerID].allyteam == myAllyTeamID or mySpecStatus then + DrawEraser(posY, player[playerID].eraserTime - now) + end + end + end end end @@ -2585,30 +2398,42 @@ function DrawTakeSignal(posY) end end -function DrawShareButtons(posY, needm, neede) +local function DrawSharingIconOverlay(posY, enabled, offsetX) + if enabled then + gl_Texture(false) + gl_Color(1, 1, 1, 0.12) + DrawRect(ModuleRefs.share.posX + widgetPosX + offsetX, posY, ModuleRefs.share.posX + widgetPosX + offsetX + (16*playerScale), posY + (16*playerScale)) + gl_Color(1, 1, 1, 1) + end + + if not enabled then + gl_Texture(false) + gl_Color(0, 0, 0, 0.45) + DrawRect(ModuleRefs.share.posX + widgetPosX + offsetX, posY, ModuleRefs.share.posX + widgetPosX + offsetX + (16*playerScale), posY + (16*playerScale)) + gl_Color(1, 1, 1, 1) + end +end + +function DrawShareButtons(posY, unitPolicy, metalPolicy, energyPolicy, unitValidationResult) gl_Color(1, 1, 1, 1) gl_Texture(pics["unitsPic"]) - DrawRect(m_share.posX + widgetPosX + (1*playerScale), posY, m_share.posX + widgetPosX + (17*playerScale), posY + (16*playerScale)) + DrawRect(ModuleRefs.share.posX + widgetPosX + (1*playerScale), posY, ModuleRefs.share.posX + widgetPosX + (17*playerScale), posY + (16*playerScale)) gl_Texture(pics["energyPic"]) - DrawRect(m_share.posX + widgetPosX + (17*playerScale), posY, m_share.posX + widgetPosX + (33*playerScale), posY + (16*playerScale)) + DrawRect(ModuleRefs.share.posX + widgetPosX + (17*playerScale), posY, ModuleRefs.share.posX + widgetPosX + (33*playerScale), posY + (16*playerScale)) gl_Texture(pics["metalPic"]) - DrawRect(m_share.posX + widgetPosX + (33*playerScale), posY, m_share.posX + widgetPosX + (49*playerScale), posY + (16*playerScale)) - gl_Texture(pics["lowPic"]) + DrawRect(ModuleRefs.share.posX + widgetPosX + (33*playerScale), posY, ModuleRefs.share.posX + widgetPosX + (49*playerScale), posY + (16*playerScale)) - if needm then - DrawRect(m_share.posX + widgetPosX + (33*playerScale), posY, m_share.posX + widgetPosX + (49*playerScale), posY + (16*playerScale)) - end - - if neede then - DrawRect(m_share.posX + widgetPosX + (17*playerScale), posY, m_share.posX + widgetPosX + (33*playerScale), posY + (16*playerScale)) - end + local shareButtonEnabled = unitPolicy.canShare and (not unitValidationResult or unitValidationResult.status ~= TransferEnums.UnitValidationOutcome.Failure) + DrawSharingIconOverlay(posY, shareButtonEnabled, 1 * playerScale) + DrawSharingIconOverlay(posY, energyPolicy.amountSendable > 0, 17 * playerScale) + DrawSharingIconOverlay(posY, metalPolicy.amountSendable > 0, 33 * playerScale) gl_Texture(false) end function DrawChatButton(posY) gl_Texture(pics["chatPic"]) - DrawRect(m_chat.posX + widgetPosX + (1*playerScale), posY, m_chat.posX + widgetPosX + (17*playerScale), posY + (16*playerScale)) + DrawRect(ModuleRefs.chat.posX + widgetPosX + (1*playerScale), posY, ModuleRefs.chat.posX + widgetPosX + (17*playerScale), posY + (16*playerScale)) end function DrawResources(energy, energyStorage, energyShare, energyConversion, metal, metalStorage, metalShare, posY, dead, maxAllyTeamEnergyStorage, maxAllyTeamMetalStorage) @@ -2618,7 +2443,7 @@ function DrawResources(energy, energyStorage, energyShare, energyConversion, met local bordersize = 0.75 local paddingLeft = 2 * playerScale local paddingRight = 2 * playerScale - local barWidth = (m_resources.width - paddingLeft - paddingRight) * (1-((1-playerScale)*0.5)) + local barWidth = (ModuleRefs.resources.width - paddingLeft - paddingRight) * (1-((1-playerScale)*0.5)) local y1Offset local y2Offset local sizeMult = playerScale @@ -2633,37 +2458,37 @@ function DrawResources(energy, energyStorage, energyShare, energyConversion, met if not maxStorage or maxStorage == 0 then return end -- protect from NaN --gl_Color(0,0,0, 0.05) --gl_Texture(false) - --DrawRect(m_resources.posX + widgetPosX + paddingLeft-bordersize, posY + y1Offset+bordersize, m_resources.posX + widgetPosX + paddingLeft + (barWidth * (metalStorage/maxStorage))+bordersize, posY + y2Offset-bordersize) + --DrawRect(ModuleRefs.resources.posX + widgetPosX + paddingLeft-bordersize, posY + y1Offset+bordersize, ModuleRefs.resources.posX + widgetPosX + paddingLeft + (barWidth * (metalStorage/maxStorage))+bordersize, posY + y2Offset-bordersize) gl_Color(1, 1, 1, 0.18) gl_Texture(pics["resbarBgPic"]) - DrawRect(m_resources.posX + widgetPosX + paddingLeft, posY + y1Offset, m_resources.posX + widgetPosX + paddingLeft + (barWidth * (metalStorage/maxStorage)), posY + y2Offset) + DrawRect(ModuleRefs.resources.posX + widgetPosX + paddingLeft, posY + y1Offset, ModuleRefs.resources.posX + widgetPosX + paddingLeft + (barWidth * (metalStorage/maxStorage)), posY + y2Offset) gl_Color(1, 1, 1, 1) gl_Texture(pics["resbarPic"]) - DrawRect(m_resources.posX + widgetPosX + paddingLeft, posY + y1Offset, m_resources.posX + widgetPosX + paddingLeft + ((barWidth / maxStorage) * metal), posY + y2Offset) + DrawRect(ModuleRefs.resources.posX + widgetPosX + paddingLeft, posY + y1Offset, ModuleRefs.resources.posX + widgetPosX + paddingLeft + ((barWidth / maxStorage) * metal), posY + y2Offset) if playerScale >= 0.9 and (barWidth / maxStorage) * metal > 0.8 then local glowsize = 10 gl_Color(1, 1, 1.2, 0.08) gl_Texture(pics["barGlowCenterPic"]) - DrawRect(m_resources.posX + widgetPosX + paddingLeft, posY + y1Offset + glowsize, m_resources.posX + widgetPosX + paddingLeft + ((barWidth / maxStorage) * metal), posY + y2Offset - glowsize) + DrawRect(ModuleRefs.resources.posX + widgetPosX + paddingLeft, posY + y1Offset + glowsize, ModuleRefs.resources.posX + widgetPosX + paddingLeft + ((barWidth / maxStorage) * metal), posY + y2Offset - glowsize) gl_Texture(pics["barGlowEdgePic"]) - DrawRect(m_resources.posX + widgetPosX + paddingLeft - (glowsize * 1.8), posY + y1Offset + glowsize, m_resources.posX + widgetPosX + paddingLeft, posY + y2Offset - glowsize) - DrawRect(m_resources.posX + widgetPosX + paddingLeft + ((barWidth / maxStorage) * metal) + (glowsize * 1.8), posY + y1Offset + glowsize, m_resources.posX + widgetPosX + paddingLeft + ((barWidth / maxStorage) * metal), posY + y2Offset - glowsize) + DrawRect(ModuleRefs.resources.posX + widgetPosX + paddingLeft - (glowsize * 1.8), posY + y1Offset + glowsize, ModuleRefs.resources.posX + widgetPosX + paddingLeft, posY + y2Offset - glowsize) + DrawRect(ModuleRefs.resources.posX + widgetPosX + paddingLeft + ((barWidth / maxStorage) * metal) + (glowsize * 1.8), posY + y1Offset + glowsize, ModuleRefs.resources.posX + widgetPosX + paddingLeft + ((barWidth / maxStorage) * metal), posY + y2Offset - glowsize) end if metalShare < 0.99 then -- default = 0.99 gl_Color(0,0,0, 0.18) gl_Texture(false) - DrawRect(m_resources.posX + widgetPosX + paddingLeft + ((barWidth * (metalStorage/maxStorage)) * metalShare) - 0.75 - bordersize, + DrawRect(ModuleRefs.resources.posX + widgetPosX + paddingLeft + ((barWidth * (metalStorage/maxStorage)) * metalShare) - 0.75 - bordersize, posY + y1Offset + 0.55 + bordersize, - m_resources.posX + widgetPosX + paddingLeft + ((barWidth * (metalStorage/maxStorage)) * metalShare) + 0.75 + bordersize, + ModuleRefs.resources.posX + widgetPosX + paddingLeft + ((barWidth * (metalStorage/maxStorage)) * metalShare) + 0.75 + bordersize, posY + y2Offset - 0.55 - bordersize) gl_Color(1, 0.25, 0.25, 1) gl_Texture(pics["resbarPic"]) - DrawRect(m_resources.posX + widgetPosX + paddingLeft + ((barWidth * (metalStorage/maxStorage)) * metalShare) - 0.75, + DrawRect(ModuleRefs.resources.posX + widgetPosX + paddingLeft + ((barWidth * (metalStorage/maxStorage)) * metalShare) - 0.75, posY + y1Offset + 0.55, - m_resources.posX + widgetPosX + paddingLeft + ((barWidth * (metalStorage/maxStorage)) * metalShare) + 0.75, + ModuleRefs.resources.posX + widgetPosX + paddingLeft + ((barWidth * (metalStorage/maxStorage)) * metalShare) + 0.75, posY + y2Offset - 0.55) end @@ -2677,52 +2502,52 @@ function DrawResources(energy, energyStorage, energyShare, energyConversion, met maxStorage = (maxAllyTeamEnergyStorage and maxAllyTeamEnergyStorage or energyStorage) --gl_Color(0,0,0, 0.05) --gl_Texture(false) - --DrawRect(m_resources.posX + widgetPosX + paddingLeft -bordersize, posY + y1Offset+bordersize, m_resources.posX + widgetPosX + paddingLeft + (barWidth * (energyStorage/maxStorage))+bordersize, posY + y2Offset-bordersize) + --DrawRect(ModuleRefs.resources.posX + widgetPosX + paddingLeft -bordersize, posY + y1Offset+bordersize, ModuleRefs.resources.posX + widgetPosX + paddingLeft + (barWidth * (energyStorage/maxStorage))+bordersize, posY + y2Offset-bordersize) gl_Color(1, 1, 0, 0.18) gl_Texture(pics["resbarBgPic"]) - DrawRect(m_resources.posX + widgetPosX + paddingLeft, posY + y1Offset, m_resources.posX + widgetPosX + paddingLeft + (barWidth * (energyStorage/maxStorage)), posY + y2Offset) + DrawRect(ModuleRefs.resources.posX + widgetPosX + paddingLeft, posY + y1Offset, ModuleRefs.resources.posX + widgetPosX + paddingLeft + (barWidth * (energyStorage/maxStorage)), posY + y2Offset) gl_Color(1, 1, 0, 1) gl_Texture(pics["resbarPic"]) - DrawRect(m_resources.posX + widgetPosX + paddingLeft, posY + y1Offset, m_resources.posX + widgetPosX + paddingLeft + ((barWidth / maxStorage) * energy), posY + y2Offset) + DrawRect(ModuleRefs.resources.posX + widgetPosX + paddingLeft, posY + y1Offset, ModuleRefs.resources.posX + widgetPosX + paddingLeft + ((barWidth / maxStorage) * energy), posY + y2Offset) if playerScale >= 0.9 and (barWidth / maxStorage) * energy > 0.8 then local glowsize = 10 gl_Color(1, 1, 0.2, 0.08) gl_Texture(pics["barGlowCenterPic"]) - DrawRect(m_resources.posX + widgetPosX + paddingLeft, posY + y1Offset + glowsize, m_resources.posX + widgetPosX + paddingLeft + ((barWidth / maxStorage) * energy), posY + y2Offset - glowsize) + DrawRect(ModuleRefs.resources.posX + widgetPosX + paddingLeft, posY + y1Offset + glowsize, ModuleRefs.resources.posX + widgetPosX + paddingLeft + ((barWidth / maxStorage) * energy), posY + y2Offset - glowsize) gl_Texture(pics["barGlowEdgePic"]) - DrawRect(m_resources.posX + widgetPosX + paddingLeft - (glowsize * 1.8), posY + y1Offset + glowsize, m_resources.posX + widgetPosX + paddingLeft, posY + y2Offset - glowsize) - DrawRect(m_resources.posX + widgetPosX + paddingLeft + ((barWidth / maxStorage) * energy) + (glowsize * 1.8), posY + y1Offset + glowsize, m_resources.posX + widgetPosX + paddingLeft + ((barWidth / maxStorage) * energy), posY + y2Offset - glowsize) + DrawRect(ModuleRefs.resources.posX + widgetPosX + paddingLeft - (glowsize * 1.8), posY + y1Offset + glowsize, ModuleRefs.resources.posX + widgetPosX + paddingLeft, posY + y2Offset - glowsize) + DrawRect(ModuleRefs.resources.posX + widgetPosX + paddingLeft + ((barWidth / maxStorage) * energy) + (glowsize * 1.8), posY + y1Offset + glowsize, ModuleRefs.resources.posX + widgetPosX + paddingLeft + ((barWidth / maxStorage) * energy), posY + y2Offset - glowsize) end if energyConversion and energyConversion ~= 0.75 and not dead then -- default = 0.75 gl_Color(0,0,0, 0.125) gl_Texture(false) - DrawRect(m_resources.posX + widgetPosX + paddingLeft + ((barWidth * (energyStorage/maxStorage)) * energyConversion) - 0.75 - bordersize, + DrawRect(ModuleRefs.resources.posX + widgetPosX + paddingLeft + ((barWidth * (energyStorage/maxStorage)) * energyConversion) - 0.75 - bordersize, posY + y1Offset + 0.55 + bordersize, - m_resources.posX + widgetPosX + paddingLeft + ((barWidth * (energyStorage/maxStorage)) * energyConversion) + 0.75 + bordersize, + ModuleRefs.resources.posX + widgetPosX + paddingLeft + ((barWidth * (energyStorage/maxStorage)) * energyConversion) + 0.75 + bordersize, posY + y2Offset - 0.55 - bordersize) gl_Color(0.9, 0.9, 0.73, 1) gl_Texture(pics["resbarPic"]) - DrawRect(m_resources.posX + widgetPosX + paddingLeft + ((barWidth * (energyStorage/maxStorage)) * energyConversion) - 0.75, + DrawRect(ModuleRefs.resources.posX + widgetPosX + paddingLeft + ((barWidth * (energyStorage/maxStorage)) * energyConversion) - 0.75, posY + y1Offset + 0.55, - m_resources.posX + widgetPosX + paddingLeft + ((barWidth * (energyStorage/maxStorage)) * energyConversion) + 0.75, + ModuleRefs.resources.posX + widgetPosX + paddingLeft + ((barWidth * (energyStorage/maxStorage)) * energyConversion) + 0.75, posY + y2Offset - 0.55) end if energyShare < 0.94 or energyShare > 0.96 then -- default = 0.94999999 gl_Color(0,0,0, 0.18) gl_Texture(false) - DrawRect(m_resources.posX + widgetPosX + paddingLeft + ((barWidth * (energyStorage/maxStorage)) * energyShare) - 0.75 - bordersize, + DrawRect(ModuleRefs.resources.posX + widgetPosX + paddingLeft + ((barWidth * (energyStorage/maxStorage)) * energyShare) - 0.75 - bordersize, posY + y1Offset + 1.1, - m_resources.posX + widgetPosX + paddingLeft + ((barWidth * (energyStorage/maxStorage)) * energyShare) + 0.75 + bordersize, + ModuleRefs.resources.posX + widgetPosX + paddingLeft + ((barWidth * (energyStorage/maxStorage)) * energyShare) + 0.75 + bordersize, posY + y2Offset - 1.1) gl_Color(1, 0.25, 0.25, 1) gl_Texture(pics["resbarPic"]) - DrawRect(m_resources.posX + widgetPosX + paddingLeft + ((barWidth * (energyStorage/maxStorage)) * energyShare) - 0.75, + DrawRect(ModuleRefs.resources.posX + widgetPosX + paddingLeft + ((barWidth * (energyStorage/maxStorage)) * energyShare) - 0.75, posY + y1Offset + 0.55, - m_resources.posX + widgetPosX + paddingLeft + ((barWidth * (energyStorage/maxStorage)) * energyShare) + 0.75, + ModuleRefs.resources.posX + widgetPosX + paddingLeft + ((barWidth * (energyStorage/maxStorage)) * energyShare) + 0.75, posY + y2Offset - 0.55) end end @@ -2736,7 +2561,7 @@ function DrawIncome(energy, metal, posY, dead) if energy > 0 then font:Print( '\255\255\255\050' .. string.formatSI(mathFloor(energy)), - m_income.posX + widgetPosX + m_income.width - 2, + ModuleRefs.income.posX + widgetPosX + ModuleRefs.income.width - 2, posY + ((fontsize*0.2)*sizeMult) + (dead and 1 or 0), fontsize, "or" @@ -2745,7 +2570,7 @@ function DrawIncome(energy, metal, posY, dead) if metal > 0 then font:Print( '\255\235\235\235' .. string.formatSI(mathFloor(metal)), - m_income.posX + widgetPosX + m_income.width - 2, + ModuleRefs.income.posX + widgetPosX + ModuleRefs.income.width - 2, posY + ((fontsize*1.15)*sizeMult) + (dead and 1 or 0), fontsize, "or" @@ -2762,10 +2587,10 @@ function DrawSidePic(team, playerID, posY, leader, dark, ai) else gl_Texture(pics["notFirstPic"]) -- sets image for not leader of team players end - DrawRect(m_side.posX + widgetPosX + (2*playerScale), posY + (1*playerScale), m_side.posX + widgetPosX + (16*playerScale), posY + (15*playerScale)) -- draws side image + DrawRect(ModuleRefs.side.posX + widgetPosX + (2*playerScale), posY + (1*playerScale), ModuleRefs.side.posX + widgetPosX + (16*playerScale), posY + (15*playerScale)) -- draws side image gl_Texture(false) else - DrawState(playerID, m_side.posX + widgetPosX, posY) + DrawState(playerID, ModuleRefs.side.posX + widgetPosX, posY) end end @@ -2779,7 +2604,7 @@ end function DrawRankImage(rankImage, posY) gl_Color(1, 1, 1, 1) gl_Texture(rankImage) - DrawRect(m_rank.posX + widgetPosX + (3*playerScale), posY + (8*playerScale) - (7.5*playerScale), m_rank.posX + widgetPosX + (17*playerScale), posY + (8*playerScale) + (7.5*playerScale)) + DrawRect(ModuleRefs.rank.posX + widgetPosX + (3*playerScale), posY + (8*playerScale) - (7.5*playerScale), ModuleRefs.rank.posX + widgetPosX + (17*playerScale), posY + (8*playerScale) + (7.5*playerScale)) end local function RectQuad(px, py, sx, sy) @@ -2805,7 +2630,7 @@ function DrawAlly(posY, team) else gl_Texture(pics["allyPic"]) end - DrawRect(m_alliance.posX + widgetPosX + (3*playerScale), posY + (1*playerScale), m_alliance.posX + widgetPosX + (playerOffset*playerScale), posY + (15*playerScale)) + DrawRect(ModuleRefs.alliance.posX + widgetPosX + (3*playerScale), posY + (1*playerScale), ModuleRefs.alliance.posX + widgetPosX + (playerOffset*playerScale), posY + (15*playerScale)) end function DrawCountry(country, posY) @@ -2823,14 +2648,14 @@ function DrawCountry(country, posY) if cached then gl_Texture(cached) gl_Color(1, 1, 1, 1) - DrawRect(m_country.posX + widgetPosX + (3*playerScale), posY + (8*playerScale) - ((images['flagHeight']/2)*playerScale), m_country.posX + widgetPosX + (17*playerScale), posY + (8*playerScale) + ((images['flagHeight']/2)*playerScale)) + DrawRect(ModuleRefs.country.posX + widgetPosX + (3*playerScale), posY + (8*playerScale) - ((flagHeight/2)*playerScale), ModuleRefs.country.posX + widgetPosX + (17*playerScale), posY + (8*playerScale) + ((flagHeight/2)*playerScale)) end end function DrawDot(posY) gl_Color(1, 1, 1, 0.70) gl_Texture(pics["currentPic"]) - DrawRect(m_indent.posX + widgetPosX - 1, posY + (3*playerScale), m_indent.posX + widgetPosX + (7*playerScale), posY + (11*playerScale)) + DrawRect(ModuleRefs.indent.posX + widgetPosX - 1, posY + (3*playerScale), ModuleRefs.indent.posX + widgetPosX + (7*playerScale), posY + (11*playerScale)) end function DrawCamera(posY, active) @@ -2840,7 +2665,7 @@ function DrawCamera(posY, active) gl_Color(1, 1, 1, 0.13) end gl_Texture(pics["cameraPic"]) - DrawRect(m_indent.posX + widgetPosX - (1.5*playerScale), posY + (2*playerScale), m_indent.posX + widgetPosX + (9*playerScale), posY + (12.4*playerScale)) + DrawRect(ModuleRefs.indent.posX + widgetPosX - (1.5*playerScale), posY + (2*playerScale), ModuleRefs.indent.posX + widgetPosX + (9*playerScale), posY + (12.4*playerScale)) end function colourNames(teamID, returnRgb) @@ -2879,8 +2704,8 @@ end function DrawAlliances(alliances, posY) -- still a problem is that teams with the same/similar color can be misleading - local posX = widgetPosX + m_name.posX - local width = m_name.width / #alliances + local posX = widgetPosX + ModuleRefs.name.posX + local width = ModuleRefs.name.width / #alliances local padding = 2 local drawn = false for i, allyPlayerID in pairs(alliances) do @@ -2948,9 +2773,9 @@ function DrawName(name, nameIsAlias, team, posY, dark, playerID, accountID, desy -- includes readystate icon if factions arent shown local xPadding = 0 - if not gameStarted and not m_side.active then + if not gameStarted and not ModuleRefs.side.active then xPadding = 16 - DrawState(playerID, m_name.posX + widgetPosX, posY) + DrawState(playerID, ModuleRefs.name.posX + widgetPosX, posY) end font2:Begin(true) @@ -2971,7 +2796,7 @@ function DrawName(name, nameIsAlias, team, posY, dark, playerID, accountID, desy font2:SetTextColor(0.45,0.45,0.45,1) end local nameYOffset = isAbsent and 5 or 4 - font2:Print(nameText, m_name.posX + widgetPosX + 3 + xPadding, posY + (nameYOffset*playerScale), fontsize, "o") + font2:Print(nameText, ModuleRefs.name.posX + widgetPosX + 3 + xPadding, posY + (nameYOffset*playerScale), fontsize, "o") --desynced = playerID == 1 local pScale = (0.5+playerScale)*0.67 --dont scale too much for the already smaller bonus font @@ -2980,23 +2805,23 @@ function DrawName(name, nameIsAlias, team, posY, dark, playerID, accountID, desy font2:SetOutlineColor(0, 0, 0, 1) end font2:SetTextColor(1,0.45,0.45,1) - font2:Print(Spring.I18N('ui.playersList.desynced'), m_name.posX + widgetPosX + 5 + xPadding + (font2:GetTextWidth(nameText)*14*pScale), posY + (5.7*playerScale), 8*pScale, "o") + font2:Print(Spring.I18N('ui.playersList.desynced'), ModuleRefs.name.posX + widgetPosX + 5 + xPadding + (font2:GetTextWidth(nameText)*14*pScale), posY + (5.7*playerScale), 8*pScale, "o") elseif pDraw and not pDraw.dead and pDraw.incomeMultiplier and pDraw.incomeMultiplier ~= 1 then if dark then font2:SetOutlineColor(0, 0, 0, 1) end if pDraw.incomeMultiplier > 1 then font2:SetTextColor(0.5,1,0.5,1) - font2:Print('+'..mathFloor((pDraw.incomeMultiplier-1+0.005)*100)..'%', m_name.posX + widgetPosX + 5 + xPadding + (font2:GetTextWidth(nameText)*14*pScale), posY + (5.7*playerScale), 8*pScale, "o") + font2:Print('+'..mathFloor((pDraw.incomeMultiplier-1+0.005)*100)..'%', ModuleRefs.name.posX + widgetPosX + 5 + xPadding + (font2:GetTextWidth(nameText)*14*pScale), posY + (5.7*playerScale), 8*pScale, "o") else font2:SetTextColor(1,0.5,0.5,1) - font2:Print(mathFloor((pDraw.incomeMultiplier-1+0.005)*100)..'%', m_name.posX + widgetPosX + 5 + xPadding + (font2:GetTextWidth(nameText)*14*pScale), posY + (5.7*playerScale), 8*pScale, "o") + font2:Print(mathFloor((pDraw.incomeMultiplier-1+0.005)*100)..'%', ModuleRefs.name.posX + widgetPosX + 5 + xPadding + (font2:GetTextWidth(nameText)*14*pScale), posY + (5.7*playerScale), 8*pScale, "o") end end font2:End() if ignored or desynced or (pDraw and pDraw.dead) then - local x = m_name.posX + widgetPosX + 2 + xPadding + local x = ModuleRefs.name.posX + widgetPosX + 2 + xPadding local y = isAbsent and (posY + (8*playerScale)) or (posY + (7*playerScale)) local w = (font2:GetTextWidth(nameText) * fontsize) + 2 local h = isAbsent and (1.5*playerScale) or (2*playerScale) @@ -3019,7 +2844,7 @@ function DrawSmallName(name, nameIsAlias, team, posY, dark, playerID, accountID, end local ignored = WG.ignoredAccounts and (WG.ignoredAccounts[accountID] or WG.ignoredAccounts[name] ~= nil) local textindent = 4 - if m_indent.active or m_rank.active or m_side.active or m_allyID.active or m_playerID.active or m_ID.active then + if ModuleRefs.indent.active or ModuleRefs.rank.active or ModuleRefs.side.active or ModuleRefs.allyID.active or ModuleRefs.playerID.active or ModuleRefs.ID.active then textindent = 0 end @@ -3045,11 +2870,11 @@ function DrawSmallName(name, nameIsAlias, team, posY, dark, playerID, accountID, font2:Begin(true) font2:SetOutlineColor(0.15+(alpha*0.33), 0.15+(alpha*0.33), 0.15+(alpha*0.33), 0.55) font2:SetTextColor(alpha, alpha, alpha, 1) - font2:Print(nameText, m_name.posX + textindent + widgetPosX + 3, posY + (4*specScale), (10*specScale * fontScaleSpec), "n") + font2:Print(nameText, ModuleRefs.name.posX + textindent + widgetPosX + 3, posY + (4*specScale), (10*specScale * fontScaleSpec), "n") font2:End() if ignored then - local x = m_name.posX + textindent + widgetPosX + 2.2 + local x = ModuleRefs.name.posX + textindent + widgetPosX + 2.2 local y = posY + (6*specScale) local w = font2:GetTextWidth(nameText) * 10 + 2 local h = 2 @@ -3069,7 +2894,7 @@ function DrawAllyID(allyID, posY, dark, dead) font:Begin(true) font:SetTextColor(0.9, 0.7, 0.9, 1) font:SetOutlineColor(0.18, 0.18, 0.18, 1) - font:Print(spacer .. allyID, m_allyID.posX + widgetPosX + (4.5*playerScale), posY + (5.3*playerScale), fontsize, "on") + font:Print(spacer .. allyID, ModuleRefs.allyID.posX + widgetPosX + (4.5*playerScale), posY + (5.3*playerScale), fontsize, "on") font:End() end @@ -3084,7 +2909,7 @@ function DrawPlayerID(playerID, posY, dark, spec) font:Begin(true) font:SetTextColor(0.7, 0.9, 0.7, spec and 0.5 or 1) font:SetOutlineColor(0.18, 0.18, 0.18, 1) - font:Print(spacer .. playerID, m_playerID.posX + widgetPosX + (4.5*usedScale), posY + (5.3*usedScale), fontsize, "on") + font:Print(spacer .. playerID, ModuleRefs.playerID.posX + widgetPosX + (4.5*usedScale), posY + (5.3*usedScale), fontsize, "on") font:End() end @@ -3097,14 +2922,14 @@ function DrawID(teamID, posY, dark, dead) font:Begin(true) font:SetTextColor(0.7, 0.7, 0.7, 1) font:SetOutlineColor(0.18, 0.18, 0.18, 1) - font:Print(spacer .. teamID, m_ID.posX + widgetPosX + (4.5*playerScale), posY + (5.3*playerScale), fontsize, "on") + font:Print(spacer .. teamID, ModuleRefs.ID.posX + widgetPosX + (4.5*playerScale), posY + (5.3*playerScale), fontsize, "on") font:End() end function DrawSkill(skill, posY, dark) local fontsize = 9.5 * (playerScale + ((1-playerScale)*0.25)) * fontScaleHigh - font:Begin(true) - font:Print(skill, m_skill.posX + widgetPosX + (4.5*playerScale), posY + (5.3*playerScale), fontsize, "o") + font:Begin(useRenderToTexture) + font:Print(skill, ModuleRefs.skill.posX + widgetPosX + (4.5*playerScale), posY + (5.3*playerScale), fontsize, "o") font:End() end @@ -3116,10 +2941,10 @@ function DrawPingCpu(pingLvl, cpuLvl, posY, spec, cpu, fps) if spec then grayvalue = 0.5 + (pingLvl / 20) gl_Color(grayvalue, grayvalue, grayvalue, (0.2 * pingLvl)) - DrawRect(m_cpuping.posX + widgetPosX + (12*specScale), posY + (1*specScale), m_cpuping.posX + widgetPosX + (21*specScale), posY + (14*specScale)) + DrawRect(ModuleRefs.cpuping.posX + widgetPosX + (12*specScale), posY + (1*specScale), ModuleRefs.cpuping.posX + widgetPosX + (21*specScale), posY + (14*specScale)) else gl_Color(pingLevelData[pingLvl].r, pingLevelData[pingLvl].g, pingLevelData[pingLvl].b) - DrawRect(m_cpuping.posX + widgetPosX + (12*playerScale), posY + (1*playerScale), m_cpuping.posX + widgetPosX + (24*playerScale), posY + (15*playerScale)) + DrawRect(ModuleRefs.cpuping.posX + widgetPosX + (12*playerScale), posY + (1*playerScale), ModuleRefs.cpuping.posX + widgetPosX + (24*playerScale), posY + (15*playerScale)) end @@ -3137,20 +2962,20 @@ function DrawPingCpu(pingLvl, cpuLvl, posY, spec, cpu, fps) end if spec then font:SetTextColor(grayvalue*0.7, grayvalue*0.7, grayvalue*0.7, 1) - font:Print(fps, m_cpuping.posX + widgetPosX + (11*specScale), posY + (5.3*playerScale), 9*specScale*fontScale, "ro") + font:Print(fps, ModuleRefs.cpuping.posX + widgetPosX + (11*specScale), posY + (5.3*playerScale), 9*specScale*fontScale, "ro") else font:SetTextColor(grayvalue, grayvalue, grayvalue, 1) - font:Print(fps, m_cpuping.posX + widgetPosX + (11*playerScale), posY + (5.3*playerScale), 9.5*playerScale*fontScale, "ro") + font:Print(fps, ModuleRefs.cpuping.posX + widgetPosX + (11*playerScale), posY + (5.3*playerScale), 9.5*playerScale*fontScale, "ro") end else grayvalue = 0.7 + (cpu / 135) gl_Texture(pics["cpuPic"]) if spec then gl_Color(grayvalue, grayvalue, grayvalue, 0.1 + (0.14 * cpuLvl)) - DrawRect(m_cpuping.posX + widgetPosX + (2*specScale), posY + (1*specScale), m_cpuping.posX + widgetPosX + (13*specScale), posY + (14*specScale*fontScale)) + DrawRect(ModuleRefs.cpuping.posX + widgetPosX + (2*specScale), posY + (1*specScale), ModuleRefs.cpuping.posX + widgetPosX + (13*specScale), posY + (14*specScale*fontScale)) else gl_Color(pingLevelData[cpuLvl].r, pingLevelData[cpuLvl].g, pingLevelData[cpuLvl].b) - DrawRect(m_cpuping.posX + widgetPosX + (1*playerScale), posY + (1*playerScale), m_cpuping.posX + widgetPosX + (14*playerScale), posY + (15*playerScale*fontScale)) + DrawRect(ModuleRefs.cpuping.posX + widgetPosX + (1*playerScale), posY + (1*playerScale), ModuleRefs.cpuping.posX + widgetPosX + (14*playerScale), posY + (15*playerScale*fontScale)) end gl_Color(1, 1, 1, 1) end @@ -3171,7 +2996,7 @@ function DrawPencil(posY, time) leftPosX = widgetPosX + widgetWidth gl_Color(1, 1, 1, (time / pencilDuration ) * 0.12) gl_Texture(pics["pencilPic"]) - DrawRect(m_indent.posX + widgetPosX - 3.5, posY + (3*playerScale), m_indent.posX + widgetPosX - 1.5 + (8*playerScale), posY + (14*playerScale)) + DrawRect(ModuleRefs.indent.posX + widgetPosX - 3.5, posY + (3*playerScale), ModuleRefs.indent.posX + widgetPosX - 1.5 + (8*playerScale), posY + (14*playerScale)) gl_Color(1, 1, 1, 1) end @@ -3179,7 +3004,7 @@ function DrawEraser(posY, time) leftPosX = widgetPosX + widgetWidth gl_Color(1, 1, 1, (time / pencilDuration ) * 0.12) gl_Texture(pics["eraserPic"]) - DrawRect(m_indent.posX + widgetPosX -0.5, posY + (3*playerScale), m_indent.posX + widgetPosX + 1.5 + (8*playerScale), posY + (14*playerScale)) + DrawRect(ModuleRefs.indent.posX + widgetPosX -0.5, posY + (3*playerScale), ModuleRefs.indent.posX + widgetPosX + 1.5 + (8*playerScale), posY + (14*playerScale)) gl_Color(1, 1, 1, 1) end @@ -3192,7 +3017,7 @@ end function NameTip(mouseX, playerID, accountID, nameIsAlias) local pTip = player[playerID] - if accountID and mouseX >= widgetPosX + (m_name.posX + (1*playerScale)) * widgetScale and mouseX <= widgetPosX + (m_name.posX + m_name.width) * widgetScale and WG.playernames then + if accountID and mouseX >= widgetPosX + (ModuleRefs.name.posX + (1*playerScale)) * widgetScale and mouseX <= widgetPosX + (ModuleRefs.name.posX + ModuleRefs.name.width) * widgetScale and WG.playernames then if WG.playernames and not pTip.history then pTip.history = WG.playernames.getAccountHistory(accountID) or {} end @@ -3224,34 +3049,19 @@ function NameTip(mouseX, playerID, accountID, nameIsAlias) end end -function ShareTip(mouseX, playerID) - if playerID == myPlayerID then - if mouseX >= widgetPosX + (m_share.posX + (1*playerScale)) * widgetScale and mouseX <= widgetPosX + (m_share.posX + (17*playerScale)) * widgetScale then - tipText = Spring.I18N('ui.playersList.requestSupport') - tipTextTime = osClock() - elseif mouseX >= widgetPosX + (m_share.posX + (19*playerScale)) * widgetScale and mouseX <= widgetPosX + (m_share.posX + (35*playerScale)) * widgetScale then - tipText = Spring.I18N('ui.playersList.requestEnergy') - tipTextTime = osClock() - elseif mouseX >= widgetPosX + (m_share.posX + (37*playerScale)) * widgetScale and mouseX <= widgetPosX + (m_share.posX + (53*playerScale)) * widgetScale then - tipText = Spring.I18N('ui.playersList.requestMetal') - tipTextTime = osClock() - end - else - if mouseX >= widgetPosX + (m_share.posX + (1*playerScale)) * widgetScale and mouseX <= widgetPosX + (m_share.posX + (17*playerScale)) * widgetScale then - tipText = Spring.I18N('ui.playersList.shareUnits') - tipTextTime = osClock() - elseif mouseX >= widgetPosX + (m_share.posX + (19*playerScale)) * widgetScale and mouseX <= widgetPosX + (m_share.posX + (35*playerScale)) * widgetScale then - tipText = Spring.I18N('ui.playersList.shareEnergy') - tipTextTime = osClock() - elseif mouseX >= widgetPosX + (m_share.posX + (37*playerScale)) * widgetScale and mouseX <= widgetPosX + (m_share.posX + (53*playerScale)) * widgetScale then - tipText = Spring.I18N('ui.playersList.shareMetal') - tipTextTime = osClock() - end +function ShareTip(mouseX, unitPolicy, metalPolicy, energyPolicy, unitValidationResult) + if mouseX >= widgetPosX + (ModuleRefs.share.posX + (1*playerScale)) * widgetScale and mouseX <= widgetPosX + (ModuleRefs.share.posX + (17*playerScale)) * widgetScale then + tipText = TeamTransfer.Units.TooltipText(unitPolicy, unitValidationResult) + elseif mouseX >= widgetPosX + (ModuleRefs.share.posX + (19*playerScale)) * widgetScale and mouseX <= widgetPosX + (ModuleRefs.share.posX + (35*playerScale)) * widgetScale then + tipText = ResourceTransfer.TooltipText(energyPolicy) + elseif mouseX >= widgetPosX + (ModuleRefs.share.posX + (37*playerScale)) * widgetScale and mouseX <= widgetPosX + (ModuleRefs.share.posX + (53*playerScale)) * widgetScale then + tipText = ResourceTransfer.TooltipText(metalPolicy) end + tipTextTime = os.clock() end function AllyTip(mouseX, playerID) - if mouseX >= widgetPosX + (m_alliance.posX + (1*playerScale)) * widgetScale and mouseX <= widgetPosX + (m_alliance.posX + (11*playerScale)) * widgetScale then + if mouseX >= widgetPosX + (ModuleRefs.alliance.posX + (1*playerScale)) * widgetScale and mouseX <= widgetPosX + (ModuleRefs.alliance.posX + (11*playerScale)) * widgetScale then if sp.AreTeamsAllied(player[playerID].team, myTeamID) then tipText = Spring.I18N('ui.playersList.becomeEnemy') tipTextTime = osClock() @@ -3263,7 +3073,7 @@ function AllyTip(mouseX, playerID) end function ResourcesTip(mouseX, energy, energyStorage, energyIncome, metal, metalStorage, metalIncome, name, teamID) - if mouseX >= widgetPosX + (m_resources.posX + (1*playerScale)) * widgetScale and mouseX <= widgetPosX + (m_resources.posX + (m_resources.width*playerScale)) * widgetScale then + if mouseX >= widgetPosX + (ModuleRefs.resources.posX + (1*playerScale)) * widgetScale and mouseX <= widgetPosX + (ModuleRefs.resources.posX + (ModuleRefs.resources.width*playerScale)) * widgetScale then if energy > 1000 then energy = mathFloor(energy / 100) * 100 else @@ -3307,7 +3117,7 @@ function ResourcesTip(mouseX, energy, energyStorage, energyIncome, metal, metalS end function IncomeTip(mouseX, energyIncome, metalIncome, name, teamID) - if mouseX >= widgetPosX + (m_income.posX + (1*playerScale)) * widgetScale and mouseX <= widgetPosX + (m_income.posX + m_resources.width) * widgetScale then + if mouseX >= widgetPosX + (ModuleRefs.income.posX + (1*playerScale)) * widgetScale and mouseX <= widgetPosX + (ModuleRefs.income.posX + ModuleRefs.resources.width) * widgetScale then if energyIncome == nil then energyIncome = 0 metalIncome = 0 @@ -3335,7 +3145,7 @@ function IncomeTip(mouseX, energyIncome, metalIncome, name, teamID) end function PingCpuTip(mouseX, pingLvl, cpuLvl, fps, gpumem, system, name, teamID, spec, apm) - if mouseX >= widgetPosX + (m_cpuping.posX + (13*playerScale)) * widgetScale and mouseX <= widgetPosX + (m_cpuping.posX + (23*playerScale)) * widgetScale then + if mouseX >= widgetPosX + (ModuleRefs.cpuping.posX + (13*playerScale)) * widgetScale and mouseX <= widgetPosX + (ModuleRefs.cpuping.posX + (23*playerScale)) * widgetScale then if pingLvl < 2000 then pingLvl = Spring.I18N('ui.playersList.milliseconds', { number = pingLvl }) elseif pingLvl >= 2000 then @@ -3344,7 +3154,7 @@ function PingCpuTip(mouseX, pingLvl, cpuLvl, fps, gpumem, system, name, teamID, tipText = Spring.I18N('ui.playersList.commandDelay', { labelColor = "\255\190\190\190", delayColor = "\255\255\255\255", delay = pingLvl }) tipTextTitle = (spec and "\255\240\240\240" or colourNames(teamID)) .. name tipTextTime = osClock() - elseif mouseX >= widgetPosX + (m_cpuping.posX + (1*playerScale)) * widgetScale and mouseX <= widgetPosX + (m_cpuping.posX + (11*playerScale)) * widgetScale then + elseif mouseX >= widgetPosX + (ModuleRefs.cpuping.posX + (1*playerScale)) * widgetScale and mouseX <= widgetPosX + (ModuleRefs.cpuping.posX + (11*playerScale)) * widgetScale then tipText = '' if not spec and apm ~= nil then tipText = tipText .. Spring.I18N('ui.playersList.apm', { apm = apm }) .."\n" @@ -3375,36 +3185,62 @@ end -- Share slider gllist --------------------------------------------------------------------------------------------------- +local function RenderShareSliderText(posY, player, resourceType, baseOffset) + local policyResult = Helpers.GetPlayerResourcePolicy(player, resourceType, myTeamID) + local case = ResourceTransfer.DecideCommunicationCase(policyResult) + local label + if case == ResourceTransfer.ResourceCommunicationCase.OnTaxFree then + label = ResourceTransfer.FormatNumberForUI(shareAmount) + else + local received, sent = ResourceTransfer.CalculateSenderTaxedAmount(policyResult, shareAmount) + label = "(Sent→Received) " .. ResourceTransfer.FormatNumberForUI(sent) .. "→" .. ResourceTransfer.FormatNumberForUI(received) + end + local textXRight = ModuleRefs.share.posX + widgetPosX + (baseOffset * playerScale) - (4 * playerScale) + local fontSize = 14 + local pad = 4 * playerScale + local textWidth = font:GetTextWidth(label) * fontSize + local bgLeft = math.floor(textXRight - textWidth - pad) + local bgRight = math.floor(textXRight + pad) + local bgBottom = math.floor(posY - 1 + sliderPosition) + local bgTop = math.floor(posY + (17*playerScale) + sliderPosition) + gl_Color(0.45,0.45,0.45,1) + RectRound(bgLeft, bgBottom, bgRight, bgTop, 2.5*playerScale) + font:Print("\255\255\255\255"..label, textXRight, posY + (3 * playerScale) + sliderPosition, fontSize, "or") +end + function CreateShareSlider() if ShareSlider then gl_DeleteList(ShareSlider) end + if energyPlayer then + Helpers.PackEnergyPolicyResult(energyPlayer.team, myTeamID, energyPlayer) + elseif metalPlayer then + Helpers.PackMetalPolicyResult(metalPlayer.team, myTeamID, metalPlayer) + end + ShareSlider = gl_CreateList(function() gl_Color(1,1,1,1) if sliderPosition then font:Begin(true) local posY + if energyPlayer ~= nil then posY = widgetPosY + widgetHeight - energyPlayer.posY gl_Texture(pics["barPic"]) - DrawRect(m_share.posX + widgetPosX + (16*playerScale), posY - (3*playerScale), m_share.posX + widgetPosX + (34*playerScale), posY + shareSliderHeight + (18*playerScale)) + DrawRect(ModuleRefs.share.posX + widgetPosX + (16*playerScale), posY - (3*playerScale), ModuleRefs.share.posX + widgetPosX + (34*playerScale), posY + shareSliderHeight + (18*playerScale)) gl_Texture(pics["energyPic"]) - DrawRect(m_share.posX + widgetPosX + (17*playerScale), posY + sliderPosition, m_share.posX + widgetPosX + (33*playerScale), posY + (16*playerScale) + sliderPosition) + DrawRect(ModuleRefs.share.posX + widgetPosX + (17*playerScale), posY + sliderPosition, ModuleRefs.share.posX + widgetPosX + (33*playerScale), posY + (16*playerScale) + sliderPosition) gl_Texture(false) - gl_Color(0.45,0.45,0.45,1) - RectRound(mathFloor(m_share.posX + widgetPosX - (28*playerScale)), mathFloor(posY - 1 + sliderPosition), mathFloor(m_share.posX + widgetPosX + (19*playerScale)), mathFloor(posY + (17*playerScale) + sliderPosition), 2.5*playerScale) - font:Print("\255\255\255\255"..shareAmount, m_share.posX + widgetPosX - (5*playerScale), posY + (3*playerScale) + sliderPosition, 14, "ocn") + RenderShareSliderText(posY, energyPlayer, "energy", 16) elseif metalPlayer ~= nil then posY = widgetPosY + widgetHeight - metalPlayer.posY gl_Texture(pics["barPic"]) - DrawRect(m_share.posX + widgetPosX + (32*playerScale), posY - 3, m_share.posX + widgetPosX + (50*playerScale), posY + shareSliderHeight + (18*playerScale)) + DrawRect(ModuleRefs.share.posX + widgetPosX + (32*playerScale), posY - 3, ModuleRefs.share.posX + widgetPosX + (50*playerScale), posY + shareSliderHeight + (18*playerScale)) gl_Texture(pics["metalPic"]) - DrawRect(m_share.posX + widgetPosX + (33*playerScale), posY + sliderPosition, m_share.posX + widgetPosX + (49*playerScale), posY + (16*playerScale) + sliderPosition) + DrawRect(ModuleRefs.share.posX + widgetPosX + (33*playerScale), posY + sliderPosition, ModuleRefs.share.posX + widgetPosX + (49*playerScale), posY + (16*playerScale) + sliderPosition) gl_Texture(false) - gl_Color(0.45,0.45,0.45,1) - RectRound(mathFloor(m_share.posX + widgetPosX - (12*playerScale)), mathFloor(posY - 1 + sliderPosition), mathFloor(m_share.posX + widgetPosX + (35*playerScale)), mathFloor(posY + (17*playerScale) + sliderPosition), 2.5*playerScale) - font:Print("\255\255\255\255"..shareAmount, m_share.posX + widgetPosX + (11*playerScale), posY + (3*playerScale) + sliderPosition, 14, "ocn") + RenderShareSliderText(posY, metalPlayer, "metal", 32) end font:End() end @@ -3502,7 +3338,7 @@ function widget:MousePress(x, y, button) end end if i > -1 then -- and i < specOffset - if m_name.active and clickedPlayer.name ~= absentName and IsOnRect(x, y, widgetPosX, posY, widgetPosX + widgetWidth, posY + (playerOffset*playerScale)) then + if ModuleRefs.name.active and clickedPlayer.name ~= absentName and IsOnRect(x, y, widgetPosX, posY, widgetPosX + widgetWidth, posY + (playerOffset*playerScale)) then if ctrl and i < specOffset then sp.SendCommands("toggleignore " .. (clickedPlayer.accountID and clickedPlayer.accountID or clickedPlayer.name)) return true @@ -3552,8 +3388,8 @@ function widget:MousePress(x, y, button) end end end - if m_share.active and clickedPlayer.dead ~= true and not hideShareIcons then - if IsOnRect(x, y, m_share.posX + widgetPosX + (1*playerScale), posY, m_share.posX + widgetPosX + (17*playerScale), posY + (playerOffset*playerScale)) then + if ModuleRefs.share.active and clickedPlayer.dead ~= true and not hideShareIcons then + if IsOnRect(x, y, ModuleRefs.share.posX + widgetPosX + (1*playerScale), posY, ModuleRefs.share.posX + widgetPosX + (17*playerScale), posY + (playerOffset*playerScale)) then -- share units button if release ~= nil then if release >= now then @@ -3561,7 +3397,7 @@ function widget:MousePress(x, y, button) --sp.SendCommands("say a: " .. Spring.I18N('ui.playersList.chat.needSupport')) Spring.SendLuaRulesMsg('msg:ui.playersList.chat.needSupport') else - sp.ShareResources(clickedPlayer.team, "units") + TeamTransfer.Units.ShareUnits(clickedPlayer.team) Spring.PlaySoundFile("beep4", 1, 'ui') end end @@ -3571,12 +3407,12 @@ function widget:MousePress(x, y, button) end return true end - if IsOnRect(x, y, m_share.posX + widgetPosX + (17*playerScale), posY, m_share.posX + widgetPosX + (33*playerScale), posY + (playerOffset*playerScale)) then + if IsOnRect(x, y, ModuleRefs.share.posX + widgetPosX + (17*playerScale), posY, ModuleRefs.share.posX + widgetPosX + (33*playerScale), posY + (playerOffset*playerScale)) then -- share energy button (initiates the slider) energyPlayer = clickedPlayer return true end - if IsOnRect(x, y, m_share.posX + widgetPosX + (33*playerScale), posY, m_share.posX + widgetPosX + (49*playerScale), posY + (playerOffset*playerScale)) then + if IsOnRect(x, y, ModuleRefs.share.posX + widgetPosX + (33*playerScale), posY, ModuleRefs.share.posX + widgetPosX + (49*playerScale), posY + (playerOffset*playerScale)) then -- share metal button (initiates the slider) metalPlayer = clickedPlayer return true @@ -3590,15 +3426,15 @@ function widget:MousePress(x, y, button) t = false if i > -1 and i < specOffset then --chat button - if m_chat.active then - if IsOnRect(x, y, m_chat.posX + widgetPosX + 1, posY, m_chat.posX + widgetPosX + 17, posY + (playerOffset*playerScale)) then + if ModuleRefs.chat.active then + if IsOnRect(x, y, ModuleRefs.chat.posX + widgetPosX + 1, posY, ModuleRefs.chat.posX + widgetPosX + 17, posY + (playerOffset*playerScale)) then sp.SendCommands("chatall", "pastetext /w " .. clickedPlayer.name .. ' \1') return true end end --ally button - if m_alliance.active and drawAllyButton and not mySpecStatus and player[i] ~= nil and player[i].dead ~= true and i ~= myPlayerID then - if IsOnRect(x, y, m_alliance.posX + widgetPosX + 1, posY, m_alliance.posX + widgetPosX + m_alliance.width, posY + (playerOffset*playerScale)) then + if ModuleRefs.alliance.active and drawAllyButton and not mySpecStatus and player[i] ~= nil and player[i].dead ~= true and i ~= myPlayerID then + if IsOnRect(x, y, ModuleRefs.alliance.posX + widgetPosX + 1, posY, ModuleRefs.alliance.posX + widgetPosX + ModuleRefs.alliance.width, posY + (playerOffset*playerScale)) then if sp.AreTeamsAllied(player[i].team, myTeamID) then sp.SendCommands("ally " .. player[i].allyteam .. " 0") else @@ -3619,7 +3455,7 @@ function widget:MousePress(x, y, button) end end --name - if m_name.active and clickedPlayer.name ~= absentName and IsOnRect(x, y, m_name.posX + widgetPosX + 1, posY, m_name.posX + widgetPosX + m_name.width, posY + 12) then + if ModuleRefs.name.active and clickedPlayer.name ~= absentName and IsOnRect(x, y, ModuleRefs.name.posX + widgetPosX + 1, posY, ModuleRefs.name.posX + widgetPosX + ModuleRefs.name.width, posY + 12) then if ctrl then sp.SendCommands("toggleignore " .. (clickedPlayer.accountID and clickedPlayer.accountID or clickedPlayer.name)) return true @@ -3687,62 +3523,20 @@ function widget:MouseRelease(x, y, button) else release = nil end - if energyPlayer ~= nil then - -- share energy/metal mouse release - if energyPlayer.team == myTeamID then - Spring.SendLuaUIMsg('alert:allyRequest:energy', 'allies') - if shareAmount == 0 then - --sp.SendCommands("say a:" .. Spring.I18N('ui.playersList.chat.needEnergy')) - Spring.SendLuaRulesMsg('msg:ui.playersList.chat.needEnergy') - else - --sp.SendCommands("say a:" .. Spring.I18N('ui.playersList.chat.needEnergyAmount', { amount = shareAmount })) - Spring.SendLuaRulesMsg('msg:ui.playersList.chat.needEnergyAmount:amount='..shareAmount) - end - elseif shareAmount > 0 then - sp.ShareResources(energyPlayer.team, "energy", shareAmount) - --sp.SendCommands("say a:" .. Spring.I18N('ui.playersList.chat.giveEnergy', { amount = shareAmount, name = energyPlayer.name })) - if sharingTax and sharingTax > 0 then - local amount2 = mathFloor(shareAmount * (1- sharingTax)) - Spring.SendLuaRulesMsg('msg:ui.playersList.chat.giveEnergy_taxed:amount='..shareAmount..':name='..(energyPlayer.orgname or energyPlayer.name)..':amount2='..amount2) - else - Spring.SendLuaRulesMsg('msg:ui.playersList.chat.giveEnergy:amount='..shareAmount..':name='..(energyPlayer.orgname or energyPlayer.name)) - end - WG.sharedEnergyFrame = spGetGameFrame() - end - sliderOrigin = nil - maxShareAmount = nil - sliderPosition = nil - shareAmount = nil - energyPlayer = nil + + if energyPlayer ~= nil and shareAmount then + Helpers.HandleResourceTransfer(energyPlayer, "energy", shareAmount, myTeamID) end if metalPlayer ~= nil and shareAmount then - if metalPlayer.team == myTeamID then - Spring.SendLuaUIMsg('alert:allyRequest:metal', 'allies') - if shareAmount == 0 then - --sp.SendCommands("say a:" .. Spring.I18N('ui.playersList.chat.needMetal')) - Spring.SendLuaRulesMsg('msg:ui.playersList.chat.needMetal') - else - --sp.SendCommands("say a:" .. Spring.I18N('ui.playersList.chat.needMetalAmount', { amount = shareAmount })) - Spring.SendLuaRulesMsg('msg:ui.playersList.chat.needMetalAmount:amount='..shareAmount) - end - elseif shareAmount > 0 then - sp.ShareResources(metalPlayer.team, "metal", shareAmount) - --sp.SendCommands("say a:" .. Spring.I18N('ui.playersList.chat.giveMetal', { amount = shareAmount, name = metalPlayer.name })) - if sharingTax and sharingTax > 0 then - local amount2 = mathFloor(shareAmount * (1- sharingTax)) - Spring.SendLuaRulesMsg('msg:ui.playersList.chat.giveMetal_taxed:amount='..shareAmount..':name='..(metalPlayer.orgname or metalPlayer.name)..':amount2='..amount2) - else - Spring.SendLuaRulesMsg('msg:ui.playersList.chat.giveMetal:amount='..shareAmount..':name='..(metalPlayer.orgname or metalPlayer.name)) - end - WG.sharedMetalFrame = spGetGameFrame() - end - sliderOrigin = nil - maxShareAmount = nil - sliderPosition = nil - shareAmount = nil - metalPlayer = nil + Helpers.HandleResourceTransfer(metalPlayer, "metal", shareAmount, myTeamID) end + + sliderOrigin = nil + sliderPosition = nil + shareAmount = 0 + energyPlayer = nil + metalPlayer = nil end end @@ -3776,7 +3570,7 @@ end local version = 1 function widget:GetConfigData() -- save - if m_name ~= nil then + if ModuleRefs.name ~= nil then local m_active_Table = {} for n, module in pairs(modules) do m_active_Table[module.name] = module.active @@ -3901,7 +3695,7 @@ function widget:SetConfigData(data) end if not data.hasresetskill then - m_skill.active = false + ModuleRefs.skill.active = false end SetModulesPositionX() @@ -3981,13 +3775,8 @@ function CheckPlayersChange() sorting = true end -- Update stall / cpu / ping info for each player - if p.spec == false then - p.needm = GetNeed("metal", p.team) - p.neede = GetNeed("energy", p.team) - p.rank = rank - else - p.needm = false - p.neede = false + if player[i].spec == false then + player[i].rank = rank end p.pingLvl = GetPingLvl(pingTime) @@ -4016,18 +3805,6 @@ function CheckPlayersChange() end end -function GetNeed(resType, teamID) - local current, _, pull, income = sp.GetTeamResources(teamID, resType) - if current == nil then - return false - end - local loss = pull - income - if loss > 0 and loss * 5 > current then - return true - end - return false -end - function updateTake(allyTeamID) for i = 0, teamN - 1 do if player[i + specOffset].allyTeam == allyTeamID then @@ -4070,8 +3847,11 @@ function widget:Update(delta) end --handles takes & related messages local mx, my = spGetMouseState() + local wasHoveringPlayerlist = hoverPlayerlist hoverPlayerlist = false - if math_isInRect(mx, my, apiAbsPosition[2] - 1, apiAbsPosition[3] - 1, apiAbsPosition[4] + 1, apiAbsPosition[1] + 1 ) then + + local insidePlayerlist = math_isInRect(mx, my, apiAbsPosition[2], apiAbsPosition[3], apiAbsPosition[4], apiAbsPosition[1]) + if insidePlayerlist then hoverPlayerlist = true if leaderboardOffset then @@ -4139,9 +3919,9 @@ function widget:Update(delta) end end if detailedToSay then - Spring.SendLuaRulesMsg('msg:ui.playersList.chat.takeTeam:name='..tookTeamName..':units='..mathFloor(afterU)..':energy='..mathFloor(afterE)..':metal='..mathFloor(afterE)) + Spring.SendLuaRulesMsg('msg:ui.playersList.chat.takeTeam:name:'..tookTeamName..':units:'..mathFloor(afterU)..':energy:'..mathFloor(afterE)..':metal:'..mathFloor(afterM)) else - Spring.SendLuaRulesMsg('msg:ui.playersList.chat.takeTeam:name='..tookTeamName) + Spring.SendLuaRulesMsg('msg:ui.playersList.chat.takeTeam:name:'..tookTeamName) end for j = 0, (specOffset*2)-1 do @@ -4180,11 +3960,16 @@ function widget:Update(delta) CreateLists() else local updateMainList2 = timeCounter > updateRate*updateRateMult - local updateMainList3 = ((m_resources.active or m_income.active) and timeFastCounter > updateFastRate*updateFastRateMult) + local updateMainList3 = ((ModuleRefs.resources.active or ModuleRefs.income.active) and timeFastCounter > updateFastRate*updateFastRateMult) if updateMainList2 or updateMainList3 then CreateLists(curFrame==0, updateMainList2, updateMainList3) end end + + if wasHoveringPlayerlist and not hoverPlayerlist then + local selectedUnits = Spring.GetSelectedUnits() + ApiExtensions.HandleHoverChange(myTeamID, selectedUnits, nil, nil) + end end --------------------------------------------------------------------------------------------------- diff --git a/luaui/Widgets/gui_advplayerslist_modules.lua b/luaui/Widgets/gui_advplayerslist_modules.lua new file mode 100644 index 00000000000..b7920d8b816 --- /dev/null +++ b/luaui/Widgets/gui_advplayerslist_modules.lua @@ -0,0 +1,213 @@ +--- Defines AdvPlayersList column modules and related metadata +---@param pics table +---@param drawAllyButton boolean +---@return table modules +---@return table moduleRefs +---@return table m_point +---@return table m_take +return function(pics, drawAllyButton) + local moduleRefs = {} + local position = 1 + + local function defineModule(key, data) + data.position = position + position = position + 1 + moduleRefs[key] = data + return data + end + + local indent = defineModule("indent", { + name = "indent", + spec = true, + play = true, + active = true, + default = true, + width = 9, + posX = 0, + pic = pics["indentPic"], + noPic = true, + }) + + defineModule("allyID", { + name = "allyid", + spec = true, + play = true, + active = false, + width = 17, + posX = 0, + pic = pics["idPic"], + }) + + defineModule("ID", { + name = "id", + spec = true, + play = true, + active = false, + width = 17, + posX = 0, + pic = pics["idPic"], + }) + + defineModule("playerID", { + name = "playerid", + spec = true, + play = true, + active = false, + width = 17, + posX = 0, + pic = pics["idPic"], + }) + + defineModule("rank", { + name = "rank", + spec = true, + play = true, + active = true, + default = false, + width = 18, + posX = 0, + pic = pics["rank6"], + }) + + defineModule("country", { + name = "country", + spec = true, + play = true, + active = true, + default = true, + width = 20, + posX = 0, + pic = pics["countryPic"], + }) + + defineModule("side", { + name = "side", + spec = true, + play = true, + active = false, + width = 18, + posX = 0, + pic = pics["sidePic"], + }) + + defineModule("skill", { + name = "skill", + spec = true, + play = true, + active = false, + width = 18, + posX = 0, + pic = pics["tsPic"], + }) + + defineModule("name", { + name = "name", + spec = true, + play = true, + active = true, + alwaysActive = true, + width = 10, + posX = 0, + noPic = true, + picGap = 7, + }) + + defineModule("cpuping", { + name = "cpuping", + spec = true, + play = true, + active = true, + width = 24, + posX = 0, + pic = pics["cpuPic"], + }) + + defineModule("resources", { + name = "resources", + spec = true, + play = true, + active = true, + width = 28, + posX = 0, + pic = pics["resourcesPic"], + picGap = 7, + }) + + defineModule("income", { + name = "income", + spec = true, + play = true, + active = false, + width = 28, + posX = 0, + pic = pics["incomePic"], + picGap = 7, + }) + + defineModule("share", { + name = "share", + spec = false, + play = true, + active = true, + width = 50, + posX = 0, + pic = pics["sharePic"], + }) + + defineModule("chat", { + name = "chat", + spec = false, + play = true, + active = false, + width = 18, + posX = 0, + pic = pics["chatPic"], + }) + + local alliance = defineModule("alliance", { + name = "ally", + spec = false, + play = true, + active = true, + width = 16, + posX = 0, + pic = pics["allyPic"], + noPic = false, + }) + + if not drawAllyButton then + alliance.width = 0 + end + + local modules = { + indent, + moduleRefs.rank, + moduleRefs.country, + moduleRefs.allyID, + moduleRefs.ID, + moduleRefs.playerID, + -- moduleRefs.side, + moduleRefs.name, + moduleRefs.skill, + moduleRefs.resources, + moduleRefs.income, + moduleRefs.cpuping, + moduleRefs.alliance, + moduleRefs.share, + moduleRefs.chat, + } + + local m_point = { + active = true, + default = true, + } + + local m_take = { + active = true, + default = true, + pic = pics["takePic"], + } + + return modules, moduleRefs, m_point, m_take +end + diff --git a/luaui/Widgets/gui_chat.lua b/luaui/Widgets/gui_chat.lua index 4de7c42cc4c..7a96e892c82 100644 --- a/luaui/Widgets/gui_chat.lua +++ b/luaui/Widgets/gui_chat.lua @@ -36,6 +36,7 @@ local LineTypes = { } local utf8 = VFS.Include('common/luaUtilities/utf8.lua') +local TeamTransfer = VFS.Include("common/luaUtilities/team_transfer/team_transfer_unsynced.lua") local badWords = VFS.Include('luaui/configs/badwords.lua') local L_DEPRECATED = LOG.DEPRECATED @@ -109,6 +110,7 @@ local allowMultiAutocomplete, allowMultiAutocompleteMax, maxLinesScrollFull = co local lineTTL, consoleLineCleanupTarget, soundErrorsLimit = config.lineTTL, config.consoleLineCleanupTarget, config.soundErrorsLimit local maxLinesScrollChatInput = config.maxLinesScrollChatInput local maxLinesScroll = config.maxLinesScroll +local scrollingPosY = config.scrollingPosY -- Color configuration (keep local for performance) local colorOther, colorAlly, colorSpec, colorSpecName = {1,1,1}, {0,1,0}, {1,1,0}, {1,1,1} @@ -696,18 +698,48 @@ local function addChatLine(gameFrame, lineType, name, nameText, text, orgLineID, local params = string.split(text, ':') local t = {} if params[1] then - for k,v in pairs(params) do - if k > 1 then - local pair = string.split(v, '=') - if pair[2] then - if playernames[pair[2]] then - t[ pair[1] ] = getPlayerColorString(pair[2], gameFrame)..playernames[pair[2]][7]..msgColor - elseif params[1]:lower():find('energy', nil, true) then - t[ pair[1] ] = energyValueColor..pair[2]..msgColor - elseif params[1]:lower():find('metal', nil, true) then - t[ pair[1] ] = metalValueColor..pair[2]..msgColor + local resourceType = nil + if params[1]:lower():find('energy', nil, true) then + resourceType = 'energy' + elseif params[1]:lower():find('metal', nil, true) then + resourceType = 'metal' + end + -- Check for explicit resourceType parameter (overrides key-based detection) + for i = 2, #params, 2 do + local key = params[i] + local value = params[i + 1] + if key == 'resourceType' and value then + resourceType = value + break + end + end + + for i = 2, #params, 2 do + local key = params[i] + local value = params[i + 1] + if key and value then + if key == 'resourceType' then + -- Skip the resourceType parameter itself + elseif playernames[value] then + t[key] = getPlayerColorString(value, gameFrame)..playernames[value][7]..msgColor + else + local shouldHighlight = false + if key:lower():find('energy', nil, true) or key:lower():find('metal', nil, true) then + shouldHighlight = true + else + shouldHighlight = TeamTransfer.Resources.SendTransferChatMessageProtocolHighlights[key] == true + end + + if shouldHighlight then + if resourceType == 'energy' then + t[key] = energyValueColor..value..msgColor + elseif resourceType == 'metal' then + t[key] = metalValueColor..value..msgColor + else + t[key] = value + end else - t[ pair[1] ] = pair[2] + t[key] = value end end end @@ -1211,6 +1243,10 @@ local function addLastUnitShareMessage() end function widget:UnitTaken(unitID, _, oldTeamID, newTeamID) + if Spring.GetGameRulesParam("isTakeInProgress") == 1 then + return + end + local oldAllyTeamID = select(6, spGetTeamInfo(oldTeamID)) local newAllyTeamID = select(6, spGetTeamInfo(newTeamID)) diff --git a/luaui/Widgets/gui_selectedunits_gl4.lua b/luaui/Widgets/gui_selectedunits_gl4.lua index 965b12c8a28..f3c040bb866 100644 --- a/luaui/Widgets/gui_selectedunits_gl4.lua +++ b/luaui/Widgets/gui_selectedunits_gl4.lua @@ -54,7 +54,6 @@ local pushElementInstance = InstanceVBOTable.pushElementInstance local popElementInstance = InstanceVBOTable.popElementInstance -local hasBadCulling = ((Platform.gpuVendor == "AMD" and Platform.osFamily == "Linux") == true) -- Localize for speedups: local glStencilFunc = gl.StencilFunc local glStencilOp = gl.StencilOp @@ -80,6 +79,9 @@ local unitWaterPass = {} local nextWaterPassCheckFrame = 0 local waterPassCheckInterval = 6 +local invalidUnits = {} -- units that are invalid for sharing with hovered player +local hoverListenerRegistered = false + local unitScale = {} local unitCanFly = {} local unitBuilding = {} @@ -167,11 +169,11 @@ local function AddPrimitiveAtUnit(unitID) width = radius length = radius end + local isInvalid = invalidUnits[unitID] ~= nil if selectionHighlight then - unitBufferUniformCache[1] = 1 + unitBufferUniformCache[1] = isInvalid and -1 or 1 gl.SetUnitBufferUniforms(unitID, unitBufferUniformCache, 6) end - --spEcho(unitID,radius,radius, spGetUnitTeam(unitID), numvertices, 1, gf) local targetVBO if unitCanFly[unitDefID] then -- Keep air on the same pre-unit path as ground for reliable visibility. @@ -182,14 +184,14 @@ local function AddPrimitiveAtUnit(unitID) unitWaterPass[unitID] = useWaterPass targetVBO = (useWaterPass and selectionVBOWater) or selectionVBOGround end - + local invalidFlag = isInvalid and 1 or 0 pushElementInstance( targetVBO, -- push into this Instance VBO Table { length, width, cornersize, additionalheight, -- lengthwidthcornerheight unitTeam[unitID], -- teamID numVertices, -- how many trianges should we make - gf, 0, 0, 0, -- the gameFrame (for animations), and any other parameters one might want to add + gf, invalidFlag, 0, 0, -- the gameFrame (for animations), and invalid flag 0, 1, 0, 1, -- These are our default UV atlas tranformations 0, 0, 0, 0 -- these are just padding zeros, that will get filled in }, @@ -200,12 +202,12 @@ local function AddPrimitiveAtUnit(unitID) ) end - local function DrawSelections(selectionVBO, isAir) if selectionVBO.usedElements > 0 then - if hasBadCulling then - gl.Culling(false) - end + -- Flat ground overlays: cull nothing. The NoGS fallback draws each shape as a + -- triangle fan whose winding varies by shape (e.g. aircraft triangles wind + -- opposite to circles), so back-face culling would drop those platters. + gl.Culling(false) glTexture(0, texture) selectShader:Activate() @@ -234,6 +236,7 @@ local function DrawSelections(selectionVBO, isAir) selectShader:Deactivate() glTexture(0, false) + gl.Culling(true) -- restore the world pass default for following widgets -- This is the correct way to exit out of the stencil mode, to not break drawing of area commands: @@ -275,6 +278,29 @@ local function RemovePrimitive(unitID) unitWaterPass[unitID] = nil end +local function OnHoverInvalidUnitsChanged(newHoverTeamID, newHoverPlayerID, invalidUnitIds) + local newInvalidUnits = {} + for _, unitID in ipairs(invalidUnitIds) do + newInvalidUnits[unitID] = true + end + + local oldInvalidUnits = invalidUnits + invalidUnits = newInvalidUnits + + -- Only rebuild primitives for units whose invalid state actually changed (otherwise we get flickering) + for unitID, _ in pairs(selUnits) do + if Spring.ValidUnitID(unitID) then + local wasInvalid = oldInvalidUnits[unitID] ~= nil + local isInvalid = newInvalidUnits[unitID] ~= nil + + if wasInvalid ~= isInvalid then + RemovePrimitive(unitID) + AddPrimitiveAtUnit(unitID) + end + end + end +end + function widget:SelectionChanged(sel) updateSelection = true end @@ -283,13 +309,14 @@ end local lastMouseOverUnitID = nil local lastMouseOverFeatureID = nil local cleanedForHiddenUI = false -local mouseOverUnitUniform = {0} +local mouseOverUnitUniform = {0, 0} local mouseOverFeatureUniform = {0} local function ClearLastMouseOver() if lastMouseOverUnitID then if spValidUnitID(lastMouseOverUnitID) then - mouseOverUnitUniform[1] = selUnits[lastMouseOverUnitID] and 1 or 0 + mouseOverUnitUniform[1] = invalidUnits[lastMouseOverUnitID] and -1 or (selUnits[lastMouseOverUnitID] and 1 or 0) + mouseOverUnitUniform[2] = 0 spSetUnitBufferUniforms(lastMouseOverUnitID, mouseOverUnitUniform, 6) end lastMouseOverUnitID = nil @@ -334,7 +361,6 @@ function widget:Update(dt) updateSelection = false local newSelUnits = {} - -- add to selection for i, unitID in ipairs(selectedUnits) do newSelUnits[unitID] = true if not selUnits[unitID] then @@ -366,6 +392,13 @@ function widget:Update(dt) end end + if not hoverListenerRegistered then + if WG['advplayerlist_api'] and WG['advplayerlist_api'].AddHoverInvalidUnitsListener then + WG['advplayerlist_api'].AddHoverInvalidUnitsListener(OnHoverInvalidUnitsChanged) + hoverListenerRegistered = true + end + end + -- We move the check for mouseovered units here, -- as this widget is the ground truth for our unitbufferuniform[2].z (#6) -- 0 means unit is un selected @@ -384,8 +417,9 @@ function widget:Update(dt) if lastMouseOverUnitID ~= unitID then ClearLastMouseOver() local newUniform = (selUnits[unitID] and 1 or 0 ) + 2 - mouseOverUnitUniform[1] = newUniform - spSetUnitBufferUniforms(unitID, mouseOverUnitUniform, 6) + -- Preserve the selection highlight value when setting mouseover + local currentHighlight = invalidUnits[unitID] and -1 or 1 + gl.SetUnitBufferUniforms(unitID, {currentHighlight, newUniform}, 6) lastMouseOverUnitID = unitID end elseif result == 'feature' and not guiHidden then @@ -431,7 +465,7 @@ local function init() shaderConfig.GROWTHRATE = 3.5 shaderConfig.TEAMCOLORIZATION = teamcolorOpacity -- not implemented, doing it via POST_SHADING below instead shaderConfig.HEIGHTOFFSET = 4 - shaderConfig.POST_SHADING = "fragColor.rgba = vec4(mix(g_color.rgb * texcolor.rgb + addRadius, vec3(1.0), "..(1-teamcolorOpacity)..") , texcolor.a * TRANSPARENCY + addRadius);" + shaderConfig.POST_SHADING = "fragColor.rgba = vec4(g_invalid > 0.5 ? vec3(1.0, 0.0, 0.0) : (g_color.rgb * texcolor.rgb + addRadius), texcolor.a * TRANSPARENCY + addRadius);" selectionVBOGround, selectShader = InitDrawPrimitiveAtUnit(shaderConfig, "selectedUnitsGround") if mapHasWater then selectionVBOWater = InitDrawPrimitiveAtUnit(shaderConfig, "selectedUnitsWater") @@ -502,6 +536,10 @@ function widget:Shutdown() Spring.LoadCmdColorsConfig('unitBox 0 1 0 1') end WG.selectedunits = nil + + if hoverListenerRegistered and WG['advplayerlist_api'] and WG['advplayerlist_api'].RemoveHoverInvalidUnitsListener then + WG['advplayerlist_api'].RemoveHoverInvalidUnitsListener(OnHoverInvalidUnitsChanged) + end end function widget:GetConfigData(data) diff --git a/luaui/Widgets/gui_tax_buildspeed_debuff.lua b/luaui/Widgets/gui_tax_buildspeed_debuff.lua index a45ce12bec7..768e992d10f 100644 --- a/luaui/Widgets/gui_tax_buildspeed_debuff.lua +++ b/luaui/Widgets/gui_tax_buildspeed_debuff.lua @@ -12,7 +12,8 @@ function widget:GetInfo() } end -if not Spring.GetModOptions().easytax then +local ModeEnums = VFS.Include("modes/sharing_mode_enums.lua") +if (tonumber(Spring.GetModOptions()[ModeEnums.ModOptions.ConstructorBuildDelay]) or 0) <= 0 then return false end diff --git a/luaui/Widgets/gui_teamstats.lua b/luaui/Widgets/gui_teamstats.lua index 72374e31da8..40911511330 100644 --- a/luaui/Widgets/gui_teamstats.lua +++ b/luaui/Widgets/gui_teamstats.lua @@ -99,6 +99,7 @@ local GetGaiaTeamID = Spring.GetGaiaTeamID local GetAllyTeamList = Spring.GetAllyTeamList local GetTeamList = Spring.GetTeamList local GetTeamStatsHistory = Spring.GetTeamStatsHistory +local ShareStats = VFS.Include("common/luaUtilities/economy/share_stats.lua") local GetTeamInfo = Spring.GetTeamInfo local GetPlayerInfo = Spring.GetPlayerInfo local GetMouseState = spGetMouseState @@ -336,6 +337,14 @@ function widget:GameFrame(n,forceupdate) history = history[#history] history.resourcesProduced = history.metalProduced + history.energyProduced/60 history.resourcesUsed = history.metalUsed + history.energyUsed/60 + -- sent/received are Lua-owned now (engine keeps only excess); fall back to + -- engine history values when no Lua stats published (vanilla / native sharing). + local mShare = ShareStats.Read(Spring, teamID, "metal") + local eShare = ShareStats.Read(Spring, teamID, "energy") + history.metalSent = mShare.sent or history.metalSent + history.energySent = eShare.sent or history.energySent + history.metalReceived = mShare.received or history.metalReceived + history.energyReceived = eShare.received or history.energyReceived history.resourcesExcess = history.metalExcess + history.energyExcess/60 history.resourcesSent = history.metalSent + history.energySent/60 history.resourcesReceived = history.metalReceived + history.energyReceived/60 diff --git a/luaui/Widgets/gui_top_bar.lua b/luaui/Widgets/gui_top_bar.lua index 5ffdb744fff..2dac1159c19 100644 --- a/luaui/Widgets/gui_top_bar.lua +++ b/luaui/Widgets/gui_top_bar.lua @@ -41,6 +41,11 @@ local sp = { GetGameSpeed = Spring.GetGameSpeed, } +local TeamTransfer = VFS.Include("common/luaUtilities/team_transfer/team_transfer_unsynced.lua") +local ShareStats = VFS.Include("common/luaUtilities/economy/share_stats.lua") + +local useRenderToTexture = Spring.GetConfigFloat("ui_rendertotexture", 1) == 1 -- much faster than drawing via DisplayLists only + -- Configuration (consolidated into table to save local slots) local cfg = { relXpos = 0.3, @@ -74,6 +79,8 @@ local _modOpts = Spring.GetModOptions() local isScenario = _modOpts ~= nil and _modOpts.scenariooptions ~= nil local chobbyLoaded = false local isSingle = false +local metalSharingEnabled = TeamTransfer.Resources.GetCachedPolicyResult(myTeamID, myTeamID, "metal").canShare +local energySharingEnabled = TeamTransfer.Resources.GetCachedPolicyResult(myTeamID, myTeamID, "energy").canShare local gameStarted = (sp.GetGameFrame() > 0) local gameFrame = sp.GetGameFrame() local gameIsOver = false @@ -81,15 +88,18 @@ local graphsWindowVisible = false -- Resources local r = { metal = { sp.GetTeamResources(myTeamID, 'metal') }, energy = { sp.GetTeamResources(myTeamID, 'energy') } } -local energyOverflowLevel, metalOverflowLevel -local allyteamOverflowingMetal = false -local allyteamOverflowingEnergy = false -local overflowingMetal = false -local overflowingEnergy = false -local showOverflowTooltip = {} +local overflow = { + metalLevel = nil, + energyLevel = nil, + allyMetal = false, + allyEnergy = false, + metal = false, + energy = false, + tooltipTime = {}, + supressNotifs = false, + isMetalmap = false, +} local showingWarning = { metal = false, energy = false } -local supressOverflowNotifs = false -local isMetalmap = false -- Wind + tide local avgWindValue, riskWindValue @@ -582,17 +592,17 @@ local function updateResbarText(res, force) if not spec and gameFrame > cfg.spawnWarpInFrame then -- display overflow notification - if (res == 'metal' and (allyteamOverflowingMetal or overflowingMetal)) or (res == 'energy' and (allyteamOverflowingEnergy or overflowingEnergy)) then - if not showOverflowTooltip[res] then showOverflowTooltip[res] = now + 1.1 end + if (res == 'metal' and (overflow.allyMetal or overflow.metal)) or (res == 'energy' and (overflow.allyEnergy or overflow.energy)) then + if not overflow.tooltipTime[res] then overflow.tooltipTime[res] = now + 1.1 end - if showOverflowTooltip[res] < now then + if overflow.tooltipTime[res] < now then local bgpadding2 = 2.2 * widgetScale local text = '' if res == 'metal' then - text = (allyteamOverflowingMetal and ' ' .. Spring.I18N('ui.topbar.resources.wastingMetal') .. ' ' or ' ' .. Spring.I18N('ui.topbar.resources.overflowing') .. ' ') - if not supressOverflowNotifs and WG['notifications'] and not isMetalmap and (not WG.sharedMetalFrame or WG.sharedMetalFrame+60 < gameFrame) then - if allyteamOverflowingMetal then + text = (overflow.allyMetal and ' ' .. Spring.I18N('ui.topbar.resources.wastingMetal') .. ' ' or ' ' .. Spring.I18N('ui.topbar.resources.overflowing') .. ' ') + if not overflow.supressNotifs and WG['notifications'] and not overflow.isMetalmap and (not WG.sharedMetalFrame or WG.sharedMetalFrame+60 < gameFrame) then + if overflow.allyMetal then if numTeamsInAllyTeam > 1 then WG['notifications'].queueNotification('WholeTeamWastingMetal') else @@ -603,9 +613,9 @@ local function updateResbarText(res, force) end end else - text = (allyteamOverflowingEnergy and ' ' .. Spring.I18N('ui.topbar.resources.wastingEnergy') .. ' ' or ' ' .. Spring.I18N('ui.topbar.resources.overflowing') .. ' ') - if not supressOverflowNotifs and WG['notifications'] and (not WG.sharedEnergyFrame or WG.sharedEnergyFrame+60 < gameFrame) then - if allyteamOverflowingEnergy then + text = (overflow.allyEnergy and ' ' .. Spring.I18N('ui.topbar.resources.wastingEnergy') .. ' ' or ' ' .. Spring.I18N('ui.topbar.resources.overflowing') .. ' ') + if not overflow.supressNotifs and WG['notifications'] and (not WG.sharedEnergyFrame or WG.sharedEnergyFrame+60 < gameFrame) then + if overflow.allyEnergy then if numTeamsInAllyTeam > 1 then WG['notifications'].queueNotification('WholeTeamWastingEnergy') else @@ -629,7 +639,7 @@ local function updateResbarText(res, force) -- background local color1, color2, color3, color4 if res == 'metal' then - if allyteamOverflowingMetal then + if overflow.allyMetal then color1 = { 0.35, 0.1, 0.1, 1 } color2 = { 0.25, 0.05, 0.05, 1 } color3 = { 1, 0.3, 0.3, 0.25 } @@ -641,7 +651,7 @@ local function updateResbarText(res, force) color4 = { 1, 1, 1, 0.44 } end else - if allyteamOverflowingEnergy then + if overflow.allyEnergy then color1 = { 0.35, 0.1, 0.1, 1 } color2 = { 0.25, 0.05, 0.05, 1 } color3 = { 1, 0.3, 0.3, 0.25 } @@ -680,7 +690,7 @@ local function updateResbarText(res, force) cache.lastWarning[res] = nil if showingWarning[res] then showingWarning[res] = false; updateRes[res][3] = true end - showOverflowTooltip[res] = nil + overflow.tooltipTime[res] = nil end end end @@ -853,11 +863,12 @@ local function updateResbar(res) end -- Share slider - if not isSingle then + local sharingEnabled = res == 'energy' and energySharingEnabled or metalSharingEnabled + if not isSingle and sharingEnabled then if res == 'energy' then - energyOverflowLevel = r[res][6] + overflow.energyLevel = r[res][6] else - metalOverflowLevel = r[res][6] + overflow.metalLevel = r[res][6] end local value = r[res][6] @@ -1128,10 +1139,10 @@ function widget:GameFrame(n) end local function updateAllyTeamOverflowing() - allyteamOverflowingMetal = false - allyteamOverflowingEnergy = false - overflowingMetal = false - overflowingEnergy = false + overflow.allyMetal = false + overflow.allyEnergy = false + overflow.metal = false + overflow.energy = false local totalEnergy = 0 local totalEnergyStorage = 0 local totalMetal = 0 @@ -1149,17 +1160,20 @@ local function updateAllyTeamOverflowing() totalMetal = totalMetal + metal totalMetalStorage = totalMetalStorage + metalStorage if teamID == myTeamID then + -- sent is Lua-owned now (engine keeps only excess); fall back to engine resSent (vanilla) + energySent = ShareStats.Read(sp, teamID, "energy").sentRecent or energySent + metalSent = ShareStats.Read(sp, teamID, "metal").sentRecent or metalSent energyPercentile = energySent / totalEnergyStorage metalPercentile = metalSent / totalMetalStorage if energyPercentile > 0.0001 then - overflowingEnergy = energyPercentile * 40 -- (1 / 0.025) = 40 - if overflowingEnergy > 1 then overflowingEnergy = 1 end + overflow.energy = energyPercentile * 40 -- (1 / 0.025) = 40 + if overflow.energy > 1 then overflow.energy = 1 end end if metalPercentile > 0.0001 then - overflowingMetal = metalPercentile * 40 -- (1 / 0.025) = 40 - if overflowingMetal > 1 then overflowingMetal = 1 end + overflow.metal = metalPercentile * 40 -- (1 / 0.025) = 40 + if overflow.metal > 1 then overflow.metal = 1 end end end end @@ -1168,13 +1182,13 @@ local function updateAllyTeamOverflowing() metalPercentile = totalMetal / totalMetalStorage if energyPercentile > 0.975 then - allyteamOverflowingEnergy = (energyPercentile - 0.975) * 40 -- (1 / 0.025) = 40 - if allyteamOverflowingEnergy > 1 then allyteamOverflowingEnergy = 1 end + overflow.allyEnergy = (energyPercentile - 0.975) * 40 -- (1 / 0.025) = 40 + if overflow.allyEnergy > 1 then overflow.allyEnergy = 1 end end if metalPercentile > 0.975 then - allyteamOverflowingMetal = (metalPercentile - 0.975) * 40 -- (1 / 0.025) = 40 - if allyteamOverflowingMetal > 1 then allyteamOverflowingMetal = 1 end + overflow.allyMetal = (metalPercentile - 0.975) * 40 -- (1 / 0.025) = 40 + if overflow.allyMetal > 1 then overflow.allyMetal = 1 end end end @@ -1277,11 +1291,11 @@ function widget:Update(dt) -- make sure conversion/overflow sliders are adjusted if mmLevel then local currentMmLevel = sp.GetTeamRulesParam(myTeamID, 'mmLevel') - if mmLevel ~= currentMmLevel or energyOverflowLevel ~= r['energy'][6] then + if mmLevel ~= currentMmLevel or overflow.energyLevel ~= r['energy'][6] then mmLevel = currentMmLevel updateResbar('energy') end - if metalOverflowLevel ~= r['metal'][6] then + if overflow.metalLevel ~= r['metal'][6] then updateResbar('metal') end end @@ -1290,11 +1304,11 @@ function widget:Update(dt) -- make sure conversion/overflow sliders are adjusted if mmLevel then local currentMmLevel = sp.GetTeamRulesParam(myTeamID, 'mmLevel') - if mmLevel ~= currentMmLevel or energyOverflowLevel ~= r['energy'][6] then + if mmLevel ~= currentMmLevel or overflow.energyLevel ~= r['energy'][6] then mmLevel = currentMmLevel updateResbar('energy') end - if metalOverflowLevel ~= r['metal'][6] then + if overflow.metalLevel ~= r['metal'][6] then updateResbar('metal') end end @@ -1331,6 +1345,18 @@ function widget:Update(dt) updateResbarText('metal') updateResbarText('energy') + -- sharing policy can change mid-game (e.g. tech_core on tech-up) + local newMetalSharing = TeamTransfer.Resources.GetCachedPolicyResult(myTeamID, myTeamID, "metal").canShare + local newEnergySharing = TeamTransfer.Resources.GetCachedPolicyResult(myTeamID, myTeamID, "energy").canShare + if newMetalSharing ~= metalSharingEnabled then + metalSharingEnabled = newMetalSharing + updateResbar('metal') + end + if newEnergySharing ~= energySharingEnabled then + energySharingEnabled = newEnergySharing + updateResbar('energy') + end + -- wind currentWind = stringFormat('%.1f', select(4, sp.GetWind())) @@ -1389,11 +1415,11 @@ local function drawResBars() if dlist.resbar[res][1] and dlist.resbar[res][2] then if not spec and gameFrame > cfg.spawnWarpInFrame and dlist.resbar[res][4] then glBlending(GL.SRC_ALPHA, GL.ONE) - if allyteamOverflowingMetal then - glColor(1, 0, 0, 0.1 * allyteamOverflowingMetal * blinkProgress) + if overflow.allyMetal then + glColor(1, 0, 0, 0.1 * overflow.allyMetal * blinkProgress) glCallList(dlist.resbar[res][4]) -- flash bar - elseif overflowingMetal then - glColor(1, 1, 1, 0.04 * overflowingMetal * (0.6 + (blinkProgress * 0.4))) + elseif overflow.metal then + glColor(1, 1, 1, 0.04 * overflow.metal * (0.6 + (blinkProgress * 0.4))) glCallList(dlist.resbar[res][4]) -- flash bar elseif r[res][1] < 1000 then local process = (r[res][1] / r[res][2]) * 13 @@ -1414,18 +1440,29 @@ local function drawResBars() glCallList(dlist.resbar[res][2]) -- sliders end - if showOverflowTooltip[res] and dlist.resbar[res][7] then glCallList(dlist.resbar[res][7]) end -- overflow warning + if not useRenderToTexture then + if dlist.resValuesBar[res] then + glCallList(dlist.resValuesBar[res]) -- res bar value + end + if dlist.resbar[res][6] then + glCallList(dlist.resbar[res][6]) -- storage + end + if dlist.resbar[res][3] then + glCallList(dlist.resbar[res][3]) -- pull, expense, income + end + end + if overflow.tooltipTime[res] and dlist.resbar[res][7] then glCallList(dlist.resbar[res][7]) end -- overflow warning end res = 'energy' if dlist.resbar[res][1] and dlist.resbar[res][2] then if not spec and gameFrame > cfg.spawnWarpInFrame and dlist.resbar[res][4] then glBlending(GL.SRC_ALPHA, GL.ONE) - if allyteamOverflowingEnergy then - glColor(1, 0, 0, 0.1 * allyteamOverflowingEnergy * blinkProgress) + if overflow.allyEnergy then + glColor(1, 0, 0, 0.1 * overflow.allyEnergy * blinkProgress) glCallList(dlist.resbar[res][4]) -- flash bar - elseif overflowingEnergy then - glColor(1, 1, 0, 0.04 * overflowingEnergy * (0.6 + (blinkProgress * 0.4))) + elseif overflow.energy then + glColor(1, 1, 0, 0.04 * overflow.energy * (0.6 + (blinkProgress * 0.4))) glCallList(dlist.resbar[res][4]) -- flash bar elseif r[res][1] < 2000 then local process = (r[res][1] / r[res][2]) * 13 @@ -1449,7 +1486,18 @@ local function drawResBars() glCallList(dlist.resbar[res][2]) -- sliders end - if showOverflowTooltip[res] and dlist.resbar[res][7] then glCallList(dlist.resbar[res][7]) end -- overflow warning + if not useRenderToTexture then + if dlist.resValuesBar[res] then + glCallList(dlist.resValuesBar[res]) -- res bar value + end + if dlist.resbar[res][6] then + glCallList(dlist.resbar[res][6]) -- storage + end + if dlist.resbar[res][3] then + glCallList(dlist.resbar[res][3]) -- pull, expense, income + end + end + if overflow.tooltipTime[res] and dlist.resbar[res][7] then glCallList(dlist.resbar[res][7]) end -- overflow warning end glPopMatrix() @@ -2141,17 +2189,17 @@ function widget:MousePress(x, y, button) end if not spec then - if not isSingle then - if mathIsInRect(x, y, shareIndicatorArea['metal'][1], shareIndicatorArea['metal'][2], shareIndicatorArea['metal'][3], shareIndicatorArea['metal'][4]) then + if not isSingle and metalSharingEnabled then + if shareIndicatorArea['metal'][1] and mathIsInRect(x, y, shareIndicatorArea['metal'][1], shareIndicatorArea['metal'][2], shareIndicatorArea['metal'][3], shareIndicatorArea['metal'][4]) then draggingShareIndicator = 'metal' end - if mathIsInRect(x, y, shareIndicatorArea['energy'][1], shareIndicatorArea['energy'][2], shareIndicatorArea['energy'][3], shareIndicatorArea['energy'][4]) then + if shareIndicatorArea['energy'][1] and mathIsInRect(x, y, shareIndicatorArea['energy'][1], shareIndicatorArea['energy'][2], shareIndicatorArea['energy'][3], shareIndicatorArea['energy'][4]) then draggingShareIndicator = 'energy' end end - if not draggingShareIndicator and mathIsInRect(x, y, conversionIndicatorArea[1], conversionIndicatorArea[2], conversionIndicatorArea[3], conversionIndicatorArea[4]) then + if not draggingShareIndicator and conversionIndicatorArea and mathIsInRect(x, y, conversionIndicatorArea[1], conversionIndicatorArea[2], conversionIndicatorArea[3], conversionIndicatorArea[4]) then draggingConversionIndicator = true end @@ -2252,7 +2300,7 @@ function widget:Initialize() if select(4,Spring.GetTeamInfo(teamID,false)) then -- is AI? local luaAI = Spring.GetTeamLuaAI(teamID) if luaAI and luaAI ~= "" and (string.find(luaAI, 'Scavengers') or string.find(luaAI, 'Raptors')) then - supressOverflowNotifs = true + overflow.supressNotifs = true break end end @@ -2341,7 +2389,7 @@ function widget:Initialize() end if WG['resource_spot_finder'] and WG['resource_spot_finder'].metalSpotsList and #WG['resource_spot_finder'].metalSpotsList > 0 and #WG['resource_spot_finder'].metalSpotsList <= 2 then -- probably speedmetal kind of map - isMetalmap = true + overflow.isMetalmap = true end end diff --git a/luaui/barwidgets.lua b/luaui/barwidgets.lua index 36e1d9cc462..d2c3a6d9718 100644 --- a/luaui/barwidgets.lua +++ b/luaui/barwidgets.lua @@ -213,6 +213,7 @@ local callInLists = { 'CommandsChanged', 'LanguageChanged', 'UnitBlocked', + 'SharePolicyChanged', 'VisibleUnitAdded', 'VisibleUnitRemoved', 'VisibleUnitsChanged', @@ -2371,6 +2372,14 @@ function widgetHandler:UnitBlocked(unitDefID, reasons) tracy.ZoneEnd() end +function widgetHandler:SharePolicyChanged(teamID, domain) + tracy.ZoneBeginN("W:SharePolicyChanged") + for _, w in ipairs(self.SharePolicyChangedList) do + w:SharePolicyChanged(teamID, domain) + end + tracy.ZoneEnd() +end + -------------------------------------------------------------------------------- -- diff --git a/modoptions.lua b/modoptions.lua index fcb09fd6554..fc320fe7ae9 100644 --- a/modoptions.lua +++ b/modoptions.lua @@ -309,6 +309,13 @@ local options = { }, }, + { + key = "sub_header", + name = "-- Tech", + type = "subheader", + section = ModeEnums.ModeCategories.Sharing, + def = true, + }, { key = ModeEnums.ModOptions.TechBlocking, name = "Tech Blocking", @@ -406,44 +413,8 @@ local options = { min = 0, max = 120, step = 1, - column = 2, - }, - { - key = ModeEnums.ModOptions.TakeMode, - name = "Take Mode", - desc = "Controls what happens when a player uses /take on an inactive ally's units", - type = "list", - section = ModeEnums.ModeCategories.Sharing, - def = ModeEnums.TakeMode.Enabled, - items = { - { key = ModeEnums.TakeMode.Enabled, name = "Enabled", desc = "All units transfer immediately" }, - { key = ModeEnums.TakeMode.Disabled, name = "Disabled", desc = "Taking is not allowed" }, - { key = ModeEnums.TakeMode.StunDelay, name = "Stun Delay", desc = "Units transfer immediately but affected units are stunned for the delay duration" }, - { key = ModeEnums.TakeMode.TakeDelay, name = "Take Delay", desc = "Unaffected units transfer immediately; affected units require a second /take after the delay" }, - }, - }, - { - key = ModeEnums.ModOptions.TakeDelaySeconds, - name = "Take Delay (seconds)", - desc = "Duration of stun (Stun Delay mode) or wait before second /take (Take Delay mode)", - type = "number", - section = ModeEnums.ModeCategories.Sharing, - def = 30, - min = 0, - max = 120, - step = 1, column = 1, }, - { - key = ModeEnums.ModOptions.TakeDelayCategory, - name = "Take Delay Category", - desc = "Which units are affected by the take delay: stunned in Stun Delay mode, or held back in Take Delay mode", - type = "list", - section = ModeEnums.ModeCategories.Sharing, - def = ModeEnums.UnitCategory.Resource, - column = 2, - items = unitSharingCategoriesWithAll, - }, { key = "sub_header", @@ -497,9 +468,53 @@ local options = { max = 1.0, step = 0.01, }, + + { + key = "sub_header", + name = "-- Takes", + type = "subheader", + section = ModeEnums.ModeCategories.Sharing, + def = true, + }, + { + key = ModeEnums.ModOptions.TakeMode, + name = "Take Mode", + desc = "Controls what happens when a player uses /take on an inactive ally's units", + type = "list", + section = ModeEnums.ModeCategories.Sharing, + def = ModeEnums.TakeMode.Enabled, + items = { + { key = ModeEnums.TakeMode.Enabled, name = "Enabled", desc = "All units transfer immediately" }, + { key = ModeEnums.TakeMode.Disabled, name = "Disabled", desc = "Taking is not allowed" }, + { key = ModeEnums.TakeMode.StunDelay, name = "Stun Delay", desc = "Units transfer immediately but affected units are stunned for the delay duration" }, + { key = ModeEnums.TakeMode.TakeDelay, name = "Take Delay", desc = "Unaffected units transfer immediately; affected units require a second /take after the delay" }, + }, + }, + { + key = ModeEnums.ModOptions.TakeDelaySeconds, + name = "Take Delay (seconds)", + desc = "Duration of stun (Stun Delay mode) or wait before second /take (Take Delay mode)", + type = "number", + section = ModeEnums.ModeCategories.Sharing, + def = 30, + min = 0, + max = 120, + step = 1, + column = 1, + }, + { + key = ModeEnums.ModOptions.TakeDelayCategory, + name = "Take Delay Category", + desc = "Which units are affected by the take delay: stunned in Stun Delay mode, or held back in Take Delay mode", + type = "list", + section = ModeEnums.ModeCategories.Sharing, + def = ModeEnums.UnitCategory.Resource, + column = 2, + items = unitSharingCategoriesWithAll, + }, { key = "sub_header", - name = "-- Allied Interactions", + name = "-- Allies", desc = "", section = ModeEnums.ModeCategories.Sharing, type = "subheader",