mirror of
https://github.com/Anuken/Mindustry.git
synced 2025-01-30 09:30:39 +07:00
Added spatial indexing of tile targets, many bugfixes
This commit is contained in:
parent
45da578756
commit
ca30f49481
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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){
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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.*/
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user