diff --git a/build.gradle b/build.gradle index c1987daf5b..c840690ca3 100644 --- a/build.gradle +++ b/build.gradle @@ -26,7 +26,7 @@ allprojects { appName = 'Mindustry' gdxVersion = '1.9.8' roboVMVersion = '2.3.0' - uCoreVersion = 'e492954e86' + uCoreVersion = 'd30ec505beb78da25fea3a5aa78f79260f2fb65b' getVersionString = { String buildVersion = getBuildVersion() diff --git a/core/src/io/anuke/mindustry/Vars.java b/core/src/io/anuke/mindustry/Vars.java index 25704f57f4..b4589573bb 100644 --- a/core/src/io/anuke/mindustry/Vars.java +++ b/core/src/io/anuke/mindustry/Vars.java @@ -34,6 +34,10 @@ public class Vars{ public static final float wavespace = 60 * 60 * 1.5f; //set ridiculously high for now public static final float coreBuildRange = 800999f; + //team of the player by default + public static final Team defaultTeam = Team.blue; + //team of the enemy in waves + public static final Team waveTeam = Team.red; public static final float enemyCoreBuildRange = 400f; //discord group URL diff --git a/core/src/io/anuke/mindustry/ai/BlockIndexer.java b/core/src/io/anuke/mindustry/ai/BlockIndexer.java index d86efbfe9f..124e069a73 100644 --- a/core/src/io/anuke/mindustry/ai/BlockIndexer.java +++ b/core/src/io/anuke/mindustry/ai/BlockIndexer.java @@ -1,17 +1,14 @@ package io.anuke.mindustry.ai; import com.badlogic.gdx.math.Vector2; -import com.badlogic.gdx.utils.Bits; -import com.badlogic.gdx.utils.IntMap; -import com.badlogic.gdx.utils.ObjectMap; -import com.badlogic.gdx.utils.ObjectSet; +import com.badlogic.gdx.utils.*; import io.anuke.mindustry.content.Items; import io.anuke.mindustry.content.blocks.Blocks; import io.anuke.mindustry.entities.TileEntity; import io.anuke.mindustry.game.EventType.TileChangeEvent; import io.anuke.mindustry.game.EventType.WorldLoadEvent; import io.anuke.mindustry.game.Team; -import io.anuke.mindustry.game.TeamInfo.TeamData; +import io.anuke.mindustry.game.Teams.TeamData; import io.anuke.mindustry.type.Item; import io.anuke.mindustry.world.Tile; import io.anuke.mindustry.world.meta.BlockFlag; @@ -21,66 +18,43 @@ import io.anuke.ucore.function.Predicate; import io.anuke.ucore.util.EnumSet; import io.anuke.ucore.util.Geometry; import io.anuke.ucore.util.Mathf; +import io.anuke.ucore.util.ThreadArray; import static io.anuke.mindustry.Vars.*; //TODO consider using quadtrees for finding specific types of blocks within an area //TODO maybe use Arrays instead of ObjectSets? -/** - * Class used for indexing special target blocks for AI. - */ +/**Class used for indexing special target blocks for AI.*/ public class BlockIndexer{ - /** - * Size of one ore quadrant. - */ + /**Size of one ore quadrant.*/ private final static int oreQuadrantSize = 20; - /** - * Size of one structure quadrant. - */ + /**Size of one structure quadrant.*/ private final static int structQuadrantSize = 12; - /** - * Set of all ores that are being scanned. - */ + /**Set of all ores that are being scanned.*/ private final ObjectSet scanOres = ObjectSet.with(Items.copper, Items.coal, Items.lead, Items.thorium, Items.titanium); private final ObjectSet itemSet = new ObjectSet<>(); - /** - * Stores all ore quadtrants on the map. - */ + /**Stores all ore quadtrants on the map.*/ private ObjectMap> ores; - /** - * Tags all quadrants. - */ + /**Tags all quadrants.*/ private Bits[] structQuadrants; - /** - * Maps teams to a map of flagged tiles by type. - */ - private ObjectMap> enemyMap = new ObjectMap<>(); - /** - * Maps teams to a map of flagged tiles by type. - */ - private ObjectMap> allyMap = new ObjectMap<>(); - /** - * Empty map for invalid teams. - */ - private ObjectMap> emptyMap = new ObjectMap<>(); - /** - * Maps tile positions to their last known tile index data. - */ + /**Maps teams to a map of flagged tiles by type.*/ + private ObjectSet[][] flagMap = new ObjectSet[Team.all.length][BlockFlag.all.length]; + /**Maps tile positions to their last known tile index data.*/ private IntMap typeMap = new IntMap<>(); - /** - * Empty array used for returning. - */ - private ObjectSet emptyArray = new ObjectSet<>(); + /**Empty set used for returning.*/ + private ObjectSet emptySet = new ObjectSet<>(); + /**Array used for returning and reusing.*/ + private Array returnArray = new ThreadArray<>(); public BlockIndexer(){ Events.on(TileChangeEvent.class, tile -> { if(typeMap.get(tile.packedPosition()) != null){ TileIndex index = typeMap.get(tile.packedPosition()); for(BlockFlag flag : index.flags){ - getMap(index.team).get(flag).remove(tile); + getFlagged(index.team)[flag.ordinal()].remove(tile); } } process(tile); @@ -88,8 +62,12 @@ public class BlockIndexer{ }); Events.on(WorldLoadEvent.class, () -> { - enemyMap.clear(); - allyMap.clear(); + flagMap = new ObjectSet[Team.all.length][BlockFlag.all.length]; + for(int i = 0; i < flagMap.length; i++){ + for(int j = 0; j < BlockFlag.all.length; j++){ + flagMap[i][j] = new ObjectSet<>(); + } + } typeMap.clear(); ores = null; @@ -115,18 +93,26 @@ public class BlockIndexer{ }); } - /** - * Get all allied blocks with a flag. - */ - public ObjectSet getAllied(Team team, BlockFlag type){ - return state.teams.has(team) ? (state.teams.get(team).ally ? allyMap : enemyMap).get(type, emptyArray) : emptyArray; + private ObjectSet[] getFlagged(Team team){ + return flagMap[team.ordinal()]; } - /** - * Get all enemy blocks with a flag. - */ - public ObjectSet getEnemy(Team team, BlockFlag type){ - return (!state.teams.get(team).ally ? allyMap : enemyMap).get(type, emptyArray); + /**Get all allied blocks with a flag.*/ + public ObjectSet getAllied(Team team, BlockFlag type){ + return flagMap[team.ordinal()][type.ordinal()]; + } + + /**Get all enemy blocks with a flag.*/ + public Array getEnemy(Team team, BlockFlag type){ + returnArray.clear(); + for(Team enemy : state.teams.enemiesOf(team)){ + if(state.teams.isActive(enemy)){ + for(Tile tile : getFlagged(enemy)[type.ordinal()]){ + returnArray.add(tile); + } + } + } + return returnArray; } public TileEntity findTile(Team team, float x, float y, float range, Predicate pred){ @@ -166,7 +152,7 @@ public class BlockIndexer{ * Only specific ore types are scanned. See {@link #scanOres}. */ public ObjectSet getOrePositions(Item item){ - return ores.get(item, emptyArray); + return ores.get(item, emptySet); } /** @@ -192,19 +178,15 @@ public class BlockIndexer{ private void process(Tile tile){ if(tile.block().flags != null && tile.getTeam() != Team.none){ - ObjectMap> map = getMap(tile.getTeam()); + ObjectSet[] map = getFlagged(tile.getTeam()); for(BlockFlag flag : tile.block().flags){ - ObjectSet arr = map.get(flag); - if(arr == null){ - arr = new ObjectSet<>(); - map.put(flag, arr); - } + ObjectSet arr = map[flag.ordinal()]; arr.add(tile); - map.put(flag, arr); + map[flag.ordinal()] = arr; } typeMap.put(tile.packedPosition(), new TileIndex(tile.block().flags, tile.getTeam())); } @@ -247,7 +229,8 @@ public class BlockIndexer{ int quadrantY = tile.y / structQuadrantSize; int index = quadrantX + quadrantY * quadWidth(); - for(TeamData data : state.teams.getTeams()){ + for(Team team : Team.all){ + TeamData data = state.teams.get(team); //fast-set this quadrant to 'occupied' if the tile just placed is already of this team if(tile.getTeam() == data.team && tile.entity != null){ @@ -284,11 +267,6 @@ public class BlockIndexer{ return Mathf.ceil(world.height() / (float) structQuadrantSize); } - private ObjectMap> getMap(Team team){ - if(!state.teams.has(team)) return emptyMap; - return state.teams.get(team).ally ? allyMap : enemyMap; - } - private void scanOres(){ ores = new ObjectMap<>(); diff --git a/core/src/io/anuke/mindustry/ai/Pathfinder.java b/core/src/io/anuke/mindustry/ai/Pathfinder.java index 528974078c..51bf87b392 100644 --- a/core/src/io/anuke/mindustry/ai/Pathfinder.java +++ b/core/src/io/anuke/mindustry/ai/Pathfinder.java @@ -1,15 +1,14 @@ package io.anuke.mindustry.ai; import com.badlogic.gdx.math.GridPoint2; +import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.IntArray; -import com.badlogic.gdx.utils.ObjectSet; -import com.badlogic.gdx.utils.ObjectSet.ObjectSetIterator; import com.badlogic.gdx.utils.Queue; import com.badlogic.gdx.utils.TimeUtils; import io.anuke.mindustry.game.EventType.TileChangeEvent; import io.anuke.mindustry.game.EventType.WorldLoadEvent; import io.anuke.mindustry.game.Team; -import io.anuke.mindustry.game.TeamInfo.TeamData; +import io.anuke.mindustry.game.Teams.TeamData; import io.anuke.mindustry.net.Net; import io.anuke.mindustry.world.Tile; import io.anuke.mindustry.world.meta.BlockFlag; @@ -30,8 +29,9 @@ public class Pathfinder{ Events.on(TileChangeEvent.class, tile -> { if(Net.client()) return; - for(TeamData data : state.teams.getTeams()){ - if(data.team != tile.getTeam() && paths[data.team.ordinal()].weights[tile.x][tile.y] >= Float.MAX_VALUE){ + for(Team team : Team.all){ + TeamData data = state.teams.get(team); + if(state.teams.isActive(team) && data.team != tile.getTeam() && paths[data.team.ordinal()].weights[tile.x][tile.y] >= Float.MAX_VALUE){ update(tile, data.team); } } @@ -43,10 +43,10 @@ public class Pathfinder{ public void update(){ if(Net.client()) return; - ObjectSetIterator iterator = new ObjectSetIterator<>(state.teams.getTeams()); - - for(TeamData team : iterator){ - updateFrontier(team.team, maxUpdate); + for(Team team : Team.all){ + if(state.teams.isActive(team)){ + updateFrontier(team, maxUpdate); + } } } @@ -107,7 +107,7 @@ public class Pathfinder{ path.lastSearchTime = TimeUtils.millis(); - ObjectSet set = world.indexer().getEnemy(team, BlockFlag.target); + Array set = world.indexer().getEnemy(team, BlockFlag.target); for(Tile other : set){ path.weights[other.x][other.y] = 0; path.searches[other.x][other.y] = path.search; @@ -173,11 +173,13 @@ public class Pathfinder{ paths = new PathData[Team.all.length]; blocked.clear(); - for(TeamData data : state.teams.getTeams()){ + for(Team team : Team.all){ PathData path = new PathData(); - paths[data.team.ordinal()] = path; + paths[team.ordinal()] = path; - createFor(data.team); + if(state.teams.isActive(team)){ + createFor(team); + } } state.spawner.checkAllQuadrants(); diff --git a/core/src/io/anuke/mindustry/core/GameState.java b/core/src/io/anuke/mindustry/core/GameState.java index 10bddc2764..76a0ae95b1 100644 --- a/core/src/io/anuke/mindustry/core/GameState.java +++ b/core/src/io/anuke/mindustry/core/GameState.java @@ -4,7 +4,7 @@ import io.anuke.mindustry.ai.WaveSpawner; import io.anuke.mindustry.game.Difficulty; import io.anuke.mindustry.game.EventType.StateChangeEvent; import io.anuke.mindustry.game.GameMode; -import io.anuke.mindustry.game.TeamInfo; +import io.anuke.mindustry.game.Teams; import io.anuke.mindustry.net.Net; import io.anuke.ucore.core.Events; @@ -16,7 +16,7 @@ public class GameState{ public Difficulty difficulty = Difficulty.normal; public boolean friendlyFire; public WaveSpawner spawner = new WaveSpawner(); - public TeamInfo teams = new TeamInfo(); + public Teams teams = new Teams(); private State state = State.menu; public void set(State astate){ diff --git a/core/src/io/anuke/mindustry/core/Logic.java b/core/src/io/anuke/mindustry/core/Logic.java index 0fe9e701d8..c703bce362 100644 --- a/core/src/io/anuke/mindustry/core/Logic.java +++ b/core/src/io/anuke/mindustry/core/Logic.java @@ -9,8 +9,7 @@ import io.anuke.mindustry.game.EventType.PlayEvent; import io.anuke.mindustry.game.EventType.ResetEvent; import io.anuke.mindustry.game.EventType.WaveEvent; import io.anuke.mindustry.game.Team; -import io.anuke.mindustry.game.TeamInfo; -import io.anuke.mindustry.game.TeamInfo.TeamData; +import io.anuke.mindustry.game.Teams; import io.anuke.mindustry.net.Net; import io.anuke.mindustry.type.Item; import io.anuke.mindustry.type.ItemStack; @@ -50,26 +49,25 @@ public class Logic extends Module{ state.set(State.playing); state.wavetime = wavespace * state.difficulty.timeScaling * 2; - for(TeamData team : state.teams.getTeams(true)){ - for(Tile tile : team.cores){ - if(debug){ - for(Item item : Item.all()){ - if(item.type == ItemType.material){ - tile.entity.items.set(item, 1000); - } - } - } - - if(world.getSector() != null){ - Array items = world.getSector().startingItems; - for(ItemStack stack : items){ - tile.entity.items.add(stack.item, stack.amount); + for(Tile tile : state.teams.get(defaultTeam).cores){ + if(debug){ + for(Item item : Item.all()){ + if(item.type == ItemType.material){ + tile.entity.items.set(item, 1000); } } } + + if(world.getSector() != null){ + Array items = world.getSector().startingItems; + for(ItemStack stack : items){ + tile.entity.items.add(stack.item, stack.amount); + } + } } + Events.fire(PlayEvent.class); } @@ -77,9 +75,7 @@ public class Logic extends Module{ state.wave = 1; state.wavetime = wavespace * state.difficulty.timeScaling; state.gameOver = false; - state.teams = new TeamInfo(); - state.teams.add(Team.blue, true); - state.teams.add(Team.red, false); + state.teams = new Teams(); Timers.clear(); Entities.clear(); @@ -96,11 +92,12 @@ public class Logic extends Module{ Events.fire(WaveEvent.class); } + //for gameOver to trigger, there must not be no cores remaining at all; obviously this never triggers in PvP private void checkGameOver(){ boolean gameOver = true; - for(TeamData data : state.teams.getTeams(true)){ - if(data.cores.size > 0){ + for(Team team : Team.all){ + if(state.teams.get(team).cores.size > 0){ gameOver = false; break; } diff --git a/core/src/io/anuke/mindustry/core/NetServer.java b/core/src/io/anuke/mindustry/core/NetServer.java index 8cb523fc33..f8ebc5986d 100644 --- a/core/src/io/anuke/mindustry/core/NetServer.java +++ b/core/src/io/anuke/mindustry/core/NetServer.java @@ -14,6 +14,7 @@ import io.anuke.mindustry.core.GameState.State; import io.anuke.mindustry.entities.Player; import io.anuke.mindustry.entities.traits.BuilderTrait.BuildRequest; import io.anuke.mindustry.entities.traits.SyncTrait; +import io.anuke.mindustry.game.Team; import io.anuke.mindustry.game.Version; import io.anuke.mindustry.gen.Call; import io.anuke.mindustry.gen.RemoteReadServer; @@ -31,6 +32,7 @@ import io.anuke.ucore.io.delta.ByteMatcherHash; import io.anuke.ucore.io.delta.DEZEncoder; import io.anuke.ucore.modules.Module; import io.anuke.ucore.util.Log; +import io.anuke.ucore.util.Mathf; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -166,6 +168,24 @@ public class NetServer extends Module{ player.setNet(player.x, player.y); player.color.set(packet.color); player.color.a = 1f; + + if(state.mode.isPvp){ + //find team with minimum amount of players and auto-assign player to that. + Team min = Mathf.findMin(Team.all, team -> { + if(state.teams.isActive(team)){ + int count = 0; + for(Player other : playerGroup.all()){ + if(other.getTeam() == team){ + count ++; + } + } + return count; + } + return Integer.MAX_VALUE; + }); + player.setTeam(min); + } + connections.put(id, player); trace.playerid = player.id; diff --git a/core/src/io/anuke/mindustry/entities/Player.java b/core/src/io/anuke/mindustry/entities/Player.java index 7419299a35..7a276a74dc 100644 --- a/core/src/io/anuke/mindustry/entities/Player.java +++ b/core/src/io/anuke/mindustry/entities/Player.java @@ -244,6 +244,10 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra return playerGroup; } + public void setTeam(Team team){ + this.team = team; + } + //endregion //region draw methods diff --git a/core/src/io/anuke/mindustry/entities/Unit.java b/core/src/io/anuke/mindustry/entities/Unit.java index b3900c5726..f9734e2450 100644 --- a/core/src/io/anuke/mindustry/entities/Unit.java +++ b/core/src/io/anuke/mindustry/entities/Unit.java @@ -6,7 +6,7 @@ import com.badlogic.gdx.math.Vector2; import io.anuke.mindustry.content.blocks.Blocks; import io.anuke.mindustry.entities.traits.*; import io.anuke.mindustry.game.Team; -import io.anuke.mindustry.game.TeamInfo.TeamData; +import io.anuke.mindustry.game.Teams.TeamData; import io.anuke.mindustry.net.Interpolator; import io.anuke.mindustry.net.Net; import io.anuke.mindustry.type.StatusEffect; @@ -179,17 +179,13 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ } public TileEntity getClosestCore(){ - if(state.teams.has(team)){ - TeamData data = state.teams.get(team); + TeamData data = state.teams.get(team); - Tile tile = Geometry.findClosest(x, y, data.cores); - if(tile == null){ - return null; - }else{ - return tile.entity; - } - }else{ + Tile tile = Geometry.findClosest(x, y, data.cores); + if(tile == null){ return null; + }else{ + return tile.entity; } } diff --git a/core/src/io/anuke/mindustry/entities/Units.java b/core/src/io/anuke/mindustry/entities/Units.java index 22e6800b2d..5bcb881a14 100644 --- a/core/src/io/anuke/mindustry/entities/Units.java +++ b/core/src/io/anuke/mindustry/entities/Units.java @@ -2,7 +2,6 @@ package io.anuke.mindustry.entities; import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.math.Vector2; -import com.badlogic.gdx.utils.ObjectSet; import io.anuke.mindustry.entities.traits.TargetTrait; import io.anuke.mindustry.entities.units.BaseUnit; import io.anuke.mindustry.game.Team; @@ -12,6 +11,7 @@ import io.anuke.ucore.entities.EntityGroup; import io.anuke.ucore.entities.EntityPhysics; import io.anuke.ucore.function.Consumer; import io.anuke.ucore.function.Predicate; +import io.anuke.ucore.util.EnumSet; import static io.anuke.mindustry.Vars.*; @@ -106,13 +106,7 @@ public class Units{ * Returns the neareset ally tile in a range. */ public static TileEntity findAllyTile(Team team, float x, float y, float range, Predicate pred){ - for(Team enemy : state.teams.alliesOf(team)){ - TileEntity entity = world.indexer().findTile(enemy, x, y, range, pred); - if(entity != null){ - return entity; - } - } - return null; + return world.indexer().findTile(team, x, y, range, pred); } /** @@ -271,7 +265,7 @@ public class Units{ * Iterates over all units that are enemies of this team. */ public static void getNearbyEnemies(Team team, Rectangle rect, Consumer cons){ - ObjectSet targets = state.teams.enemiesOf(team); + EnumSet targets = state.teams.enemiesOf(team); for(Team other : targets){ EntityGroup group = unitGroups[other.ordinal()]; diff --git a/core/src/io/anuke/mindustry/entities/units/BaseUnit.java b/core/src/io/anuke/mindustry/entities/units/BaseUnit.java index 315c2f88a8..29465610d6 100644 --- a/core/src/io/anuke/mindustry/entities/units/BaseUnit.java +++ b/core/src/io/anuke/mindustry/entities/units/BaseUnit.java @@ -1,7 +1,6 @@ package io.anuke.mindustry.entities.units; import com.badlogic.gdx.graphics.g2d.TextureRegion; -import com.badlogic.gdx.utils.ObjectSet; import io.anuke.annotations.Annotations.Loc; import io.anuke.annotations.Annotations.Remote; import io.anuke.mindustry.Vars; @@ -15,7 +14,6 @@ import io.anuke.mindustry.entities.traits.ShooterTrait; import io.anuke.mindustry.entities.traits.SpawnerTrait; import io.anuke.mindustry.entities.traits.TargetTrait; import io.anuke.mindustry.game.Team; -import io.anuke.mindustry.game.TeamInfo.TeamData; import io.anuke.mindustry.gen.Call; import io.anuke.mindustry.graphics.Palette; import io.anuke.mindustry.net.Net; @@ -188,16 +186,14 @@ public abstract class BaseUnit extends Unit implements ShooterTrait{ } public TileEntity getClosestEnemyCore(){ - if(Vars.state.teams.has(team)){ - ObjectSet datas = Vars.state.teams.enemyDataOf(team); - for(TeamData data : datas){ - Tile tile = Geometry.findClosest(x, y, data.cores); - if(tile != null){ - return tile.entity; - } + for(Team enemy : Vars.state.teams.enemiesOf(team)){ + Tile tile = Geometry.findClosest(x, y, Vars.state.teams.get(enemy).cores); + if(tile != null){ + return tile.entity; } } + return null; } diff --git a/core/src/io/anuke/mindustry/entities/units/GroundUnit.java b/core/src/io/anuke/mindustry/entities/units/GroundUnit.java index 38bcda1ff3..e4be523d19 100644 --- a/core/src/io/anuke/mindustry/entities/units/GroundUnit.java +++ b/core/src/io/anuke/mindustry/entities/units/GroundUnit.java @@ -241,8 +241,18 @@ public abstract class GroundUnit extends BaseUnit{ } protected void moveAwayFromCore(){ + Team enemy = null; + for(Team team : Vars.state.teams.enemiesOf(team)){ + if(Vars.state.teams.isActive(team)){ + enemy = team; + break; + } + } + + if(enemy == null) return; + Tile tile = world.tileWorld(x, y); - Tile targetTile = world.pathfinder().getTargetTile(Vars.state.teams.enemiesOf(team).first(), tile); + Tile targetTile = world.pathfinder().getTargetTile(enemy, tile); TileEntity core = getClosestCore(); if(tile == targetTile || core == null || distanceTo(core) < 90f) return; diff --git a/core/src/io/anuke/mindustry/game/GameMode.java b/core/src/io/anuke/mindustry/game/GameMode.java index ffadb03d8b..91d20c6303 100644 --- a/core/src/io/anuke/mindustry/game/GameMode.java +++ b/core/src/io/anuke/mindustry/game/GameMode.java @@ -14,11 +14,15 @@ public enum GameMode{ noWaves{{ disableWaves = true; hidden = true; + autoSpawn = true; + }}, + pvp{{ + disableWaves = true; + isPvp = true; + hidden = true; }}; - public boolean infiniteResources; - public boolean disableWaveTimer; - public boolean disableWaves; - public boolean hidden; + + public boolean infiniteResources, disableWaveTimer, disableWaves, hidden, autoSpawn, isPvp; public String description(){ return Bundles.get("mode." + name() + ".description"); diff --git a/core/src/io/anuke/mindustry/game/TeamInfo.java b/core/src/io/anuke/mindustry/game/TeamInfo.java deleted file mode 100644 index 8ddb84624d..0000000000 --- a/core/src/io/anuke/mindustry/game/TeamInfo.java +++ /dev/null @@ -1,143 +0,0 @@ -package io.anuke.mindustry.game; - -import com.badlogic.gdx.utils.Array; -import com.badlogic.gdx.utils.ObjectMap; -import com.badlogic.gdx.utils.ObjectSet; -import io.anuke.mindustry.world.Tile; -import io.anuke.ucore.util.ThreadArray; -import io.anuke.ucore.util.ThreadSet; - -/** - * Class for various team-based utilities. - */ -public class TeamInfo{ - private ObjectMap map = new ObjectMap<>(); - private ThreadSet allies = new ThreadSet<>(), - enemies = new ThreadSet<>(); - private ThreadSet allyData = new ThreadSet<>(), - enemyData = new ThreadSet<>(); - private ThreadSet allTeamData = new ThreadSet<>(); - private ThreadSet allTeams = new ThreadSet<>(); - private int allyBits = 0; - private int enemyBits = 0; - - /** - * Returns all teams on a side. - */ - public ObjectSet getTeams(boolean ally){ - return ally ? allyData : enemyData; - } - - /** - * Returns all team data. - */ - public ObjectSet getTeams(){ - return allTeamData; - } - - /** - * Register a team. - * - * @param team The team type enum. - * @param ally Whether this team is an ally with the player or an enemy with the player. - * In PvP situations with dedicated servers, the sides can be arbitrary. - */ - public void add(Team team, boolean ally){ - if(has(team)) throw new RuntimeException("Can't define team information twice!"); - - TeamData data = new TeamData(team, ally); - - if(ally){ - allies.add(team); - allyData.add(data); - allyBits |= (1 << team.ordinal()); - }else{ - enemies.add(team); - enemyData.add(data); - enemyBits |= (1 << team.ordinal()); - } - - allTeamData.add(data); - allTeams.add(team); - - map.put(team, data); - } - - /** - * Returns team data by type. Call {@link #has(Team)} first to make sure it's active! - */ - public TeamData get(Team team){ - if(!has(team)) throw new RuntimeException("This team is not active! Check has() before calling get()."); - return map.get(team); - } - - /** - * Returns whether the specified team is active, e.g. whether it is participating in the game. - */ - public boolean has(Team team){ - return map.containsKey(team); - } - - /** - * Returns a set of all teams that are enemies of this team. - * For teams not active, an empty set is returned. - */ - public ObjectSet enemiesOf(Team team){ - boolean ally = allies.contains(team); - boolean enemy = enemies.contains(team); - - //this team isn't even in the game, so target everything! - if(!ally && !enemy) return allTeams; - - return ally ? enemies : allies; - } - - /** - * Returns a set of all teams that are allies of this team. - * For teams not active, an empty set is returned. - */ - public ObjectSet alliesOf(Team team){ - boolean ally = allies.contains(team); - boolean enemy = enemies.contains(team); - - //this team isn't even in the game, so target everything! - if(!ally && !enemy) return allTeams; - - return !ally ? enemies : allies; - } - - /** - * Returns a set of all teams that are enemies of this team. - * For teams not active, an empty set is returned. - */ - public ObjectSet enemyDataOf(Team team){ - boolean ally = allies.contains(team); - boolean enemy = enemies.contains(team); - - //this team isn't even in the game, so target everything! - if(!ally && !enemy) return allTeamData; - - return ally ? enemyData : allyData; - } - - /** - * Returns whether or not these two teams are enemies. - */ - public boolean areEnemies(Team team, Team other){ - if(team == other) return false; //fast fail to be more efficient - boolean ally = (allyBits & (1 << team.ordinal())) != 0; - boolean enemy = (enemyBits & (1 << other.ordinal())) != 0; - return (ally == enemy) || !ally; //if it's not in the game, target everything. - } - - public class TeamData{ - public final Array cores = new ThreadArray<>(); - public final Team team; - public final boolean ally; - - public TeamData(Team team, boolean ally){ - this.team = team; - this.ally = ally; - } - } -} diff --git a/core/src/io/anuke/mindustry/game/Teams.java b/core/src/io/anuke/mindustry/game/Teams.java new file mode 100644 index 0000000000..1ed7138613 --- /dev/null +++ b/core/src/io/anuke/mindustry/game/Teams.java @@ -0,0 +1,66 @@ +package io.anuke.mindustry.game; + +import com.badlogic.gdx.utils.Array; +import io.anuke.mindustry.Vars; +import io.anuke.mindustry.world.Tile; +import io.anuke.ucore.util.EnumSet; +import io.anuke.ucore.util.ThreadArray; + +/** + * Class for various team-based utilities. + */ +public class Teams{ + private TeamData[] map = new TeamData[Team.all.length]; + + /** + * Register a team. + * + * @param team The team type enum. + * @param enemies The array of enemies of this team. Any team not in this array is considered neutral. + */ + public void add(Team team, Team... enemies){ + if(map[team.ordinal()] != null) throw new RuntimeException("Can't define team information twice!"); + + map[team.ordinal()] = new TeamData(team, EnumSet.of(enemies)); + } + + /**Returns team data by type.*/ + public TeamData get(Team team){ + if(map[team.ordinal()] == null){ + //By default, a non-defined team will be enemies of everything. + Team[] others = new Team[Team.all.length-1]; + for(int i = 0, j = 0; i < Team.all.length; i++){ + if(Team.all[i] != team) others[j++] = Team.all[i]; + } + add(team, others); + } + return map[team.ordinal()]; + } + + /**Returns whether a team is active, e.g. whether it has any cores remaining.*/ + public boolean isActive(Team team){ + //the enemy wave team is always active + return (!Vars.state.mode.disableWaves && team == Vars.waveTeam) || get(team).cores.size > 0; + } + + /**Returns a set of all teams that are enemies of this team.*/ + public EnumSet enemiesOf(Team team){ + return get(team).enemies; + } + + /**Returns whether {@param other} is an enemy of {@param #team}.*/ + public boolean areEnemies(Team team, Team other){ + return enemiesOf(team).contains(other); + } + + public class TeamData{ + public final Array cores = new ThreadArray<>(); + public final EnumSet enemies; + public final Team team; + + public TeamData(Team team, EnumSet enemies){ + this.team = team; + this.enemies = enemies; + } + } +} diff --git a/core/src/io/anuke/mindustry/graphics/OverlayRenderer.java b/core/src/io/anuke/mindustry/graphics/OverlayRenderer.java index 437b4b90e2..4194ec477f 100644 --- a/core/src/io/anuke/mindustry/graphics/OverlayRenderer.java +++ b/core/src/io/anuke/mindustry/graphics/OverlayRenderer.java @@ -7,7 +7,7 @@ import com.badlogic.gdx.utils.Array; import io.anuke.mindustry.content.blocks.Blocks; import io.anuke.mindustry.entities.Player; import io.anuke.mindustry.entities.TileEntity; -import io.anuke.mindustry.game.TeamInfo.TeamData; +import io.anuke.mindustry.game.Team; import io.anuke.mindustry.input.InputHandler; import io.anuke.mindustry.world.Block; import io.anuke.mindustry.world.Tile; @@ -63,13 +63,13 @@ public class OverlayRenderer{ Lines.stroke(buildFadeTime*2f); if(buildFadeTime > 0.005f){ - for(TeamData data : state.teams.enemyDataOf(player.getTeam())){ - for(Tile core : data.cores){ + for(Team enemy : state.teams.enemiesOf(player.getTeam())){ + for(Tile core : state.teams.get(enemy).cores){ float dst = Vector2.dst(player.x, player.y, core.drawx(), core.drawy()); if(dst < enemyCoreBuildRange * 1.5f){ Draw.color(Color.DARK_GRAY); Lines.poly(core.drawx(), core.drawy() - 2, 200, enemyCoreBuildRange); - Draw.color(Palette.accent, data.team.color, 0.5f + Mathf.absin(Timers.time(), 10f, 0.5f)); + Draw.color(Palette.accent, enemy.color, 0.5f + Mathf.absin(Timers.time(), 10f, 0.5f)); Lines.poly(core.drawx(), core.drawy(), 200, enemyCoreBuildRange); } } diff --git a/core/src/io/anuke/mindustry/io/versions/Save16.java b/core/src/io/anuke/mindustry/io/versions/Save16.java index ad35778110..f4d30b2088 100644 --- a/core/src/io/anuke/mindustry/io/versions/Save16.java +++ b/core/src/io/anuke/mindustry/io/versions/Save16.java @@ -117,8 +117,7 @@ public class Save16 extends SaveFileVersion{ tile.entity.read(stream); - if(tile.block() == StorageBlocks.core && - state.teams.has(t)){ + if(tile.block() == StorageBlocks.core){ state.teams.get(t).cores.add(tile); } }else if(wallid == 0){ diff --git a/core/src/io/anuke/mindustry/maps/generation/WorldGenerator.java b/core/src/io/anuke/mindustry/maps/generation/WorldGenerator.java index 973cea917f..d37668a9f3 100644 --- a/core/src/io/anuke/mindustry/maps/generation/WorldGenerator.java +++ b/core/src/io/anuke/mindustry/maps/generation/WorldGenerator.java @@ -84,8 +84,7 @@ public class WorldGenerator{ Team team = tile.getTeam(); - if(tile.block() == StorageBlocks.core && - state.teams.has(team)){ + if(tile.block() == StorageBlocks.core){ state.teams.get(team).cores.add(tile); } diff --git a/core/src/io/anuke/mindustry/maps/missions/BattleMission.java b/core/src/io/anuke/mindustry/maps/missions/BattleMission.java index c6544a5675..97230888e5 100644 --- a/core/src/io/anuke/mindustry/maps/missions/BattleMission.java +++ b/core/src/io/anuke/mindustry/maps/missions/BattleMission.java @@ -39,8 +39,12 @@ public class BattleMission implements Mission{ @Override public boolean isComplete(){ - //TODO check all enemy teams, not just the first - return Vars.state.teams.getTeams(false).first().cores.size == 0; + for(Team team : Vars.state.teams.enemiesOf(Vars.defaultTeam)){ + if(Vars.state.teams.isActive(team)){ + return false; + } + } + return true; } @Override diff --git a/core/src/io/anuke/mindustry/maps/missions/ResourceMission.java b/core/src/io/anuke/mindustry/maps/missions/ResourceMission.java index b8f6ff7ca8..d2a21f2975 100644 --- a/core/src/io/anuke/mindustry/maps/missions/ResourceMission.java +++ b/core/src/io/anuke/mindustry/maps/missions/ResourceMission.java @@ -27,7 +27,7 @@ public class ResourceMission implements Mission{ @Override public boolean isComplete(){ - return Vars.state.teams.getTeams(true).first().cores.first().entity.items.has(item, amount); + return Vars.state.teams.get(Vars.defaultTeam).cores.first().entity.items.has(item, amount); } @Override diff --git a/core/src/io/anuke/mindustry/net/NetworkIO.java b/core/src/io/anuke/mindustry/net/NetworkIO.java index 167bd25740..17d4024396 100644 --- a/core/src/io/anuke/mindustry/net/NetworkIO.java +++ b/core/src/io/anuke/mindustry/net/NetworkIO.java @@ -7,11 +7,11 @@ import io.anuke.mindustry.content.blocks.Blocks; import io.anuke.mindustry.entities.Player; import io.anuke.mindustry.game.GameMode; import io.anuke.mindustry.game.Team; -import io.anuke.mindustry.game.TeamInfo; -import io.anuke.mindustry.game.TeamInfo.TeamData; +import io.anuke.mindustry.game.Teams; +import io.anuke.mindustry.game.Teams.TeamData; +import io.anuke.mindustry.game.Version; import io.anuke.mindustry.maps.Map; import io.anuke.mindustry.maps.MapMeta; -import io.anuke.mindustry.game.Version; import io.anuke.mindustry.world.Tile; import io.anuke.mindustry.world.blocks.BlockPart; import io.anuke.ucore.core.Core; @@ -119,11 +119,16 @@ public class NetworkIO{ } //write team data - stream.writeByte(state.teams.getTeams().size); - for(TeamData data : state.teams.getTeams()){ + for(Team team : Team.all){ + TeamData data = state.teams.get(team); stream.writeByte(data.team.ordinal()); - stream.writeBoolean(data.ally); - stream.writeShort(data.cores.size); + + stream.writeByte(data.enemies.size()); + for(Team enemy : data.enemies){ + stream.writeByte(enemy.ordinal()); + } + + stream.writeByte(data.cores.size); for(Tile tile : data.cores){ stream.writeInt(tile.packedPosition()); } @@ -253,14 +258,21 @@ public class NetworkIO{ } player.reset(); - state.teams = new TeamInfo(); + state.teams = new Teams(); byte teams = stream.readByte(); for(int i = 0; i < teams; i++){ Team team = Team.all[stream.readByte()]; - boolean ally = stream.readBoolean(); - short cores = stream.readShort(); - state.teams.add(team, ally); + + byte enemies = stream.readByte(); + Team[] enemyArr = new Team[enemies]; + for(int j = 0; j < enemies; j++){ + enemyArr[j] = Team.all[stream.readByte()]; + } + + state.teams.add(team, enemyArr); + + byte cores = stream.readByte(); for(int j = 0; j < cores; j++){ state.teams.get(team).cores.add(world.tile(stream.readInt())); diff --git a/core/src/io/anuke/mindustry/world/Build.java b/core/src/io/anuke/mindustry/world/Build.java index a08e0ae067..e9e5bf359f 100644 --- a/core/src/io/anuke/mindustry/world/Build.java +++ b/core/src/io/anuke/mindustry/world/Build.java @@ -6,7 +6,6 @@ import io.anuke.mindustry.content.blocks.Blocks; import io.anuke.mindustry.entities.Units; import io.anuke.mindustry.game.EventType.BlockBuildEvent; import io.anuke.mindustry.game.Team; -import io.anuke.mindustry.game.TeamInfo.TeamData; import io.anuke.mindustry.type.Recipe; import io.anuke.mindustry.world.blocks.BuildBlock.BuildEntity; import io.anuke.ucore.core.Events; @@ -138,8 +137,8 @@ public class Build{ } //check for enemy cores - for(TeamData data : state.teams.enemyDataOf(team)){ - for(Tile core : data.cores){ + for(Team enemy : state.teams.enemiesOf(team)){ + for(Tile core : state.teams.get(enemy).cores){ if(Vector2.dst(x*tilesize + type.offset(), y*tilesize + type.offset(), core.drawx(), core.drawy()) < enemyCoreBuildRange + type.size*tilesize/2f){ return false; } diff --git a/core/src/io/anuke/mindustry/world/blocks/storage/CoreBlock.java b/core/src/io/anuke/mindustry/world/blocks/storage/CoreBlock.java index 7b23a65ccf..bcbebc69e3 100644 --- a/core/src/io/anuke/mindustry/world/blocks/storage/CoreBlock.java +++ b/core/src/io/anuke/mindustry/world/blocks/storage/CoreBlock.java @@ -176,9 +176,7 @@ public class CoreBlock extends StorageBlock{ //TODO more dramatic effects super.onDestroyed(tile); - if(state.teams.has(tile.getTeam())){ - state.teams.get(tile.getTeam()).cores.removeValue(tile, true); - } + state.teams.get(tile.getTeam()).cores.removeValue(tile, true); } @Override diff --git a/core/src/io/anuke/mindustry/world/blocks/units/UnitPad.java b/core/src/io/anuke/mindustry/world/blocks/units/UnitPad.java index 3a247e11e2..e0ee770fd0 100644 --- a/core/src/io/anuke/mindustry/world/blocks/units/UnitPad.java +++ b/core/src/io/anuke/mindustry/world/blocks/units/UnitPad.java @@ -8,7 +8,6 @@ import io.anuke.mindustry.content.fx.BlockFx; import io.anuke.mindustry.entities.TileEntity; import io.anuke.mindustry.entities.units.BaseUnit; import io.anuke.mindustry.entities.units.UnitType; -import io.anuke.mindustry.game.Team; import io.anuke.mindustry.gen.Call; import io.anuke.mindustry.graphics.Palette; import io.anuke.mindustry.graphics.Shaders; @@ -36,6 +35,9 @@ import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; +import static io.anuke.mindustry.Vars.state; +import static io.anuke.mindustry.Vars.waveTeam; + public class UnitPad extends Block{ protected float gracePeriodMultiplier = 23f; protected float speedupTime = 60f * 60f * 20; @@ -142,7 +144,7 @@ public class UnitPad extends Block{ entity.time += Timers.delta() * entity.speedScl; - boolean isEnemy = tile.getTeam() == Team.red; + boolean isEnemy = tile.getTeam() == waveTeam && state.mode.autoSpawn; if(isEnemy){ entity.warmup += Timers.delta(); diff --git a/core/src/io/anuke/mindustry/world/meta/BlockFlag.java b/core/src/io/anuke/mindustry/world/meta/BlockFlag.java index e43631b7db..216326a7d5 100644 --- a/core/src/io/anuke/mindustry/world/meta/BlockFlag.java +++ b/core/src/io/anuke/mindustry/world/meta/BlockFlag.java @@ -18,6 +18,8 @@ public enum BlockFlag{ /**Special flag for command center blocks.*/ comandCenter(Float.MAX_VALUE); + public final static BlockFlag[] all = values(); + public final float cost; BlockFlag(float cost){