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);