New splash damage algorithm

This commit is contained in:
Anuken
2021-02-15 18:00:47 -05:00
parent 710a55dc2d
commit c942331117
5 changed files with 77 additions and 59 deletions

View File

@@ -1609,6 +1609,12 @@ public class Fx{
Fill.square(e.x, e.y, e.rotation * tilesize / 2f);
}),
lightBlock = new Effect(60, e -> {
color(e.color);
alpha(e.fout() * 1);
Fill.square(e.x, e.y, e.rotation * tilesize / 2f);
}),
overdriveBlockFull = new Effect(60, e -> {
color(e.color);
alpha(e.fslope() * 0.4f);

View File

@@ -565,9 +565,9 @@ public class UnitTypes implements ContentList{
hitEffect = Fx.pulverize;
lifetime = 10f;
speed = 1f;
splashDamageRadius = 70f;
splashDamageRadius = 58f;
instantDisappear = true;
splashDamage = 80f;
splashDamage = 85f;
killShooter = true;
hittable = false;
collidesAir = true;
@@ -769,7 +769,7 @@ public class UnitTypes implements ContentList{
width = height = 19f;
collidesTiles = true;
ammoMultiplier = 4f;
splashDamageRadius = 95f;
splashDamageRadius = 80f;
splashDamage = 65f;
backColor = Pal.sapBulletBack;
frontColor = lightningColor = Pal.sapBullet;
@@ -867,7 +867,7 @@ public class UnitTypes implements ContentList{
width = height = 25f;
collidesTiles = collides = true;
ammoMultiplier = 4f;
splashDamageRadius = 90f;
splashDamageRadius = 80f;
splashDamage = 75f;
backColor = Pal.sapBulletBack;
frontColor = lightningColor = Pal.sapBullet;
@@ -888,7 +888,7 @@ public class UnitTypes implements ContentList{
lifetime = 90f;
width = height = 20f;
collidesTiles = false;
splashDamageRadius = 80f;
splashDamageRadius = 70f;
splashDamage = 40f;
backColor = Pal.sapBulletBack;
frontColor = lightningColor = Pal.sapBullet;
@@ -1367,7 +1367,7 @@ public class UnitTypes implements ContentList{
healPercent = 15f;
splashDamage = 230f;
splashDamageRadius = 120f;
splashDamageRadius = 81f;
}};
}});
}};
@@ -1537,7 +1537,7 @@ public class UnitTypes implements ContentList{
width = 15f;
collidesTiles = false;
ammoMultiplier = 4f;
splashDamageRadius = 60f;
splashDamageRadius = 50f;
splashDamage = 80f;
backColor = Pal.missileYellowBack;
frontColor = Pal.missileYellow;

View File

@@ -6,7 +6,6 @@ import arc.math.*;
import arc.math.geom.*;
import arc.struct.*;
import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.core.*;
import mindustry.game.EventType.*;
@@ -25,11 +24,10 @@ public class Damage{
private static Rect hitrect = new Rect();
private static Vec2 tr = new Vec2(), seg1 = new Vec2(), seg2 = new Vec2();
private static Seq<Unit> units = new Seq<>();
private static GridBits bits = new GridBits(30, 30);
private static IntQueue propagation = new IntQueue();
private static IntSet collidedBlocks = new IntSet();
private static Building tmpBuilding;
private static Unit tmpUnit;
private static IntFloatMap damages = new IntFloatMap();
/** Creates a dynamic explosion based on specified parameters. */
public static void dynamicExplosion(float x, float y, float flammability, float explosiveness, float power, float radius, boolean damage){
@@ -365,64 +363,85 @@ public class Damage{
if(ground){
if(!complete){
int trad = (int)(radius / tilesize);
Tile tile = world.tileWorld(x, y);
if(tile != null){
tileDamage(team, tile.x, tile.y, trad, damage);
}
//increase damage slightly to compensate for new algorithm
tileDamage(team, World.toTile(x), World.toTile(y), radius / tilesize, damage * 1.1f);
}else{
completeDamage(team, x, y, radius, damage);
}
}
}
public static void tileDamage(Team team, int startx, int starty, int baseRadius, float baseDamage){
//tile damage is posted, so that destroying a block that causes a chain explosion will run in the next frame
//this prevents recursive damage calls from messing up temporary variables
public static void tileDamage(Team team, int x, int y, float baseRadius, float damage){
Core.app.post(() -> {
bits.clear();
propagation.clear();
int bitOffset = bits.width() / 2;
var in = world.build(x, y);
//spawned inside a multiblock. this means that damage needs to be dealt directly.
//why? because otherwise the building would absorb everything in one cell, which means much less damage than a nearby explosion.
//this needs to be compensated
if(in != null && in.team != team && in.block.size > 1 && in.health > damage){
//deal the damage of an entire side * 2, to be equivalent with the maximum "standard" side damage + 1
in.damage(damage * (in.block.size * 2));
//no need to continue with the explosion
return;
}
propagation.addFirst(PropCell.get((byte)0, (byte)0, (short)baseDamage));
//clamp radius to fit bits
int radius = Math.min(baseRadius, bits.width() / 2);
//cap radius to prevent lag
float radius = Math.min(baseRadius, 30), rad2 = radius * radius;
int rays = Mathf.ceil(radius * 2 * Mathf.pi);
double spacing = Math.PI * 2.0 / rays;
damages.clear();
while(!propagation.isEmpty()){
int prop = propagation.removeLast();
int x = PropCell.x(prop);
int y = PropCell.y(prop);
int damage = PropCell.damage(prop);
//manhattan distance used for calculating falloff, results in a diamond pattern
int dst = Math.abs(x) + Math.abs(y);
//raycast from each angle
for(int i = 0; i <= rays; i++){
float dealt = 0f;
int startX = x;
int startY = y;
int endX = x + (int)(Math.cos(spacing * i) * radius), endY = y + (int)(Math.sin(spacing * i) * radius);
int scaledDamage = (int)(damage * (1f - (float)dst / radius));
int xDist = Math.abs(endX - startX);
int yDist = -Math.abs(endY - startY);
int xStep = (startX < endX ? +1 : -1);
int yStep = (startY < endY ? +1 : -1);
int error = xDist + yDist;
bits.set(bitOffset + x, bitOffset + y);
Tile tile = world.tile(startx + x, starty + y);
while(startX != endX || startY != endY){
var build = world.build(startX, startY);
if(build != null && build.team != team){
//damage dealt at circle edge
float edgeScale = 0.6f;
float mult = (1f-(Mathf.dst2(startX, startY, x, y) / rad2) + edgeScale) / (1f + edgeScale);
float next = damage * mult - dealt;
//register damage dealt
int p = Point2.pack(startX, startY);
damages.put(p, Math.max(damages.get(p), next));
//register as hit
dealt += build.health;
if(scaledDamage <= 0 || tile == null) continue;
//apply damage to entity if needed
if(tile.build != null && tile.build.team != team){
int health = (int)(tile.build.health / (tile.block().size * tile.block().size));
if(tile.build.health > 0){
tile.build.damage(scaledDamage);
scaledDamage -= health;
if(scaledDamage <= 0) continue;
if(next - dealt <= 0){
break;
}
}
}
for(Point2 p : Geometry.d4){
if(!bits.get(bitOffset + x + p.x, bitOffset + y + p.y)){
propagation.addFirst(PropCell.get((byte)(x + p.x), (byte)(y + p.y), (short)scaledDamage));
if(2 * error - yDist > xDist - 2 * error){
error += yDist;
startX += xStep;
}else{
error += xDist;
startY += yStep;
}
}
}
});
//apply damage
for(var e : damages){
int cx = Point2.x(e.key), cy = Point2.y(e.key);
var build = world.build(cx, cy);
if(build != null){
build.damage(e.value);
}
}
});
}
private static void completeDamage(Team team, float x, float y, float radius, float damage){
@@ -443,11 +462,4 @@ public class Damage{
float scaled = Mathf.lerp(1f - dist / radius, 1f, falloff);
return damage * scaled;
}
@Struct
static class PropCellStruct{
byte x;
byte y;
short damage;
}
}

View File

@@ -15,7 +15,7 @@ import static mindustry.Vars.*;
public class AIController implements UnitController{
protected static final Vec2 vec = new Vec2();
protected static final int timerTarget = 0, timerTarget2 = 1, timerTarget3 = 2, timerTarget4 = 2;
protected static final int timerTarget = 0, timerTarget2 = 1, timerTarget3 = 2, timerTarget4 = 3;
protected Unit unit;
protected Interval timer = new Interval(4);

View File

@@ -33,8 +33,8 @@ public class NuclearReactor extends PowerGenerator{
public float itemDuration = 120; //time to consume 1 fuel
public float heating = 0.01f; //heating per frame * fullness
public float smokeThreshold = 0.3f; //threshold at which block starts smoking
public int explosionRadius = 40;
public int explosionDamage = 1350;
public int explosionRadius = 20;
public int explosionDamage = 1250;
public float flashThreshold = 0.46f; //heat threshold at which the lights start flashing
public float coolantPower = 0.5f;