Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions common/luaUtilities/economy/share_stats.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
-- Lua-side sent/received sharing stats via team rules params (engine keeps only excess; sent/received are conserved, tracked Lua-side per RecoilEngine#3032)

local ResourceTypes = VFS.Include("gamedata/resource_types.lua")
local METAL = ResourceTypes.METAL

local ShareStats = {}

local function suffix(resourceType)
return resourceType == METAL and "m" or "e"
end

-- per-team rules-param keys: cumulative sent/received + last tick's send (top bar overflow indicator)
local function cumSentKey(rt) return "sharestat_" .. suffix(rt) .. "_sent" end
local function cumRecvKey(rt) return "sharestat_" .. suffix(rt) .. "_received" end
local function recentSentKey(rt) return "sharestat_" .. suffix(rt) .. "_sent_recent" end
local function recentRecvKey(rt) return "sharestat_" .. suffix(rt) .. "_received_recent" end

ShareStats.cumSentKey = cumSentKey
ShareStats.cumRecvKey = cumRecvKey
ShareStats.recentSentKey = recentSentKey
ShareStats.recentRecvKey = recentRecvKey

-- allies (and spectators) can read; matches the visibility of the engine stats it replaces
local RULES_ACCESS = { allied = true }

---Record one cadence tick of solver results into the per-team rules params.
---@param springRepo SpringSynced
---@param results EconomyTeamResult[]
function ShareStats.Publish(springRepo, results)
for i = 1, #results do
local r = results[i]
local rt = r.resourceType
local sent = (springRepo.GetTeamRulesParam(r.teamId, cumSentKey(rt)) or 0) + (r.sent or 0)
local received = (springRepo.GetTeamRulesParam(r.teamId, cumRecvKey(rt)) or 0) + (r.received or 0)
springRepo.SetTeamRulesParam(r.teamId, cumSentKey(rt), sent, RULES_ACCESS)
springRepo.SetTeamRulesParam(r.teamId, cumRecvKey(rt), received, RULES_ACCESS)
springRepo.SetTeamRulesParam(r.teamId, recentSentKey(rt), r.sent or 0, RULES_ACCESS)
springRepo.SetTeamRulesParam(r.teamId, recentRecvKey(rt), r.received or 0, RULES_ACCESS)
end
end

---Read a team's sharing stats for one resource. Fields are nil when no Lua stats have
---been published (e.g. vanilla / native sharing), letting callers fall back to engine values.
---@param springApi table Spring (or a synced-repo) exposing GetTeamRulesParam
---@param teamID number
---@param resourceType ResourceName
---@return { sent: number?, received: number?, sentRecent: number?, receivedRecent: number? }
function ShareStats.Read(springApi, teamID, resourceType)
return {
sent = springApi.GetTeamRulesParam(teamID, cumSentKey(resourceType)),
received = springApi.GetTeamRulesParam(teamID, cumRecvKey(resourceType)),
sentRecent = springApi.GetTeamRulesParam(teamID, recentSentKey(resourceType)),
receivedRecent = springApi.GetTeamRulesParam(teamID, recentRecvKey(resourceType)),
}
end

return ShareStats
56 changes: 56 additions & 0 deletions common/luaUtilities/team_transfer/gui_advplayerlist/validation.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
--- Unit validation helpers for advplayerslist.lua
--- partition depends only on the sender's modes, so memoise once per selection, not per player
local UnitShared = VFS.Include("common/luaUtilities/team_transfer/unit_transfer_shared.lua")

local UnitValidationHelpers = {}

-- The selection these memos describe (nil when nothing is selected).
local currentSelection = nil
-- Memoised ValidateUnits result for a shareable (canShare) receiver under currentSelection.
local sharedPartition = nil
-- Memoised trivial result for a non-shareable receiver (ValidateUnits short-circuits).
local deniedResult = nil
-- separate backing tables so ValidateUnits fills in place and both can be live in one draw pass
local sharedScratch = {}
local deniedScratch = {}

---Record the active selection and drop the per-selection memos; partition computed lazily later.
---@param selectedUnits number[]?
function UnitValidationHelpers.SetSelection(selectedUnits)
currentSelection = (selectedUnits and #selectedUnits > 0) and selectedUnits or nil
sharedPartition = nil
deniedResult = nil
end

---Drop the memos without touching the selection; used when our own sharing policy changes.
function UnitValidationHelpers.InvalidateValidations()
sharedPartition = nil
deniedResult = nil
end

---Validate the current selection for a single receiver, memoised; nil when nothing selected.
---@param myTeamID number
---@param receiverTeamID number
---@return UnitValidationResult | nil
function UnitValidationHelpers.GetPlayerUnitValidation(myTeamID, receiverTeamID)
if not currentSelection then
return nil
end

local policyResult = UnitShared.GetCachedPolicyResult(myTeamID, receiverTeamID, Spring)
if not policyResult.canShare then
-- denied receivers all short-circuit to the same empty partition, so compute once
if not deniedResult then
deniedResult = UnitShared.ValidateUnits(policyResult, currentSelection, Spring, nil, deniedScratch)
end
return deniedResult
end

-- Identical for every shareable receiver (modes are the sender's), so compute once.
if not sharedPartition then
sharedPartition = UnitShared.ValidateUnits(policyResult, currentSelection, Spring, nil, sharedScratch)
end
return sharedPartition
end

return UnitValidationHelpers
6 changes: 3 additions & 3 deletions gamedata/modrules.lua
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,10 @@ local modrules = {
},

system = {
allowTake = true, -- Enables and disables the /take UI command.
allowTake = false, -- Engine /take is disabled; the Lua take system (cmd_take) owns /take so it falls through to LuaUI.
LuaAllocLimit = 1536, -- default: 1536. Global Lua alloc limit (in megabytes)
enableSmoothMesh = true,

pathFinderSystem = useQTPFS and 1 or 0, -- Which pathfinder does the game use? Can be 0 - The legacy default pathfinder, 1 - Quad-Tree Pathfinder System (QTPFS) or -1 - disabled.
--pathFinderUpdateRate = 0.0001, -- default: 0.007. Controls how often the pathfinder updates; larger values means more rapid updates
pathFinderRawDistMult = 100000, -- default: 1.25. Engine does raw move with a limited distance, this multiplier adjusts that
Expand All @@ -102,7 +102,7 @@ local modrules = {
pfUpdateRateScale = 1, -- default: 1. Multiplier for the update rate
pfRawMoveSpeedThreshold = 0, -- default: 0. Controls the speed modifier (which includes typemap boosts and up/down hill modifiers) under which units will never do raw move, regardless of distance etc. Defaults to 0, which means units will not try to raw-move into unpathable terrain (e.g. typemapped lava, cliffs, water). You can set it to some positive value to make them avoid pathable but very slow terrain (for example if you set it to 0.2 then they will not raw-move across terrain where they move at 20% speed or less, and will use normal pathing instead - which may still end up taking them through that path).
pfHcostMult = 0.2, -- default: 0.2. A float value between 0 and 2. Controls how aggressively the pathing search prioritizes nodes going in the direction of the goal. Higher values mean pathing is cheaper, but can start producing degenerate paths where the unit goes straight at the goal and then has to hug a wall.
nativeExcessSharing = Spring.GetModOptions().easytax==false and Spring.GetModOptions().tax_resource_sharing_amount==0, -- default: true. If true, the engine will handle resource overflow sharing between allied teams. If false, overflow sharing is disabled and we use Lua implementation in game_tax_resource_sharing.lua gadget.
nativeExcessSharing = false, -- default: true. If true, the engine will handle resource overflow sharing between allied teams. If false, overflow sharing is disabled and we use Lua implementation in game_tax_resource_sharing.lua gadget.
},

transportability = {
Expand Down
25 changes: 24 additions & 1 deletion luarules/gadgets.lua
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ local callInLists = {
"GameOver",
"GameID",
"TeamDied",
"TeamShare",
"ResourceExcess",

"PlayerAdded",
"PlayerChanged",
Expand Down Expand Up @@ -787,6 +789,7 @@ function gadgetHandler:RemoveGadgetRaw(gadget)
for _, listname in ipairs(callInLists) do
ArrayRemove(self[listname .. 'List'], gadget)
end

self:DeregisterAllowCommands(gadget)

for id, g in pairs(self.CMDIDs) do
Expand All @@ -803,7 +806,7 @@ end

function gadgetHandler:UpdateCallIn(name)
local listName = name .. 'List'
local forceUpdate = (name == 'GotChatMsg' or name == 'RecvFromSynced') -- redundant?
local forceUpdate = (name == 'GotChatMsg' or name == 'RecvFromSynced')

_G[name] = nil

Expand Down Expand Up @@ -1307,6 +1310,26 @@ function gadgetHandler:TeamDied(teamID)
return
end

function gadgetHandler:TeamShare(teamID, targetTeamID, metalShare, energyShare)
for _, g in ipairs(self.TeamShareList) do
g:TeamShare(teamID, targetTeamID, metalShare, energyShare)
end
return
end

-- Engine fires this every frame (CSyncedLuaHandle::ResourceExcess); returning true from any
-- gadget tells the engine that overflow was handled, so it skips native buffering. Single
-- owner is a game-side convention.
function gadgetHandler:ResourceExcess(excesses)
local handled = false
for _, g in ipairs(self.ResourceExcessList) do
if g:ResourceExcess(excesses) then
handled = true
end
end
return handled
end

function gadgetHandler:TeamChanged(teamID)
for _, g in ipairs(self.TeamChangedList) do
g:TeamChanged(teamID)
Expand Down
17 changes: 8 additions & 9 deletions luarules/gadgets/ai_simpleai.lua
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ local MakeHashedPosTable = VFS.Include("luarules/utilities/damgam_lib/hashpostab
local HashPosTable = MakeHashedPosTable()

local positionCheckLibrary = VFS.Include("luarules/utilities/damgam_lib/position_checks.lua")
local ResourceTypes = VFS.Include("gamedata/resource_types.lua")

-- manually appoint units to avoid making
-- (note that transports, stockpilers and objects/walls are auto skipped)
Expand Down Expand Up @@ -187,12 +188,10 @@ local spGetUnitPosition = Spring.GetUnitPosition
local spGetUnitCommandCount = Spring.GetUnitCommandCount
local spGetUnitHealth = Spring.GetUnitHealth
local spGetUnitAllyTeam = Spring.GetUnitAllyTeam
local spGetTeamResources = Spring.GetTeamResources
local spTestBuildOrder = Spring.TestBuildOrder
local spGetFullBuildQueue = Spring.GetFullBuildQueue
local spGetTeamUnits = Spring.GetTeamUnits
local spGetAllUnits = Spring.GetAllUnits
local spSetTeamResource = Spring.SetTeamResource
local spGetTeamInfo = Spring.GetTeamInfo
local spGetTeamLuaAI = Spring.GetTeamLuaAI
local spDgunCommand = CMD.DGUN
Expand Down Expand Up @@ -320,8 +319,8 @@ local function SimpleConstructionProjectSelection(unitID, unitDefID, unitTeam, u
--tracy.ZoneBeginN("SimpleAI:SimpleConstructionProjectSelection")
local success = false

local mcurrent, mstorage, _, _, _ = spGetTeamResources(unitTeam, "metal")
local ecurrent, estorage, _, _, _ = spGetTeamResources(unitTeam, "energy")
local mcurrent, mstorage, _, _, _ = GG.GetTeamResources(unitTeam, "metal")
local ecurrent, estorage, _, _, _ = GG.GetTeamResources(unitTeam, "energy")
local unitposx, _, unitposz = spGetUnitPosition(unitID)

local buildOptions = BuildOptions[unitDefID]
Expand Down Expand Up @@ -487,15 +486,15 @@ if gadgetHandler:IsSyncedCode() then
--tracy.ZoneBeginN("SimpleAI:GameFrame")
local teamID = SimpleAITeamIDs[i]
local _, _, _, _, _, allyTeamID = spGetTeamInfo(teamID)
local mcurrent, mstorage = spGetTeamResources(teamID, "metal")
local ecurrent, estorage = spGetTeamResources(teamID, "energy")
local mcurrent, mstorage = GG.GetTeamResources(teamID, "metal")
local ecurrent, estorage = GG.GetTeamResources(teamID, "energy")

-- resource boost (teamID is always in SimpleAITeamIDs)
if mcurrent < mstorage * 0.20 then
spSetTeamResource(teamID, "m", mstorage * 0.25)
Spring.SetTeamResource(teamID, ResourceTypes.METAL, mstorage * 0.25)
end
if ecurrent < estorage * 0.20 then
spSetTeamResource(teamID, "e", estorage * 0.25)
Spring.SetTeamResource(teamID, ResourceTypes.ENERGY, estorage * 0.25)
end

local luaAI = spGetTeamLuaAI(teamID)
Expand Down Expand Up @@ -528,7 +527,7 @@ if gadgetHandler:IsSyncedCode() then

if nearestEnemy and unitHealthPercentage > 30 then
if ecurrent < estorage*0.9 then
spSetTeamResource(teamID, "e", estorage*0.9)
Spring.SetTeamResource(teamID, ResourceTypes.ENERGY, estorage * 0.9)
end
spGiveOrderToUnit(unitID, spDgunCommand, {nearestEnemy}, 0)
local nearestEnemies = spGetUnitsInCylinder(unitposx, unitposz, 300)
Expand Down
4 changes: 2 additions & 2 deletions luarules/gadgets/cmd_dev_helpers.lua
Original file line number Diff line number Diff line change
Expand Up @@ -633,8 +633,8 @@ if gadgetHandler:IsSyncedCode() then
local teamID = Spring.GetUnitTeam(unitID)
local unitDefID = Spring.GetUnitDefID(unitID)
Spring.DestroyUnit(unitID, false, true) -- this doesnt give back resources in itself
Spring.AddTeamResource(teamID, 'metal', UnitDefs[unitDefID].metalCost)
Spring.AddTeamResource(teamID, 'energy', UnitDefs[unitDefID].energyCost)
GG.AddTeamResource(teamID, 'metal', UnitDefs[unitDefID].metalCost)
GG.AddTeamResource(teamID, 'energy', UnitDefs[unitDefID].energyCost)
elseif action == 'wreck' then
local unitDefID = Spring.GetUnitDefID(unitID)
local x, y, z = Spring.GetUnitPosition(unitID)
Expand Down
2 changes: 1 addition & 1 deletion luarules/gadgets/cmd_give.lua
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ if gadgetHandler:IsSyncedCode() then
-- give resources
if unitName == "metal" or unitName == "energy" then
-- Give resources instead of units
Spring.AddTeamResource(teamID, unitName, amount)
GG.AddTeamResource(teamID, unitName, amount)
Spring.SendMessageToTeam(teamID, "You have been given: "..amount.." "..unitName)
Spring.SendMessageToPlayer(playerID, "You have given team "..teamID..": "..amount.." "..unitName)
return
Expand Down
Loading
Loading