From 5a3305f95deddb84ada64193633fa75481d379d4 Mon Sep 17 00:00:00 2001 From: Anuken Date: Tue, 15 Feb 2022 10:25:03 -0500 Subject: [PATCH] Pathfinding cleanup --- core/src/mindustry/ai/ControlPathfinder.java | 97 +++++++++---------- .../mindustry/ctype/UnlockableContent.java | 17 ++++ core/src/mindustry/type/StatusEffect.java | 13 +++ core/src/mindustry/type/UnitType.java | 29 +++--- 4 files changed, 90 insertions(+), 66 deletions(-) diff --git a/core/src/mindustry/ai/ControlPathfinder.java b/core/src/mindustry/ai/ControlPathfinder.java index 61f75fe101..3110cb20e4 100644 --- a/core/src/mindustry/ai/ControlPathfinder.java +++ b/core/src/mindustry/ai/ControlPathfinder.java @@ -23,36 +23,33 @@ public class ControlPathfinder{ private static final int updateInterval = 1000 / updateFPS; private static final int wallImpassableCap = 100_000; + public static final PathCost + + costGround = (team, tile) -> + //deep is impassable + PathTile.allDeep(tile) ? impassable : + //impassable same-team or neutral block + PathTile.solid(tile) && (PathTile.team(tile) == team || PathTile.team(tile) == 0) ? impassable : + //impassable synthetic enemy block + ((PathTile.team(tile) != team && PathTile.team(tile) != 0) && PathTile.solid(tile) ? wallImpassableCap : 0) + + 1 + + (PathTile.nearSolid(tile) ? 6 : 0) + + (PathTile.nearLiquid(tile) ? 8 : 0) + + (PathTile.deep(tile) ? 6000 : 0) + + (PathTile.damages(tile) ? 50 : 0), + + costLegs = (team, tile) -> + PathTile.legSolid(tile) ? impassable : 1 + + (PathTile.deep(tile) ? 6000 : 0) + + (PathTile.nearSolid(tile) || PathTile.solid(tile) ? 3 : 0), + + costNaval = (team, tile) -> + (PathTile.solid(tile) || !PathTile.liquid(tile) ? impassable : 1) + + (PathTile.nearGround(tile) || PathTile.nearSolid(tile) ? 2 : 0) + + (PathTile.deep(tile) ? 0 : 1); + public static boolean showDebug = false; - public static final Seq costTypes = Seq.with( - //ground - (team, tile) -> - //deep is impassable - PathTile.allDeep(tile) ? impassable : - //impassable same-team or neutral block - PathTile.solid(tile) && (PathTile.team(tile) == team || PathTile.team(tile) == 0) ? impassable : - //impassable synthetic enemy block - ((PathTile.team(tile) != team && PathTile.team(tile) != 0) && PathTile.solid(tile) ? wallImpassableCap : 0) + - 1 + - (PathTile.nearSolid(tile) ? 6 : 0) + - (PathTile.nearLiquid(tile) ? 8 : 0) + - (PathTile.deep(tile) ? 6000 : 0) + - (PathTile.damages(tile) ? 50 : 0), - - //legs - (team, tile) -> - PathTile.legSolid(tile) ? impassable : 1 + - (PathTile.deep(tile) ? 6000 : 0) + - (PathTile.nearSolid(tile) || PathTile.solid(tile) ? 3 : 0), - - //water - (team, tile) -> - (PathTile.solid(tile) || !PathTile.liquid(tile) ? impassable : 1) + - (PathTile.nearGround(tile) || PathTile.nearSolid(tile) ? 2 : 0) + - (PathTile.deep(tile) ? 0 : 1) - ); - //static access probably faster than object access static int wwidth, wheight; //increments each tile change @@ -153,16 +150,16 @@ public class ControlPathfinder{ //uninitialized if(threads == null) return false; - int pathType = unit.pathType(); + PathCost costType = unit.type.pathCost; //if the destination can be trivially reached in a straight line, do that. - if((!requests.containsKey(unit) || requests.get(unit).curId != pathId) && !raycast(pathType, unit.tileX(), unit.tileY(), World.toTile(destination.x), World.toTile(destination.y))){ + if((!requests.containsKey(unit) || requests.get(unit).curId != pathId) && !raycast(costType, unit.tileX(), unit.tileY(), World.toTile(destination.x), World.toTile(destination.y))){ out.set(destination); return true; } //destination is impassable, can't go there. - if(solid(pathType, world.packArray(World.toTile(destination.x), World.toTile(destination.y)))){ + if(solid(costType, world.packArray(World.toTile(destination.x), World.toTile(destination.y)))){ return false; } @@ -172,7 +169,7 @@ public class ControlPathfinder{ var req = new PathRequest(thread); req.unit = unit; - req.pathType = pathType; + req.cost = costType; req.destination.set(destination); req.curId = pathId; req.team = unit.team.id; @@ -224,7 +221,7 @@ public class ControlPathfinder{ Tile tile = tile(items[i]); float dst = unit.dst2(tile); //TODO maybe put this on a timer since raycasts can be expensive? - if(dst < minDst && !permissiveRaycast(pathType, tileX, tileY, tile.x, tile.y)){ + if(dst < minDst && !permissiveRaycast(costType, tileX, tileY, tile.x, tile.y)){ req.pathIndex = Math.max(dst <= range * range ? i + 1 : i, req.pathIndex); minDst = Math.min(dst, minDst); } @@ -237,7 +234,7 @@ public class ControlPathfinder{ if((req.raycastTimer += Time.delta) >= 50f){ for(int i = len - 1; i > req.pathIndex; i--){ int val = items[i]; - if(!raycast(pathType, tileX, tileY, val % wwidth, val / wwidth)){ + if(!raycast(costType, tileX, tileY, val % wwidth, val / wwidth)){ req.rayPathIndex = i; break; } @@ -288,14 +285,14 @@ public class ControlPathfinder{ requests.clear(); } - private static boolean raycast(int type, int x1, int y1, int x2, int y2){ + private static boolean raycast(PathCost cost, int x1, int y1, int x2, int y2){ int ww = world.width(), wh = world.height(); int x = x1, dx = Math.abs(x2 - x), sx = x < x2 ? 1 : -1; int y = y1, dy = Math.abs(y2 - y), sy = y < y2 ? 1 : -1; int e2, err = dx - dy; while(x >= 0 && y >= 0 && x < ww && y < wh){ - if(avoid(type, x + y * wwidth)) return true; + if(avoid(cost, x + y * wwidth)) return true; if(x == x2 && y == y2) return false; e2 = 2 * err; @@ -314,7 +311,7 @@ public class ControlPathfinder{ return true; } - private static boolean permissiveRaycast(int type, int x1, int y1, int x2, int y2){ + private static boolean permissiveRaycast(PathCost type, int x1, int y1, int x2, int y2){ int ww = world.width(), wh = world.height(); int x = x1, dx = Math.abs(x2 - x), sx = x < x2 ? 1 : -1; int y = y1, dy = Math.abs(y2 - y), sy = y < y2 ? 1 : -1; @@ -337,8 +334,8 @@ public class ControlPathfinder{ return true; } - static boolean cast(int pathType, int from, int to){ - return raycast(pathType, from % wwidth, from / wwidth, to % wwidth, to / wwidth); + static boolean cast(PathCost cost, int from, int to){ + return raycast(cost, from % wwidth, from / wwidth, to % wwidth, to / wwidth); } private Tile tile(int pos){ @@ -351,25 +348,25 @@ public class ControlPathfinder{ return Math.abs(x - x2) + Math.abs(y - y2); } - private static int tcost(int team, int type, int tilePos){ - return costTypes.items[type].getCost(team, pathfinder.tiles[tilePos]); + private static int tcost(int team, PathCost cost, int tilePos){ + return cost.getCost(team, pathfinder.tiles[tilePos]); } - private static int cost(int type, int tilePos){ - return costTypes.items[type].getCost(-1, pathfinder.tiles[tilePos]); + private static int cost(PathCost cost, int tilePos){ + return cost.getCost(-1, pathfinder.tiles[tilePos]); } - private static boolean avoid(int type, int tilePos){ + private static boolean avoid(PathCost type, int tilePos){ int cost = cost(type, tilePos); return cost == impassable || cost >= 2; } - private static boolean solid(int type, int tilePos){ + private static boolean solid(PathCost type, int tilePos){ int cost = cost(type, tilePos); return cost == impassable || cost >= 6000; } - private static float tileCost(int type, int a, int b){ + private static float tileCost(PathCost type, int a, int b){ //currently flat cost return cost(type, b); } @@ -423,7 +420,7 @@ public class ControlPathfinder{ volatile boolean done = false; volatile boolean foundEnd = false; volatile Unit unit; - volatile int pathType; + volatile PathCost cost; volatile int team; volatile int lastWorldUpdate; @@ -492,9 +489,9 @@ public class ControlPathfinder{ if(newx >= wwidth || newy >= wheight || newx < 0 || newy < 0) continue; //in fallback mode, enemy walls are passable - if(tcost(team, pathType, next) == impassable) continue; + if(tcost(team, cost, next) == impassable) continue; - float add = tileCost(pathType, current, next); + float add = tileCost(cost, current, next); float currentCost = costs.get(current); //the cost can include an impassable enemy wall, so cap the cost if so and add the base cost instead @@ -552,7 +549,7 @@ public class ControlPathfinder{ int output = 1, input = 2; while(input < len){ - if(cast(pathType, result.get(output - 1), result.get(input))){ + if(cast(cost, result.get(output - 1), result.get(input))){ result.swap(output, input - 1); output++; } diff --git a/core/src/mindustry/ctype/UnlockableContent.java b/core/src/mindustry/ctype/UnlockableContent.java index 73843d4a2e..2538a84219 100644 --- a/core/src/mindustry/ctype/UnlockableContent.java +++ b/core/src/mindustry/ctype/UnlockableContent.java @@ -2,13 +2,16 @@ package mindustry.ctype; import arc.*; import arc.func.*; +import arc.graphics.*; import arc.graphics.g2d.*; +import arc.graphics.g2d.TextureAtlas.*; import arc.scene.ui.layout.*; import arc.util.*; import mindustry.annotations.Annotations.*; import mindustry.content.TechTree.*; import mindustry.game.EventType.*; import mindustry.graphics.*; +import mindustry.graphics.MultiPacker.*; import mindustry.type.*; import mindustry.ui.*; import mindustry.world.meta.*; @@ -91,6 +94,20 @@ public abstract class UnlockableContent extends MappableContent{ } + protected void makeOutline(PageType page, MultiPacker packer, TextureRegion region, boolean makeNew, Color outlineColor, int outlineRadius){ + if(region instanceof AtlasRegion at && region.found()){ + String name = at.name; + if(!makeNew || !packer.has(name + "-outline")){ + PixmapRegion base = Core.atlas.getPixmap(region); + var result = Pixmaps.outline(base, outlineColor, outlineRadius); + if(Core.settings.getBool("linear", true)){ + Pixmaps.bleed(result); + } + packer.add(page, name + (makeNew ? "-outline" : ""), result); + } + } + } + /** @return items needed to research this content */ public ItemStack[] researchRequirements(){ return ItemStack.empty; diff --git a/core/src/mindustry/type/StatusEffect.java b/core/src/mindustry/type/StatusEffect.java index cabeb2c728..24bd06ee7d 100644 --- a/core/src/mindustry/type/StatusEffect.java +++ b/core/src/mindustry/type/StatusEffect.java @@ -10,6 +10,8 @@ import mindustry.ctype.*; import mindustry.entities.*; import mindustry.entities.units.*; import mindustry.gen.*; +import mindustry.graphics.*; +import mindustry.graphics.MultiPacker.*; import mindustry.world.meta.*; public class StatusEffect extends UnlockableContent{ @@ -47,6 +49,8 @@ public class StatusEffect extends UnlockableContent{ public Effect effect = Fx.none; /** Affinity & opposite values for stat displays. */ public ObjectSet affinities = new ObjectSet<>(), opposites = new ObjectSet<>(); + /** Set to false to disable outline generation. */ + public boolean outline = true; /** Transition handler map. */ protected ObjectMap transitions = new ObjectMap<>(); /** Called on init. */ @@ -181,6 +185,15 @@ public class StatusEffect extends UnlockableContent{ return false; } + @Override + public void createIcons(MultiPacker packer){ + super.createIcons(packer); + + if(outline){ + makeOutline(PageType.ui, packer, uiIcon, true, Pal.gray, 3); + } + } + @Override public String toString(){ return localizedName; diff --git a/core/src/mindustry/type/UnitType.java b/core/src/mindustry/type/UnitType.java index f322878169..c521c26875 100644 --- a/core/src/mindustry/type/UnitType.java +++ b/core/src/mindustry/type/UnitType.java @@ -13,6 +13,8 @@ import arc.scene.ui.layout.*; import arc.struct.*; import arc.util.*; import mindustry.*; +import mindustry.ai.*; +import mindustry.ai.Pathfinder.*; import mindustry.ai.types.*; import mindustry.annotations.Annotations.*; import mindustry.content.*; @@ -180,6 +182,8 @@ public class UnitType extends UnlockableContent{ public boolean forceMultiTarget = false; public boolean hidden = false; public boolean internal = false; + /** Function used for calculating cost of moving with ControlPathfinder. Does not affect "normal" flow field pathfinding. */ + public @Nullable PathCost pathCost; /** A sample of the unit that this type creates. Do not modify! */ public @Nullable Unit sample; @@ -417,6 +421,13 @@ public class UnitType extends UnlockableContent{ } } + if(pathCost == null){ + pathCost = + example instanceof WaterMovec ? ControlPathfinder.costNaval : + allowLegStep ? ControlPathfinder.costLegs : + ControlPathfinder.costGround; + } + if(flying){ envEnabled |= Env.space; } @@ -589,20 +600,6 @@ public class UnitType extends UnlockableContent{ clipSize = Math.max(region.width * 2f, clipSize); } - private void makeOutline(MultiPacker packer, TextureRegion region, boolean makeNew){ - if(region instanceof AtlasRegion at && region.found()){ - String name = at.name; - if(!makeNew || !packer.has(name + "-outline")){ - PixmapRegion base = Core.atlas.getPixmap(region); - var result = Pixmaps.outline(base, outlineColor, outlineRadius); - if(Core.settings.getBool("linear", true)){ - Pixmaps.bleed(result); - } - packer.add(PageType.main, name + (makeNew ? "-outline" : ""), result); - } - } - } - public void getRegionsToOutline(Seq out){ for(Weapon weapon : weapons){ for(var part : weapon.parts){ @@ -640,11 +637,11 @@ public class UnitType extends UnlockableContent{ if(outlines){ //outlines only created when forced at the moment - makeOutline(packer, region, alwaysCreateOutline); + makeOutline(PageType.main, packer, region, alwaysCreateOutline, outlineColor, outlineRadius); for(Weapon weapon : weapons){ if(!weapon.name.isEmpty()){ - makeOutline(packer, weapon.region, true); + makeOutline(PageType.main, packer, weapon.region, true, outlineColor, outlineRadius); } } }