From dd5ce7e36519af0e56dcedd06275b042e319a3d9 Mon Sep 17 00:00:00 2001 From: Anuken Date: Mon, 27 Dec 2021 14:46:51 -0500 Subject: [PATCH] Regen suppression unit + system --- core/assets-raw/sprites/units/quell-cell.png | Bin 0 -> 995 bytes core/assets-raw/sprites/units/quell.png | Bin 0 -> 3651 bytes core/assets/icons/icons.properties | 1 + core/assets/logicids.dat | Bin 3903 -> 3910 bytes core/src/mindustry/ai/BlockIndexer.java | 2 + core/src/mindustry/content/Fx.java | 24 ++++ core/src/mindustry/content/UnitTypes.java | 38 +++++- .../abilities/SuppressionFieldAbility.java | 111 ++++++++++++++++++ .../mindustry/entities/comp/BuildingComp.java | 10 ++ .../world/blocks/defense/MendProjector.java | 23 +++- .../world/blocks/defense/RegenProjector.java | 9 +- 11 files changed, 208 insertions(+), 10 deletions(-) create mode 100644 core/assets-raw/sprites/units/quell-cell.png create mode 100644 core/assets-raw/sprites/units/quell.png create mode 100644 core/src/mindustry/entities/abilities/SuppressionFieldAbility.java diff --git a/core/assets-raw/sprites/units/quell-cell.png b/core/assets-raw/sprites/units/quell-cell.png new file mode 100644 index 0000000000000000000000000000000000000000..9c381e0415ad0f465551c193cf169af5aa7c1615 GIT binary patch literal 995 zcmeAS@N?(olHy`uVBq!ia0vp^i$Iuz4M-mPBqj}{7>k44ofy`glX=O&z&zK}#WAE} z&fB|zvu1fnv|W64(<@DQ?qB;fVcYCNJt3iFxcKc>Z|Mj|9!uG`}QBZzlz4V{kY#R-~HcwW4p}01MW(5IzDDU z`_^(bNoLoEbP&(dTfR{1s6~!rd}o2uvEyrg&WTk$_G9|gYu8*oc-p`DQuUG7qU*|5STw!1^na(YsQp>y zQP&y9XKR0Hi2m=+01`Z)@fg~L%He)5X5PP|3%CBv(RaP^ zxy0|f?W0=~{bCRIrY=kJalLV?!TIxEiGC>{zrW8=GBNw$W%IRHcPyGz#}zi!P$##0 zx^3;{Ssd#_CA^J}JzSn2wOUZ|bC6*f=fMG`txy$1W^W>7Qa%lHHoX3?cBl~!n zVpxAcP^Qjxt=`;}p4sb5uFg-K`}sua+jM{9-UzpR zzZF`|yZ#X4!?Z(@!&-Qw>ZJ5qcE&NEoQ^Dm&yMTTV zk8?}C@S~}MjU6A170Nb`N$8=K z(-IcNphF9t*Ob`qxfCJ9C!3UQ%Cy3uZOh^I8&P+EJNLPC>{n?Lcqs3*$H0h3@Skr- zxEqVoq4O;ki_zopr0Kl=r ALjV8( literal 0 HcmV?d00001 diff --git a/core/assets-raw/sprites/units/quell.png b/core/assets-raw/sprites/units/quell.png new file mode 100644 index 0000000000000000000000000000000000000000..1ce9a121c2f1a29e44c9ec36c819452427c0293e GIT binary patch literal 3651 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 delta 19 bcmX>mw_k3 pred, Cons cons){ + if(team == null) return false; + breturnArray.clear(); var buildings = team.data().buildings; diff --git a/core/src/mindustry/content/Fx.java b/core/src/mindustry/content/Fx.java index 1744b616d3..fb455c11b1 100644 --- a/core/src/mindustry/content/Fx.java +++ b/core/src/mindustry/content/Fx.java @@ -1447,6 +1447,30 @@ public class Fx{ Fill.square(e.x, e.y, e.fslope() * 1.5f + 0.14f, 45f); }), + regenSuppressParticle = new Effect(30f, e -> { + color(Pal.sapBullet, e.color, e.fin()); + stroke(e.fout() * 1.4f + 0.5f); + + randLenVectors(e.id, 4, 17f * e.fin(), (x, y) -> { + lineAngle(e.x + x, e.y + y, Mathf.angle(x, y), e.fslope() * 3f + 0.5f); + }); + }), + + regenSuppressSeek = new Effect(140f, e -> { + e.lifetime = Mathf.randomSeed(e.id, 120f, 200f); + + if(!(e.data instanceof Position to)) return; + + Tmp.v2.set(to).sub(e.x, e.y).nor().rotate90(1).scl(Mathf.randomSeedRange(e.id, 1f) * 50f); + + Tmp.bz2.set(Tmp.v1.set(e.x, e.y), Tmp.v2.add(e.x, e.y), Tmp.v3.set(to)); + + Tmp.bz2.valueAt(Tmp.v4, e.fout()); + + color(Pal.sapBullet); + Fill.circle(Tmp.v4.x, Tmp.v4.y, e.fslope() * 2f + 0.1f); + }).followParent(false).rotWithParent(false), + surgeCruciSmoke = new Effect(160f, e -> { color(Pal.slagOrange); alpha(0.6f); diff --git a/core/src/mindustry/content/UnitTypes.java b/core/src/mindustry/content/UnitTypes.java index 662faa746b..513476b6ef 100644 --- a/core/src/mindustry/content/UnitTypes.java +++ b/core/src/mindustry/content/UnitTypes.java @@ -51,7 +51,7 @@ public class UnitTypes{ //air + payload public static @EntityDef({Unitc.class, Payloadc.class}) UnitType mega, - incite, emanate; + incite, emanate, quell; //air + payload, legacy public static @EntityDef(value = {Unitc.class, Payloadc.class}, legacy = true) UnitType quad; @@ -2502,7 +2502,36 @@ public class UnitTypes{ //endregion //region erekir - flying - //TODO + //TODO orb, suppress healing + quell = new UnitType("quell"){{ + envDisabled = 0; + + outlineColor = Pal.darkOutline; + lowAltitude = false; + flying = true; + drag = 0.06f; + speed = 1.1f; + rotateSpeed = 3.5f; + accel = 0.1f; + health = 3000f; + armor = 4f; + hitSize = 36f; + payloadCapacity = Mathf.sqr(3f) * tilePayload; + + engineSize = 4.8f; + engineOffset = 61 / 4f; + + abilities.add(new SuppressionFieldAbility(){{ + orbRadius = 5.3f; + }}); + + float es = 3.9f; + + setEnginesMirror( + new UnitEngine(62 / 4f, -60 / 4f, es, 315f), + new UnitEngine(72 / 4f, -29 / 4f, 3f, 315f) + ); + }}; //endregion //region erekir - neoplasm @@ -2646,9 +2675,8 @@ public class UnitTypes{ }}; emanate = new UnitType("emanate"){{ - //TODO not a real enemy, should not be counted or have flying AI - defaultController = FlyingAI::new; - //isCounted = false; + defaultController = BuilderAI::new; + isCounted = false; envDisabled = 0; outlineColor = Pal.darkOutline; diff --git a/core/src/mindustry/entities/abilities/SuppressionFieldAbility.java b/core/src/mindustry/entities/abilities/SuppressionFieldAbility.java new file mode 100644 index 0000000000..b7755f33bf --- /dev/null +++ b/core/src/mindustry/entities/abilities/SuppressionFieldAbility.java @@ -0,0 +1,111 @@ +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.gen.*; +import mindustry.graphics.*; + +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.5f, orbMidScl = 0.62f, orbSinScl = 8f, orbSinMag = 1f; + public Color color1 = Pal.sap.cpy().mul(1.6f), color2 = Pal.sap; + public float layer = Layer.effect; + + public int particles = 15; + public float particleSize = 4f; + public float particleLen = 7f; + public float rotateScl = 3f; + public float particleLife = 110f; + public Interp particleInterp = f -> Interp.circleOut.apply(Interp.slope.apply(f)); + public Color particleColor = Pal.sap.cpy().a(0.8f); + + public float applyParticleChance = 13f; + + protected boolean any; + protected float timer; + protected float heat = 0f; + + @Override + public void update(Unit unit){ + 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); + + 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); + }); + } + } + + timer = 0f; + } + + heat = Mathf.lerpDelta(heat, any ? 1f : 0f, 0.09f); + + } + + @Override + public void draw(Unit unit){ + Draw.z(layer); + + float rad = orbRadius + Mathf.absin(orbSinScl, orbSinMag); + + Draw.color(color2); + Fill.circle(unit.x, unit.y, rad); + + Draw.color(color1); + Fill.circle(unit.x, unit.y, rad * orbMidScl); + + float base = (Time.time / particleLife); + rand.setSeed(unit.id); + Draw.color(particleColor); + for(int i = 0; i < particles; i++){ + float fin = (rand.random(1f) + base) % 1f, fout = 1f - fin; + float angle = rand.random(360f) + (Time.time / rotateScl + unit.rotation) % 360f; + float len = particleLen * particleInterp.apply(fout); + Fill.circle( + unit.x + Angles.trnsx(angle, len), + unit.y + Angles.trnsy(angle, len), + particleSize * Mathf.slope(fin) + ); + } + + //TODO improve + if(heat > 0.001f){ + Draw.color(Pal.sapBullet); + Lines.stroke(1.2f * heat * Mathf.absin(10f, 1f)); + Lines.circle(unit.x, unit.y, range); + } + + Draw.reset(); + } +} diff --git a/core/src/mindustry/entities/comp/BuildingComp.java b/core/src/mindustry/entities/comp/BuildingComp.java index 7d7cf7d157..32c194356a 100644 --- a/core/src/mindustry/entities/comp/BuildingComp.java +++ b/core/src/mindustry/entities/comp/BuildingComp.java @@ -76,6 +76,8 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc, LiquidModule liquids; ConsumeModule cons; + public transient float healSuppressionTime = -1f; + private transient float timeScale = 1f, timeScaleDuration; private transient float dumpAccum; @@ -329,6 +331,14 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc, timeScale = Math.max(timeScale, intensity); } + public void applyHealSuppression(float amount){ + healSuppressionTime = Math.max(healSuppressionTime, Time.time + amount); + } + + public boolean isHealSuppressed(){ + return Time.time <= healSuppressionTime; + } + public Building nearby(int dx, int dy){ return world.build(tile.x + dx, tile.y + dy); } diff --git a/core/src/mindustry/world/blocks/defense/MendProjector.java b/core/src/mindustry/world/blocks/defense/MendProjector.java index a42d6cecb9..9fdbf01e0a 100644 --- a/core/src/mindustry/world/blocks/defense/MendProjector.java +++ b/core/src/mindustry/world/blocks/defense/MendProjector.java @@ -65,6 +65,19 @@ public class MendProjector extends Block{ indexer.eachBlock(player.team(), x * tilesize + offset, y * tilesize + offset, range, other -> true, other -> Drawf.selected(other, Tmp.c1.set(baseColor).a(Mathf.absin(4f, 1f)))); } + /** @return whether a building has regen/healing suppressed; if so, spawns particles on it. */ + public static boolean checkSuppression(Building build){ + if(build.isHealSuppressed()){ + if(Mathf.chanceDelta(0.04)){ + Fx.regenSuppressParticle.at(build.x + Mathf.range(build.block.size * tilesize/2f - 1f), build.y + Mathf.range(build.block.size * tilesize/2f - 1f)); + } + + return true; + } + + return false; + } + public class MendBuild extends Building implements Ranged{ public float heat, charge = Mathf.random(reload), phaseHeat, smoothEfficiency; @@ -75,21 +88,23 @@ public class MendProjector extends Block{ @Override public void updateTile(){ + boolean canHeal = !checkSuppression(this); + smoothEfficiency = Mathf.lerpDelta(smoothEfficiency, efficiency(), 0.08f); - heat = Mathf.lerpDelta(heat, consValid() || cheating() ? 1f : 0f, 0.08f); + heat = Mathf.lerpDelta(heat, consValid() && canHeal ? 1f : 0f, 0.08f); charge += heat * delta(); phaseHeat = Mathf.lerpDelta(phaseHeat, Mathf.num(cons.optionalValid()), 0.1f); - if(cons.optionalValid() && timer(timerUse, useTime) && efficiency() > 0){ + if(cons.optionalValid() && timer(timerUse, useTime) && efficiency() > 0 && canHeal){ consume(); } - if(charge >= reload){ + if(charge >= reload && canHeal){ float realRange = range + phaseHeat * phaseRangeBoost; charge = 0f; - indexer.eachBlock(this, realRange, Building::damaged, other -> { + indexer.eachBlock(this, realRange, b -> b.damaged() && !b.isHealSuppressed(), other -> { other.heal(other.maxHealth() * (healPercent + phaseHeat * phaseBoost) / 100f * efficiency()); Fx.healBlockFull.at(other.x, other.y, other.block.size, baseColor); }); diff --git a/core/src/mindustry/world/blocks/defense/RegenProjector.java b/core/src/mindustry/world/blocks/defense/RegenProjector.java index 5292c6d104..19f747e742 100644 --- a/core/src/mindustry/world/blocks/defense/RegenProjector.java +++ b/core/src/mindustry/world/blocks/defense/RegenProjector.java @@ -85,6 +85,8 @@ public class RegenProjector extends Block{ @Override public void updateTile(){ + //TODO particles when heal suppressed + if(lastChange != world.tileChanges){ lastChange = world.tileChanges; updateTargets(); @@ -95,10 +97,15 @@ public class RegenProjector extends Block{ totalTime += warmup * Time.delta; didRegen = false; + //no healing when suppressed + if(MendProjector.checkSuppression(this)){ + return; + } + if(consValid()){ //use Math.max to prevent stacking for(Building build : targets){ - if(!build.damaged()) continue; + if(!build.damaged() || build.isHealSuppressed()) continue; didRegen = true;