Blast doors

This commit is contained in:
Anuken
2022-04-08 13:53:10 -04:00
parent 91acf1647a
commit 759d872115
12 changed files with 160 additions and 32 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 732 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

+1
View File
@@ -553,3 +553,4 @@
63150=ship-reconstructor|block-ship-reconstructor-ui 63150=ship-reconstructor|block-ship-reconstructor-ui
63149=radar|block-radar-ui 63149=radar|block-radar-ui
63148=turret-unit-build-tower|unit-turret-unit-build-tower-ui 63148=turret-unit-build-tower|unit-turret-unit-build-tower-ui
63147=blast-door|block-blast-door-ui
Binary file not shown.
+23 -22
View File
@@ -29,7 +29,7 @@ public class ControlPathfinder{
//deep is impassable //deep is impassable
PathTile.allDeep(tile) ? impassable : PathTile.allDeep(tile) ? impassable :
//impassable same-team or neutral block //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 //impassable synthetic enemy block
((PathTile.team(tile) != team && PathTile.team(tile) != 0) && PathTile.solid(tile) ? wallImpassableCap : 0) + ((PathTile.team(tile) != team && PathTile.team(tile) != 0) && PathTile.solid(tile) ? wallImpassableCap : 0) +
1 + 1 +
@@ -152,15 +152,16 @@ public class ControlPathfinder{
if(threads == null) return false; if(threads == null) return false;
PathCost costType = unit.type.pathCost; PathCost costType = unit.type.pathCost;
int team = unit.team.id;
//if the destination can be trivially reached in a straight line, do that. //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); out.set(destination);
return true; return true;
} }
//destination is impassable, can't go there. //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; return false;
} }
@@ -173,7 +174,7 @@ public class ControlPathfinder{
req.cost = costType; req.cost = costType;
req.destination.set(destination); req.destination.set(destination);
req.curId = pathId; req.curId = pathId;
req.team = unit.team.id; req.team = team;
req.lastUpdateId = state.updateId; req.lastUpdateId = state.updateId;
req.lastPos.set(unit); req.lastPos.set(unit);
req.lastWorldUpdate = worldUpdateId; req.lastWorldUpdate = worldUpdateId;
@@ -222,7 +223,7 @@ public class ControlPathfinder{
Tile tile = tile(items[i]); Tile tile = tile(items[i]);
float dst = unit.dst2(tile); float dst = unit.dst2(tile);
//TODO maybe put this on a timer since raycasts can be expensive? //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); req.pathIndex = Math.max(dst <= range * range ? i + 1 : i, req.pathIndex);
minDst = Math.min(dst, minDst); minDst = Math.min(dst, minDst);
} }
@@ -235,7 +236,7 @@ public class ControlPathfinder{
if((req.raycastTimer += Time.delta) >= 50f){ if((req.raycastTimer += Time.delta) >= 50f){
for(int i = len - 1; i > req.pathIndex; i--){ for(int i = len - 1; i > req.pathIndex; i--){
int val = items[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; req.rayPathIndex = i;
break; break;
} }
@@ -288,14 +289,14 @@ public class ControlPathfinder{
requests.clear(); 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 ww = world.width(), wh = world.height();
int x = x1, dx = Math.abs(x2 - x), sx = x < x2 ? 1 : -1; 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 y = y1, dy = Math.abs(y2 - y), sy = y < y2 ? 1 : -1;
int e2, err = dx - dy; int e2, err = dx - dy;
while(x >= 0 && y >= 0 && x < ww && y < wh){ 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; if(x == x2 && y == y2) return false;
//TODO no diagonals???? is this a good idea? //TODO no diagonals???? is this a good idea?
@@ -326,14 +327,14 @@ public class ControlPathfinder{
return true; 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 ww = world.width(), wh = world.height();
int x = x1, dx = Math.abs(x2 - x), sx = x < x2 ? 1 : -1; 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 y = y1, dy = Math.abs(y2 - y), sy = y < y2 ? 1 : -1;
int err = dx - dy; int err = dx - dy;
while(x >= 0 && y >= 0 && x < ww && y < wh){ 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; if(x == x2 && y == y2) return false;
//no diagonals //no diagonals
@@ -349,8 +350,8 @@ public class ControlPathfinder{
return true; return true;
} }
static boolean cast(PathCost cost, int from, int to){ static boolean cast(int team, PathCost cost, int from, int to){
return raycast(cost, from % wwidth, from / wwidth, to % wwidth, to / wwidth); return raycast(team, cost, from % wwidth, from / wwidth, to % wwidth, to / wwidth);
} }
private Tile tile(int pos){ private Tile tile(int pos){
@@ -367,23 +368,23 @@ public class ControlPathfinder{
return cost.getCost(team, pathfinder.tiles[tilePos]); return cost.getCost(team, pathfinder.tiles[tilePos]);
} }
private static int cost(PathCost cost, int tilePos){ private static int cost(int team, PathCost cost, int tilePos){
return cost.getCost(-1, pathfinder.tiles[tilePos]); return cost.getCost(team, pathfinder.tiles[tilePos]);
} }
private static boolean avoid(PathCost type, int tilePos){ private static boolean avoid(int team, PathCost type, int tilePos){
int cost = cost(type, tilePos); int cost = cost(team, type, tilePos);
return cost == impassable || cost >= 2; return cost == impassable || cost >= 2;
} }
private static boolean solid(PathCost type, int tilePos){ private static boolean solid(int team, PathCost type, int tilePos){
int cost = cost(type, tilePos); int cost = cost(team, type, tilePos);
return cost == impassable || cost >= 6000; 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 //currently flat cost
return cost(type, b); return cost(team, type, b);
} }
static class PathfindThread extends Thread{ static class PathfindThread extends Thread{
@@ -506,7 +507,7 @@ public class ControlPathfinder{
//in fallback mode, enemy walls are passable //in fallback mode, enemy walls are passable
if(tcost(team, cost, next) == impassable) continue; 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); 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 //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; int output = 1, input = 2;
while(input < len){ 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); result.swap(output, input - 1);
output++; output++;
} }
+14 -6
View File
@@ -40,7 +40,7 @@ public class Pathfinder implements Runnable{
public static final Seq<PathCost> costTypes = Seq.with( public static final Seq<PathCost> costTypes = Seq.with(
//ground //ground
(team, tile) -> (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.health(tile) * 5 +
(PathTile.nearSolid(tile) ? 2 : 0) + (PathTile.nearSolid(tile) ? 2 : 0) +
(PathTile.nearLiquid(tile) ? 6 : 0) + (PathTile.nearLiquid(tile) ? 6 : 0) +
@@ -146,21 +146,22 @@ public class Pathfinder implements Runnable{
} }
/** Packs a tile into its internal representation. */ /** 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(); boolean nearLiquid = false, nearSolid = false, nearGround = false, solid = tile.solid(), allDeep = tile.floor().isDeep();
for(int i = 0; i < 4; i++){ for(int i = 0; i < 4; i++){
Tile other = tile.nearby(i); Tile other = tile.nearby(i);
if(other != null){ if(other != null) {
Floor floor = other.floor(); Floor floor = other.floor();
boolean osolid = other.solid(); boolean osolid = other.solid();
if(floor.isLiquid) nearLiquid = true; 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.isLiquid) nearGround = true;
if(!floor.isDeep()) allDeep = false; if(!floor.isDeep()) allDeep = false;
//other tile is now near solid //other tile is now near solid
if(solid){ if(solid && !tile.block().teamPassable){
tiles[other.array()] |= PathTile.bitMaskNearSolid; tiles[other.array()] |= PathTile.bitMaskNearSolid;
} }
} }
@@ -179,10 +180,15 @@ public class Pathfinder implements Runnable{
nearSolid, nearSolid,
tile.floor().isDeep(), tile.floor().isDeep(),
tile.floor().damageTaken > 0.00001f, 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. */ /** Starts or restarts the pathfinding thread. */
private void start(){ private void start(){
stop(); stop();
@@ -551,5 +557,7 @@ public class Pathfinder implements Runnable{
boolean damages; boolean damages;
//whether all tiles nearby are deep //whether all tiles nearby are deep
boolean allDeep; boolean allDeep;
//block teamPassable is true
boolean teamPassable;
} }
} }
+8 -1
View File
@@ -80,7 +80,7 @@ public class Blocks{
//defense //defense
copperWall, copperWallLarge, titaniumWall, titaniumWallLarge, plastaniumWall, plastaniumWallLarge, thoriumWall, thoriumWallLarge, door, doorLarge, copperWall, copperWallLarge, titaniumWall, titaniumWallLarge, plastaniumWall, plastaniumWallLarge, thoriumWall, thoriumWallLarge, door, doorLarge,
phaseWall, phaseWallLarge, surgeWall, surgeWallLarge, phaseWall, phaseWallLarge, surgeWall, surgeWallLarge,
berylliumWall, berylliumWallLarge, tungstenWall, tungstenWallLarge, carbideWall, carbideWallLarge, berylliumWall, berylliumWallLarge, tungstenWall, tungstenWallLarge, blastDoor, carbideWall, carbideWallLarge,
mender, mendProjector, overdriveProjector, overdriveDome, forceProjector, shockMine, 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 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; 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"){{ carbideWall = new Wall("carbide-wall"){{
requirements(Category.defense, with(Items.thorium, 6, Items.carbide, 6)); requirements(Category.defense, with(Items.thorium, 6, Items.carbide, 6));
health = 240 * wallHealthMultiplier; health = 240 * wallHealthMultiplier;
@@ -234,7 +234,9 @@ public class ErekirTechTree{
node(tungstenWall, () -> { node(tungstenWall, () -> {
node(tungstenWallLarge, () -> { node(tungstenWallLarge, () -> {
node(blastDoor, () -> {
});
}); });
node(carbideWall, () -> { node(carbideWall, () -> {
+2 -2
View File
@@ -1996,12 +1996,12 @@ public class Fx{
dooropen = new Effect(10, e -> { dooropen = new Effect(10, e -> {
stroke(e.fout() * 1.6f); 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 -> { doorclose = new Effect(10, e -> {
stroke(e.fout() * 1.6f); 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 -> { dooropenlarge = new Effect(10, e -> {
+2
View File
@@ -88,6 +88,8 @@ public class Block extends UnlockableContent implements Senseable{
public boolean solid; public boolean solid;
/** whether this block CAN be solid. */ /** whether this block CAN be solid. */
public boolean solidifes; public boolean solidifes;
/** if true, this counts as a non-solid block to this team. */
public boolean teamPassable;
/** whether this is rotatable */ /** whether this is rotatable */
public boolean rotate; public boolean rotate;
/** if rotate is true and this is false, the region won't rotate when drawing */ /** if rotate is true and this is false, the region won't rotate when drawing */
@@ -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<Unit> units = new Seq<>();
protected final static Boolf<Unit> 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();
}
}
}
@@ -100,7 +100,7 @@ public class Door extends Wall{
} }
public void effect(){ public void effect(){
(open ? closefx : openfx).at(this); (open ? closefx : openfx).at(this, size);
} }
public void updateChained(){ public void updateChained(){