Added spatial indexing of tile targets, many bugfixes

This commit is contained in:
Anuken 2018-06-20 22:56:53 -04:00
parent 45da578756
commit ca30f49481
11 changed files with 105 additions and 52 deletions

View File

@ -1,10 +1,12 @@
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 io.anuke.mindustry.content.Items;
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;
@ -13,11 +15,12 @@ import io.anuke.mindustry.type.Item;
import io.anuke.mindustry.world.Tile;
import io.anuke.mindustry.world.meta.BlockFlag;
import io.anuke.ucore.core.Events;
import io.anuke.ucore.entities.trait.Entity;
import io.anuke.ucore.function.Predicate;
import io.anuke.ucore.util.EnumSet;
import io.anuke.ucore.util.Mathf;
import static io.anuke.mindustry.Vars.state;
import static io.anuke.mindustry.Vars.world;
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?
@ -77,6 +80,12 @@ public class BlockIndexer {
}
}
for (int x = 0; x < quadWidth(); x++) {
for (int y = 0; y < quadHeight(); y++) {
updateQuadrant(world.tile(x * structQuadrantSize, y * structQuadrantSize));
}
}
scanOres();
});
}
@ -91,6 +100,36 @@ public class BlockIndexer {
return (!state.teams.get(team).ally ? allyMap : enemyMap).get(type, emptyArray);
}
public TileEntity findTile(Team team, float x, float y, float range, Predicate<Tile> pred){
Entity closest = null;
float dst = 0;
for(int rx = Math.max((int)((x-range)/tilesize/structQuadrantSize), 0); rx <= (int)((x+range)/tilesize/structQuadrantSize) && rx < quadWidth(); rx ++){
for(int ry = Math.max((int)((y-range)/tilesize/structQuadrantSize), 0); ry <= (int)((y+range)/tilesize/structQuadrantSize) && ry < quadHeight(); ry ++){
if(!getQuad(team, rx, ry)) continue;
for(int tx = rx * structQuadrantSize; tx < (rx + 1) * structQuadrantSize && tx < world.width(); tx ++){
for(int ty = ry * structQuadrantSize; ty < (ry + 1) * structQuadrantSize && ty < world.height(); ty ++ ){
Tile other = world.tile(tx, ty);
if(other == null || other.entity == null || !pred.test(other)) continue;
TileEntity e = other.entity;
float ndst = Vector2.dst(x, y, e.x, e.y);
if(ndst < range && (closest == null || ndst < dst)){
dst = ndst;
closest = e;
}
}
}
}
}
return (TileEntity) closest;
}
/**Returns a set of tiles that have ores of the specified type nearby.
* While each tile in the set is not guaranteed to have an ore directly on it,
* each tile will at least have an ore within {@link #oreQuadrantSize} / 2 blocks of it.
@ -124,7 +163,8 @@ public class BlockIndexer {
//this quadrant is now 'dirty', re-scan the whole thing
int quadrantX = tile.x / structQuadrantSize;
int quadrantY = tile.y / structQuadrantSize;
int index = quadrantX * Mathf.ceil(world.width() / (float)structQuadrantSize) + quadrantY;
int index = quadrantX + quadrantY * quadWidth();
//Log.info("Updating quadrant: {0} {1}", quadrantX, quadrantY);
for(TeamData data : state.teams.getTeams()) {
@ -150,6 +190,19 @@ public class BlockIndexer {
}
}
private boolean getQuad(Team team, int quadrantX, int quadrantY){
int index = quadrantX + quadrantY * Mathf.ceil(world.width() / (float)structQuadrantSize);
return structQuadrants[team.ordinal()].get(index);
}
private int quadWidth(){
return Mathf.ceil(world.width() / (float)structQuadrantSize);
}
private int quadHeight(){
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;

View File

@ -105,7 +105,7 @@ public class WaveSpawner {
for (int y = quady * quadsize; y < world.height() && y < (quady + 1)*quadsize; y++) {
Tile tile = world.tile(x, y);
if(tile.solid() || world.pathfinder().getValueforTeam(Team.red, x, y) == Float.MAX_VALUE){
if(tile == null || tile.solid() || world.pathfinder().getValueforTeam(Team.red, x, y) == Float.MAX_VALUE){
setQuad(quadx, quady, false);
break outer;
}

View File

@ -93,11 +93,11 @@ public class World extends Module{
}
public int width(){
return currentMap.meta.width;
return tiles.length;
}
public int height(){
return currentMap.meta.height;
return tiles[0].length;
}
public Tile tile(int packed){

View File

@ -662,7 +662,7 @@ public class Player extends Unit implements BuilderTrait, CarryTrait {
if(local){
int index = stream.readByte();
players[index].readSaveSuper(stream);
dead = false;
players[index].dead = false;
}
}

View File

@ -10,10 +10,8 @@ import io.anuke.mindustry.world.Block;
import io.anuke.mindustry.world.Tile;
import io.anuke.ucore.entities.EntityGroup;
import io.anuke.ucore.entities.EntityPhysics;
import io.anuke.ucore.entities.trait.Entity;
import io.anuke.ucore.function.Consumer;
import io.anuke.ucore.function.Predicate;
import io.anuke.ucore.util.Mathf;
import static io.anuke.mindustry.Vars.*;
@ -91,43 +89,24 @@ public class Units {
/**Returns the neareset ally tile in a range.*/
public static TileEntity findAllyTile(Team team, float x, float y, float range, Predicate<Tile> pred){
return findTile(x, y, range, tile -> !state.teams.areEnemies(team, tile.getTeam()) && pred.test(tile));
for(Team enemy : state.teams.alliesOf(team)){
TileEntity entity = world.indexer().findTile(enemy, x, y, range, pred);
if(entity != null){
return entity;
}
}
return null;
}
/**Returns the neareset enemy tile in a range.*/
public static TileEntity findEnemyTile(Team team, float x, float y, float range, Predicate<Tile> pred){
return findTile(x, y, range, tile -> state.teams.areEnemies(team, tile.getTeam()) && pred.test(tile));
}
//TODO optimize, spatial caching of tiles
/**Returns the neareset tile entity in a range.*/
public static TileEntity findTile(float x, float y, float range, Predicate<Tile> pred){
Entity closest = null;
float dst = 0;
int rad = (int)(range/tilesize)+1;
int tilex = Mathf.scl2(x, tilesize);
int tiley = Mathf.scl2(y, tilesize);
for(int rx = -rad; rx <= rad; rx ++){
for(int ry = -rad; ry <= rad; ry ++){
Tile other = world.tile(rx+tilex, ry+tiley);
if(other != null) other = other.target();
if(other == null || other.entity == null || !pred.test(other)) continue;
TileEntity e = other.entity;
float ndst = Vector2.dst(x, y, e.x, e.y);
if(ndst < range && (closest == null || ndst < dst)){
dst = ndst;
closest = e;
for(Team enemy : state.teams.enemiesOf(team)){
TileEntity entity = world.indexer().findTile(enemy, x, y, range, pred);
if(entity != null){
return entity;
}
}
}
return (TileEntity) closest;
return null;
}
/**Iterates over all units on all teams, including players.*/

View File

@ -5,6 +5,7 @@ import io.anuke.annotations.Annotations.Remote;
import io.anuke.mindustry.content.fx.ExplosionFx;
import io.anuke.mindustry.entities.TileEntity;
import io.anuke.mindustry.entities.Unit;
import io.anuke.mindustry.entities.Units;
import io.anuke.mindustry.entities.bullet.Bullet;
import io.anuke.mindustry.entities.traits.TargetTrait;
import io.anuke.mindustry.game.Team;
@ -113,7 +114,7 @@ public abstract class BaseUnit extends Unit{
public void targetClosest(){
if(target != null) return;
//target = Units.getClosestTarget(team, x, y, inventory.getAmmoRange());
target = Units.getClosestTarget(team, x, y, inventory.getAmmoRange());
}
public UnitState getStartState(){
@ -254,12 +255,14 @@ public abstract class BaseUnit extends Unit{
public void writeSave(DataOutput stream) throws IOException {
super.writeSave(stream);
stream.writeByte(type.id);
stream.writeBoolean(isWave);
}
@Override
public void readSave(DataInput stream) throws IOException {
super.readSave(stream);
byte type = stream.readByte();
this.isWave = stream.readBoolean();
this.type = UnitType.getByID(type);
add();

View File

@ -53,7 +53,7 @@ public abstract class GroundUnit extends BaseUnit {
public void update() {
super.update();
if(target == null){
if(!velocity.isZero(0.001f) && (target == null || (inventory.hasAmmo() && distanceTo(target) <= inventory.getAmmoRange()))){
rotation = Mathf.lerpDelta(rotation, velocity.angle(), 0.2f);
}
}
@ -104,7 +104,6 @@ public abstract class GroundUnit extends BaseUnit {
super.updateTargeting();
if(Units.invalidateTarget(target, team, x, y, Float.MAX_VALUE)){
if(target != null) Log.info("Invalidating target {0}", target);
target = null;
}
}

View File

@ -76,6 +76,18 @@ public class TeamInfo {
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) {

View File

@ -1,6 +1,7 @@
package io.anuke.mindustry.io;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.utils.ObjectMap;
import io.anuke.ucore.function.Supplier;
import java.io.InputStream;
@ -24,6 +25,10 @@ public class Map {
this.stream = streamSupplier;
}
public Map(String unknownName, int width, int height){
this(unknownName, new MapMeta(0, new ObjectMap<>(), width, height, null), true, () -> null);
}
public String getDisplayName(){
return meta.tags.get("name", name);
}

View File

@ -9,13 +9,13 @@ import io.anuke.mindustry.entities.traits.TypeTrait;
import io.anuke.mindustry.game.Difficulty;
import io.anuke.mindustry.game.GameMode;
import io.anuke.mindustry.game.Team;
import io.anuke.mindustry.io.Map;
import io.anuke.mindustry.io.SaveFileVersion;
import io.anuke.mindustry.world.Block;
import io.anuke.mindustry.world.Tile;
import io.anuke.mindustry.world.blocks.BlockPart;
import io.anuke.ucore.entities.Entities;
import io.anuke.ucore.entities.EntityGroup;
import io.anuke.ucore.entities.EntityPhysics;
import io.anuke.ucore.entities.trait.Entity;
import io.anuke.ucore.util.Bits;
@ -23,7 +23,8 @@ import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import static io.anuke.mindustry.Vars.*;
import static io.anuke.mindustry.Vars.state;
import static io.anuke.mindustry.Vars.world;
public class Save16 extends SaveFileVersion {
@ -39,7 +40,8 @@ public class Save16 extends SaveFileVersion {
//general state
byte mode = stream.readByte();
String mapname = stream.readUTF();
world.setMap(world.maps().getByName(mapname));
Map map = world.maps().getByName(mapname);
world.setMap(map);
int wave = stream.readInt();
byte difficulty = stream.readByte();
@ -55,13 +57,13 @@ public class Save16 extends SaveFileVersion {
int blocksize = stream.readInt();
IntMap<Block> map = new IntMap<>();
IntMap<Block> blockMap = new IntMap<>();
for(int i = 0; i < blocksize; i ++){
String name = stream.readUTF();
int id = stream.readShort();
map.put(id, Block.getByName(name));
blockMap.put(id, Block.getByName(name));
}
//entities
@ -82,7 +84,9 @@ public class Save16 extends SaveFileVersion {
short width = stream.readShort();
short height = stream.readShort();
EntityPhysics.resizeTree(0, 0, width * tilesize, height * tilesize);
if(map == null){
world.setMap(new Map("unknown", width, height));
}
world.beginMapLoad();

View File

@ -41,9 +41,7 @@ public class ProcGen {
if(dst < 20){
elevation = 0;
}
if(r > 0.9){
}else if(r > 0.9){
marker.floor = (byte)Blocks.water.id;
elevation = 0;