diff --git a/luarules/gadgets/game_disable_assist_ally.lua b/luarules/gadgets/game_disable_assist_ally.lua index a3bd88a89f0..d1460bce53a 100644 --- a/luarules/gadgets/game_disable_assist_ally.lua +++ b/luarules/gadgets/game_disable_assist_ally.lua @@ -32,6 +32,54 @@ local function isComplete(u) end + +-- create a table of all mex and geo unitDefIDs +local isMex = {} +local isGeo = {} +for unitDefID, unitDef in pairs(UnitDefs) do + if unitDef.extractsMetal > 0 then + isMex[unitDefID] = true + end + if unitDef.customParams.geothermal then + isGeo[unitDefID] = true + end +end + +local function existsNonOwnedMex(myTeam, x, y, z) + local units = Spring.GetUnitsInCylinder(x, z, 10) + for k, unitID in ipairs(units) do + if isMex[Spring.GetUnitDefID(unitID)] then + if Spring.GetUnitTeam(unitID) ~= myTeam then + return true + end + end + end + return false +end + +local function existsNonOwnedGeo(myTeam, x, y, z) + local units = Spring.GetUnitsInCylinder(x, z, 10) + for k, unitID in ipairs(units) do + if isGeo[Spring.GetUnitDefID(unitID)] then + if Spring.GetUnitTeam(unitID) ~= myTeam then + return true + end + end + end + return false +end + +function gadget:AllowUnitCreation(unitDefID, builderID, builderTeam, x, y, z) + -- Disallow upgrading allied mexes and allied geos + if isMex[unitDefID] then + return not existsNonOwnedMex(builderTeam, x, y, z) + elseif isGeo[unitDefID] then + return not existsNonOwnedGeo(builderTeam, x, y, z) + end + return true +end + + function gadget:AllowCommand(unitID, unitDefID, unitTeam, cmdID, cmdParams, cmdOptions, cmdTag, synced) -- Disallow guard commands onto labs, units that have buildOptions or can assist @@ -49,13 +97,18 @@ function gadget:AllowCommand(unitID, unitDefID, unitTeam, cmdID, cmdParams, cmdO return true end - -- Also disallow assisting building (caused by a repair command) units under construction + -- Disallow assisting blueprints (caused by a repair command) -- Area repair doesn't cause assisting, so it's fine that we can't properly filter it if (cmdID == CMD.REPAIR and #cmdParams == 1) then local targetID = cmdParams[1] - local targetTeam = Spring.GetUnitTeam(targetID) + if not targetID or Spring.GetUnitDefID(targetID) == nil then -- ignore ill-formed calls + return true + end + + local targetTeam = Spring.GetUnitTeam(targetID) + if (unitTeam ~= Spring.GetUnitTeam(targetID)) and Spring.AreTeamsAllied(unitTeam, targetTeam) then if(not isComplete(targetID)) then return false @@ -64,7 +117,17 @@ function gadget:AllowCommand(unitID, unitDefID, unitTeam, cmdID, cmdParams, cmdO return true end - - + -- Disallow changing the move_state value of builders to ROAM (move_state of roam causes builders to auto-assist ally construction) + if (cmdID == CMD.MOVE_STATE and cmdParams[1] == 2 and UnitDefs[unitDefID].isBuilder) then + Spring.GiveOrderToUnit(unitID, CMD.MOVE_STATE, {0}, 0) -- make toggling still work between Hold and Maneuver + return false + end return true end + + +function gadget:UnitCreated(unitID, unitDefID, unitTeam) + if UnitDefs[unitDefID].isBuilder then + Spring.GiveOrderToUnit(unitID, CMD.MOVE_STATE, {0}, 0) + end +end \ No newline at end of file diff --git a/luarules/gadgets/game_disable_unit_sharing.lua b/luarules/gadgets/game_disable_unit_sharing.lua index 0b0bd82154e..8f6e3091db1 100644 --- a/luarules/gadgets/game_disable_unit_sharing.lua +++ b/luarules/gadgets/game_disable_unit_sharing.lua @@ -17,21 +17,65 @@ if not gadgetHandler:IsSyncedCode() then return false end -if not (Spring.GetModOptions().disable_unit_sharing - -- tax force enables this - or (Spring.GetModOptions().tax_resource_sharing_amount or 0) ~= 0) - -- unit market handles the restriction instead if enabled so that selling still works - or Spring.GetModOptions().unit_market then +local tax_resource_sharing_enabled = Spring.GetModOptions().tax_resource_sharing_amount ~= nil and Spring.GetModOptions().tax_resource_sharing_amount > 0 +local disable_share_econ_and_lab = Spring.GetModOptions().disable_unit_sharing_economy_and_production or tax_resource_sharing_enabled +local disable_share_combat_units = Spring.GetModOptions().disable_unit_sharing_combat_units +local disable_share_all = Spring.GetModOptions().disable_unit_sharing_all + +if not disable_share_econ_and_lab and not disable_share_combat_units and not disable_share_all then return false end -function gadget:AllowUnitTransfer(unitID, unitDefID, fromTeamID, toTeamID, capture) +local isEconOrLab = {} +local isCombatUnitOrTacticalBuilding = {} + +for unitDefID, unitDef in pairs(UnitDefs) do + -- Mark econ units + if unitDef.customParams.unitgroup == "energy" or unitDef.customParams.unitgroup == "metal" then + isEconOrLab[unitDefID] = true + elseif unitDef.canResurrect then + isEconOrLab[unitDefID] = true + + + -- Mark labs and mobile production + elseif unitDef.isFactory or unitDef.isBuilder then + isEconOrLab[unitDefID] = true + end + + -- Mark combat units and tactical buildings + if unitDef.isBuilding and not isEconOrLab[unitDefID] then + isCombatUnitOrTacticalBuilding[unitDefID] = true + elseif #unitDef.weapons > 0 then + isCombatUnitOrTacticalBuilding[unitDefID] = true + end +end + + + + + +-- Returns whether the unit is allowed to be shared according to the unit sharing restrictions. +local function unitTypeAllowedToBeShared(unitDefID) + if disable_share_all then return false end + if disable_share_econ_and_lab and isEconOrLab[unitDefID] then return false end + if disable_share_combat_units and isCombatUnitOrTacticalBuilding[unitDefID] then return false end + return true +end +GG.disable_unit_sharing_unitTypeAllowedToBeShared = unitTypeAllowedToBeShared + +if Spring.GetModOptions().unit_market then + -- let unit market handle unit sharing so that buying units will still work. + return false +end + + +function gadget:AllowUnitTransfer(unitID, unitDefID, fromTeamID, toTeamID, capture) if(capture) then return true end - return false + return unitTypeAllowedToBeShared(unitDefID) end diff --git a/luarules/gadgets/game_unit_market.lua b/luarules/gadgets/game_unit_market.lua index 1ad9663038c..9bc8a7cd504 100644 --- a/luarules/gadgets/game_unit_market.lua +++ b/luarules/gadgets/game_unit_market.lua @@ -65,6 +65,7 @@ local sellCmd = { params = { '0', 'Not For Sale', 'For Sale' } } + local buildPower = {} local realBuildSpeed = {} local unitBuildSpeed = {} @@ -99,17 +100,59 @@ local function setNotForSale(unitID) unitsForSale[unitID] = nil end -local function setUnitOnSale(unitID, price, toggle) - if unitsForSale[unitID] == nil or unitsForSale[unitID] == 0 or toggle == false then - unitsForSale[unitID] = price - spSetUnitRulesParam(unitID, "unitPrice", price, RPAccess) - UnitSaleBroadcast(unitID, price, spGetUnitTeam(unitID)) - setForSaleState(unitID, 1) - return true - else +local tax_resource_sharing_enabled = Spring.GetModOptions().tax_resource_sharing_amount ~= nil and Spring.GetModOptions().tax_resource_sharing_amount > 0 +local tax_resource_amount = Spring.GetModOptions().tax_resource_sharing_amount or 0 + +local disable_unit_sharing_enabled = ( + Spring.GetModOptions().disable_unit_sharing_economy_and_production + or Spring.GetModOptions().disable_unit_sharing_combat_units + or Spring.GetModOptions().disable_unit_sharing_all + or tax_resource_sharing_enabled) +local saleWhitelist = {} +if tax_resource_sharing_enabled ~= 0 or Spring.GetModOptions().disable_assist_ally_construction then + AllowPlayersSellUnfinished = false -- needs to be off, otherwise the buyer can assist their unfinished blueprint after buying it +end + + +local t2conNames = {"armack", "armacv", "armaca", "armacsub", "corack", "coracv", "coraca", "coracsub", "legack", "legacv", "legaca", "legacsub"} +local isT2Con = {} +for _, name in ipairs(t2conNames) do + if UnitDefNames[name] then + isT2Con[UnitDefNames[name].id] = true + end +end +t2conNames = nil + +local function setUnitOnSale(unitID, specifiedPrice, toggle) + if not spValidUnitID(unitID) then return false end + local unitDefID = spGetUnitDefID(unitID) + if not unitDefID then return false end + local unitDef = UnitDefs[unitDefID] + if not unitDef then return false end + local finished = (select(5,spGetUnitHealth(unitID))==1) + if not AllowPlayersSellUnfinished and not finished then return false end + local price + if not specifiedPrice or specifiedPrice <= 0 then + price = unitDef.metalCost or 1 -- in case the unit's metal cost is somehow 0 or nil + else + price = specifiedPrice + end + + -- When tax resource sharing is on, only allow selling t2 cons through unit market + if tax_resource_sharing_enabled then + if not isT2Con[unitDefID] then return false end + end + + if toggle and not (unitsForSale[unitID] == nil or unitsForSale[unitID] == 0) then setNotForSale(unitID) return false end + + unitsForSale[unitID] = price + spSetUnitRulesParam(unitID, "unitPrice", price, RPAccess) + UnitSaleBroadcast(unitID, price, spGetUnitTeam(unitID)) + setForSaleState(unitID, 1) + return true end local function getAIdiscount(newTeamID, oldTeamID, price) @@ -128,40 +171,19 @@ local function getAIdiscount(newTeamID, oldTeamID, price) end end -local function offerUnitForSale(unitID, sale_price, msgFromTeamID) - if not spValidUnitID(unitID) then return end - local unitDefID = spGetUnitDefID(unitID) - if not unitDefID then return end - local unitDef = UnitDefs[unitDefID] - if not unitDef then return end - local unitTeamID = spGetUnitTeam(unitID) - if msgFromTeamID ~= unitTeamID and not spIsCheatingEnabled() then return end -- in cheat mode you can set other units for sale, not just your own - local finished = (select(5,spGetUnitHealth(unitID))==1) - if not AllowPlayersSellUnfinished and not finished then return end - local price - if sale_price > 0 then price = sale_price -- for now we only support fair price, but for future - 0 = set price automatically - else price = unitDef.metalCost - end - if price <= 0 then return end - local selling = setUnitOnSale(unitID, price, true) - if not selling then - price = 0 - end -end -local disable_unit_sharing = ( - Spring.GetModOptions().disable_unit_sharing - or (Spring.GetModOptions().tax_resource_sharing_amount or 0) ~= 0) -and Spring.GetModOptions().unit_market -local saleWhitelist = {} + + + local function tryToBuyUnit(unitID, msgFromTeamID) + if not unitID or unitsForSale[unitID] == nil or unitsForSale[unitID] == 0 then return end local unitDefID = spGetUnitDefID(unitID) if not unitDefID then return end local unitDef = UnitDefs[unitDefID] if not unitDef then return end - + local old_ownerTeamID = spGetUnitTeam(unitID) local _, _, _, isAiTeam = spGetTeamInfo(old_ownerTeamID) if not spAreTeamsAllied(old_ownerTeamID, msgFromTeamID) then return end @@ -177,19 +199,24 @@ local function tryToBuyUnit(unitID, msgFromTeamID) if (current < price) then return end - if disable_unit_sharing then + if disable_unit_sharing_enabled then saleWhitelist[unitID] = true end TransferUnit(unitID, msgFromTeamID) if msgFromTeamID ~= old_ownerTeamID and price > 0 then -- don't send resources to yourself ShareTeamResource(msgFromTeamID, old_ownerTeamID, "metal", price) + -- if tax resource sharing is on, refund tax to the seller + if tax_resource_sharing_enabled then + local taxAmount = price * tax_resource_amount + Spring.AddTeamResource(old_ownerTeamID, "metal", taxAmount) + end end setNotForSale(unitID) UnitSoldBroadcast(unitID, price, old_ownerTeamID, msgFromTeamID) end -if disable_unit_sharing then +if disable_unit_sharing_enabled then function gadget:AllowUnitTransfer(unitID, unitDefID, fromTeamID, toTeamID, capture) if(capture) then return true @@ -198,7 +225,11 @@ if disable_unit_sharing then saleWhitelist[unitID] = nil return true end - return false + if(GG.disable_unit_sharing_unitTypeAllowedToBeShared) then + return GG.disable_unit_sharing_unitTypeAllowedToBeShared(unitDefID) + else + return true + end end end @@ -233,7 +264,9 @@ function gadget:RecvLuaMsg(msg, playerID) local unitID = tonumber(words[2]) --local sale_price = tonumber(words[3]) -- at the moment we only support "fair" price, but it is possible here to set unit price by client, for now we send 0 - set price automatically - offerUnitForSale(unitID, 0, msgFromTeamID) + local unitTeamID = spGetUnitTeam(unitID) + if msgFromTeamID ~= unitTeamID and not spIsCheatingEnabled() then return end -- in cheat mode you can set other units for sale, not just your own + setUnitOnSale(unitID, 0, false) elseif words[1] == "unitTryToBuy" then local unitID = tonumber(words[2]) tryToBuyUnit(unitID, msgFromTeamID) diff --git a/modoptions.lua b/modoptions.lua index 19f1a598ab3..8baa89c941e 100644 --- a/modoptions.lua +++ b/modoptions.lua @@ -228,7 +228,7 @@ local options = { step = 1, }, - { + { key = "sub_header", section = "options_main", type = "separator", @@ -244,8 +244,9 @@ local options = { key = "tax_resource_sharing_amount", name = "Resource Sharing Tax", desc = "Taxes resource sharing".."\255\128\128\128".." and overflow (engine TODO:)\n".. - "Set to [0] to turn off. Recommened: [0.4]. (Ranges: 0 - 0.99)\n".. - "*Disables: Reclaiming of Allied Units, [Unit Sharing] and [Assisting Ally Construction] to prevent loopholes", + "Set to [0] to turn off. Recommend: [0.4]. (Ranges: 0 - 0.99)\n".. + "Enable [Unit Market] to allow buying t2 cons\n".. + "*Disables: Reclaiming of Allied Units, [Economy and Lab Sharing] and [Assisting Ally Construction] to prevent loopholes\n", type = "number", def = 0, min = 0, @@ -253,17 +254,10 @@ local options = { step = 0.01, section = "options_main", column = 1, - lock = {"disable_unit_sharing","disable_assist_ally_construction"}, - unlock = {"disable_unit_sharing_forced","disable_assist_ally_construction_forced"}, - }, - { - key = "disable_unit_sharing", - name = "Disable Unit Sharing", - desc = "Disable sharing units and structures to allies", - type = "bool", - section = "options_main", - def = false, + lock = {"disable_unit_sharing_economy_and_production","disable_assist_ally_construction"}, + unlock = {"disable_unit_sharing_economy_and_production_forced","disable_assist_ally_construction_forced"}, }, + { key = "disable_assist_ally_construction", name = "Disable Assist Ally Construction", @@ -271,25 +265,46 @@ local options = { type = "bool", section = "options_main", def = false, - column = 1.76, }, - { key = "tax_padding", name = "", type = "subheader", section = "options_main", column = -3, }, - { - key = "disable_unit_sharing_forced", - --name = "\255\252\191\76".."Disable Unit Sharing [Forced ON]", - name = "\255\252\191\76".."Disable Unit Sharing Disable Assist Ally Construction", + { + key = "disable_unit_sharing_economy_and_production", + name = "Disable Economy and Lab Sharing", + desc = "Disable sharing of economy and labs (including mobile engineers)", + type = "bool", + section = "options_main", + def = false, + }, + { + key = "disable_assist_ally_construction_forced", + --name = "\255\252\191\76".."Disable Assist Ally Construction [Forced ON]", + name = "\255\252\191\76".."Disable Assist Ally Construction [■]", type = "subheader", section = "options_main", }, { - key = "disable_assist_ally_construction_forced", - --name = "\255\252\191\76".."Disable Assist Ally Construction [Forced ON]", - name = "\255\252\191\76".."[■] [■]", + key = "disable_unit_sharing_economy_and_production_forced", + --name = "\255\252\191\76".."Disable Unit Sharing [Forced ON]", + name = "\255\252\191\76".."Disable Economy and Lab Sharing [■]", type = "subheader", section = "options_main", - column = 1.505, - font = 4, }, + { + key = "disable_unit_sharing_combat_units", + name = "Disable Combat Unit Sharing", + desc = "Disable sharing combat units and tactical structures (e.g static defense, jammers, LRPC's)", + type = "bool", + section = "options_main", + def = false, + }, + { + key = "disable_unit_sharing_all", + name = "Disable All Unit Sharing", + desc = "Disable all unit sharing (including scouts and transports)", + type = "bool", + section = "options_main", + def = false, + }, + { key = "unit_market", name = "Unit Market",