Implemented different heuristics for different difficulties
This commit is contained in:
@@ -2,12 +2,14 @@ package io.anuke.mindustry.ai;
|
|||||||
|
|
||||||
import com.badlogic.gdx.ai.pfa.Heuristic;
|
import com.badlogic.gdx.ai.pfa.Heuristic;
|
||||||
import io.anuke.mindustry.Vars;
|
import io.anuke.mindustry.Vars;
|
||||||
|
import io.anuke.mindustry.world.Block;
|
||||||
import io.anuke.mindustry.world.Tile;
|
import io.anuke.mindustry.world.Tile;
|
||||||
import io.anuke.mindustry.world.blocks.types.defense.Turret;
|
import io.anuke.mindustry.world.blocks.types.defense.Turret;
|
||||||
import io.anuke.mindustry.world.blocks.types.production.Drill;
|
import io.anuke.mindustry.world.blocks.types.production.Drill;
|
||||||
import io.anuke.mindustry.world.blocks.types.production.Generator;
|
import io.anuke.mindustry.world.blocks.types.production.Generator;
|
||||||
import io.anuke.mindustry.world.blocks.types.production.Pump;
|
import io.anuke.mindustry.world.blocks.types.production.Pump;
|
||||||
import io.anuke.mindustry.world.blocks.types.production.Smelter;
|
import io.anuke.mindustry.world.blocks.types.production.Smelter;
|
||||||
|
import io.anuke.ucore.function.Predicate;
|
||||||
|
|
||||||
public class Heuristics {
|
public class Heuristics {
|
||||||
/**How many times more it costs to go through a destructible block than an empty block.*/
|
/**How many times more it costs to go through a destructible block than an empty block.*/
|
||||||
@@ -36,6 +38,11 @@ public class Heuristics {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static class DestrutiveHeuristic implements Heuristic<Tile> {
|
public static class DestrutiveHeuristic implements Heuristic<Tile> {
|
||||||
|
private final Predicate<Block> frees;
|
||||||
|
|
||||||
|
public DestrutiveHeuristic(Predicate<Block> frees){
|
||||||
|
this.frees = frees;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public float estimate(Tile node, Tile other){
|
public float estimate(Tile node, Tile other){
|
||||||
@@ -51,8 +58,11 @@ public class Heuristics {
|
|||||||
//if this block has solid blocks near it, increase the cost, as we don't want enemies hugging walls
|
//if this block has solid blocks near it, increase the cost, as we don't want enemies hugging walls
|
||||||
if(node.occluded) cost += Vars.tilesize*occludedMultiplier;
|
if(node.occluded) cost += Vars.tilesize*occludedMultiplier;
|
||||||
|
|
||||||
|
if(other.getLinked() != null) other = other.getLinked();
|
||||||
|
if(node.getLinked() != null) node = node.getLinked();
|
||||||
|
|
||||||
//generators are free!
|
//generators are free!
|
||||||
if(generator(other) || generator(node)) cost = 0;
|
if(frees.test(other.block()) || frees.test(node.block())) cost = 0;
|
||||||
|
|
||||||
return cost;
|
return cost;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
package io.anuke.mindustry.ai;
|
package io.anuke.mindustry.ai;
|
||||||
|
|
||||||
import com.badlogic.gdx.ai.pfa.Heuristic;
|
|
||||||
import com.badlogic.gdx.ai.pfa.PathFinderRequest;
|
import com.badlogic.gdx.ai.pfa.PathFinderRequest;
|
||||||
import com.badlogic.gdx.ai.pfa.PathSmoother;
|
import com.badlogic.gdx.ai.pfa.PathSmoother;
|
||||||
import com.badlogic.gdx.math.MathUtils;
|
import com.badlogic.gdx.math.MathUtils;
|
||||||
import com.badlogic.gdx.math.Vector2;
|
import com.badlogic.gdx.math.Vector2;
|
||||||
import io.anuke.mindustry.Vars;
|
import io.anuke.mindustry.Vars;
|
||||||
import io.anuke.mindustry.ai.Heuristics.DestrutiveHeuristic;
|
|
||||||
import io.anuke.mindustry.entities.enemies.Enemy;
|
import io.anuke.mindustry.entities.enemies.Enemy;
|
||||||
import io.anuke.mindustry.game.SpawnPoint;
|
import io.anuke.mindustry.game.SpawnPoint;
|
||||||
import io.anuke.mindustry.world.Tile;
|
import io.anuke.mindustry.world.Tile;
|
||||||
@@ -20,8 +18,6 @@ public class Pathfind{
|
|||||||
/**Maximum time taken per frame on pathfinding for a single path.*/
|
/**Maximum time taken per frame on pathfinding for a single path.*/
|
||||||
private static final long maxTime = 1000000 * 5;
|
private static final long maxTime = 1000000 * 5;
|
||||||
|
|
||||||
/**Heuristic for determining cost between two tiles*/
|
|
||||||
Heuristic<Tile> heuristic = new DestrutiveHeuristic();
|
|
||||||
/**Tile graph, for determining conenctions between two tiles*/
|
/**Tile graph, for determining conenctions between two tiles*/
|
||||||
TileGraph graph = new TileGraph();
|
TileGraph graph = new TileGraph();
|
||||||
/**Smoother that removes extra nodes from a path.*/
|
/**Smoother that removes extra nodes from a path.*/
|
||||||
@@ -53,38 +49,17 @@ public class Pathfind{
|
|||||||
enemy.node = -1;
|
enemy.node = -1;
|
||||||
return vector.set(enemy.x, enemy.y);
|
return vector.set(enemy.x, enemy.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
//if an enemy is idle for a while, it's probably stuck
|
|
||||||
//if(enemy.idletime > EnemyType.maxIdle){
|
|
||||||
|
|
||||||
//Tile target = path[enemy.node];
|
|
||||||
//if(Vars.world.raycastWorld(enemy.x, enemy.y, target.worldx(), target.worldy()) != null) {
|
|
||||||
// if (enemy.node > 1)
|
|
||||||
// enemy.node = enemy.node - 1;
|
|
||||||
// enemy.idletime = 0;
|
|
||||||
//}
|
|
||||||
|
|
||||||
//else, must be blocked by a playermade block, do nothing
|
|
||||||
//}
|
|
||||||
|
|
||||||
//-1 is only possible here if both pathfindings failed, which should NOT happen
|
|
||||||
//check graph code
|
|
||||||
|
|
||||||
if(enemy.node <= -1){
|
if(enemy.node <= -1){
|
||||||
return vector.set(enemy.x, enemy.y);
|
return vector.set(enemy.x, enemy.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(enemy.node >= path.length){
|
|
||||||
enemy.node = -1;
|
|
||||||
return vector.set(enemy.x, enemy.y);
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO documentation on what this does
|
//TODO documentation on what this does
|
||||||
Tile prev = path[enemy.node - 1];
|
Tile prev = path[enemy.node - 1];
|
||||||
|
|
||||||
Tile target = path[enemy.node];
|
Tile target = path[enemy.node];
|
||||||
|
|
||||||
//a bridge has broken
|
//a bridge has been broken, re-path
|
||||||
if(!Vars.world.passable(target.x, target.y)){
|
if(!Vars.world.passable(target.x, target.y)){
|
||||||
remakePath();
|
remakePath();
|
||||||
return vector.set(enemy.x, enemy.y);
|
return vector.set(enemy.x, enemy.y);
|
||||||
@@ -131,6 +106,7 @@ public class Pathfind{
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**Re-calculate paths for all enemies. Runs when a path changes while moving.*/
|
||||||
private void remakePath(){
|
private void remakePath(){
|
||||||
for(int i = 0; i < Vars.control.enemyGroup.amount(); i ++){
|
for(int i = 0; i < Vars.control.enemyGroup.amount(); i ++){
|
||||||
Enemy enemy = Vars.control.enemyGroup.all().get(i);
|
Enemy enemy = Vars.control.enemyGroup.all().get(i);
|
||||||
@@ -169,13 +145,13 @@ public class Pathfind{
|
|||||||
|
|
||||||
//warmup
|
//warmup
|
||||||
for(int i = 0; i < 100; i ++){
|
for(int i = 0; i < 100; i ++){
|
||||||
point.finder.searchNodePath(point.start, Vars.control.getCore(), heuristic, point.path);
|
point.finder.searchNodePath(point.start, Vars.control.getCore(), Vars.control.getDifficulty().heuristic, point.path);
|
||||||
point.path.clear();
|
point.path.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
Timers.mark();
|
Timers.mark();
|
||||||
for(int i = 0; i < amount; i ++){
|
for(int i = 0; i < amount; i ++){
|
||||||
point.finder.searchNodePath(point.start, Vars.control.getCore(), heuristic, point.path);
|
point.finder.searchNodePath(point.start, Vars.control.getCore(), Vars.control.getDifficulty().heuristic, point.path);
|
||||||
point.path.clear();
|
point.path.clear();
|
||||||
}
|
}
|
||||||
UCore.log("Time elapsed: " + Timers.elapsed() + "ms\nAverage MS per path: " + Timers.elapsed()/amount);
|
UCore.log("Time elapsed: " + Timers.elapsed() + "ms\nAverage MS per path: " + Timers.elapsed()/amount);
|
||||||
@@ -190,7 +166,7 @@ public class Pathfind{
|
|||||||
|
|
||||||
point.pathTiles = null;
|
point.pathTiles = null;
|
||||||
|
|
||||||
point.request = new PathFinderRequest<>(point.start, Vars.control.getCore(), heuristic, point.path);
|
point.request = new PathFinderRequest<>(point.start, Vars.control.getCore(), Vars.control.getDifficulty().heuristic, point.path);
|
||||||
point.request.statusChanged = true; //IMPORTANT!
|
point.request.statusChanged = true; //IMPORTANT!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -254,6 +254,7 @@ public class NetClient extends Module {
|
|||||||
Gdx.app.postRunnable(() -> {
|
Gdx.app.postRunnable(() -> {
|
||||||
//duplicates.
|
//duplicates.
|
||||||
if(Vars.control.enemyGroup.getByID(player.id) != null) return;
|
if(Vars.control.enemyGroup.getByID(player.id) != null) return;
|
||||||
|
|
||||||
player.getInterpolator().last.set(player.x, player.y);
|
player.getInterpolator().last.set(player.x, player.y);
|
||||||
player.getInterpolator().target.set(player.x, player.y);
|
player.getInterpolator().target.set(player.x, player.y);
|
||||||
player.add();
|
player.add();
|
||||||
|
|||||||
@@ -1,13 +1,25 @@
|
|||||||
package io.anuke.mindustry.game;
|
package io.anuke.mindustry.game;
|
||||||
|
|
||||||
|
import com.badlogic.gdx.ai.pfa.Heuristic;
|
||||||
|
import io.anuke.mindustry.ai.Heuristics.DestrutiveHeuristic;
|
||||||
|
import io.anuke.mindustry.world.Tile;
|
||||||
|
import io.anuke.mindustry.world.blocks.types.LiquidBlock;
|
||||||
|
import io.anuke.mindustry.world.blocks.types.PowerBlock;
|
||||||
|
import io.anuke.mindustry.world.blocks.types.defense.Turret;
|
||||||
|
import io.anuke.mindustry.world.blocks.types.distribution.Conveyor;
|
||||||
|
import io.anuke.mindustry.world.blocks.types.distribution.Router;
|
||||||
|
import io.anuke.mindustry.world.blocks.types.production.Drill;
|
||||||
|
import io.anuke.mindustry.world.blocks.types.production.Generator;
|
||||||
|
import io.anuke.mindustry.world.blocks.types.production.Smelter;
|
||||||
import io.anuke.ucore.util.Bundles;
|
import io.anuke.ucore.util.Bundles;
|
||||||
|
|
||||||
public enum Difficulty {
|
public enum Difficulty {
|
||||||
easy(4f, 2f),
|
easy(4f, 2f, new DestrutiveHeuristic(b -> b instanceof Generator)),
|
||||||
normal(2f, 1f),
|
normal(2f, 1f, new DestrutiveHeuristic(b -> b instanceof Smelter || b instanceof Generator)),
|
||||||
hard(1.5f, 0.5f),
|
hard(1.5f, 0.5f, new DestrutiveHeuristic(b -> b instanceof Turret || b instanceof Generator || b instanceof Drill || b instanceof Smelter)),
|
||||||
insane(0.5f, 0.25f),
|
insane(0.5f, 0.25f, new DestrutiveHeuristic(b -> b instanceof Turret || b instanceof Generator || b instanceof Drill || b instanceof Smelter || b instanceof Router)),
|
||||||
purge(0.35f, 0.01f);
|
purge(0.35f, 0.01f, new DestrutiveHeuristic(b -> b instanceof Turret || b instanceof Generator || b instanceof Drill || b instanceof Router
|
||||||
|
|| b instanceof Smelter || b instanceof Conveyor || b instanceof LiquidBlock || b instanceof PowerBlock));
|
||||||
|
|
||||||
/**The scaling of how many waves it takes for one more enemy of a type to appear.
|
/**The scaling of how many waves it takes for one more enemy of a type to appear.
|
||||||
* For example: with enemeyScaling = 2 and the default scaling being 2, it would take 4 waves for
|
* For example: with enemeyScaling = 2 and the default scaling being 2, it would take 4 waves for
|
||||||
@@ -16,9 +28,12 @@ public enum Difficulty {
|
|||||||
/**Multiplier of the time between waves.*/
|
/**Multiplier of the time between waves.*/
|
||||||
public final float timeScaling;
|
public final float timeScaling;
|
||||||
|
|
||||||
Difficulty(float enemyScaling, float timeScaling){
|
public final Heuristic<Tile> heuristic;
|
||||||
|
|
||||||
|
Difficulty(float enemyScaling, float timeScaling, Heuristic<Tile> heuristic){
|
||||||
this.enemyScaling = enemyScaling;
|
this.enemyScaling = enemyScaling;
|
||||||
this.timeScaling = timeScaling;
|
this.timeScaling = timeScaling;
|
||||||
|
this.heuristic = heuristic;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -117,6 +117,7 @@ public class JoinDialog extends FloatingDialog {
|
|||||||
while(t.getCause() != null){
|
while(t.getCause() != null){
|
||||||
t = t.getCause();
|
t = t.getCause();
|
||||||
}
|
}
|
||||||
|
//TODO localize
|
||||||
String error = t.getMessage() == null ? "" : t.getMessage().toLowerCase();
|
String error = t.getMessage() == null ? "" : t.getMessage().toLowerCase();
|
||||||
if(error.contains("connection refused")) {
|
if(error.contains("connection refused")) {
|
||||||
error = "connection refused";
|
error = "connection refused";
|
||||||
@@ -125,7 +126,7 @@ public class JoinDialog extends FloatingDialog {
|
|||||||
}else if(error.contains("invalid argument")) {
|
}else if(error.contains("invalid argument")) {
|
||||||
error = "invalid IP or port!";
|
error = "invalid IP or port!";
|
||||||
}else if(t.getClass().toString().toLowerCase().contains("sockettimeout")){
|
}else if(t.getClass().toString().toLowerCase().contains("sockettimeout")){
|
||||||
error = "timed out!";
|
error = "timed out!\nmake sure the host has port forwarding set up,\nand that the address is correct!";
|
||||||
}else{
|
}else{
|
||||||
error = Strings.parseException(e, false);
|
error = Strings.parseException(e, false);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user