| 1 | -- $Id$ |
|---|
| 2 | ---------------------------------------------------------------- |
|---|
| 3 | --Metal Hax |
|---|
| 4 | ---------------------------------------------------------------- |
|---|
| 5 | |
|---|
| 6 | function gadget:GetInfo() |
|---|
| 7 | return { |
|---|
| 8 | name = "Mex Hax", |
|---|
| 9 | desc = "Takes over mexes from the engine.", |
|---|
| 10 | author = "Evil4Zerggin", |
|---|
| 11 | date = "24 May 2008", |
|---|
| 12 | license = "GNU GPL, v2 or later", |
|---|
| 13 | layer = 0, |
|---|
| 14 | enabled = false -- loaded by default? |
|---|
| 15 | } |
|---|
| 16 | end |
|---|
| 17 | |
|---|
| 18 | if (gadgetHandler:IsSyncedCode()) then |
|---|
| 19 | |
|---|
| 20 | ---------------------------------------------------------------- |
|---|
| 21 | --parameters |
|---|
| 22 | ---------------------------------------------------------------- |
|---|
| 23 | local maxMetal = 0.001 --scale metal by this value; acts like extractsMetal |
|---|
| 24 | local desiredEnergy = 0.8 -- on which percentage to hover |
|---|
| 25 | local storedEnergyWeight = 0.2 -- how much to weight the energy to spend on how much energy is stored |
|---|
| 26 | local deltaEWeight = 0.2 --how much to weight the energy to spend on how quickly energy is changing |
|---|
| 27 | |
|---|
| 28 | ---------------------------------------------------------------- |
|---|
| 29 | --speed-ups |
|---|
| 30 | ---------------------------------------------------------------- |
|---|
| 31 | local mapSizeX = Game.mapSizeX |
|---|
| 32 | local mapSizeZ = Game.mapSizeZ |
|---|
| 33 | local extractorRadius = Game.extractorRadius |
|---|
| 34 | |
|---|
| 35 | local GetGroundInfo = Spring.GetGroundInfo |
|---|
| 36 | local GetUnitPosition = Spring.GetUnitPosition |
|---|
| 37 | local SetUnitResourcing = Spring.SetUnitResourcing |
|---|
| 38 | local SetUnitMetalExtraction = Spring.SetUnitMetalExtraction |
|---|
| 39 | local GetTeamResources = Spring.GetTeamResources |
|---|
| 40 | local GetUnitTeam = Spring.GetUnitTeam |
|---|
| 41 | |
|---|
| 42 | ---------------------------------------------------------------- |
|---|
| 43 | --constants |
|---|
| 44 | ---------------------------------------------------------------- |
|---|
| 45 | |
|---|
| 46 | local metalMapScale = 16 |
|---|
| 47 | local metalMapScaleSq = 256 |
|---|
| 48 | |
|---|
| 49 | local metalMapMaxX = math.floor(mapSizeX / metalMapScale) - 1 |
|---|
| 50 | local metalMapMaxZ = math.floor(mapSizeZ / metalMapScale) - 1 |
|---|
| 51 | local extractSearchMax = math.floor(extractorRadius / metalMapScale) |
|---|
| 52 | |
|---|
| 53 | ---------------------------------------------------------------- |
|---|
| 54 | --locals |
|---|
| 55 | ---------------------------------------------------------------- |
|---|
| 56 | |
|---|
| 57 | --2-D array that stores the metal map |
|---|
| 58 | local metalMap = {} |
|---|
| 59 | |
|---|
| 60 | --3-D array that stores what mexes are lined up to control each particular square on the metal map |
|---|
| 61 | local controlMap = {} |
|---|
| 62 | |
|---|
| 63 | --circle indicating whether a particular metal map square is within the extractor radius of the origin |
|---|
| 64 | local circleMask = {} |
|---|
| 65 | |
|---|
| 66 | --table of mexes; entries are {amount of metal that the mex is over, shares of energy the mex takes} |
|---|
| 67 | local mexes = {} |
|---|
| 68 | |
|---|
| 69 | --indexed by teams, entries are {table of mexes, autoManage, doUpdate, energy, energy shares, } |
|---|
| 70 | local teamInfos = {} |
|---|
| 71 | |
|---|
| 72 | ---------------------------------------------------------------- |
|---|
| 73 | --initialization |
|---|
| 74 | ---------------------------------------------------------------- |
|---|
| 75 | |
|---|
| 76 | --setup metal map, control map |
|---|
| 77 | local function SetupMaps() |
|---|
| 78 | for i = 0, metalMapMaxX do |
|---|
| 79 | metalMap[i] = {} |
|---|
| 80 | controlMap[i] = {} |
|---|
| 81 | for j = 0, metalMapMaxZ do |
|---|
| 82 | local metal |
|---|
| 83 | _, metal, _, _, _, _, _, _ = GetGroundInfo(metalMapScale * i + 8, metalMapScale * j + 8) |
|---|
| 84 | metalMap[i][j] = metal * maxMetal |
|---|
| 85 | controlMap[i][j] = {} |
|---|
| 86 | end |
|---|
| 87 | end |
|---|
| 88 | end |
|---|
| 89 | |
|---|
| 90 | --setup circle mask |
|---|
| 91 | local function SetupCircleMask() |
|---|
| 92 | do |
|---|
| 93 | local radiusSq = extractorRadius * extractorRadius |
|---|
| 94 | for i = 0, extractSearchMax do |
|---|
| 95 | circleMask[i] = {} |
|---|
| 96 | circleMask[-i] = {} |
|---|
| 97 | for j = 0, extractSearchMax do |
|---|
| 98 | if ((i*i + j*j) * metalMapScaleSq < radiusSq) then |
|---|
| 99 | circleMask[i][j] = true |
|---|
| 100 | circleMask[-i][j] = true |
|---|
| 101 | circleMask[i][-j] = true |
|---|
| 102 | circleMask[-i][-j] = true |
|---|
| 103 | end |
|---|
| 104 | end |
|---|
| 105 | end |
|---|
| 106 | end |
|---|
| 107 | end |
|---|
| 108 | |
|---|
| 109 | local function SetupTeamList() |
|---|
| 110 | local teamList = Spring.GetTeamList() |
|---|
| 111 | for i=1,#teamList do |
|---|
| 112 | local teamID = teamList[i] |
|---|
| 113 | teamInfos[teamID] = {{}, true, false, 0, 0,} |
|---|
| 114 | end |
|---|
| 115 | end |
|---|
| 116 | |
|---|
| 117 | ---------------------------------------------------------------- |
|---|
| 118 | --functions |
|---|
| 119 | ---------------------------------------------------------------- |
|---|
| 120 | |
|---|
| 121 | local function GetMexMod(energy, baseMetal) |
|---|
| 122 | if (baseMetal <= 0) then return 0 end |
|---|
| 123 | |
|---|
| 124 | return 1 + math.sqrt(0.2 * energy / baseMetal) |
|---|
| 125 | end |
|---|
| 126 | |
|---|
| 127 | local function GetMexShares(baseMetal) |
|---|
| 128 | return baseMetal |
|---|
| 129 | end |
|---|
| 130 | |
|---|
| 131 | local function GetOverdriveEnergy(teamID) |
|---|
| 132 | local teamInfo = teamInfos[teamID] |
|---|
| 133 | if (teamInfo) then |
|---|
| 134 | return teamInfo[4] |
|---|
| 135 | else |
|---|
| 136 | return nil |
|---|
| 137 | end |
|---|
| 138 | end |
|---|
| 139 | |
|---|
| 140 | local function GetOverdriveMetal(teamID) |
|---|
| 141 | local teamInfo = teamInfos[teamID] |
|---|
| 142 | if (teamInfo) then |
|---|
| 143 | return math.sqrt(0.2 * teamInfo[4] * teamInfo[5]) |
|---|
| 144 | else |
|---|
| 145 | return nil |
|---|
| 146 | end |
|---|
| 147 | end |
|---|
| 148 | |
|---|
| 149 | local function GetDifferentialEnergyPerMetal(teamID) |
|---|
| 150 | local teamInfo = teamInfos[teamID] |
|---|
| 151 | if (teamInfo and teamInfo[5] > 0) then |
|---|
| 152 | return math.sqrt(20 * teamInfo[4] / teamInfo[5]) |
|---|
| 153 | else |
|---|
| 154 | return nil |
|---|
| 155 | end |
|---|
| 156 | end |
|---|
| 157 | |
|---|
| 158 | local function GetTotalGlobalMetal() |
|---|
| 159 | local result = 0 |
|---|
| 160 | for i = 0, metalMapMaxX do |
|---|
| 161 | for j = 0, metalMapMaxZ do |
|---|
| 162 | result = result + metalMap[i][j] |
|---|
| 163 | end |
|---|
| 164 | end |
|---|
| 165 | return result |
|---|
| 166 | end |
|---|
| 167 | |
|---|
| 168 | local function GetMetalMapCoords(posX, posZ) |
|---|
| 169 | return math.floor(posX / metalMapScale), math.floor(posZ / metalMapScale) |
|---|
| 170 | end |
|---|
| 171 | |
|---|
| 172 | local function AddMex(unitID, unitDefID, unitTeam) |
|---|
| 173 | local unitDef = UnitDefs[unitDefID] |
|---|
| 174 | if ((unitDef.speed or 0) ~= 0) then return end |
|---|
| 175 | |
|---|
| 176 | local metal = 0 |
|---|
| 177 | local posX, _, posZ = GetUnitPosition(unitID) |
|---|
| 178 | local mPosX, mPosZ = GetMetalMapCoords(posX, posZ) |
|---|
| 179 | |
|---|
| 180 | --establish how far we are willing to search |
|---|
| 181 | local backX = math.min(extractSearchMax, mPosX) |
|---|
| 182 | local frontX = math.min(extractSearchMax, metalMapMaxX - mPosX) |
|---|
| 183 | local backZ = math.min(extractSearchMax, mPosZ) |
|---|
| 184 | local frontZ = math.min(extractSearchMax, metalMapMaxZ - mPosZ) |
|---|
| 185 | |
|---|
| 186 | --iterate over the search area |
|---|
| 187 | for i = -backX, frontX do |
|---|
| 188 | for j = -backZ, frontZ do |
|---|
| 189 | --check our pre-calculated mask to see if each spot is within the extractor radius |
|---|
| 190 | if (circleMask[i][j]) then |
|---|
| 191 | --position on metal map |
|---|
| 192 | local iPos = mPosX + i |
|---|
| 193 | local jPos = mPosZ + j |
|---|
| 194 | |
|---|
| 195 | --if spot not already claimed, give the metal to the mex |
|---|
| 196 | if (not controlMap[iPos][jPos][1]) then |
|---|
| 197 | metal = metal + metalMap[iPos][jPos] |
|---|
| 198 | end |
|---|
| 199 | |
|---|
| 200 | --add the mex to the queue of controllers |
|---|
| 201 | table.insert(controlMap[iPos][jPos], unitID) |
|---|
| 202 | end |
|---|
| 203 | end |
|---|
| 204 | end |
|---|
| 205 | mexes[unitID] = {metal, GetMexShares(metal),} |
|---|
| 206 | --doUpdate |
|---|
| 207 | teamInfos[unitTeam][1][unitID] = true |
|---|
| 208 | teamInfos[unitTeam][3] = true |
|---|
| 209 | end |
|---|
| 210 | |
|---|
| 211 | local function RemoveMex(unitID, unitTeam) |
|---|
| 212 | if (not mexes[unitID]) then return end |
|---|
| 213 | |
|---|
| 214 | local metal = 0 |
|---|
| 215 | local posX, _, posZ = GetUnitPosition(unitID) |
|---|
| 216 | local mPosX, mPosZ = GetMetalMapCoords(posX, posZ) |
|---|
| 217 | |
|---|
| 218 | --establish how far we are willing to search |
|---|
| 219 | local backX = math.min(extractSearchMax, mPosX) |
|---|
| 220 | local frontX = math.min(extractSearchMax, metalMapMaxX - mPosX) |
|---|
| 221 | local backZ = math.min(extractSearchMax, mPosZ) |
|---|
| 222 | local frontZ = math.min(extractSearchMax, metalMapMaxZ - mPosZ) |
|---|
| 223 | |
|---|
| 224 | --iterate over the search area |
|---|
| 225 | for i = -backX, frontX do |
|---|
| 226 | for j = -backZ, frontZ do |
|---|
| 227 | --position on metal map |
|---|
| 228 | local iPos = mPosX + i |
|---|
| 229 | local jPos = mPosZ + j |
|---|
| 230 | |
|---|
| 231 | --if we control this spot, give it to the next in line |
|---|
| 232 | if (controlMap[iPos][jPos][1] == unitID) then |
|---|
| 233 | --remove any mexes in line which no longer exist |
|---|
| 234 | local nextID |
|---|
| 235 | |
|---|
| 236 | repeat |
|---|
| 237 | table.remove(controlMap[iPos][jPos], 1) |
|---|
| 238 | nextID = controlMap[iPos][jPos][1] |
|---|
| 239 | until (not nextID or mexes[nextID]) |
|---|
| 240 | |
|---|
| 241 | --add the metal to the new owner |
|---|
| 242 | if (nextID) then |
|---|
| 243 | mexes[nextID][1] = mexes[nextID][1] + metalMap[iPos][jPos] |
|---|
| 244 | mexes[nextID][2] = GetMexShares(mexes[nextID][1]) |
|---|
| 245 | teamInfos[GetUnitTeam(nextID)][3] = true |
|---|
| 246 | end |
|---|
| 247 | end |
|---|
| 248 | end |
|---|
| 249 | end |
|---|
| 250 | --remove mex |
|---|
| 251 | teamInfos[unitTeam][1][unitID] = nil |
|---|
| 252 | mexes[unitID] = nil |
|---|
| 253 | --doUpdate |
|---|
| 254 | teamInfos[unitTeam][3] = true |
|---|
| 255 | end |
|---|
| 256 | |
|---|
| 257 | ---------------------------------------------------------------- |
|---|
| 258 | --callins |
|---|
| 259 | ---------------------------------------------------------------- |
|---|
| 260 | |
|---|
| 261 | function gadget:Initialize() |
|---|
| 262 | SetupMaps() |
|---|
| 263 | SetupCircleMask() |
|---|
| 264 | Spring.SendMessage("Total Global Metal:" .. GetTotalGlobalMetal()) |
|---|
| 265 | SetupTeamList() |
|---|
| 266 | end |
|---|
| 267 | |
|---|
| 268 | function gadget:UnitFinished(unitID, unitDefID, unitTeam) |
|---|
| 269 | AddMex(unitID, unitDefID, unitTeam) |
|---|
| 270 | end |
|---|
| 271 | |
|---|
| 272 | function gadget:UnitDestroyed(unitID, unitDefID, unitTeam) |
|---|
| 273 | RemoveMex(unitID, unitTeam) |
|---|
| 274 | end |
|---|
| 275 | |
|---|
| 276 | function gadget:UnitTaken(unitID, unitDefID, oldTeamID, teamID) |
|---|
| 277 | RemoveMex(unitID, oldTeamID) |
|---|
| 278 | AddMex(unitID, unitDefID, teamID) |
|---|
| 279 | end |
|---|
| 280 | |
|---|
| 281 | function gadget:UnitGiven(unitID, unitDefID, teamID, oldTeamID) |
|---|
| 282 | RemoveMex(unitID, oldTeamID) |
|---|
| 283 | AddMex(unitID, unitDefID, teamID) |
|---|
| 284 | end |
|---|
| 285 | |
|---|
| 286 | function gadget:GameFrame(n) |
|---|
| 287 | if (((n+30) % 32) > 0.1) then return end |
|---|
| 288 | |
|---|
| 289 | for teamID, teamInfo in pairs(teamInfos) do |
|---|
| 290 | |
|---|
| 291 | --cleanup |
|---|
| 292 | if (teamInfo[4] < 0) then |
|---|
| 293 | teamInfo[4] = 0 |
|---|
| 294 | end |
|---|
| 295 | if (teamInfo[5] < 0) then |
|---|
| 296 | teamInfo[5] = 0 |
|---|
| 297 | end |
|---|
| 298 | |
|---|
| 299 | if (teamInfo[2] or teamInfo[3]) then |
|---|
| 300 | --automanage |
|---|
| 301 | if (teamInfo[2]) then |
|---|
| 302 | --determine how much to spend |
|---|
| 303 | if (teamInfo[5] > 0) then |
|---|
| 304 | local eCur, eMax, ePull, eInc, eExp, _, eSent, eRec = GetTeamResources(teamID, "energy") |
|---|
| 305 | if (eCur == nil) then return end |
|---|
| 306 | local deltaE = eInc - eExp + eRec - eSent |
|---|
| 307 | local eChange = storedEnergyWeight * (eCur - eMax * desiredEnergy) + deltaEWeight * deltaE |
|---|
| 308 | teamInfo[4] = teamInfo[4] + eChange |
|---|
| 309 | |
|---|
| 310 | if (teamInfo[4] < 0) then |
|---|
| 311 | teamInfo[4] = 0 |
|---|
| 312 | end |
|---|
| 313 | else |
|---|
| 314 | --if no mexes, reset energy budget to zero |
|---|
| 315 | teamInfo[4] = 0 |
|---|
| 316 | end |
|---|
| 317 | end |
|---|
| 318 | |
|---|
| 319 | --update |
|---|
| 320 | if (teamInfo[3]) then |
|---|
| 321 | --recalculate shares |
|---|
| 322 | local shares = 0 |
|---|
| 323 | for unitID, _ in pairs(teamInfo[1]) do |
|---|
| 324 | shares = shares + mexes[unitID][2] |
|---|
| 325 | end |
|---|
| 326 | teamInfo[5] = shares |
|---|
| 327 | |
|---|
| 328 | --done |
|---|
| 329 | teamInfo[3] = false |
|---|
| 330 | end |
|---|
| 331 | |
|---|
| 332 | Spring.SendMessage("Player " .. teamID .. " Energy Budget: " .. teamInfo[4] .. " Shares: " .. teamInfo[5]) |
|---|
| 333 | |
|---|
| 334 | --pump the energy |
|---|
| 335 | for unitID, _ in pairs(teamInfo[1]) do |
|---|
| 336 | local energyThisMex = teamInfo[4] * mexes[unitID][2] / teamInfo[5] |
|---|
| 337 | local metalThisMex = GetMexMod(energyThisMex, mexes[unitID][1]) * mexes[unitID][1] |
|---|
| 338 | Spring.SetUnitResourcing(unitID, "cue", energyThisMex * 2) |
|---|
| 339 | Spring.SetUnitResourcing(unitID, "cmm", metalThisMex * 2) |
|---|
| 340 | end |
|---|
| 341 | end |
|---|
| 342 | end |
|---|
| 343 | end |
|---|
| 344 | |
|---|
| 345 | ---------------------------------------------------------------- |
|---|
| 346 | --UNSYNCED |
|---|
| 347 | ---------------------------------------------------------------- |
|---|
| 348 | else |
|---|
| 349 | |
|---|
| 350 | end |
|---|