New team system / Prototype dedicated PvP gamemode

This commit is contained in:
Anuken
2018-08-20 23:45:27 -04:00
parent 7a5e61fd2e
commit 2e958d8d8f
25 changed files with 255 additions and 312 deletions

View File

@@ -26,7 +26,7 @@ allprojects {
appName = 'Mindustry' appName = 'Mindustry'
gdxVersion = '1.9.8' gdxVersion = '1.9.8'
roboVMVersion = '2.3.0' roboVMVersion = '2.3.0'
uCoreVersion = 'e492954e86' uCoreVersion = 'd30ec505beb78da25fea3a5aa78f79260f2fb65b'
getVersionString = { getVersionString = {
String buildVersion = getBuildVersion() String buildVersion = getBuildVersion()

View File

@@ -34,6 +34,10 @@ public class Vars{
public static final float wavespace = 60 * 60 * 1.5f; public static final float wavespace = 60 * 60 * 1.5f;
//set ridiculously high for now //set ridiculously high for now
public static final float coreBuildRange = 800999f; 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; public static final float enemyCoreBuildRange = 400f;
//discord group URL //discord group URL

View File

@@ -1,17 +1,14 @@
package io.anuke.mindustry.ai; package io.anuke.mindustry.ai;
import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.Bits; import com.badlogic.gdx.utils.*;
import com.badlogic.gdx.utils.IntMap;
import com.badlogic.gdx.utils.ObjectMap;
import com.badlogic.gdx.utils.ObjectSet;
import io.anuke.mindustry.content.Items; import io.anuke.mindustry.content.Items;
import io.anuke.mindustry.content.blocks.Blocks; import io.anuke.mindustry.content.blocks.Blocks;
import io.anuke.mindustry.entities.TileEntity; import io.anuke.mindustry.entities.TileEntity;
import io.anuke.mindustry.game.EventType.TileChangeEvent; import io.anuke.mindustry.game.EventType.TileChangeEvent;
import io.anuke.mindustry.game.EventType.WorldLoadEvent; import io.anuke.mindustry.game.EventType.WorldLoadEvent;
import io.anuke.mindustry.game.Team; 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.type.Item;
import io.anuke.mindustry.world.Tile; import io.anuke.mindustry.world.Tile;
import io.anuke.mindustry.world.meta.BlockFlag; 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.EnumSet;
import io.anuke.ucore.util.Geometry; import io.anuke.ucore.util.Geometry;
import io.anuke.ucore.util.Mathf; import io.anuke.ucore.util.Mathf;
import io.anuke.ucore.util.ThreadArray;
import static io.anuke.mindustry.Vars.*; import static io.anuke.mindustry.Vars.*;
//TODO consider using quadtrees for finding specific types of blocks within an area //TODO consider using quadtrees for finding specific types of blocks within an area
//TODO maybe use Arrays instead of ObjectSets? //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{ public class BlockIndexer{
/** /**Size of one ore quadrant.*/
* Size of one ore quadrant.
*/
private final static int oreQuadrantSize = 20; private final static int oreQuadrantSize = 20;
/** /**Size of one structure quadrant.*/
* Size of one structure quadrant.
*/
private final static int structQuadrantSize = 12; 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<Item> scanOres = ObjectSet.with(Items.copper, Items.coal, Items.lead, Items.thorium, Items.titanium); private final ObjectSet<Item> scanOres = ObjectSet.with(Items.copper, Items.coal, Items.lead, Items.thorium, Items.titanium);
private final ObjectSet<Item> itemSet = new ObjectSet<>(); private final ObjectSet<Item> itemSet = new ObjectSet<>();
/** /**Stores all ore quadtrants on the map.*/
* Stores all ore quadtrants on the map.
*/
private ObjectMap<Item, ObjectSet<Tile>> ores; private ObjectMap<Item, ObjectSet<Tile>> ores;
/** /**Tags all quadrants.*/
* Tags all quadrants.
*/
private Bits[] structQuadrants; private Bits[] structQuadrants;
/** /**Maps teams to a map of flagged tiles by type.*/
* Maps teams to a map of flagged tiles by type. private ObjectSet<Tile>[][] flagMap = new ObjectSet[Team.all.length][BlockFlag.all.length];
*/ /**Maps tile positions to their last known tile index data.*/
private ObjectMap<BlockFlag, ObjectSet<Tile>> enemyMap = new ObjectMap<>();
/**
* Maps teams to a map of flagged tiles by type.
*/
private ObjectMap<BlockFlag, ObjectSet<Tile>> allyMap = new ObjectMap<>();
/**
* Empty map for invalid teams.
*/
private ObjectMap<BlockFlag, ObjectSet<Tile>> emptyMap = new ObjectMap<>();
/**
* Maps tile positions to their last known tile index data.
*/
private IntMap<TileIndex> typeMap = new IntMap<>(); private IntMap<TileIndex> typeMap = new IntMap<>();
/** /**Empty set used for returning.*/
* Empty array used for returning. private ObjectSet<Tile> emptySet = new ObjectSet<>();
*/ /**Array used for returning and reusing.*/
private ObjectSet<Tile> emptyArray = new ObjectSet<>(); private Array<Tile> returnArray = new ThreadArray<>();
public BlockIndexer(){ public BlockIndexer(){
Events.on(TileChangeEvent.class, tile -> { Events.on(TileChangeEvent.class, tile -> {
if(typeMap.get(tile.packedPosition()) != null){ if(typeMap.get(tile.packedPosition()) != null){
TileIndex index = typeMap.get(tile.packedPosition()); TileIndex index = typeMap.get(tile.packedPosition());
for(BlockFlag flag : index.flags){ for(BlockFlag flag : index.flags){
getMap(index.team).get(flag).remove(tile); getFlagged(index.team)[flag.ordinal()].remove(tile);
} }
} }
process(tile); process(tile);
@@ -88,8 +62,12 @@ public class BlockIndexer{
}); });
Events.on(WorldLoadEvent.class, () -> { Events.on(WorldLoadEvent.class, () -> {
enemyMap.clear(); flagMap = new ObjectSet[Team.all.length][BlockFlag.all.length];
allyMap.clear(); for(int i = 0; i < flagMap.length; i++){
for(int j = 0; j < BlockFlag.all.length; j++){
flagMap[i][j] = new ObjectSet<>();
}
}
typeMap.clear(); typeMap.clear();
ores = null; ores = null;
@@ -115,18 +93,26 @@ public class BlockIndexer{
}); });
} }
/** private ObjectSet<Tile>[] getFlagged(Team team){
* Get all allied blocks with a flag. return flagMap[team.ordinal()];
*/
public ObjectSet<Tile> getAllied(Team team, BlockFlag type){
return state.teams.has(team) ? (state.teams.get(team).ally ? allyMap : enemyMap).get(type, emptyArray) : emptyArray;
} }
/** /**Get all allied blocks with a flag.*/
* Get all enemy blocks with a flag. public ObjectSet<Tile> getAllied(Team team, BlockFlag type){
*/ return flagMap[team.ordinal()][type.ordinal()];
public ObjectSet<Tile> getEnemy(Team team, BlockFlag type){ }
return (!state.teams.get(team).ally ? allyMap : enemyMap).get(type, emptyArray);
/**Get all enemy blocks with a flag.*/
public Array<Tile> 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<Tile> pred){ public TileEntity findTile(Team team, float x, float y, float range, Predicate<Tile> pred){
@@ -166,7 +152,7 @@ public class BlockIndexer{
* Only specific ore types are scanned. See {@link #scanOres}. * Only specific ore types are scanned. See {@link #scanOres}.
*/ */
public ObjectSet<Tile> getOrePositions(Item item){ public ObjectSet<Tile> 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){ private void process(Tile tile){
if(tile.block().flags != null && if(tile.block().flags != null &&
tile.getTeam() != Team.none){ tile.getTeam() != Team.none){
ObjectMap<BlockFlag, ObjectSet<Tile>> map = getMap(tile.getTeam()); ObjectSet<Tile>[] map = getFlagged(tile.getTeam());
for(BlockFlag flag : tile.block().flags){ for(BlockFlag flag : tile.block().flags){
ObjectSet<Tile> arr = map.get(flag); ObjectSet<Tile> arr = map[flag.ordinal()];
if(arr == null){
arr = new ObjectSet<>();
map.put(flag, arr);
}
arr.add(tile); arr.add(tile);
map.put(flag, arr); map[flag.ordinal()] = arr;
} }
typeMap.put(tile.packedPosition(), new TileIndex(tile.block().flags, tile.getTeam())); typeMap.put(tile.packedPosition(), new TileIndex(tile.block().flags, tile.getTeam()));
} }
@@ -247,7 +229,8 @@ public class BlockIndexer{
int quadrantY = tile.y / structQuadrantSize; int quadrantY = tile.y / structQuadrantSize;
int index = quadrantX + quadrantY * quadWidth(); 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 //fast-set this quadrant to 'occupied' if the tile just placed is already of this team
if(tile.getTeam() == data.team && tile.entity != null){ if(tile.getTeam() == data.team && tile.entity != null){
@@ -284,11 +267,6 @@ public class BlockIndexer{
return Mathf.ceil(world.height() / (float) structQuadrantSize); return Mathf.ceil(world.height() / (float) structQuadrantSize);
} }
private ObjectMap<BlockFlag, ObjectSet<Tile>> getMap(Team team){
if(!state.teams.has(team)) return emptyMap;
return state.teams.get(team).ally ? allyMap : enemyMap;
}
private void scanOres(){ private void scanOres(){
ores = new ObjectMap<>(); ores = new ObjectMap<>();

View File

@@ -1,15 +1,14 @@
package io.anuke.mindustry.ai; package io.anuke.mindustry.ai;
import com.badlogic.gdx.math.GridPoint2; import com.badlogic.gdx.math.GridPoint2;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.IntArray; 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.Queue;
import com.badlogic.gdx.utils.TimeUtils; import com.badlogic.gdx.utils.TimeUtils;
import io.anuke.mindustry.game.EventType.TileChangeEvent; import io.anuke.mindustry.game.EventType.TileChangeEvent;
import io.anuke.mindustry.game.EventType.WorldLoadEvent; import io.anuke.mindustry.game.EventType.WorldLoadEvent;
import io.anuke.mindustry.game.Team; 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.net.Net;
import io.anuke.mindustry.world.Tile; import io.anuke.mindustry.world.Tile;
import io.anuke.mindustry.world.meta.BlockFlag; import io.anuke.mindustry.world.meta.BlockFlag;
@@ -30,8 +29,9 @@ public class Pathfinder{
Events.on(TileChangeEvent.class, tile -> { Events.on(TileChangeEvent.class, tile -> {
if(Net.client()) return; if(Net.client()) return;
for(TeamData data : state.teams.getTeams()){ for(Team team : Team.all){
if(data.team != tile.getTeam() && paths[data.team.ordinal()].weights[tile.x][tile.y] >= Float.MAX_VALUE){ 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); update(tile, data.team);
} }
} }
@@ -43,10 +43,10 @@ public class Pathfinder{
public void update(){ public void update(){
if(Net.client()) return; if(Net.client()) return;
ObjectSetIterator<TeamData> iterator = new ObjectSetIterator<>(state.teams.getTeams()); for(Team team : Team.all){
if(state.teams.isActive(team)){
for(TeamData team : iterator){ updateFrontier(team, maxUpdate);
updateFrontier(team.team, maxUpdate); }
} }
} }
@@ -107,7 +107,7 @@ public class Pathfinder{
path.lastSearchTime = TimeUtils.millis(); path.lastSearchTime = TimeUtils.millis();
ObjectSet<Tile> set = world.indexer().getEnemy(team, BlockFlag.target); Array<Tile> set = world.indexer().getEnemy(team, BlockFlag.target);
for(Tile other : set){ for(Tile other : set){
path.weights[other.x][other.y] = 0; path.weights[other.x][other.y] = 0;
path.searches[other.x][other.y] = path.search; path.searches[other.x][other.y] = path.search;
@@ -173,11 +173,13 @@ public class Pathfinder{
paths = new PathData[Team.all.length]; paths = new PathData[Team.all.length];
blocked.clear(); blocked.clear();
for(TeamData data : state.teams.getTeams()){ for(Team team : Team.all){
PathData path = new PathData(); 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(); state.spawner.checkAllQuadrants();

View File

@@ -4,7 +4,7 @@ import io.anuke.mindustry.ai.WaveSpawner;
import io.anuke.mindustry.game.Difficulty; import io.anuke.mindustry.game.Difficulty;
import io.anuke.mindustry.game.EventType.StateChangeEvent; import io.anuke.mindustry.game.EventType.StateChangeEvent;
import io.anuke.mindustry.game.GameMode; 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.mindustry.net.Net;
import io.anuke.ucore.core.Events; import io.anuke.ucore.core.Events;
@@ -16,7 +16,7 @@ public class GameState{
public Difficulty difficulty = Difficulty.normal; public Difficulty difficulty = Difficulty.normal;
public boolean friendlyFire; public boolean friendlyFire;
public WaveSpawner spawner = new WaveSpawner(); public WaveSpawner spawner = new WaveSpawner();
public TeamInfo teams = new TeamInfo(); public Teams teams = new Teams();
private State state = State.menu; private State state = State.menu;
public void set(State astate){ public void set(State astate){

View File

@@ -9,8 +9,7 @@ import io.anuke.mindustry.game.EventType.PlayEvent;
import io.anuke.mindustry.game.EventType.ResetEvent; import io.anuke.mindustry.game.EventType.ResetEvent;
import io.anuke.mindustry.game.EventType.WaveEvent; import io.anuke.mindustry.game.EventType.WaveEvent;
import io.anuke.mindustry.game.Team; import io.anuke.mindustry.game.Team;
import io.anuke.mindustry.game.TeamInfo; import io.anuke.mindustry.game.Teams;
import io.anuke.mindustry.game.TeamInfo.TeamData;
import io.anuke.mindustry.net.Net; import io.anuke.mindustry.net.Net;
import io.anuke.mindustry.type.Item; import io.anuke.mindustry.type.Item;
import io.anuke.mindustry.type.ItemStack; import io.anuke.mindustry.type.ItemStack;
@@ -50,26 +49,25 @@ public class Logic extends Module{
state.set(State.playing); state.set(State.playing);
state.wavetime = wavespace * state.difficulty.timeScaling * 2; state.wavetime = wavespace * state.difficulty.timeScaling * 2;
for(TeamData team : state.teams.getTeams(true)){ for(Tile tile : state.teams.get(defaultTeam).cores){
for(Tile tile : team.cores){ if(debug){
if(debug){ for(Item item : Item.all()){
for(Item item : Item.all()){ if(item.type == ItemType.material){
if(item.type == ItemType.material){ tile.entity.items.set(item, 1000);
tile.entity.items.set(item, 1000);
}
}
}
if(world.getSector() != null){
Array<ItemStack> items = world.getSector().startingItems;
for(ItemStack stack : items){
tile.entity.items.add(stack.item, stack.amount);
} }
} }
} }
if(world.getSector() != null){
Array<ItemStack> items = world.getSector().startingItems;
for(ItemStack stack : items){
tile.entity.items.add(stack.item, stack.amount);
}
}
} }
Events.fire(PlayEvent.class); Events.fire(PlayEvent.class);
} }
@@ -77,9 +75,7 @@ public class Logic extends Module{
state.wave = 1; state.wave = 1;
state.wavetime = wavespace * state.difficulty.timeScaling; state.wavetime = wavespace * state.difficulty.timeScaling;
state.gameOver = false; state.gameOver = false;
state.teams = new TeamInfo(); state.teams = new Teams();
state.teams.add(Team.blue, true);
state.teams.add(Team.red, false);
Timers.clear(); Timers.clear();
Entities.clear(); Entities.clear();
@@ -96,11 +92,12 @@ public class Logic extends Module{
Events.fire(WaveEvent.class); 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(){ private void checkGameOver(){
boolean gameOver = true; boolean gameOver = true;
for(TeamData data : state.teams.getTeams(true)){ for(Team team : Team.all){
if(data.cores.size > 0){ if(state.teams.get(team).cores.size > 0){
gameOver = false; gameOver = false;
break; break;
} }

View File

@@ -14,6 +14,7 @@ import io.anuke.mindustry.core.GameState.State;
import io.anuke.mindustry.entities.Player; import io.anuke.mindustry.entities.Player;
import io.anuke.mindustry.entities.traits.BuilderTrait.BuildRequest; import io.anuke.mindustry.entities.traits.BuilderTrait.BuildRequest;
import io.anuke.mindustry.entities.traits.SyncTrait; import io.anuke.mindustry.entities.traits.SyncTrait;
import io.anuke.mindustry.game.Team;
import io.anuke.mindustry.game.Version; import io.anuke.mindustry.game.Version;
import io.anuke.mindustry.gen.Call; import io.anuke.mindustry.gen.Call;
import io.anuke.mindustry.gen.RemoteReadServer; 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.io.delta.DEZEncoder;
import io.anuke.ucore.modules.Module; import io.anuke.ucore.modules.Module;
import io.anuke.ucore.util.Log; import io.anuke.ucore.util.Log;
import io.anuke.ucore.util.Mathf;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
@@ -166,6 +168,24 @@ public class NetServer extends Module{
player.setNet(player.x, player.y); player.setNet(player.x, player.y);
player.color.set(packet.color); player.color.set(packet.color);
player.color.a = 1f; 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); connections.put(id, player);
trace.playerid = player.id; trace.playerid = player.id;

View File

@@ -244,6 +244,10 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra
return playerGroup; return playerGroup;
} }
public void setTeam(Team team){
this.team = team;
}
//endregion //endregion
//region draw methods //region draw methods

View File

@@ -6,7 +6,7 @@ import com.badlogic.gdx.math.Vector2;
import io.anuke.mindustry.content.blocks.Blocks; import io.anuke.mindustry.content.blocks.Blocks;
import io.anuke.mindustry.entities.traits.*; import io.anuke.mindustry.entities.traits.*;
import io.anuke.mindustry.game.Team; 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.Interpolator;
import io.anuke.mindustry.net.Net; import io.anuke.mindustry.net.Net;
import io.anuke.mindustry.type.StatusEffect; import io.anuke.mindustry.type.StatusEffect;
@@ -179,17 +179,13 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ
} }
public TileEntity getClosestCore(){ 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); Tile tile = Geometry.findClosest(x, y, data.cores);
if(tile == null){ if(tile == null){
return null;
}else{
return tile.entity;
}
}else{
return null; return null;
}else{
return tile.entity;
} }
} }

View File

@@ -2,7 +2,6 @@ package io.anuke.mindustry.entities;
import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.ObjectSet;
import io.anuke.mindustry.entities.traits.TargetTrait; import io.anuke.mindustry.entities.traits.TargetTrait;
import io.anuke.mindustry.entities.units.BaseUnit; import io.anuke.mindustry.entities.units.BaseUnit;
import io.anuke.mindustry.game.Team; 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.entities.EntityPhysics;
import io.anuke.ucore.function.Consumer; import io.anuke.ucore.function.Consumer;
import io.anuke.ucore.function.Predicate; import io.anuke.ucore.function.Predicate;
import io.anuke.ucore.util.EnumSet;
import static io.anuke.mindustry.Vars.*; import static io.anuke.mindustry.Vars.*;
@@ -106,13 +106,7 @@ public class Units{
* Returns the neareset ally tile in a range. * Returns the neareset ally tile in a range.
*/ */
public static TileEntity findAllyTile(Team team, float x, float y, float range, Predicate<Tile> pred){ public static TileEntity findAllyTile(Team team, float x, float y, float range, Predicate<Tile> pred){
for(Team enemy : state.teams.alliesOf(team)){ return world.indexer().findTile(team, x, y, range, pred);
TileEntity entity = world.indexer().findTile(enemy, x, y, range, pred);
if(entity != null){
return entity;
}
}
return null;
} }
/** /**
@@ -271,7 +265,7 @@ public class Units{
* Iterates over all units that are enemies of this team. * Iterates over all units that are enemies of this team.
*/ */
public static void getNearbyEnemies(Team team, Rectangle rect, Consumer<Unit> cons){ public static void getNearbyEnemies(Team team, Rectangle rect, Consumer<Unit> cons){
ObjectSet<Team> targets = state.teams.enemiesOf(team); EnumSet<Team> targets = state.teams.enemiesOf(team);
for(Team other : targets){ for(Team other : targets){
EntityGroup<BaseUnit> group = unitGroups[other.ordinal()]; EntityGroup<BaseUnit> group = unitGroups[other.ordinal()];

View File

@@ -1,7 +1,6 @@
package io.anuke.mindustry.entities.units; package io.anuke.mindustry.entities.units;
import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.utils.ObjectSet;
import io.anuke.annotations.Annotations.Loc; import io.anuke.annotations.Annotations.Loc;
import io.anuke.annotations.Annotations.Remote; import io.anuke.annotations.Annotations.Remote;
import io.anuke.mindustry.Vars; 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.SpawnerTrait;
import io.anuke.mindustry.entities.traits.TargetTrait; import io.anuke.mindustry.entities.traits.TargetTrait;
import io.anuke.mindustry.game.Team; import io.anuke.mindustry.game.Team;
import io.anuke.mindustry.game.TeamInfo.TeamData;
import io.anuke.mindustry.gen.Call; import io.anuke.mindustry.gen.Call;
import io.anuke.mindustry.graphics.Palette; import io.anuke.mindustry.graphics.Palette;
import io.anuke.mindustry.net.Net; import io.anuke.mindustry.net.Net;
@@ -188,16 +186,14 @@ public abstract class BaseUnit extends Unit implements ShooterTrait{
} }
public TileEntity getClosestEnemyCore(){ public TileEntity getClosestEnemyCore(){
if(Vars.state.teams.has(team)){
ObjectSet<TeamData> datas = Vars.state.teams.enemyDataOf(team);
for(TeamData data : datas){ for(Team enemy : Vars.state.teams.enemiesOf(team)){
Tile tile = Geometry.findClosest(x, y, data.cores); Tile tile = Geometry.findClosest(x, y, Vars.state.teams.get(enemy).cores);
if(tile != null){ if(tile != null){
return tile.entity; return tile.entity;
}
} }
} }
return null; return null;
} }

View File

@@ -241,8 +241,18 @@ public abstract class GroundUnit extends BaseUnit{
} }
protected void moveAwayFromCore(){ 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 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(); TileEntity core = getClosestCore();
if(tile == targetTile || core == null || distanceTo(core) < 90f) return; if(tile == targetTile || core == null || distanceTo(core) < 90f) return;

View File

@@ -14,11 +14,15 @@ public enum GameMode{
noWaves{{ noWaves{{
disableWaves = true; disableWaves = true;
hidden = true; hidden = true;
autoSpawn = true;
}},
pvp{{
disableWaves = true;
isPvp = true;
hidden = true;
}}; }};
public boolean infiniteResources;
public boolean disableWaveTimer; public boolean infiniteResources, disableWaveTimer, disableWaves, hidden, autoSpawn, isPvp;
public boolean disableWaves;
public boolean hidden;
public String description(){ public String description(){
return Bundles.get("mode." + name() + ".description"); return Bundles.get("mode." + name() + ".description");

View File

@@ -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<Team, TeamData> map = new ObjectMap<>();
private ThreadSet<Team> allies = new ThreadSet<>(),
enemies = new ThreadSet<>();
private ThreadSet<TeamData> allyData = new ThreadSet<>(),
enemyData = new ThreadSet<>();
private ThreadSet<TeamData> allTeamData = new ThreadSet<>();
private ThreadSet<Team> allTeams = new ThreadSet<>();
private int allyBits = 0;
private int enemyBits = 0;
/**
* Returns all teams on a side.
*/
public ObjectSet<TeamData> getTeams(boolean ally){
return ally ? allyData : enemyData;
}
/**
* Returns all team data.
*/
public ObjectSet<TeamData> 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<Team> 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<Team> 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<TeamData> 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<Tile> cores = new ThreadArray<>();
public final Team team;
public final boolean ally;
public TeamData(Team team, boolean ally){
this.team = team;
this.ally = ally;
}
}
}

View File

@@ -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<Team> 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<Tile> cores = new ThreadArray<>();
public final EnumSet<Team> enemies;
public final Team team;
public TeamData(Team team, EnumSet<Team> enemies){
this.team = team;
this.enemies = enemies;
}
}
}

View File

@@ -7,7 +7,7 @@ import com.badlogic.gdx.utils.Array;
import io.anuke.mindustry.content.blocks.Blocks; import io.anuke.mindustry.content.blocks.Blocks;
import io.anuke.mindustry.entities.Player; import io.anuke.mindustry.entities.Player;
import io.anuke.mindustry.entities.TileEntity; 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.input.InputHandler;
import io.anuke.mindustry.world.Block; import io.anuke.mindustry.world.Block;
import io.anuke.mindustry.world.Tile; import io.anuke.mindustry.world.Tile;
@@ -63,13 +63,13 @@ public class OverlayRenderer{
Lines.stroke(buildFadeTime*2f); Lines.stroke(buildFadeTime*2f);
if(buildFadeTime > 0.005f){ if(buildFadeTime > 0.005f){
for(TeamData data : state.teams.enemyDataOf(player.getTeam())){ for(Team enemy : state.teams.enemiesOf(player.getTeam())){
for(Tile core : data.cores){ for(Tile core : state.teams.get(enemy).cores){
float dst = Vector2.dst(player.x, player.y, core.drawx(), core.drawy()); float dst = Vector2.dst(player.x, player.y, core.drawx(), core.drawy());
if(dst < enemyCoreBuildRange * 1.5f){ if(dst < enemyCoreBuildRange * 1.5f){
Draw.color(Color.DARK_GRAY); Draw.color(Color.DARK_GRAY);
Lines.poly(core.drawx(), core.drawy() - 2, 200, enemyCoreBuildRange); 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); Lines.poly(core.drawx(), core.drawy(), 200, enemyCoreBuildRange);
} }
} }

View File

@@ -117,8 +117,7 @@ public class Save16 extends SaveFileVersion{
tile.entity.read(stream); tile.entity.read(stream);
if(tile.block() == StorageBlocks.core && if(tile.block() == StorageBlocks.core){
state.teams.has(t)){
state.teams.get(t).cores.add(tile); state.teams.get(t).cores.add(tile);
} }
}else if(wallid == 0){ }else if(wallid == 0){

View File

@@ -84,8 +84,7 @@ public class WorldGenerator{
Team team = tile.getTeam(); Team team = tile.getTeam();
if(tile.block() == StorageBlocks.core && if(tile.block() == StorageBlocks.core){
state.teams.has(team)){
state.teams.get(team).cores.add(tile); state.teams.get(team).cores.add(tile);
} }

View File

@@ -39,8 +39,12 @@ public class BattleMission implements Mission{
@Override @Override
public boolean isComplete(){ public boolean isComplete(){
//TODO check all enemy teams, not just the first for(Team team : Vars.state.teams.enemiesOf(Vars.defaultTeam)){
return Vars.state.teams.getTeams(false).first().cores.size == 0; if(Vars.state.teams.isActive(team)){
return false;
}
}
return true;
} }
@Override @Override

View File

@@ -27,7 +27,7 @@ public class ResourceMission implements Mission{
@Override @Override
public boolean isComplete(){ 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 @Override

View File

@@ -7,11 +7,11 @@ import io.anuke.mindustry.content.blocks.Blocks;
import io.anuke.mindustry.entities.Player; import io.anuke.mindustry.entities.Player;
import io.anuke.mindustry.game.GameMode; import io.anuke.mindustry.game.GameMode;
import io.anuke.mindustry.game.Team; import io.anuke.mindustry.game.Team;
import io.anuke.mindustry.game.TeamInfo; import io.anuke.mindustry.game.Teams;
import io.anuke.mindustry.game.TeamInfo.TeamData; import io.anuke.mindustry.game.Teams.TeamData;
import io.anuke.mindustry.game.Version;
import io.anuke.mindustry.maps.Map; import io.anuke.mindustry.maps.Map;
import io.anuke.mindustry.maps.MapMeta; import io.anuke.mindustry.maps.MapMeta;
import io.anuke.mindustry.game.Version;
import io.anuke.mindustry.world.Tile; import io.anuke.mindustry.world.Tile;
import io.anuke.mindustry.world.blocks.BlockPart; import io.anuke.mindustry.world.blocks.BlockPart;
import io.anuke.ucore.core.Core; import io.anuke.ucore.core.Core;
@@ -119,11 +119,16 @@ public class NetworkIO{
} }
//write team data //write team data
stream.writeByte(state.teams.getTeams().size); for(Team team : Team.all){
for(TeamData data : state.teams.getTeams()){ TeamData data = state.teams.get(team);
stream.writeByte(data.team.ordinal()); 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){ for(Tile tile : data.cores){
stream.writeInt(tile.packedPosition()); stream.writeInt(tile.packedPosition());
} }
@@ -253,14 +258,21 @@ public class NetworkIO{
} }
player.reset(); player.reset();
state.teams = new TeamInfo(); state.teams = new Teams();
byte teams = stream.readByte(); byte teams = stream.readByte();
for(int i = 0; i < teams; i++){ for(int i = 0; i < teams; i++){
Team team = Team.all[stream.readByte()]; Team team = Team.all[stream.readByte()];
boolean ally = stream.readBoolean();
short cores = stream.readShort(); byte enemies = stream.readByte();
state.teams.add(team, ally); 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++){ for(int j = 0; j < cores; j++){
state.teams.get(team).cores.add(world.tile(stream.readInt())); state.teams.get(team).cores.add(world.tile(stream.readInt()));

View File

@@ -6,7 +6,6 @@ import io.anuke.mindustry.content.blocks.Blocks;
import io.anuke.mindustry.entities.Units; import io.anuke.mindustry.entities.Units;
import io.anuke.mindustry.game.EventType.BlockBuildEvent; import io.anuke.mindustry.game.EventType.BlockBuildEvent;
import io.anuke.mindustry.game.Team; import io.anuke.mindustry.game.Team;
import io.anuke.mindustry.game.TeamInfo.TeamData;
import io.anuke.mindustry.type.Recipe; import io.anuke.mindustry.type.Recipe;
import io.anuke.mindustry.world.blocks.BuildBlock.BuildEntity; import io.anuke.mindustry.world.blocks.BuildBlock.BuildEntity;
import io.anuke.ucore.core.Events; import io.anuke.ucore.core.Events;
@@ -138,8 +137,8 @@ public class Build{
} }
//check for enemy cores //check for enemy cores
for(TeamData data : state.teams.enemyDataOf(team)){ for(Team enemy : state.teams.enemiesOf(team)){
for(Tile core : data.cores){ 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){ if(Vector2.dst(x*tilesize + type.offset(), y*tilesize + type.offset(), core.drawx(), core.drawy()) < enemyCoreBuildRange + type.size*tilesize/2f){
return false; return false;
} }

View File

@@ -176,9 +176,7 @@ public class CoreBlock extends StorageBlock{
//TODO more dramatic effects //TODO more dramatic effects
super.onDestroyed(tile); 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 @Override

View File

@@ -8,7 +8,6 @@ import io.anuke.mindustry.content.fx.BlockFx;
import io.anuke.mindustry.entities.TileEntity; import io.anuke.mindustry.entities.TileEntity;
import io.anuke.mindustry.entities.units.BaseUnit; import io.anuke.mindustry.entities.units.BaseUnit;
import io.anuke.mindustry.entities.units.UnitType; import io.anuke.mindustry.entities.units.UnitType;
import io.anuke.mindustry.game.Team;
import io.anuke.mindustry.gen.Call; import io.anuke.mindustry.gen.Call;
import io.anuke.mindustry.graphics.Palette; import io.anuke.mindustry.graphics.Palette;
import io.anuke.mindustry.graphics.Shaders; import io.anuke.mindustry.graphics.Shaders;
@@ -36,6 +35,9 @@ import java.io.DataInputStream;
import java.io.DataOutputStream; import java.io.DataOutputStream;
import java.io.IOException; import java.io.IOException;
import static io.anuke.mindustry.Vars.state;
import static io.anuke.mindustry.Vars.waveTeam;
public class UnitPad extends Block{ public class UnitPad extends Block{
protected float gracePeriodMultiplier = 23f; protected float gracePeriodMultiplier = 23f;
protected float speedupTime = 60f * 60f * 20; protected float speedupTime = 60f * 60f * 20;
@@ -142,7 +144,7 @@ public class UnitPad extends Block{
entity.time += Timers.delta() * entity.speedScl; entity.time += Timers.delta() * entity.speedScl;
boolean isEnemy = tile.getTeam() == Team.red; boolean isEnemy = tile.getTeam() == waveTeam && state.mode.autoSpawn;
if(isEnemy){ if(isEnemy){
entity.warmup += Timers.delta(); entity.warmup += Timers.delta();

View File

@@ -18,6 +18,8 @@ public enum BlockFlag{
/**Special flag for command center blocks.*/ /**Special flag for command center blocks.*/
comandCenter(Float.MAX_VALUE); comandCenter(Float.MAX_VALUE);
public final static BlockFlag[] all = values();
public final float cost; public final float cost;
BlockFlag(float cost){ BlockFlag(float cost){