package mindustry.core; import arc.*; import arc.math.*; import arc.util.*; import mindustry.annotations.Annotations.*; import mindustry.core.GameState.*; import mindustry.game.EventType.*; import mindustry.game.*; import mindustry.game.Teams.*; import mindustry.gen.*; import mindustry.type.*; import mindustry.type.Weather.*; import mindustry.world.*; import mindustry.world.blocks.*; import mindustry.world.blocks.ConstructBlock.*; import mindustry.world.blocks.storage.CoreBlock.*; import java.util.*; import static mindustry.Vars.*; /** * Logic module. * Handles all logic for entities and waves. * Handles game state events. * Does not store any game state itself. *

* This class should not call any outside methods to change state of modules, but instead fire events. */ public class Logic implements ApplicationListener{ public Logic(){ Events.on(BlockDestroyEvent.class, event -> { //blocks that get broken are appended to the team's broken block queue Tile tile = event.tile; Block block = tile.block(); //skip null entities or un-rebuildables, for obvious reasons; also skip client since they can't modify these requests if(tile.build == null || !tile.block().rebuildable || net.client()) return; if(block instanceof ConstructBlock){ ConstructBuild entity = tile.bc(); //update block to reflect the fact that something was being constructed if(entity.cblock != null && entity.cblock.synthetic()){ block = entity.cblock; }else{ //otherwise this was a deconstruction that was interrupted, don't want to rebuild that return; } } TeamData data = state.teams.get(tile.team()); //remove existing blocks that have been placed here. //painful O(n) iteration + copy for(int i = 0; i < data.blocks.size; i++){ BlockPlan b = data.blocks.get(i); if(b.x == tile.x && b.y == tile.y){ data.blocks.removeIndex(i); break; } } data.blocks.addFirst(new BlockPlan(tile.x, tile.y, (short)tile.build.rotation, block.id, tile.build.config())); }); Events.on(BlockBuildEndEvent.class, event -> { if(!event.breaking){ TeamData data = state.teams.get(event.team); Iterator it = data.blocks.iterator(); while(it.hasNext()){ BlockPlan b = it.next(); Block block = content.block(b.block); if(event.tile.block().bounds(event.tile.x, event.tile.y, Tmp.r1).overlaps(block.bounds(b.x, b.y, Tmp.r2))){ it.remove(); } } } }); Events.on(LaunchItemEvent.class, e -> state.secinfo.handleItemExport(e.stack)); //when loading a 'damaged' sector, propagate the damage Events.on(WorldLoadEvent.class, e -> { if(state.isCampaign()){ long seconds = state.rules.sector.getSecondsPassed(); CoreBuild core = state.rules.defaultTeam.core(); //THE WAVES NEVER END state.rules.waves = true; //apply fractional damage based on how many turns have passed for this sector //float turnsPassed = seconds / (turnDuration / 60f); //TODO sector damage disabled for now //if(state.rules.sector.hasWaves() && turnsPassed > 0 && state.rules.sector.hasBase()){ // SectorDamage.apply(turnsPassed / sectorDestructionTurns); //} //add resources based on turns passed if(state.rules.sector.save != null && core != null){ //update correct storage capacity state.rules.sector.save.meta.secinfo.storageCapacity = core.storageCapacity; //add new items received state.rules.sector.calculateReceivedItems().each((item, amount) -> core.items.add(item, amount)); //clear received items state.rules.sector.setExtraItems(new ItemSeq()); //validation for(Item item : content.items()){ //ensure positive items if(core.items.get(item) < 0) core.items.set(item, 0); //cap the items if(core.items.get(item) > core.storageCapacity) core.items.set(item, core.storageCapacity); } } state.rules.sector.setSecondsPassed(0); } //enable infinite ammo for wave team by default state.rules.waveTeam.rules().infiniteAmmo = true; //save settings Core.settings.manualSave(); }); } /** Adds starting items, resets wave time, and sets state to playing. */ public void play(){ state.set(State.playing); //grace period of 2x wave time before game starts state.wavetime = state.rules.waveSpacing * 2; Events.fire(new PlayEvent()); //add starting items if(!state.isCampaign()){ for(TeamData team : state.teams.getActive()){ if(team.hasCore()){ Building entity = team.core(); entity.items.clear(); for(ItemStack stack : state.rules.loadout){ entity.items.add(stack.item, stack.amount); } } } } } public void reset(){ State prev = state.getState(); //recreate gamestate - sets state to menu state = new GameState(); //fire change event, since it was technically changed Events.fire(new StateChangeEvent(prev, State.menu)); Groups.clear(); Time.clear(); Events.fire(new ResetEvent()); //save settings on reset Core.settings.manualSave(); } public void skipWave(){ if(state.isCampaign()){ //warp time spent forward because the wave was just skipped. state.secinfo.internalTimeSpent += state.wavetime; } state.wavetime = 0; } public void runWave(){ spawner.spawnEnemies(); state.wave++; state.wavetime = state.rules.waveSpacing; Events.fire(new WaveEvent()); } private void checkGameState(){ //campaign maps do not have a 'win' state! if(state.isCampaign()){ //gameover only when cores are dead if(state.teams.playerCores().size == 0 && !state.gameOver){ state.gameOver = true; Events.fire(new GameOverEvent(state.rules.waveTeam)); } //check if there are no enemy spawns if(state.rules.waves && spawner.countSpawns() + state.teams.cores(state.rules.waveTeam).size <= 0){ //if yes, waves get disabled state.rules.waves = false; } //TODO capturing is disabled /* //if there's a "win" wave and no enemies are present, win automatically if(state.rules.waves && state.enemies == 0 && state.rules.winWave > 0 && state.wave >= state.rules.winWave && !spawner.isSpawning()){ //the sector has been conquered - waves get disabled state.rules.waves = false; //fire capture event Events.fire(new SectorCaptureEvent(state.rules.sector)); //save, just in case if(!headless){ control.saves.saveSector(state.rules.sector); } }*/ }else{ if(!state.rules.attackMode && state.teams.playerCores().size == 0 && !state.gameOver){ state.gameOver = true; Events.fire(new GameOverEvent(state.rules.waveTeam)); }else if(state.rules.attackMode){ Team alive = null; for(TeamData team : state.teams.getActive()){ if(team.hasCore()){ if(alive != null){ return; } alive = team.team; } } if(alive != null && !state.gameOver){ Events.fire(new GameOverEvent(alive)); state.gameOver = true; } } } } private void updateWeather(){ for(WeatherEntry entry : state.rules.weather){ //update cooldown entry.cooldown -= Time.delta; //create new event when not active if(entry.cooldown < 0 && !entry.weather.isActive()){ float duration = Mathf.random(entry.minDuration, entry.maxDuration); entry.cooldown = duration + Mathf.random(entry.minFrequency, entry.maxFrequency); Tmp.v1.setToRandomDirection(); Call.createWeather(entry.weather, entry.intensity, duration, Tmp.v1.x, Tmp.v1.y); } } } @Remote(called = Loc.both) public static void updateGameOver(Team winner){ state.gameOver = true; } @Remote(called = Loc.both) public static void gameOver(Team winner){ state.stats.wavesLasted = state.wave; ui.restart.show(winner); netClient.setQuiet(); } @Override public void dispose(){ //save the settings before quitting Core.settings.manualSave(); } @Override public void update(){ Events.fire(Trigger.update); universe.updateGlobal(); if(Core.settings.modified() && !state.isPlaying()){ Core.settings.forceSave(); } if(state.isGame()){ if(!net.client()){ state.enemies = Groups.unit.count(u -> u.team() == state.rules.waveTeam && u.type().isCounted); } if(!state.isPaused()){ state.teams.updateTeamStats(); if(state.isCampaign()){ state.secinfo.update(); } if(state.isCampaign()){ universe.update(); } Time.update(); //weather is serverside if(!net.client()){ updateWeather(); for(TeamData data : state.teams.getActive()){ if(data.hasAI()){ data.ai.update(); } } } if(state.rules.waves && state.rules.waveTimer && !state.gameOver){ if(!isWaitingWave()){ state.wavetime = Math.max(state.wavetime - Time.delta, 0); } } if(!net.client() && state.wavetime <= 0 && state.rules.waves){ runWave(); } //apply weather attributes state.envAttrs.clear(); Groups.weather.each(w -> state.envAttrs.add(w.weather.attrs, w.opacity)); Groups.update(); } if(!net.client() && !world.isInvalidMap() && !state.isEditor() && state.rules.canGameOver){ checkGameState(); } } } /** @return whether the wave timer is paused due to enemies */ public boolean isWaitingWave(){ return (state.rules.waitEnemies || (state.wave >= state.rules.winWave && state.rules.winWave > 0)) && state.enemies > 0; } }