WIP: shields absorb rail/laser bullets

This commit is contained in:
Anuken
2026-09-21 00:04:42 -04:00
parent 081409e687
commit c92cd927d1
6 changed files with 135 additions and 9 deletions

View File

@@ -683,7 +683,7 @@ public class BlockIndexer{
@Override
public void hitbox(T build){
tmp.setCentered(build.x, build.y, build.getShieldBounds());
tmp.setCentered(build.x, build.y, build.getShieldBounds() * 2f);
}
@Override

View File

@@ -29,7 +29,7 @@ public class Damage{
private static final Vec2 vec = new Vec2(), seg1 = new Vec2(), seg2 = new Vec2();
private static final IntSet collidedBlocks = new IntSet();
private static final IntFloatMap damages = new IntFloatMap();
private static final Seq<Collided> collided = new Seq<>();
private static final Seq<Collided> collided = new Seq<>(), shieldHits = new Seq<>();
private static final Pool<Collided> collidePool = Pools.get(Collided.class, Collided::new);
private static final Seq<Building> builds = new Seq<>();
private static final FloatSeq distances = new FloatSeq();
@@ -153,16 +153,26 @@ public class Damage{
}
public static float findLength(Bullet b, float length, boolean laser, int pierceCap){
return findLength(b, length, laser, pierceCap, false);
}
private static float findLength(Bullet b, float length, boolean laser, int pierceCap, boolean absorb){
if(pierceCap > 0){
length = findPierceLength(b, pierceCap, laser, length);
length = findPierceLength(b, pierceCap, laser, length, absorb);
}else if(laser){
length = findLaserLength(b, length);
length = findLaserLength(b, length, absorb);
}else{
length = findShieldLength(b, length, false, absorb);
}
return length;
}
public static float findLaserLength(Bullet b, float length){
return findLaserLength(b, length, false);
}
private static float findLaserLength(Bullet b, float length, boolean absorb){
vec.trnsExact(b.rotation(), length);
furthest = null;
@@ -170,7 +180,9 @@ public class Damage{
boolean found = World.raycast(b.tileX(), b.tileY(), World.toTile(b.x + vec.x), World.toTile(b.y + vec.y),
(x, y) -> (furthest = world.tile(x, y)) != null && furthest.team() != b.team && (furthest.build != null && furthest.build.absorbLasers()));
return found && furthest != null ? Math.max(6f, b.dst(furthest.worldx(), furthest.worldy())) : length;
float result = found && furthest != null ? Math.max(6f, b.dst(furthest.worldx(), furthest.worldy())) : length;
return findShieldLength(b, result, true, absorb);
}
public static float findPierceLength(Bullet b, int pierceCap, float length){
@@ -178,6 +190,10 @@ public class Damage{
}
public static float findPierceLength(Bullet b, int pierceCap, boolean laser, float length){
return findPierceLength(b, pierceCap, laser, length, false);
}
private static float findPierceLength(Bullet b, int pierceCap, boolean laser, float length, boolean absorb){
vec.trnsExact(b.rotation(), length);
rect.setPosition(b.x, b.y).setSize(vec.x, vec.y).normalize().grow(3f);
@@ -215,14 +231,66 @@ public class Damage{
//return either the length when not enough things were pierced,
//or the last pierced object if there were enough blockages
return Math.min(distances.size < pierceCap || pierceCap <= 0 ? length : Math.max(6f, distances.get(pierceCap - 1)), maxDst);
float result = Math.min(distances.size < pierceCap || pierceCap <= 0 ? length : Math.max(6f, distances.get(pierceCap - 1)), maxDst);
return findShieldLength(b, result, laser, absorb);
}
/**
* Finds the enemy shields hit by a laser, in order, until one absorbs the rest of its damage.
* @param absorb whether to actually apply the hits to the shields; otherwise the first shield is assumed to absorb everything.
* If the shields only absorb part of the damage, the bullet's damage is reduced accordingly.
* @return the length of the laser, cut short if a shield absorbed it.
*/
private static float findShieldLength(Bullet b, float length, boolean laser, boolean absorb){
float damage = b.type.shieldDamage(b);
if(!(laser || b.type.shieldAbsorb) || length <= 0f || damage <= 0f) return length;
seg1.set(b.x, b.y);
seg2.trnsExact(b.rotation(), length).add(seg1);
rect.setPosition(seg1.x, seg1.y).setSize(seg2.x - seg1.x, seg2.y - seg1.y).normalize();
var shields = indexer.getEnemyShields(b.team, rect.x, rect.y, rect.width, rect.height);
for(int i = 0; i < shields.size; i++){
Building build = shields.get(i);
if(build instanceof ShieldProvider shield){
Vec2 hit = shield.intersectLaser(seg1.x, seg1.y, seg2.x, seg2.y, damage);
if(hit != null){
shieldHits.add(collidePool.obtain().set(hit.x, hit.y, build));
}
}
}
float result = length, remaining = damage;
shieldHits.sort(c -> Mathf.dst2(seg1.x, seg1.y, c.x, c.y));
for(int i = 0; i < shieldHits.size; i++){
Collided c = shieldHits.get(i);
float absorbed = absorb ? ((ShieldProvider)c.target).absorbLaser(c.x, c.y, remaining) : remaining;
remaining -= absorbed;
if(remaining <= 0f){
result = Mathf.dst(seg1.x, seg1.y, c.x, c.y);
break;
}
}
if(absorb && remaining > 0f && remaining < damage){
b.damage *= remaining / damage;
}
collidePool.freeAll(shieldHits);
shieldHits.clear();
return result;
}
/** Collides a bullet with blocks in a laser, taking into account absorption blocks. Resulting length is stored in the bullet's fdata. */
public static float collideLaser(Bullet b, float length, boolean large, boolean laser, int pierceCap){
float resultLength = findPierceLength(b, pierceCap, laser, length);
float resultLength = findPierceLength(b, pierceCap, laser, length, true);
collideLine(b, b.team, b.x, b.y, b.rotation(), resultLength, large, laser, pierceCap);
collideLine(b, b.team, b.x, b.y, b.rotation(), resultLength, large, laser, pierceCap, false);
b.fdata = resultLength;
@@ -254,7 +322,11 @@ public class Damage{
* Only enemies of the specified team are damaged.
*/
public static void collideLine(Bullet hitter, Team team, float x, float y, float angle, float length, boolean large, boolean laser, int pierceCap){
length = findLength(hitter, length, laser, pierceCap);
collideLine(hitter, team, x, y, angle, length, large, laser, pierceCap, true);
}
private static void collideLine(Bullet hitter, Team team, float x, float y, float angle, float length, boolean large, boolean laser, int pierceCap, boolean absorb){
length = findLength(hitter, length, laser, pierceCap, absorb);
hitter.fdata = length;
collidedBlocks.clear();

View File

@@ -65,6 +65,8 @@ public class BulletType extends Content implements Cloneable{
public boolean removeAfterPierce = true;
/** For piercing lasers, setting this to true makes it get absorbed by plastanium walls. */
public boolean laserAbsorb = true;
/** Whether force shields absorb this bullet when it hits as a line, even if it isn't a laser. Used by rails. */
public boolean shieldAbsorb = false;
/** Whether this bullet is considered a laser bullet and thus absorbed by plastanium walls. */
public boolean laserBullet = false;
/** Life fraction at which this bullet has the best range/damage/etc. Used for lasers and continuous turrets. */

View File

@@ -25,6 +25,7 @@ public class RailBulletType extends BulletType{
keepVelocity = false;
lifetime = 1f;
delayFrags = true;
shieldAbsorb = true;
}
@Override

View File

@@ -1,8 +1,21 @@
package mindustry.world.blocks;
import arc.math.geom.*;
import arc.util.*;
public interface ShieldProvider{
/** @return whether the shield was able to absorb the explosion; this should apply damage to the shield if true is returned. */
boolean absorbExplosion(float x, float y, float damage);
/** @return maximum (not current size!) half-size of the shield square bounding box */
float getShieldBounds();
/** @return the first point where the segment hits this shield, stored in a shared vector (copy it), or null if it doesn't. Must not have side effects. */
default @Nullable Vec2 intersectLaser(float x1, float y1, float x2, float y2, float damage){
return null;
}
/** Applies a laser hit at x, y. @return how much of the damage was absorbed; anything less than the full damage means the laser continues through. */
default float absorbLaser(float x, float y, float damage){
return 0f;
}
}

View File

@@ -57,6 +57,7 @@ public class ForceProjector extends Block{
//lambdas need to be static to prevent GC
protected static ForceProjector paramBlock;
protected static ForceBuild paramEntity;
protected static final Vec2 laserHit = new Vec2();
protected static final Cons<Bullet> shieldConsumer = bullet -> {
if(bullet.team != paramEntity.team && bullet.type.absorbable && !bullet.absorbed &&
Intersector.isInRegularPolygon(paramBlock.sides, paramEntity.x, paramEntity.y, paramEntity.realRadius(), paramBlock.shieldRotation, bullet.x, bullet.y)){
@@ -277,6 +278,43 @@ public class ForceProjector extends Block{
return absorb;
}
@Override
public @Nullable Vec2 intersectLaser(float x1, float y1, float x2, float y2, float damage){
float radius = realRadius();
if(broken || radius <= 0f) return null;
if(Intersector.isInRegularPolygon(sides, x, y, radius, shieldRotation, x1, y1)){
return laserHit.set(x1, y1);
}
float best = Float.MAX_VALUE;
for(int i = 0; i < sides; i++){
Tmp.v1.trns(shieldRotation + i * 360f / sides, radius).add(x, y);
Tmp.v2.trns(shieldRotation + (i + 1) * 360f / sides, radius).add(x, y);
if(Intersector.intersectSegments(x1, y1, x2, y2, Tmp.v1.x, Tmp.v1.y, Tmp.v2.x, Tmp.v2.y, Tmp.v3)){
float dst = Tmp.v3.dst2(x1, y1);
if(dst < best){
best = dst;
laserHit.set(Tmp.v3);
}
}
}
return best == Float.MAX_VALUE ? null : laserHit;
}
@Override
public float absorbLaser(float lx, float ly, float damage){
float absorbed = broken ? 0f : Math.min(damage, Math.max(shieldHealth + phaseShieldBoost * phaseHeat - buildup, 0f));
if(absorbed > 0f){
absorbEffect.at(lx, ly);
hit = 1f;
buildup += damage;
}
return absorbed;
}
@Override
public float getShieldBounds(){
return radius + phaseRadiusBoost;