function centraliseCamera() local centerPosition = WPos.New(Map.BottomRight.X/2, Map.BottomRight.Y/2, 0) Camera.Position = centerPosition end function getWinLossConditionID() local missionDescription if (endless) then missionDescription = "Do not allow " .. playerLives .. " enemies to the exit!" else missionDescription = "Do not allow " .. playerLives .. " enemies to the exit and survive " .. wavesToSurvive .. " waves!" end return humanPlayer.AddPrimaryObjective(missionDescription) end function inTable(value, t) return Utils.Any(t, function(element) if (element == value) then return true end return false end) end function filterTable(t, filter) local filteredTable = {} for i, v in ipairs(t) do if inTable(v.Type, filter) then filteredTable[#filteredTable + 1] = v end end if (#filteredTable == 0) then error("filterTable Error: Filtered table empty") end return filteredTable end function getSpawns() local spawns = filterTable(Map.NamedActors, {"enemy_spawn"}) if (#spawns == 0) then error("getSpawns Error: No spawns on map") end return spawns end function getObjectives() local objectives = filterTable(Map.NamedActors, {"enemy_objective"}) if (#objectives == 0) then error("getObjectives Error: No objectives on map") end return objectives end function displayGameStateText() local gameState if (endless == nil) then error("displayGameStateText Error: Boolean endless is nil") end if (endless) then gameState = "WAVE: " .. waveNumber .. "\n\n" else if (wavesToSurvive == nil) then error("displayGameStateText Error: Variable wavesToSurvive is nil") elseif (wavesToSurvive < 1) then error("displayGameStateText Error: Variable wavesToSurvive is out of bounds") end gameState = "WAVE: " .. waveNumber .. "/" .. wavesToSurvive .. "\n\n" end gameState = gameState .. "LIVES: [" for i = 1, initialPlayerLives do if (playerLives >= i) then gameState = gameState .. "|" else gameState = gameState .. " " end end gameState = gameState .. "]" UserInterface.SetMissionText(gameState) end function disguiseActorsAs(actors, appearance) for i, v in ipairs(actors) do v.DisguiseAsType(appearance, humanPlayer) end return actors end function initialiseSpawns() enemySpawns = makeOwnerOfActors(enemyPlayer, enemySpawns) if (spawnAppearance ~= nil) then enemySpawns = disguiseActorsAs(enemySpawns, spawnAppearance) end end function addObjectiveTrigger(objective) Trigger.OnPassengerEntered(objective, function(objective, actor) if (objective.HasPassengers) then removeEnemy(actor) objective.UnloadPassenger() actor.Destroy() playerLives = playerLives - 1 if (playerLives == 0) then endGame(false) end displayGameStateText() else print("addObjectiveTrigger ERROR CATCH: OnPassengerEntered triggered without passengers") end end) end function addObjectiveTriggers() for i, v in ipairs(enemyObjectives) do addObjectiveTrigger(v) end end function makeOwnerOfActors(player, actors) for i, v in ipairs(actors) do v.Owner = player end return actors end function initialiseObjectives() enemyObjectives = makeOwnerOfActors(enemyPlayer, enemyObjectives) if (objectiveAppearance ~= nil) then enemyObjectives = disguiseActorsAs(enemyObjectives, objectiveAppearance) end addObjectiveTriggers() end function getPathTiles() local tiles = filterTable(Map.NamedActors, {"path_tile"}) if (#tiles == 0) then error("getPathTiles Error: No tiles on map") end return tiles end function getPathBorderTiles() local tiles = filterTable(Map.NamedActors, {"path_border_tile"}) if (#tiles == 0) then error("getPathBorderTiles Error: No tile borders on map") end return tiles end function setPlayerCash(player, cash) player.Cash = cash end function actualiseActors(actors) for i, v in ipairs(actors) do v.IsInWorld = true end end function deactualiseActors(actors) for i, v in ipairs(actors) do v.IsInWorld = false end end function actualisePath() actualiseActors(pathTiles) end function actualisePathBorders() actualiseActors(pathBorderTiles) end function deactualisePath() deactualiseActors(pathTiles) end function deactualisePathBorders() deactualiseActors(pathBorderTiles) end function attachProductionTrigger(actor) Trigger.OnProduction(actor, function(producer, produced) if (produced.Type == "ready") then produced.Destroy() exitBuildMode() playing = true enterPlayMode() end end) end function createBuildingActor() local actorPosition = enemyObjectives[1].Location local actor = Actor.Create("build_control", true, {Location = actorPosition, Owner = humanPlayer}) attachProductionTrigger(actor) return actor end function enterBuildMode() if (playing) then error("enterBuildMode Error: Cannot enter build mode whilst playing") end deactualisePathBorders() buildingActor = createBuildingActor() externalScriptFunction("OnEnterBuildMode") building = true displayGameStateText() end function exitBuildMode() building = false buildingActor.Destroy() actualisePathBorders() end function getPossibleEnemies(allowance) --TODO: Clean this up local possibleEnemies = {} for i, v in ipairs(enemies) do if (v.Cost <= allowance) then if ((v.MinWave ~= nil) and (v.MaxWave ~= nil)) then if ((v.MinWave <= waveNumber) and (waveNumber <= v.MaxWave)) then if (v.Shares == nil) then table.insert(possibleEnemies, 1, v) elseif (v.Shares <= 0) then error("getPossibleEnemies Error: Enemy shares is out of bounds") else for i = 1, v.Shares do table.insert(possibleEnemies, 1, v) end end end elseif (v.MinWave ~= nil) then if (v.MinWave <= waveNumber) then if (v.Shares == nil) then table.insert(possibleEnemies, 1, v) elseif (v.Shares <= 0) then error("getPossibleEnemies Error: Enemy shares is out of bounds") else for i = 1, v.Shares do table.insert(possibleEnemies, 1, v) end end end elseif (v.MaxWave ~= nil) then if (waveNumber <= v.MaxWave) then if (v.Shares == nil) then table.insert(possibleEnemies, 1, v) else if (v.Shares <= 0) then error("getPossibleEnemies Error: Enemy shares is out of bounds") end for i = 1, v.Shares do table.insert(possibleEnemies, 1, v) end end end else if (v.Shares == nil) then table.insert(possibleEnemies, 1, v) elseif (v.Shares <= 0) then error("getPossibleEnemies Error: Enemy shares is out of bounds") else for i = 1, v.Shares do table.insert(possibleEnemies, 1, v) end end end end end return possibleEnemies end function getMaxCostUnit(units) local maxCost = -1 for i, v in ipairs(units) do if (v.Cost > maxCost) then maxCost = v.Cost end end return maxCost end function isCustomWave() for i, v in ipairs(customWaves) do if (v.Number == waveNumber) then return true end end return false end function getCustomWave() --TODO: Clean this up for i, v in ipairs(customWaves) do if (v.Number == waveNumber) then for j, a in ipairs(v.Enemies) do for i = 1, a.NumberOf do table.insert(wave, #wave + 1, a.Type) end end if (v.Text ~= nil) then Media.DisplayMessage(v.Text,"") end if (v.WaveReward ~= nil) then waveReward = v.WaveReward else waveReward = getWaveReward() end return end end end function populateWave() --TODO: Clean this up waveNumber = waveNumber + 1 if (isCustomWave()) then getCustomWave() else local allowance = getWaveAllowance() local possibleEnemies = getPossibleEnemies(allowance) if (#possibleEnemies == 0) then return end while (allowance ~= 0) do if (getMaxCostUnit(possibleEnemies) > allowance) then possibleEnemies = getPossibleEnemies(allowance) end local enemy = Utils.Random(possibleEnemies) table.insert(wave, 1, enemy.Type) allowance = allowance - enemy.Cost end waveReward = getWaveReward() end end function enterPlayMode() if (building) then error("enterPlayMode Error: Cannot enter play mode whilst building") end deactualisePath() populateWave() tickCounter = 0 if (cashPerWave) then playerCash = humanPlayer.Cash end externalScriptFunction("OnEnterPlayMode") playing = true displayGameStateText() end function getNewPlayerCash() local cash = playerCash + waveReward return cash end function exitPlayMode() playing = false actualisePath() if (cashPerWave) then Trigger.AfterDelay(1, function() humanPlayer.Cash = getNewPlayerCash() end) end end function waveEmpty() if (#wave == 0) then return true end return false end function createUnit(unitType, unitLocation) local unit = Actor.Create(unitType, true, {Owner = enemyPlayer, Location = unitLocation}) return unit end function getRandomObjective() local objective = Utils.Random(enemyObjectives) return objective end function findInTable(value, t) for i, v in ipairs(t) do if (v == value) then return i end end error("findInTable Error: Value not in table") end function removeEnemy(enemy) local key = findInTable(enemy, aliveEnemies) table.remove(aliveEnemies, key) table.remove(aliveEnemyObjectives, key) end function addOnIdleTrigger(unit, objective) Trigger.OnIdle(unit, function(unit) unit.Stop() unit.EnterTransport(objective) end) end function addOnKilledTrigger(unit) Trigger.OnKilled(unit, function(unit) removeEnemy(unit) if (cashPerWave) then Trigger.AfterDelay(1, function() humanPlayer.Cash = playerCash end) end end) end function addEnemyTriggers(unit, objective) addOnIdleTrigger(unit, objective) addOnKilledTrigger(unit) end function sendUnit(spawn) local unitType = table.remove(wave, 1) local unit = createUnit(unitType, spawn.Location) table.insert(aliveEnemies, 1, unit) local objective = getRandomObjective() table.insert(aliveEnemyObjectives, 1, objective) addEnemyTriggers(unit, objective) end function getRandomSpawn() local spawn = Utils.Random(enemySpawns) return spawn end function sendUnits() if (parallelUnitSending) then for i = 1, #enemySpawns do if (not waveEmpty()) then local spawn = enemySpawns[i] sendUnit(spawn) end end else local spawn = getRandomSpawn() sendUnit(spawn) end end function enemiesAlive() if (#aliveEnemies == 0) then return false end return true end function finalWave() if (wavesToSurvive <= waveNumber) then return true end return false end function endGame(won) if (won) then humanPlayer.MarkCompletedObjective(missionID) else humanPlayer.MarkFailedObjective(missionID) end end function externalScriptFunction(functionName) for i, v in ipairs(externalScripts) do if (v[functionName] ~= nil) then v[functionName]() end end end function Tick() tickCounter = tickCounter + 1 if (playing == true) then externalScriptFunction("OnPlayModeTick") if ((not waveEmpty()) and (tickCounter % sendDelay == 0) and (tickCounter >= initialSendDelay)) then sendUnits() elseif ((not enemiesAlive()) and (waveEmpty())) then if (not endless) then if (finalWave()) then endGame(true) return end end exitPlayMode() enterBuildMode() end else externalScriptFunction("OnBuildModeTick") end end function WorldLoaded() centraliseCamera() missionID = getWinLossConditionID() enemySpawns = getSpawns() initialiseSpawns() enemyObjectives = getObjectives() initialiseObjectives() pathTiles = getPathTiles() pathBorderTiles = getPathBorderTiles() setPlayerCash(humanPlayer, startingCash) enterBuildMode() end --PLAYER VARIABLES-- humanPlayer = Player.GetPlayer("Multi0") enemyPlayer = Player.GetPlayer("Creeps") initialPlayerLives = playerLives --MAP VARIABLES-- enemySpawns = {} enemyObjectives = {} tickCounter = 0 --WIN LOSS CONDITION VARIABLES-- missionID = nil --PLAY MODE VARIABLES-- playing = false aliveEnemies = {} aliveEnemyObjectives = {} --EXTERNAL SCRIPT VARIABLES-- externalScripts = {} --WAVE VARIABLES-- wave = {} waveNumber = 0 waveReward = nil playerCash = nil --BUILDING MODE VARIABLES-- building = false buildingActor = nil pathTiles = {} pathBorderTiles = {} --TODO: Add support for killing the objective actor instead --TODO: Fix disguising spawns and objectives