-------------------------------------------------------------------------------- -------------------------------------------------------------------------------- function gadget:GetInfo() return { name = "Chicken Spawner", desc = "Spawns burrows and chickens", author = "quantum", date = "April 29, 2008", license = "GNU GPL, v2 or later", layer = 0, enabled = true -- loaded by default? } end -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- if (gadgetHandler:IsSyncedCode()) then -- BEGIN SYNCED -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -- -- Speed-ups and upvalues -- local Spring = Spring local math = math local Game = Game local table = table local ipairs = ipairs local pairs = pairs local queenID local targetCache local luaAI local chickenTeamID local burrows = {} local burrowSpawnProgress = 0 local commanders = {} local maxTries = 100 local computerTeams = {} local humanTeams = {} local lagging = false local cpuUsages = {} local chickenBirths = {} local kills = {} local idleQueue = {} local turrets = {} local timeOfLastSpawn = 0 local specialBugCount = 0 local hiveposx = 0 local hiveposz = 0 gameMode = 'normal' --Spring.GetModOption("gamemode") --local tooltipMessage = "Kill chickens and collect their eggs to get metal." local mexes = { "arm_metal_extractor", "core_metal_extractor", } -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -- -- Teams -- local modes = { [0] = 0, [1] = 'FLEAS: Easy', -- [4] = 'FLEAS Eggs: Easy', [2] = 'FLEAS: Normal', -- [5] = 'FLEAS Eggs: Normal', [3] = 'FLEAS: Hard', [4] = 'FLEAS: Cruel', [5] = 'Fleas: Impossible Cream', -- [6] = 'FLEAS Eggs: Hard', } for i, v in ipairs(modes) do -- make it bi-directional modes[v] = i end local function CompareDifficulty(...) level = 1 for _, difficulty in ipairs{...} do if (modes[difficulty] > level) then level = modes[difficulty] end end return modes[level] end if (not gameMode) then -- set human and computer teams humanTeams[0] = true computerTeams[1] = true chickenTeamID = 1 luaAI = defaultDifficulty else local teams = Spring.GetTeamList() local highestLevel = 0 for _, teamID in ipairs(teams) do local teamLuaAI = Spring.GetTeamLuaAI(teamID) if (teamLuaAI and teamLuaAI ~= "") then luaAI = teamLuaAI highestLevel = CompareDifficulty(teamLuaAI, highestLevel) chickenTeamID = teamID computerTeams[teamID] = true else humanTeams[teamID] = true end end luaAI = highestLevel end local gaiaTeamID = Spring.GetGaiaTeamID() computerTeams[gaiaTeamID] = nil humanTeams[gaiaTeamID] = nil if (gameMode and luaAI == 0) then return false end -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -- -- Utility -- local function SetToList(set) local list = {} for k in pairs(set) do table.insert(list, k) end return list end local function SetCount(set) local count = 0 for k in pairs(set) do count = count + 1 end return count end -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -- -- Difficulty -- do -- load config file local CONFIG_FILE = "LuaRules/Configs/spawn_defs.lua" local VFSMODE = VFS.RAW_FIRST local s = assert(VFS.LoadFile(CONFIG_FILE, VFSMODE)) local chunk = assert(loadstring(s, file)) setfenv(chunk, gadget) chunk() end local function SetGlobals(difficulty) for key, value in pairs(gadget.difficulties[difficulty]) do gadget[key] = value end gadget.difficulties = nil end SetGlobals(luaAI or defaultDifficulty) -- set difficulty -- adjust for player and chicken bot count local malus = SetCount(humanTeams)^playerMalus burrowSpawnRate = burrowSpawnRate/malus/SetCount(computerTeams) local function DisableBuildButtons(unitID, ...) for _, unitName in ipairs({...}) do local cmdDescID = Spring.FindUnitCmdDesc(unitID, -UnitDefNames[unitName].id) if (cmdDescID) then local cmdArray = {disabled = true, tooltip = tooltipMessage} Spring.EditUnitCmdDesc(unitID, cmdDescID, cmdArray) end end end -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -- -- Game Rules -- local function SetupUnit(unitName) Spring.SetGameRulesParam(unitName.."Count", 0) Spring.SetGameRulesParam(unitName.."Kills", 0) end Spring.SetGameRulesParam("lagging", 0) Spring.SetGameRulesParam("queenTime", queenTime) for unitName in pairs(chickenTypes) do SetupUnit(unitName) end for unitName in pairs(defenders) do SetupUnit(unitName) end SetupUnit(burrowName) SetupUnit(queenName) local difficulty = modes[luaAI or defaultDifficulty] Spring.SetGameRulesParam("difficulty", difficulty) local function UpdateUnitCount() local teamUnitCounts = Spring.GetTeamUnitsCounts(chickenTeamID) for unitDefID, count in pairs(teamUnitCounts) do if (unitDefID ~= "n") then Spring.SetGameRulesParam(UnitDefs[unitDefID].name.."Count", count) end end end -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -- -- CPU Lag Prevention -- local function DetectCpuLag() local setspeed,actualspeed,paused = Spring.GetGameSpeed() if paused then return end local n = Spring.GetGameFrame() local players = Spring.GetPlayerList() for _, playerID in ipairs(players) do local _, active, spectator, _, _, _, cpuUsage = Spring.GetPlayerInfo(playerID) if (cpuUsage > 0) then cpuUsages[playerID] = {cpuUsage=math.min(cpuUsage, 1.2), frame=n} end end if (n > 30*10) then local toRemove = {} for playerID, t in pairs(cpuUsages) do if (n-t.frame > 30*60) then table.insert(toRemove, playerID) end local _, active = Spring.GetPlayerInfo(playerID) if (not active) then table.insert(toRemove, playerID) end end for _, playerID in ipairs(toRemove) do cpuUsages[playerID] = nil end local cpuUsageCount = 0 local cpuUsageSum = 0 for playerID, t in pairs(cpuUsages) do cpuUsageSum = cpuUsageSum + t.cpuUsage cpuUsageCount = cpuUsageCount + 1 end local averageCpu = (cpuUsageSum/cpuUsageCount) * (setspeed/actualspeed) if (averageCpu > lagTrigger+triggerTolerance) then if (not lagging) then Spring.SetGameRulesParam("lagging", 1) end lagging = true end if (averageCpu < lagTrigger-triggerTolerance) then if (lagging) then Spring.SetGameRulesParam("lagging", 0) end lagging = false end end end local function KillOldChicken() for unitID, birthDate in pairs(chickenBirths) do local age = Spring.GetGameSeconds() - birthDate if (age > maxAge + math.random(10)) then Spring.DestroyUnit(unitID) end end end -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -- -- Game End Stuff -- local function KillAllChicken() local chickenUnits = Spring.GetTeamUnits(chickenTeamID) for _, unitID in ipairs(chickenUnits) do Spring.DestroyUnit(unitID) end end local function KillAllComputerUnits() for teamID in pairs(computerTeams) do local teamUnits = Spring.GetTeamUnits(teamID) for _, unitID in ipairs(teamUnits) do Spring.DestroyUnit(unitID) end end end -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -- -- Spawn Dynamics -- local function IsPlayerUnitNear(x, z, r) for teamID in pairs(humanTeams) do if (#Spring.GetUnitsInCylinder(x, z, r, teamID) > 0) then return true end end end local function AttackNearestEnemy(unitID) local targetID = Spring.GetUnitNearestEnemy(unitID) if (targetID) then local tx, ty, tz = Spring.GetUnitPosition(targetID) Spring.GiveOrderToUnit(unitID, CMD.FIGHT, {tx, ty, tz}, {}) end end --local function SpawnEggs(x, y, z) -- local choices = {} -- for name in pairs(chickenTypes) do -- if (not chickenTypes[name].noegg and chickenTypes[name].time <= Spring.GetGameSeconds()) then -- table.insert(choices, name) -- end -- end -- for name in pairs(defenders) do -- if (not defenders[name].noegg and defenders[name].time <= Spring.GetGameSeconds()) then -- table.insert(choices, name) -- end -- end -- for i=1, burrowEggs do -- local choice = choices[math.random(#choices)] -- local rx, rz = math.random(-30, 30), math.random(-30, 30) -- Spring.CreateFeature(choice.."_egg", x+rx, y, z+rz, math.random(-32000, 32000)) -- end --end local function ChooseTarget() local humanTeamList = SetToList(humanTeams) if (#humanTeamList == 0) then return end local teamID = humanTeamList[math.random(#humanTeamList)] local units = Spring.GetTeamUnits(teamID) local unitID = units[math.random(#units)] return {Spring.GetUnitPosition(unitID)} end local function ChooseChicken(units) local s = Spring.GetGameSeconds() units = units or chickenTypes choices = {} for chickenName, c in pairs(units) do if (c.time <= s and (c.obsolete or math.huge) > s) then local chance = math.floor((c.initialChance or 1) + (s-c.time) * (c.chanceIncrease or 0)) for i=1, chance do table.insert(choices, chickenName) end end end if (#choices < 1) then return else return choices[math.random(#choices)], choices[math.random(#choices)] end end local function SpawnChicken(burrowID, spawnNumber, chickenName) local x, z local bx, by, bz = Spring.GetUnitPosition(burrowID) if (not bx or not by or not bz) then return end local tries = 0 local s = spawnSquare for i=1, spawnNumber do repeat x = math.random(bx - s, bx + s) z = math.random(bz - s, bz + s) s = s + spawnSquareIncrement tries = tries + 1 until (not Spring.GetGroundBlocked(x, z) or tries > spawnNumber + maxTries) local unitID = Spring.CreateUnit(chickenName, x, 0, z, "n", chickenTeamID) if (targetCache) then Spring.GiveOrderToUnit(unitID, CMD.FIGHT, targetCache, {}) end chickenBirths[unitID] = Spring.GetGameSeconds() end local sec = Spring.GetGameSeconds() if (sec >= 480) then for i=1, 2 do repeat x = math.random(bx - s, bx + s) z = math.random(bz - s, bz + s) s = s + spawnSquareIncrement tries = tries + 1 until (not Spring.GetGroundBlocked(x, z) or tries > spawnNumber + maxTries) local unitID = Spring.CreateUnit("dieflea", x, 0, z, "n", chickenTeamID) if (targetCache) then Spring.GiveOrderToUnit(unitID, CMD.FIGHT, targetCache, {}) end chickenBirths[unitID] = Spring.GetGameSeconds() end end if (sec >= 800 and specialBugCount <= 4) then for i=1, 1 do repeat x = math.random(bx - s, bx + s) z = math.random(bz - s, bz + s) s = s + spawnSquareIncrement tries = tries + 1 until (not Spring.GetGroundBlocked(x, z) or tries > spawnNumber + maxTries) local unitID = Spring.CreateUnit("dieflea", x, 0, z, "n", chickenTeamID) if (targetCache) then Spring.GiveOrderToUnit(unitID, CMD.FIGHT, targetCache, {}) end chickenBirths[unitID] = Spring.GetGameSeconds() end end if (sec >= 2000 and specialBugCount == 22) then for i=1, 1 do repeat x = math.random(bx - s, bx + s) z = math.random(bz - s, bz + s) s = s + spawnSquareIncrement tries = tries + 1 until (not Spring.GetGroundBlocked(x, z) or tries > spawnNumber + maxTries) local unitID = Spring.CreateUnit("dieflea", x, 0, z, "n", chickenTeamID) if (targetCache) then Spring.GiveOrderToUnit(unitID, CMD.FIGHT, targetCache, {}) end chickenBirths[unitID] = Spring.GetGameSeconds() end end if (sec >= 600 and specialBugCount >= 35) then for i=1, 1 do repeat x = math.random(bx - s, bx + s) z = math.random(bz - s, bz + s) s = s + spawnSquareIncrement tries = tries + 1 until (not Spring.GetGroundBlocked(x, z) or tries > spawnNumber + maxTries) local unitID = Spring.CreateUnit("dieflea", x, 0, z, "n", chickenTeamID) if (targetCache) then Spring.GiveOrderToUnit(unitID, CMD.PATROL, {hiveposx, 0, hiveposz}, {}) end chickenBirths[unitID] = Spring.GetGameSeconds() end end specialBugCount = specialBugCount + 1 if (specialBugCount >= 40) then specialBugCount = 0 end end local function SpawnTurret(burrowID, turret) if (math.random() > defenderChance and defenderChance < 1 or not turret) then return end local x, z local bx, by, bz = Spring.GetUnitPosition(burrowID) local tries = 0 local s = spawnSquare local spawnNumber = math.max(math.floor(defenderChance), 1) for i=1, spawnNumber do repeat x = math.random(bx - s, bx + s) z = math.random(bz - s, bz + s) s = s + spawnSquareIncrement tries = tries + 1 until (not Spring.GetGroundBlocked(x, z) or tries > spawnNumber + maxTries) local unitID = Spring.CreateUnit(turret, x, 0, z, "n", chickenTeamID) -- FIXME Spring.SetUnitBlocking(unitID, false) turrets[unitID] = Spring.GetGameSeconds() end end local function SpawnBurrow(number) if (queenID) then return end local t = Spring.GetGameSeconds() local unitDefID = UnitDefNames[burrowName].id for i=1, 1 do local x, z local tries = 0 repeat x = math.random(spawnSquare, Game.mapSizeX - spawnSquare) z = math.random(spawnSquare, Game.mapSizeZ - spawnSquare) local y = Spring.GetGroundHeight(x, z) tries = tries + 1 local blocking = Spring.TestBuildOrder(UnitDefNames["arm_metal_maker"].id, x, y, z, 1) if (blocking == 2) then local proximity = Spring.GetUnitsInCylinder(x, z, minBaseDistance) local vicinity = Spring.GetUnitsInCylinder(x, z, maxBaseDistance) local humanUnitsInVicinity = false local humanUnitsInProximity = false for i=1,vicinity['n'],1 do if (Spring.GetUnitTeam(vicinity[i]) ~= chickenTeamID) then humanUnitsInVicinity = true break end end for i=1,proximity['n'],1 do if (Spring.GetUnitTeam(proximity[i]) ~= chickenTeamID) then humanUnitsInProximity = true break end end if (humanUnitsInProximity or not humanUnitsInVicinity) then blocking = 1 end end until (blocking == 2 or tries > maxTries) local unitID = Spring.CreateUnit(burrowName, x, 0, z, "n", chickenTeamID) hiveposx = x hiveposz = z burrows[unitID] = true Spring.SetUnitBlocking(unitID, false) end end local function SpawnQueen() local x, z local tries = 0 repeat x = math.random(0, Game.mapSizeX) z = math.random(0, Game.mapSizeZ) local y = Spring.GetGroundHeight(x, z) tries = tries + 1 local blocking = Spring.TestBuildOrder(UnitDefNames["core_krogoth"].id, x, y, z, 1) until (blocking == 2 or tries > maxTries) return Spring.CreateUnit(queenName, x, 0, z, "n", chickenTeamID) end local function Wave() local t = Spring.GetGameSeconds() if (Spring.GetTeamUnitCount(chickenTeamID) > maxChicken or lagging or t < gracePeriod) then return end local chicken1Name, chicken2Name = ChooseChicken(chickenTypes) local turret = ChooseChicken(defenders) local squadNumber = t*timeSpawnBonus+firstSpawnSize local chicken1Number = math.ceil(waveRatio * squadNumber * chickenTypes[chicken1Name].squadSize) local chicken2Number = math.floor((1-waveRatio) * squadNumber * chickenTypes[chicken2Name].squadSize) if (queenID) then SpawnChicken(queenID, chicken1Number*queenSpawnMult, chicken1Name) SpawnChicken(queenID, chicken2Number*queenSpawnMult, chicken2Name) else for burrowID in pairs(burrows) do SpawnChicken(burrowID, chicken1Number, chicken1Name) SpawnChicken(burrowID, chicken2Number, chicken2Name) SpawnTurret(burrowID, turret) end return chicken1Name, chicken2Name, chicken1Number, chicken2Number end end -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -- -- Get rid of the AI -- local function DisableUnit(unitID) Spring.MoveCtrl.Enable(unitID) Spring.MoveCtrl.SetNoBlocking(unitID, true) Spring.MoveCtrl.SetPosition(unitID, Game.mapSizeX+4000, 0, Game.mapSizeZ+4000) Spring.SetUnitHealth(unitID, {paralyze=99999999}) Spring.SetUnitNoDraw(unitID, true) Spring.SetUnitStealth(unitID, true) Spring.SetUnitNoSelect(unitID, true) commanders[unitID] = nil end local function DisableComputerUnits() for teamID in pairs(computerTeams) do local teamUnits = Spring.GetTeamUnits(teamID) for _, unitID in ipairs(teamUnits) do DisableUnit(unitID) end end end -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -- -- Call-ins -- function gadget:UnitCreated(unitID, unitDefID, unitTeam) local name = UnitDefs[unitDefID].name if (name == "arm_commander" or (name == "armbossflea" or name == "core_commander") then commanders[unitID] = true end -- if (chickenTeamID == unitTeam and name ~= "arm_commander" and name ~= "core_commander" ) then -- local n = Spring.GetGameRulesParam(name.."Count") -- Spring.SetGameRulesParam(name.."Count", n+1) -- end if (alwaysVisible and unitTeam == chickenTeamID) then Spring.SetUnitAlwaysVisible(unitID, true) end -- if (eggs) then -- DisableBuildButtons(unitID, unpack(mexes)) -- end end function gadget:GameFrame(n) local t = Spring.GetGameSeconds() if (n == 1) then DisableComputerUnits() end if ((n+19) % (30 * chickenSpawnRate) < 0.1) then local args = {Wave()} if (#args > 0) then _G.chickenEventArgs = {type="wave", unpack(args)} SendToUnsynced("ChickenEvent") _G.chickenEventArgs = nil end end if ((n+21) % 30 < 0.1) then DetectCpuLag() KillOldChicken() UpdateUnitCount() targetCache = ChooseTarget() for _, unitID in ipairs(Spring.GetTeamUnits(chickenTeamID)) do if (targetCache and (not Spring.GetUnitCommands(unitID) or #Spring.GetUnitCommands(unitID) < 1)) then Spring.GiveOrderToUnit(unitID, CMD.FIGHT, targetCache, {}) end end if (t >= queenTime) then if (not queenID) then _G.chickenEventArgs = {type="queen"} SendToUnsynced("ChickenEvent") _G.chickenEventArgs = nil queenID = SpawnQueen() local xp = (malus or 1) - 1 Spring.SetUnitExperience(queenID, xp) end end local burrowCount = SetCount(burrows) local timeSinceLastSpawn = t - timeOfLastSpawn local burrowSpawnTime = burrowSpawnRate*0.25*(burrowCount+1) local malus = SetCount(humanTeams)^playerMalus if (burrowCount/malus <= 7 and t > 300) then burrowSpawnTime = burrowSpawnTime * burrowCount/(burrowCount + 5) end if (burrowSpawnTime < timeSinceLastSpawn and not lagging and burrowCount < maxBurrows) then SpawnBurrow() timeOfLastSpawn = t _G.chickenEventArgs = {type="burrowSpawn"} SendToUnsynced("ChickenEvent") _G.chickenEventArgs = nil end end end function gadget:UnitDestroyed(unitID, unitDefID, unitTeam) chickenBirths[unitID] = nil commanders[unitID] = nil turrets[unitID] = nil local name = UnitDefs[unitDefID].name if (unitTeam == chickenTeamID) then if (chickenTypes[name] or defenders[name]) then local kills = Spring.GetGameRulesParam(name.."Kills") Spring.SetGameRulesParam(name.."Kills", kills + 1) end if (name == queenName) then KillAllComputerUnits() KillAllChicken() end end if (name == burrowName) then burrows[unitID] = nil -- if (eggs) then -- SpawnEggs(Spring.GetUnitPosition(unitID)) -- end local burrowCount = SetCount(burrows) if (burrowCount <= 3) then SpawnBurrow() end end -- if (eggs and chickenTypes[name] and not chickenTypes[name].noegg) then -- local x, y, z = Spring.GetUnitPosition(unitID) -- Spring.CreateFeature(name.."_egg", x, y, z, math.random(-32000, 32000)) -- end -- if (eggs and defenders[name] and not defenders[name].noegg) then -- local x, y, z = Spring.GetUnitPosition(unitID) -- Spring.CreateFeature(name.."_egg", x, y, z, math.random(-32000, 32000)) -- end end function gadget:TeamDied(teamID) humanTeams[teamID] = nil computerTeams[teamID] = nil end function gadget:AllowCommand(unitID, unitDefID, teamID, cmdID, cmdParams, cmdOptions) -- if (eggs) then -- for _, mexName in ipairs(mexes) do -- if (UnitDefNames[mexName].id == -cmdID) then -- return false -- command was used -- end -- end -- end return true -- command was not used end -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- else -- END SYNCED -- BEGIN UNSYNCED -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- local Script = Script local SYNCED = SYNCED function WrapToLuaUI() if (Script.LuaUI('ChickenEvent')) then local chickenEventArgs = {} for k, v in spairs(SYNCED.chickenEventArgs) do chickenEventArgs[k] = v end Script.LuaUI.ChickenEvent(chickenEventArgs) end end function gadget:Initialize() gadgetHandler:AddSyncAction('ChickenEvent', WrapToLuaUI) end -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- end -- END UNSYNCED --------------------------------------------------------------------------------