341 lines
12 KiB
Java
341 lines
12 KiB
Java
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.
|
|
* <p>
|
|
* This class should <i>not</i> 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<BlockPlan> 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;
|
|
}
|
|
|
|
}
|