diff --git a/core/assets-raw/sprites/blocks/walls/blast-door-open.png b/core/assets-raw/sprites/blocks/walls/blast-door-open.png new file mode 100644 index 0000000000..afe9aa0f1f Binary files /dev/null and b/core/assets-raw/sprites/blocks/walls/blast-door-open.png differ diff --git a/core/assets-raw/sprites/blocks/walls/blast-door.png b/core/assets-raw/sprites/blocks/walls/blast-door.png new file mode 100644 index 0000000000..bc4710e43d Binary files /dev/null and b/core/assets-raw/sprites/blocks/walls/blast-door.png differ diff --git a/core/assets/icons/icons.properties b/core/assets/icons/icons.properties index fd1827c84d..3836884d7c 100755 --- a/core/assets/icons/icons.properties +++ b/core/assets/icons/icons.properties @@ -553,3 +553,4 @@ 63150=ship-reconstructor|block-ship-reconstructor-ui 63149=radar|block-radar-ui 63148=turret-unit-build-tower|unit-turret-unit-build-tower-ui +63147=blast-door|block-blast-door-ui diff --git a/core/assets/logicids.dat b/core/assets/logicids.dat index 03df1cb60b..4567c7e3d9 100644 Binary files a/core/assets/logicids.dat and b/core/assets/logicids.dat differ diff --git a/core/src/mindustry/ai/ControlPathfinder.java b/core/src/mindustry/ai/ControlPathfinder.java index b34c674b1e..77b9f7dea9 100644 --- a/core/src/mindustry/ai/ControlPathfinder.java +++ b/core/src/mindustry/ai/ControlPathfinder.java @@ -29,7 +29,7 @@ public class ControlPathfinder{ //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 : + PathTile.solid(tile) && ((PathTile.team(tile) == team && !PathTile.teamPassable(tile)) || PathTile.team(tile) == 0) ? impassable : //impassable synthetic enemy block ((PathTile.team(tile) != team && PathTile.team(tile) != 0) && PathTile.solid(tile) ? wallImpassableCap : 0) + 1 + @@ -152,15 +152,16 @@ public class ControlPathfinder{ if(threads == null) return false; PathCost costType = unit.type.pathCost; + int team = unit.team.id; //if the destination can be trivially reached in a straight line, do that. - if((!requests.containsKey(unit) || requests.get(unit).curId != pathId) && !raycast(costType, unit.tileX(), unit.tileY(), World.toTile(destination.x), World.toTile(destination.y))){ + if((!requests.containsKey(unit) || requests.get(unit).curId != pathId) && !raycast(team, 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(costType, world.packArray(World.toTile(destination.x), World.toTile(destination.y)))){ + if(solid(team, costType, world.packArray(World.toTile(destination.x), World.toTile(destination.y)))){ return false; } @@ -173,7 +174,7 @@ public class ControlPathfinder{ req.cost = costType; req.destination.set(destination); req.curId = pathId; - req.team = unit.team.id; + req.team = team; req.lastUpdateId = state.updateId; req.lastPos.set(unit); req.lastWorldUpdate = worldUpdateId; @@ -222,7 +223,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(costType, tileX, tileY, tile.x, tile.y)){ + if(dst < minDst && !permissiveRaycast(team, costType, tileX, tileY, tile.x, tile.y)){ req.pathIndex = Math.max(dst <= range * range ? i + 1 : i, req.pathIndex); minDst = Math.min(dst, minDst); } @@ -235,7 +236,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(costType, tileX, tileY, val % wwidth, val / wwidth)){ + if(!raycast(team, costType, tileX, tileY, val % wwidth, val / wwidth)){ req.rayPathIndex = i; break; } @@ -288,14 +289,14 @@ public class ControlPathfinder{ requests.clear(); } - private static boolean raycast(PathCost type, int x1, int y1, int x2, int y2){ + private static boolean raycast(int team, 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; int e2, err = dx - dy; while(x >= 0 && y >= 0 && x < ww && y < wh){ - if(avoid(type, x + y * wwidth)) return true; + if(avoid(team, type, x + y * wwidth)) return true; if(x == x2 && y == y2) return false; //TODO no diagonals???? is this a good idea? @@ -326,14 +327,14 @@ public class ControlPathfinder{ return true; } - private static boolean permissiveRaycast(PathCost type, int x1, int y1, int x2, int y2){ + private static boolean permissiveRaycast(int team, 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; int err = dx - dy; while(x >= 0 && y >= 0 && x < ww && y < wh){ - if(solid(type, x + y * wwidth)) return true; + if(solid(team, type, x + y * wwidth)) return true; if(x == x2 && y == y2) return false; //no diagonals @@ -349,8 +350,8 @@ public class ControlPathfinder{ return true; } - static boolean cast(PathCost cost, int from, int to){ - return raycast(cost, from % wwidth, from / wwidth, to % wwidth, to / wwidth); + static boolean cast(int team, PathCost cost, int from, int to){ + return raycast(team, cost, from % wwidth, from / wwidth, to % wwidth, to / wwidth); } private Tile tile(int pos){ @@ -367,23 +368,23 @@ public class ControlPathfinder{ return cost.getCost(team, pathfinder.tiles[tilePos]); } - private static int cost(PathCost cost, int tilePos){ - return cost.getCost(-1, pathfinder.tiles[tilePos]); + private static int cost(int team, PathCost cost, int tilePos){ + return cost.getCost(team, pathfinder.tiles[tilePos]); } - private static boolean avoid(PathCost type, int tilePos){ - int cost = cost(type, tilePos); + private static boolean avoid(int team, PathCost type, int tilePos){ + int cost = cost(team, type, tilePos); return cost == impassable || cost >= 2; } - private static boolean solid(PathCost type, int tilePos){ - int cost = cost(type, tilePos); + private static boolean solid(int team, PathCost type, int tilePos){ + int cost = cost(team, type, tilePos); return cost == impassable || cost >= 6000; } - private static float tileCost(PathCost type, int a, int b){ + private static float tileCost(int team, PathCost type, int a, int b){ //currently flat cost - return cost(type, b); + return cost(team, type, b); } static class PathfindThread extends Thread{ @@ -506,7 +507,7 @@ public class ControlPathfinder{ //in fallback mode, enemy walls are passable if(tcost(team, cost, next) == impassable) continue; - float add = tileCost(cost, current, next); + float add = tileCost(team, 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 @@ -564,7 +565,7 @@ public class ControlPathfinder{ int output = 1, input = 2; while(input < len){ - if(cast(cost, result.get(output - 1), result.get(input))){ + if(cast(team, cost, result.get(output - 1), result.get(input))){ result.swap(output, input - 1); output++; } diff --git a/core/src/mindustry/ai/Pathfinder.java b/core/src/mindustry/ai/Pathfinder.java index faaa19a45b..7de7314112 100644 --- a/core/src/mindustry/ai/Pathfinder.java +++ b/core/src/mindustry/ai/Pathfinder.java @@ -40,7 +40,7 @@ public class Pathfinder implements Runnable{ public static final Seq costTypes = Seq.with( //ground (team, tile) -> - (PathTile.allDeep(tile) || (PathTile.team(tile) == team || PathTile.team(tile) == 0) && PathTile.solid(tile)) ? impassable : 1 + + (PathTile.allDeep(tile) || ((PathTile.team(tile) == team && !PathTile.teamPassable(tile)) || PathTile.team(tile) == 0) && PathTile.solid(tile)) ? impassable : 1 + PathTile.health(tile) * 5 + (PathTile.nearSolid(tile) ? 2 : 0) + (PathTile.nearLiquid(tile) ? 6 : 0) + @@ -146,21 +146,22 @@ public class Pathfinder implements Runnable{ } /** Packs a tile into its internal representation. */ - private int packTile(Tile tile, int prev){ + public int packTile(Tile tile, int prev){ boolean nearLiquid = false, nearSolid = false, nearGround = false, solid = tile.solid(), allDeep = tile.floor().isDeep(); for(int i = 0; i < 4; i++){ Tile other = tile.nearby(i); - if(other != null){ + if(other != null) { Floor floor = other.floor(); boolean osolid = other.solid(); if(floor.isLiquid) nearLiquid = true; - if(osolid) nearSolid = true; + //TODO potentially strange behavior when teamPassable is false for other teams? + if(osolid && !other.block().teamPassable) nearSolid = true; if(!floor.isLiquid) nearGround = true; if(!floor.isDeep()) allDeep = false; //other tile is now near solid - if(solid){ + if(solid && !tile.block().teamPassable){ tiles[other.array()] |= PathTile.bitMaskNearSolid; } } @@ -179,10 +180,15 @@ public class Pathfinder implements Runnable{ nearSolid, tile.floor().isDeep(), tile.floor().damageTaken > 0.00001f, - allDeep + allDeep, + tile.block().teamPassable ); } + public int get(int x, int y){ + return tiles[x + y * wwidth]; + } + /** Starts or restarts the pathfinding thread. */ private void start(){ stop(); @@ -551,5 +557,7 @@ public class Pathfinder implements Runnable{ boolean damages; //whether all tiles nearby are deep boolean allDeep; + //block teamPassable is true + boolean teamPassable; } } diff --git a/core/src/mindustry/content/Blocks.java b/core/src/mindustry/content/Blocks.java index ec5aec3958..b7e74cd5b5 100644 --- a/core/src/mindustry/content/Blocks.java +++ b/core/src/mindustry/content/Blocks.java @@ -80,7 +80,7 @@ public class Blocks{ //defense copperWall, copperWallLarge, titaniumWall, titaniumWallLarge, plastaniumWall, plastaniumWallLarge, thoriumWall, thoriumWallLarge, door, doorLarge, phaseWall, phaseWallLarge, surgeWall, surgeWallLarge, - berylliumWall, berylliumWallLarge, tungstenWall, tungstenWallLarge, carbideWall, carbideWallLarge, + berylliumWall, berylliumWallLarge, tungstenWall, tungstenWallLarge, blastDoor, carbideWall, carbideWallLarge, mender, mendProjector, overdriveProjector, overdriveDome, forceProjector, shockMine, scrapWall, scrapWallLarge, scrapWallHuge, scrapWallGigantic, thruster, //ok, these names are getting ridiculous, but at least I don't have humongous walls yet @@ -1610,6 +1610,13 @@ public class Blocks{ size = 2; }}; + blastDoor = new AutoDoor("blast-door"){{ + requirements(Category.defense, with(Items.tungsten, 24, Items.silicon, 24)); + health = 175 * wallHealthMultiplier * 4; + armor = 14f; + size = 2; + }}; + carbideWall = new Wall("carbide-wall"){{ requirements(Category.defense, with(Items.thorium, 6, Items.carbide, 6)); health = 240 * wallHealthMultiplier; diff --git a/core/src/mindustry/content/ErekirTechTree.java b/core/src/mindustry/content/ErekirTechTree.java index a9178752d5..e6c52630b7 100644 --- a/core/src/mindustry/content/ErekirTechTree.java +++ b/core/src/mindustry/content/ErekirTechTree.java @@ -234,7 +234,9 @@ public class ErekirTechTree{ node(tungstenWall, () -> { node(tungstenWallLarge, () -> { + node(blastDoor, () -> { + }); }); node(carbideWall, () -> { diff --git a/core/src/mindustry/content/Fx.java b/core/src/mindustry/content/Fx.java index 7728afeefe..b635288352 100644 --- a/core/src/mindustry/content/Fx.java +++ b/core/src/mindustry/content/Fx.java @@ -1996,12 +1996,12 @@ public class Fx{ dooropen = new Effect(10, e -> { stroke(e.fout() * 1.6f); - Lines.square(e.x, e.y, tilesize / 2f + e.fin() * 2f); + Lines.square(e.x, e.y, e.rotation * tilesize / 2f + e.fin() * 2f); }), doorclose = new Effect(10, e -> { stroke(e.fout() * 1.6f); - Lines.square(e.x, e.y, tilesize / 2f + e.fout() * 2f); + Lines.square(e.x, e.y, e.rotation * tilesize / 2f + e.fout() * 2f); }), dooropenlarge = new Effect(10, e -> { diff --git a/core/src/mindustry/world/Block.java b/core/src/mindustry/world/Block.java index 3536b83e81..1e10a2b883 100644 --- a/core/src/mindustry/world/Block.java +++ b/core/src/mindustry/world/Block.java @@ -88,6 +88,8 @@ public class Block extends UnlockableContent implements Senseable{ public boolean solid; /** whether this block CAN be solid. */ public boolean solidifes; + /** if true, this counts as a non-solid block to this team. */ + public boolean teamPassable; /** whether this is rotatable */ public boolean rotate; /** if rotate is true and this is false, the region won't rotate when drawing */ diff --git a/core/src/mindustry/world/blocks/defense/AutoDoor.java b/core/src/mindustry/world/blocks/defense/AutoDoor.java new file mode 100644 index 0000000000..7161536135 --- /dev/null +++ b/core/src/mindustry/world/blocks/defense/AutoDoor.java @@ -0,0 +1,107 @@ +package mindustry.world.blocks.defense; + +import arc.audio.*; +import arc.func.*; +import arc.graphics.g2d.*; +import arc.math.*; +import arc.math.geom.*; +import arc.struct.*; +import arc.util.io.*; +import mindustry.annotations.Annotations.*; +import mindustry.content.*; +import mindustry.entities.*; +import mindustry.gen.*; +import mindustry.logic.*; +import mindustry.world.*; + +import static mindustry.Vars.*; + +public class AutoDoor extends Wall{ + protected final static Rect rect = new Rect(); + protected final static Seq units = new Seq<>(); + protected final static Boolf groundCheck = u -> u.isGrounded() && !u.type.allowLegStep; + + public final int timerToggle = timers++; + + public float checkInterval = 20f; + public Effect openfx = Fx.dooropen; + public Effect closefx = Fx.doorclose; + public Sound doorSound = Sounds.door; + public @Load("@-open") TextureRegion openRegion; + public float triggerMargin = 10f; + + public AutoDoor(String name){ + super(name); + solid = false; + solidifes = true; + update = true; + teamPassable = true; + + noUpdateDisabled = true; + drawDisabled = true; + } + + @Remote(called = Loc.server) + public static void autoDoorToggle(Tile tile, boolean open){ + if(tile == null || !(tile.build instanceof AutoDoorBuild build)) return; + build.setOpen(open); + } + + public class AutoDoorBuild extends Building{ + public boolean open = false; + + public AutoDoorBuild(){ + //make sure it is staggered + timer.reset(timerToggle, Mathf.random(checkInterval)); + } + + @Override + public void updateTile(){ + if(timer(timerToggle, checkInterval) && !net.client()){ + units.clear(); + team.data().tree().intersect(rect.setSize(size * tilesize + triggerMargin * 2f).setCenter(x, y), units); + boolean shouldOpen = units.contains(groundCheck); + + if(open != shouldOpen){ + Call.autoDoorToggle(tile, shouldOpen); + } + } + } + + @Override + public double sense(LAccess sensor){ + if(sensor == LAccess.enabled) return open ? 1 : 0; + return super.sense(sensor); + } + + public void setOpen(boolean open){ + this.open = open; + (!open ? closefx : openfx).at(this, size); + doorSound.at(this); + pathfinder.updateTile(tile); + } + + @Override + public void draw(){ + Draw.rect(open ? openRegion : region, x, y); + } + + @Override + public boolean checkSolid(){ + return !open; + } + + @Override + public void write(Writes write){ + super.write(write); + write.bool(open); + } + + @Override + public void read(Reads read, byte revision){ + super.read(read, revision); + open = read.bool(); + } + } + +} diff --git a/core/src/mindustry/world/blocks/defense/Door.java b/core/src/mindustry/world/blocks/defense/Door.java index 6dc84cc5f5..8a48640bbe 100644 --- a/core/src/mindustry/world/blocks/defense/Door.java +++ b/core/src/mindustry/world/blocks/defense/Door.java @@ -100,7 +100,7 @@ public class Door extends Wall{ } public void effect(){ - (open ? closefx : openfx).at(this); + (open ? closefx : openfx).at(this, size); } public void updateChained(){