diff --git a/core/assets-raw/sprites/units/weapons/disrupt-missile-fin.png b/core/assets-raw/sprites/units/weapons/disrupt-missile-fin.png new file mode 100644 index 0000000000..9b5173fb28 Binary files /dev/null and b/core/assets-raw/sprites/units/weapons/disrupt-missile-fin.png differ diff --git a/core/assets-raw/sprites/units/weapons/disrupt-missile.png b/core/assets-raw/sprites/units/weapons/disrupt-missile.png index 6aadf02c8a..d2a9ff6e3a 100644 Binary files a/core/assets-raw/sprites/units/weapons/disrupt-missile.png and b/core/assets-raw/sprites/units/weapons/disrupt-missile.png differ diff --git a/core/src/mindustry/content/Blocks.java b/core/src/mindustry/content/Blocks.java index 3862b910f6..b49ea9d992 100644 --- a/core/src/mindustry/content/Blocks.java +++ b/core/src/mindustry/content/Blocks.java @@ -3225,20 +3225,20 @@ public class Blocks{ draw = new DrawTurret("reinforced-"){{ parts.addAll( new RegionPart("-barrel"){{ + progress = PartProgress.warmup.curve(Interp.pow2In); moveY = -5f * 4f / 3f; heatColor = Color.valueOf("f03b0e"); mirror = false; - interp = Interp.pow2In; }}, new RegionPart("-side"){{ + heatProgress = PartProgress.warmup; + progress = PartProgress.warmup.curve(Interp.pow2Out); mirror = true; moveX = 2f * 4f / 3f; moveY = -0.5f; rotMove = -40f; - progress = heatProgress = PartProgress.warmup; under = true; heatColor = Color.red.cpy(); - interp = Interp.pow2Out; }}); }}; diff --git a/core/src/mindustry/content/UnitTypes.java b/core/src/mindustry/content/UnitTypes.java index 869502cf83..303ec92800 100644 --- a/core/src/mindustry/content/UnitTypes.java +++ b/core/src/mindustry/content/UnitTypes.java @@ -2923,7 +2923,7 @@ public class UnitTypes{ mirror = false; reload = 1f; shootOnDeath = true; - bullet = new ExplosionBulletType(120f, 30f){{ + bullet = new ExplosionBulletType(110f, 22f){{ shootEffect = Fx.massiveExplosion; }}; }}); @@ -2977,14 +2977,13 @@ public class UnitTypes{ }}); } - //TODO needs weapons! cool missiles or something weapons.add(new Weapon("disrupt-weapon"){{ x = 78f / 4f; y = -10f / 4f; mirror = true; rotate = true; rotateSpeed = 0.4f; - reload = 60f; + reload = 70f; layerOffset = -20f; recoil = 1f; rotationLimit = 22f; @@ -3010,8 +3009,9 @@ public class UnitTypes{ }}); bullet = new BulletType(){{ - shootEffect = Fx.shootBig; - smokeEffect = Fx.shootBigSmoke2; + shootEffect = Fx.sparkShoot; + smokeEffect = Fx.shootSmokeTitan; + hitColor = Pal.suppress; shake = 1f; speed = 0f; keepVelocity = false; @@ -3023,14 +3023,52 @@ public class UnitTypes{ outlineColor = Pal.darkOutline; health = 45; homingDelay = 10f; + lowAltitude = true; + engineSize = 3f; + deathExplosionEffect = Fx.none; + + parts.add(new ShapePart(){{ + layer = Layer.effect; + circle = true; + y = -0.25f; + radius = 1.5f; + color = Pal.suppress; + colorTo = Color.white; + progress = PartProgress.life.curve(Interp.pow5In); + }}); + + parts.add(new RegionPart("-fin"){{ + mirror = true; + progress = PartProgress.life.mul(3f).curve(Interp.pow5In); + rotMove = 32f; + rotation = -6f; + moveY = 1.5f; + x = 3f / 4f; + y = -6f / 4f; + }}); weapons.add(new Weapon(){{ shootCone = 360f; mirror = false; reload = 1f; shootOnDeath = true; - bullet = new ExplosionBulletType(120f, 30f){{ - shootEffect = Fx.massiveExplosion; + bullet = new ExplosionBulletType(135f, 25f){{ + suppressionRange = 140f; + shootEffect = new ExplosionEffect(){{ + lifetime = 50f; + waveStroke = 5f; + waveLife = 8f; + waveColor = Color.white; + sparkColor = smokeColor = Pal.suppress; + waveRad = 40f; + smokeSize = 4f; + smokes = 7; + smokeSizeBase = 0f; + sparks = 10; + sparkRad = 40f; + sparkLen = 6f; + sparkStroke = 2f; + }}; }}; }}); }}; diff --git a/core/src/mindustry/entities/Damage.java b/core/src/mindustry/entities/Damage.java index 21006bddd6..d105279395 100644 --- a/core/src/mindustry/entities/Damage.java +++ b/core/src/mindustry/entities/Damage.java @@ -14,6 +14,8 @@ import mindustry.gen.*; import mindustry.graphics.*; import mindustry.type.*; import mindustry.world.*; +import mindustry.world.blocks.defense.MendProjector.*; +import mindustry.world.blocks.defense.RegenProjector.*; import static mindustry.Vars.*; @@ -29,6 +31,34 @@ public class Damage{ private static Building tmpBuilding; private static Unit tmpUnit; private static IntFloatMap damages = new IntFloatMap(); + private static Seq builds = new Seq<>(); + + public static void applySuppression(Team team, float x, float y, float range, float reload, float maxDelay, float applyParticleChance, @Nullable Position source){ + builds.clear(); + indexer.eachBlock(null, x, y, range, build -> build.team != team, build -> { + float prev = build.healSuppressionTime; + build.applyHealSuppression(reload + 1f); + + //TODO maybe should be block field instead of instanceof check + if(build.wasRecentlyHealed(60f * 12f) || (build instanceof MendBuild || build instanceof RegenProjectorBuild)){ + + //add prev check so ability spam doesn't lead to particle spam (essentially, recently suppressed blocks don't get new particles) + if(!headless && prev - Time.time <= reload/2f){ + builds.add(build); + } + } + }); + + //to prevent particle spam, the amount of particles is to remain constant (scales with number of buildings) + float scaledChance = applyParticleChance / builds.size; + for(var build : builds){ + if(Mathf.chance(scaledChance)){ + Time.run(Mathf.random(maxDelay), () -> { + Fx.regenSuppressSeek.at(build.x + Mathf.range(build.block.size * tilesize / 2f), build.y + Mathf.range(build.block.size * tilesize / 2f), 0f, source); + }); + } + } + } /** 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){ diff --git a/core/src/mindustry/entities/abilities/SuppressionFieldAbility.java b/core/src/mindustry/entities/abilities/SuppressionFieldAbility.java index ddbe055b66..5cb7c992c8 100644 --- a/core/src/mindustry/entities/abilities/SuppressionFieldAbility.java +++ b/core/src/mindustry/entities/abilities/SuppressionFieldAbility.java @@ -3,26 +3,19 @@ package mindustry.entities.abilities; import arc.graphics.*; import arc.graphics.g2d.*; import arc.math.*; -import arc.struct.*; import arc.util.*; -import mindustry.*; -import mindustry.content.*; +import mindustry.entities.*; import mindustry.gen.*; import mindustry.graphics.*; -import mindustry.world.blocks.defense.MendProjector.*; -import mindustry.world.blocks.defense.RegenProjector.*; - -import static mindustry.Vars.*; public class SuppressionFieldAbility extends Ability{ protected static Rand rand = new Rand(); - protected static Seq builds = new Seq<>(); public float reload = 60f * 1.5f; public float range = 200f; public float orbRadius = 4.1f, orbMidScl = 0.33f, orbSinScl = 8f, orbSinMag = 1f; - public Color color = Pal.sap.cpy().mul(1.6f); + public Color color = Pal.suppress; public float layer = Layer.effect; public float x = 0f, y = 0f; @@ -37,49 +30,17 @@ public class SuppressionFieldAbility extends Ability{ public float applyParticleChance = 13f; - protected boolean any; protected float timer; - protected float heat = 0f; @Override public void update(Unit unit){ if(!active) return; if((timer += Time.delta) >= reload){ - any = false; - builds.clear(); - Vars.indexer.eachBlock(null, unit.x,unit.y, range, build -> true, build -> { - if(build.team != unit.team){ - float prev = build.healSuppressionTime; - build.applyHealSuppression(reload + 1f); - - //TODO maybe should be block field instead of instanceof check - if(build.wasRecentlyHealed(60f * 12f) || (build instanceof MendBuild || build instanceof RegenProjectorBuild)){ - any = true; - - //add prev check so ability spam doesn't lead to particle spam (essentially, recently suppressed blocks don't get new particles) - if(!headless && prev - Time.time <= reload/2f){ - builds.add(build); - } - } - } - }); - - //to prevent particle spam, the amount of particles is to remain constant (scales with number of buildings) - float scaledChance = applyParticleChance / builds.size; - for(var build : builds){ - if(Mathf.chance(scaledChance)){ - Time.run(Mathf.random(reload), () -> { - Fx.regenSuppressSeek.at(build.x + Mathf.range(build.block.size * tilesize / 2f), build.y + Mathf.range(build.block.size * tilesize / 2f), 0f, unit); - }); - } - } - + Tmp.v1.set(x, y).rotate(unit.rotation - 90f).add(unit); + Damage.applySuppression(unit.team, Tmp.v1.x, Tmp.v1.y, range, reload, reload, applyParticleChance, unit); timer = 0f; } - - heat = Mathf.lerpDelta(heat, any ? 1f : 0f, 0.09f); - } @Override @@ -87,8 +48,8 @@ public class SuppressionFieldAbility extends Ability{ Draw.z(layer); float rad = orbRadius + Mathf.absin(orbSinScl, orbSinMag); - Tmp.v1.set(x, y).rotate(unit.rotation - 90f); - float rx = unit.x + Tmp.v1.x, ry = unit.y + Tmp.v1.y; + Tmp.v1.set(x, y).rotate(unit.rotation - 90f).add(unit); + float rx = Tmp.v1.x, ry = Tmp.v1.y; float base = (Time.time / particleLife); rand.setSeed(unit.id + hashCode()); @@ -112,13 +73,6 @@ public class SuppressionFieldAbility extends Ability{ Draw.color(color); Fill.circle(rx, ry, rad * orbMidScl); - //TODO improve - if(heat > 0.001f && false){ - Draw.color(Pal.sapBullet); - Lines.stroke(1.2f * heat * Mathf.absin(10f, 1f)); - Lines.circle(rx, ry, range); - } - Draw.reset(); } } diff --git a/core/src/mindustry/entities/bullet/BulletType.java b/core/src/mindustry/entities/bullet/BulletType.java index 81441e4ac8..60efac8679 100644 --- a/core/src/mindustry/entities/bullet/BulletType.java +++ b/core/src/mindustry/entities/bullet/BulletType.java @@ -5,6 +5,7 @@ import arc.audio.*; import arc.graphics.*; import arc.graphics.g2d.*; import arc.math.*; +import arc.math.geom.*; import arc.struct.*; import arc.util.*; import mindustry.*; @@ -171,6 +172,8 @@ public class BulletType extends Content implements Cloneable{ /** Use a negative value to disable homing delay. */ public float homingDelay = -1f; + public float suppressionRange = -1f, suppressionDuration = 60f * 8f, suppressionEffectChance = 50f; + public Color lightningColor = Pal.surge; public int lightning; public int lightningLength = 5, lightningLengthRand = 0; @@ -310,6 +313,11 @@ public class BulletType extends Content implements Cloneable{ Damage.createIncend(x, y, incendSpread, incendAmount); } + if(suppressionRange > 0){ + //bullets are pooled, require separate Vec2 instance + Damage.applySuppression(b.team, b.x, b.y, suppressionRange, suppressionDuration, 0f, suppressionEffectChance, new Vec2(b.x, b.y)); + } + if(splashDamageRadius > 0 && !b.absorbed){ Damage.damage(b.team, x, y, splashDamageRadius, splashDamage * b.damageMultiplier(), false, collidesAir, collidesGround, scaledSplashDamage); diff --git a/core/src/mindustry/entities/part/DrawPart.java b/core/src/mindustry/entities/part/DrawPart.java index 00e465e07e..06b6c168fc 100644 --- a/core/src/mindustry/entities/part/DrawPart.java +++ b/core/src/mindustry/entities/part/DrawPart.java @@ -19,7 +19,7 @@ public abstract class DrawPart{ /** Parameters for drawing a part in draw(). */ public static class PartParams{ //TODO document - public float warmup, reload, smoothReload, heat; + public float warmup, reload, smoothReload, heat, life; public float x, y, rotation; public int sideOverride = -1; @@ -32,6 +32,7 @@ public abstract class DrawPart{ this.y = y; this.rotation = rotation; this.sideOverride = -1; + this.life = 0f; return this; } } @@ -45,7 +46,9 @@ public abstract class DrawPart{ /** Weapon warmup, 0 when not firing, 1 when actively shooting. Not equivalent to heat. */ warmup = p -> p.warmup, /** Weapon heat, 1 when just fired, 0, when it has cooled down (duration depends on weapon) */ - heat = p -> p.heat; + heat = p -> p.heat, + /** Lifetime fraction, 0 to 1. Only for missiles. */ + life = p -> p.life; float get(PartParams p); @@ -78,7 +81,7 @@ public abstract class DrawPart{ } default PartProgress mul(float amount){ - return p -> get(p) * amount; + return p -> Mathf.clamp(get(p) * amount); } default PartProgress min(PartProgress other){ diff --git a/core/src/mindustry/entities/part/RegionPart.java b/core/src/mindustry/entities/part/RegionPart.java index 37d3a5d905..a1728ed75b 100644 --- a/core/src/mindustry/entities/part/RegionPart.java +++ b/core/src/mindustry/entities/part/RegionPart.java @@ -3,7 +3,6 @@ package mindustry.entities.part; import arc.*; import arc.graphics.*; import arc.graphics.g2d.*; -import arc.math.*; import arc.struct.*; import arc.util.*; import mindustry.graphics.*; @@ -30,7 +29,6 @@ public class RegionPart extends DrawPart{ /** Progress function for heat alpha. */ public PartProgress heatProgress = PartProgress.heat; public Blending blending = Blending.normal; - public Interp interp = Interp.linear; public float layer = -1, layerOffset = 0f; public float outlineLayerOffset = -0.001f; public float rotation, rotMove; @@ -64,7 +62,6 @@ public class RegionPart extends DrawPart{ float prevZ = Draw.z(); float prog = progress.get(params); - prog = interp.apply(prog); int len = mirror && params.sideOverride == -1 ? 2 : 1; for(int s = 0; s < len; s++){ diff --git a/core/src/mindustry/entities/part/ShapePart.java b/core/src/mindustry/entities/part/ShapePart.java new file mode 100644 index 0000000000..80407f66d7 --- /dev/null +++ b/core/src/mindustry/entities/part/ShapePart.java @@ -0,0 +1,64 @@ +package mindustry.entities.part; + +import arc.graphics.*; +import arc.graphics.g2d.*; +import arc.math.*; +import arc.util.*; + +public class ShapePart extends DrawPart{ + public boolean circle = false; + public int sides = 3; + public float radius = 3f, radiusTo = -1f; + public float x, y, rotation; + public float moveX, moveY, rotMove; + public Color color = Color.white; + public @Nullable Color colorTo; + public boolean mirror = false; + public PartProgress progress = PartProgress.warmup; + public float layer = -1f, layerOffset = 0f; + + @Override + public void draw(PartParams params){ + float z = Draw.z(); + if(layer > 0) Draw.z(layer); + if(under && turretShading) Draw.z(z - 0.0001f); + + Draw.z(Draw.z() + layerOffset); + + float prog = progress.get(params); + int len = mirror && params.sideOverride == -1 ? 2 : 1; + + for(int s = 0; s < len; s++){ + //use specific side if necessary + int i = params.sideOverride == -1 ? s : params.sideOverride; + + float sign = i == 1 ? -1 : 1; + Tmp.v1.set((x + moveX * prog) * sign, y + moveY * prog).rotate(params.rotation - 90); + + float + rx = params.x + Tmp.v1.x, + ry = params.y + Tmp.v1.y, + rad = radiusTo < 0 ? radius : Mathf.lerp(radius, radiusTo, prog); + + if(color != null && colorTo != null){ + Draw.color(color, colorTo, prog); + }else if(color != null){ + Draw.color(color); + } + + if(!circle){ + Fill.poly(rx, ry, sides, rad, rotMove * prog * sign + params.rotation - 90); + }else{ + Fill.circle(rx, ry, rad); + } + if(color != null) Draw.color(); + } + + Draw.z(z); + } + + @Override + public void load(String name){ + + } +} diff --git a/core/src/mindustry/graphics/Pal.java b/core/src/mindustry/graphics/Pal.java index 0915b08c4b..c1bca3c7f7 100644 --- a/core/src/mindustry/graphics/Pal.java +++ b/core/src/mindustry/graphics/Pal.java @@ -16,6 +16,8 @@ public class Pal{ sapBullet = Color.valueOf("bf92f9"), sapBulletBack = Color.valueOf("6d56bf"), + suppress = Pal.sap.cpy().mul(1.6f), + regen = Color.valueOf("d1efff"), reactorPurple = Color.valueOf("bf92f9"), diff --git a/core/src/mindustry/type/UnitType.java b/core/src/mindustry/type/UnitType.java index 370c810e46..b3bb0d5c8d 100644 --- a/core/src/mindustry/type/UnitType.java +++ b/core/src/mindustry/type/UnitType.java @@ -593,6 +593,9 @@ public class UnitType extends UnlockableContent{ part.getOutlines(out); } } + for(var part : parts){ + part.getOutlines(out); + } } @Override @@ -811,6 +814,9 @@ public class UnitType extends UnlockableContent{ }else{ DrawPart.params.set(0f, 0f, 0f, 0f, unit.x, unit.y, unit.rotation); } + if(unit instanceof Scaled s){ + DrawPart.params.life = s.fin(); + } for(int i = 0; i < parts.size; i++){ var part = parts.items[i]; part.draw(DrawPart.params); diff --git a/core/src/mindustry/type/unit/MissileUnitType.java b/core/src/mindustry/type/unit/MissileUnitType.java index 6610f36149..0839e72722 100644 --- a/core/src/mindustry/type/unit/MissileUnitType.java +++ b/core/src/mindustry/type/unit/MissileUnitType.java @@ -27,7 +27,7 @@ public class MissileUnitType extends UnitType{ trailLength = 7; hidden = true; speed = 4f; - lifetime = 60f * 1.6f; + lifetime = 60f * 1.7f; rotateSpeed = 2.5f; range = 30f; //TODO weapons, etc