diff --git a/core/assets/maps/delta.png b/core/assets/maps/delta.png index 8cde6f4a3a..c04d38eb49 100644 Binary files a/core/assets/maps/delta.png and b/core/assets/maps/delta.png differ diff --git a/core/assets/maps/maze.png b/core/assets/maps/maze.png index 82684d1761..5e681efa2e 100644 Binary files a/core/assets/maps/maze.png and b/core/assets/maps/maze.png differ diff --git a/core/assets/sprites/sprites.png b/core/assets/sprites/sprites.png index 067e9768f4..bd59694131 100644 Binary files a/core/assets/sprites/sprites.png and b/core/assets/sprites/sprites.png differ diff --git a/core/src/io/anuke/mindustry/Vars.java b/core/src/io/anuke/mindustry/Vars.java index b2a9d05a4b..b4edd72ee3 100644 --- a/core/src/io/anuke/mindustry/Vars.java +++ b/core/src/io/anuke/mindustry/Vars.java @@ -34,12 +34,16 @@ public class Vars{ public static boolean debug = false; //whether to debug openGL info public static boolean debugGL = false; + //whether profiling is shown + public static boolean profile = false; + //whether the player can clip through walls + public static boolean noclip = false; //whether to draw chunk borders public static boolean debugChunks = false; //whether turrets have infinite ammo (only with debug) public static boolean infiniteAmmo = true; //whether to show paths of enemies - public static boolean showPaths = false; + public static boolean showPaths = true; //number of save slots-- increasing may lead to layout issues //TODO named save slots, possibly with a scroll dialog public static final int saveSlots = 4; diff --git a/core/src/io/anuke/mindustry/ai/MHueristic.java b/core/src/io/anuke/mindustry/ai/MHueristic.java index a55e07ced1..9b663363ad 100644 --- a/core/src/io/anuke/mindustry/ai/MHueristic.java +++ b/core/src/io/anuke/mindustry/ai/MHueristic.java @@ -4,22 +4,29 @@ import com.badlogic.gdx.ai.pfa.Heuristic; import io.anuke.mindustry.Vars; import io.anuke.mindustry.world.Tile; +import io.anuke.mindustry.world.World; public class MHueristic implements Heuristic<Tile>{ //so this means that the cost of going through solids is 10x going through non solids - float multiplier = 10f; + static float multiplier = 10f; @Override public float estimate(Tile node, Tile other){ + return estimateStatic(node, other); + } + + public static float estimateStatic(Tile node, Tile other){ float cost = Math.abs(node.worldx() - other.worldx()) + Math.abs(node.worldy() - other.worldy()); //TODO balance multiplier if(node.breakable() && node.block().solid) cost += Vars.tilesize*multiplier; if(other.breakable() && other.block().solid) cost += Vars.tilesize*multiplier; - for(Tile tile : node.getNearby()){ - if(tile != null && tile.solid()){ - //don't go near solid tiles! - cost += Vars.tilesize*3; + for(int dx = -1; dx <= 1; dx ++){ + for(int dy = -1; dy <= 1; dy ++){ + Tile tile = World.tile(node.x + dx, node.y + dy); + if(tile != null && tile.solid()){ + cost += Vars.tilesize*5; + } } } return cost; diff --git a/core/src/io/anuke/mindustry/ai/Pathfind.java b/core/src/io/anuke/mindustry/ai/Pathfind.java index 578dd02fa8..646c8a7f12 100644 --- a/core/src/io/anuke/mindustry/ai/Pathfind.java +++ b/core/src/io/anuke/mindustry/ai/Pathfind.java @@ -1,57 +1,88 @@ package io.anuke.mindustry.ai; import com.badlogic.gdx.ai.pfa.PathFinder; -import com.badlogic.gdx.ai.pfa.PathFinderRequest; import com.badlogic.gdx.ai.pfa.PathSmoother; import com.badlogic.gdx.ai.pfa.indexed.IndexedAStarPathFinder; +import com.badlogic.gdx.math.MathUtils; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.utils.Array; +import io.anuke.mindustry.Vars; +import io.anuke.mindustry.entities.effect.Fx; import io.anuke.mindustry.entities.enemies.Enemy; import io.anuke.mindustry.world.Tile; import io.anuke.mindustry.world.World; -import io.anuke.ucore.core.Timers; +import io.anuke.ucore.core.Effects; +import io.anuke.ucore.util.Angles; +import io.anuke.ucore.util.Tmp; public class Pathfind{ static MHueristic heuristic = new MHueristic(); - static PassTileGraph graph = new PassTileGraph(); - static PathFinder<Tile> finder = new IndexedAStarPathFinder<Tile>(graph); + static PassTileGraph passgraph = new PassTileGraph(); + static PathFinder<Tile> passpathfinder; + static Array<SmoothGraphPath> paths = new Array<>(); + static Tile[][] pathSequences; static PathSmoother<Tile, Vector2> smoother = new PathSmoother<Tile, Vector2>(new Raycaster()); static Vector2 vector = new Vector2(); static public Vector2 find(Enemy enemy){ - findNode(enemy); - - if(enemy.node <= -1) return vector.set(enemy.x, enemy.y); + if(enemy.node == -1){ + findNode(enemy); + } //-1 is only possible here if both pathfindings failed, which should NOT happen //check graph code - //Tile[] path = enemy.path; - Array<Tile> path = enemy.gpath.nodes; + Tile[] path = enemy.path; + + Tile prev = path[enemy.node - 1]; - Tile target = path.get(enemy.node); + Tile target = path[enemy.node]; + + float projectLen = Vector2.dst(prev.worldx(), prev.worldy(), target.worldx(), target.worldy()) / 6f; + + Vector2 projection = projectPoint(prev.worldx(), prev.worldy(), + target.worldx(), target.worldy(), enemy.x, enemy.y); + + boolean canProject = true; + + if(projectLen < 8 || !onLine(projection, prev.worldx(), prev.worldy(), target.worldx(), target.worldy())){ + canProject = false; + }else{ + projection.add(Angles.translation(Angles.angle(prev.worldx(), prev.worldy(), + target.worldx(), target.worldy()), projectLen)); + } float dst = Vector2.dst(enemy.x, enemy.y, target.worldx(), target.worldy()); - if(dst < 2){ - if(enemy.node <= path.size-2) + if(dst < 8){ + if(enemy.node <= path.length-2) enemy.node ++; - target = path.get(enemy.node); + target = path[enemy.node]; + } + + if(canProject && projection.dst(enemy.x, enemy.y) < Vector2.dst(target.x, target.y, enemy.x, enemy.y)){ + vector.set(projection); + }else{ + vector.set(target.worldx(), target.worldy()); } //near the core, stop - if(enemy.node == path.size - 1){ + if(enemy.node == path.length - 1){ vector.set(target.worldx(), target.worldy()); } - return vector.set(target.worldx(), target.worldy()); + return vector; } + static public void reset(){ + paths.clear(); + pathSequences = null; + passpathfinder = new IndexedAStarPathFinder<Tile>(passgraph); + } + static public void updatePath(){ - - /* if(paths.size == 0 || paths.size != World.spawnpoints.size){ paths.clear(); pathSequences = new Tile[World.spawnpoints.size][0]; @@ -61,6 +92,13 @@ public class Pathfind{ } } + //TODO make this work? + /* + PathFinderRequest<Tile> request = new PathFinderRequest<Tile>(); + request.startNode = World.spawnpoints.get(0); + request.endNode = World.core; + passpathfinder.search(request, 1000); */ + for(int i = 0; i < paths.size; i ++){ SmoothGraphPath path = paths.get(i); @@ -85,47 +123,18 @@ public class Pathfind{ Effects.effect(Fx.ind, tile.worldx(), tile.worldy()); } - }*/ + } } static void findNode(Enemy enemy){ - /* enemy.path = pathSequences[enemy.spawn]; Tile[] path = enemy.path; - */ - - - - if(enemy.node == -1 || (Timers.get(enemy, "pathfind", 120) && enemy.request.pathFound)){ - - enemy.gpath = new SmoothGraphPath(); - enemy.finder = new IndexedAStarPathFinder<Tile>(graph); - enemy.gpath.clear(); - - enemy.request = new PathFinderRequest<Tile>(World.tileWorld(enemy.x, enemy.y), - World.core, - heuristic, enemy.gpath); - enemy.request.statusChanged = true; - - enemy.node = -2; - } - - if(enemy.gpath != null && !enemy.request.pathFound){ - enemy.request.executionFrames ++; - if(enemy.finder.search(enemy.request, 1000000 / 5)){ - smoother.smoothPath(enemy.gpath); - enemy.node = 1; - //UCore.log("done in " + enemy.request.executionFrames + " frames with path of size " + enemy.gpath.getCount()); - } - } - - /* Tile closest = null; float ldst = 0f; int cindex = -1; - for(int i = 0; i < path.size; i ++){ - Tile tile = path.get(i); + for(int i = 0; i < path.length; i ++){ + Tile tile = path[i]; float dst = Vector2.dst(tile.worldx(), tile.worldy(), enemy.x, enemy.y); if(closest == null || dst < ldst){ @@ -134,6 +143,17 @@ public class Pathfind{ cindex = i; } } - enemy.node = cindex;*/ + enemy.node = Math.max(cindex, 1); + } + + private static boolean onLine(Vector2 vector, float x1, float y1, float x2, float y2){ + return MathUtils.isEqual(vector.dst(x1, y1) + vector.dst(x2, y2), Vector2.dst(x1, y1, x2, y2), 0.01f); + } + + private static Vector2 projectPoint(float x1, float y1, float x2, float y2, float pointx, float pointy){ + float px = x2-x1, py = y2-y1, dAB = px*px + py*py; + float u = ((pointx - x1) * px + (pointy - y1) * py) / dAB; + float x = x1 + u * px, y = y1 + u * py; + return Tmp.v3.set(x, y); //this is D } } diff --git a/core/src/io/anuke/mindustry/ai/TileConnection.java b/core/src/io/anuke/mindustry/ai/TileConnection.java index a5201e6be6..26ce5bf0a9 100644 --- a/core/src/io/anuke/mindustry/ai/TileConnection.java +++ b/core/src/io/anuke/mindustry/ai/TileConnection.java @@ -4,7 +4,7 @@ import com.badlogic.gdx.ai.pfa.Connection; import io.anuke.mindustry.world.Tile; -public class TileConnection implements Connection{ +public class TileConnection implements Connection<Tile>{ Tile a, b; public TileConnection(Tile a, Tile b){ @@ -14,16 +14,16 @@ public class TileConnection implements Connection{ @Override public float getCost(){ - return Math.abs(a.worldx() - b.worldx()) + Math.abs(a.worldy() - b.worldy()); + return MHueristic.estimateStatic(a, b); } @Override - public Object getFromNode(){ + public Tile getFromNode(){ return a; } @Override - public Object getToNode(){ + public Tile getToNode(){ return b; } diff --git a/core/src/io/anuke/mindustry/core/Control.java b/core/src/io/anuke/mindustry/core/Control.java index 9b4ac2ba76..f9e090429a 100644 --- a/core/src/io/anuke/mindustry/core/Control.java +++ b/core/src/io/anuke/mindustry/core/Control.java @@ -243,7 +243,7 @@ public class Control extends Module{ for(int i = 0; i < spawnamount; i ++){ int index = i; - float range = 8f; + float range = 12f; Timers.run(index*50f, ()->{ try{ @@ -419,6 +419,10 @@ public class Control extends Module{ } } + if(Inputs.keyUp(Keys.O)){ + Vars.noclip = !Vars.noclip; + } + if(Inputs.keyUp(Keys.Y)){ if(Inputs.keyDown(Keys.SHIFT_LEFT)){ new HealerEnemy(0).set(player.x, player.y).add(); diff --git a/core/src/io/anuke/mindustry/entities/Player.java b/core/src/io/anuke/mindustry/entities/Player.java index 5286def09b..ff4d9800a7 100644 --- a/core/src/io/anuke/mindustry/entities/Player.java +++ b/core/src/io/anuke/mindustry/entities/Player.java @@ -35,6 +35,8 @@ public class Player extends DestructibleEntity{ @Override public void onDeath(){ + if(Vars.debug) return; + remove(); Effects.effect(Fx.explosion, this); Effects.shake(4f, 5f, this); @@ -85,7 +87,12 @@ public class Player extends DestructibleEntity{ vector.limit(speed); - move(vector.x*Timers.delta(), vector.y*Timers.delta()); + if(!Vars.noclip){ + move(vector.x*Timers.delta(), vector.y*Timers.delta()); + }else{ + x += vector.x*Timers.delta(); + y += vector.y*Timers.delta(); + } if(!shooting){ direction.add(vector); diff --git a/core/src/io/anuke/mindustry/entities/enemies/Enemy.java b/core/src/io/anuke/mindustry/entities/enemies/Enemy.java index 45a9011c18..c48e0999fb 100644 --- a/core/src/io/anuke/mindustry/entities/enemies/Enemy.java +++ b/core/src/io/anuke/mindustry/entities/enemies/Enemy.java @@ -1,7 +1,5 @@ package io.anuke.mindustry.entities.enemies; -import com.badlogic.gdx.ai.pfa.PathFinder; -import com.badlogic.gdx.ai.pfa.PathFinderRequest; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.utils.Array; @@ -9,7 +7,6 @@ import com.badlogic.gdx.utils.reflect.ClassReflection; import io.anuke.mindustry.Vars; import io.anuke.mindustry.ai.Pathfind; -import io.anuke.mindustry.ai.SmoothGraphPath; import io.anuke.mindustry.entities.Bullet; import io.anuke.mindustry.entities.BulletType; import io.anuke.mindustry.entities.Player; @@ -23,7 +20,12 @@ import io.anuke.ucore.util.Mathf; import io.anuke.ucore.util.Tmp; public class Enemy extends DestructibleEntity{ - public final static Color[] tierColors = {Color.YELLOW, Color.ORANGE, Color.RED, Color.MAGENTA}; + public final static Color[] tierColors = { + Color.valueOf("ffe451"), + Color.valueOf("f48e20"), + Color.valueOf("ff6757"), + Color.valueOf("ff2d86") + }; public final static int maxtier = 4; protected float speed = 0.3f; @@ -37,11 +39,9 @@ public class Enemy extends DestructibleEntity{ protected String shootsound = "enemyshoot"; protected int damage; - public PathFinderRequest<Tile> request = new PathFinderRequest<>();; - public SmoothGraphPath gpath; public int spawn; public int node = -1; - public PathFinder<Tile> finder; + public Tile[] path; public Vector2 direction = new Vector2(); public float xvelocity, yvelocity; @@ -80,16 +80,23 @@ public class Enemy extends DestructibleEntity{ Vector2 shift = Tmp.v3.setZero(); float shiftRange = hitbox.width + 3f; + float avoidRange = 16f; + float avoidSpeed = 0.1f; for(SolidEntity other : entities){ float dst = other.distanceTo(this); - if(other != this && other instanceof Enemy && dst < shiftRange){ - float scl = Mathf.clamp(1.4f - dst/shiftRange); - shift.add((x - other.x) * scl, (y - other.y) * scl); + if(other != this && other instanceof Enemy){ + if(dst < shiftRange){ + float scl = Mathf.clamp(1.4f - dst/shiftRange); + shift.add((x - other.x) * scl, (y - other.y) * scl); + }else if(dst < avoidRange){ + Tmp.v2.set((x - other.x), (y - other.y)).setLength(avoidSpeed); + shift.add(Tmp.v2); + } } } - shift.nor(); + shift.limit(1f); vec.add(shift.scl(0.5f)); move(vec.x*Timers.delta(), vec.y*Timers.delta()); @@ -127,7 +134,6 @@ public class Enemy extends DestructibleEntity{ public void findClosestNode(){ Pathfind.find(this); - /* int index = 0; int cindex = -1; @@ -143,6 +149,8 @@ public class Enemy extends DestructibleEntity{ index ++; } + node = Math.max(cindex, 1); + //set node to that index node = cindex; @@ -153,7 +161,7 @@ public class Enemy extends DestructibleEntity{ Timers.run(Mathf.random(15f), ()->{ set(x2 * Vars.tilesize, y2 * Vars.tilesize); }); - }*/ + } } @Override diff --git a/core/src/io/anuke/mindustry/resource/Weapon.java b/core/src/io/anuke/mindustry/resource/Weapon.java index 395ceb5021..60c54bc669 100644 --- a/core/src/io/anuke/mindustry/resource/Weapon.java +++ b/core/src/io/anuke/mindustry/resource/Weapon.java @@ -49,7 +49,7 @@ public enum Weapon{ bullet(p, p.x, p.y, ang + Mathf.range(8)); - Effects.effect(Fx.shoot, p.x + vector.x, p.y+vector.y); + Effects.effect(Fx.shoot2, p.x + vector.x, p.y+vector.y); } }, flamer(5, BulletType.flame, "Shoots a stream of fire.", stack(Item.steel, 60), stack(Item.coal, 60)){ diff --git a/core/src/io/anuke/mindustry/ui/fragments/HudFragment.java b/core/src/io/anuke/mindustry/ui/fragments/HudFragment.java index da90f16e4e..837f0c4fd6 100644 --- a/core/src/io/anuke/mindustry/ui/fragments/HudFragment.java +++ b/core/src/io/anuke/mindustry/ui/fragments/HudFragment.java @@ -7,6 +7,7 @@ import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.utils.Array; import io.anuke.mindustry.Mindustry; +import io.anuke.mindustry.Vars; import io.anuke.mindustry.core.GameState; import io.anuke.mindustry.core.GameState.State; import io.anuke.mindustry.resource.Item; @@ -141,18 +142,22 @@ public class HudFragment implements Fragment{ aleft(); new label((StringSupplier)()->"[purple]entities: " + Entities.amount()).left(); row(); + new label((StringSupplier)()->"[orange]noclip: " + Vars.noclip).left(); + row(); new label("[red]DEBUG MODE").scale(0.5f).left(); }}.end(); - new table(){{ - atop(); - new table("button"){{ - defaults().left().growX(); + if(profile){ + new table(){{ atop(); - aleft(); - new label((StringSupplier)()->Profiler.formatDisplayTimes()); - }}.width(400f).end(); - }}.end(); + new table("button"){{ + defaults().left().growX(); + atop(); + aleft(); + new label((StringSupplier)()->Profiler.formatDisplayTimes()); + }}.width(400f).end(); + }}.end(); + } } } diff --git a/core/src/io/anuke/mindustry/world/World.java b/core/src/io/anuke/mindustry/world/World.java index 3ae112b316..e5b6597b0f 100644 --- a/core/src/io/anuke/mindustry/world/World.java +++ b/core/src/io/anuke/mindustry/world/World.java @@ -150,6 +150,8 @@ public class World{ World.seed = seed; Generator.generate(mapPixmaps[map.ordinal()]); + Pathfind.reset(); + //TODO multiblock core placeBlock(core.x, core.y, ProductionBlocks.core, 0);