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!ia0y~yU|0mg9Bd2>42M36Ni#4oFct^7J29*~C-ahlfqAZ{ zi(^Q|oVRxaXU+1EXuJ68rdOKq+`sl|!nWCkdO||U%8Pujeph}T^Hix{T z{PWH$3m=Q#nRS_~{pAVkhxfYbtLy*&eZPJC_8+^yipIG8xZf||{oj0JyUe}=?n-ky zK4w4r)^asTX4i&v5YN(EzEJC^MUG>9XMxhO<7t!U4HD@dnWd$z;~mGQ{N`?dAILq=e*PXs#ucu;>%eZr#&&jMGG`+U;f2Xjh z{aNNw*BQoVYkz5o{_oK^w1?}qf>cs#wdxPey}C(?%fjcJpLAPAXc5P2t0UW17HpSz zeIty4fk9J#`Q@Fzwr`RTQ}XmcmK4^rYaky^`90$i27~0FM2zBkIdr<0@Ib|EJ^lh znb`NS=-g$)GX6r}mdPPKCl!KD&zt=^r6<<8M&;1S7ZR7NmmZOr_RK;wdAq>;&J9~P z%0JR}v5>T!R$V-&qwvT4M%}-jzXFx#gmfAf-gek7Fn!;z^J_C&Io$8X%==e#;ntry z`mQ%Vm-t<`eRNBrU+m%D)MZINt~YKqIDg(N(J%FIZ)$&^p=4tA!OP}rukKhhsg5gb zs-aG9_jKFZ%dsr0JDLu2-mt38nIQR33(zogU#=Q}4`O4c~K5KvWnlY?*!q>9B zzS?o8_kJt1ntQ#fOfoyZUjOse*ISjZ&(ij^7vFG7;@XY3Tc7RqXxlKIqgwcpey4)V zk9Gn59vU-Lm6Q zxRcOBE2kwaib01KIDaH*B=At)X^(*s zkKjMwj&L^?r9BPTwORQlfpAs1K&UPO;fD%3J)m8fCN2V{an^L HB{Ts5z_G$Z 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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 literal 0 HcmV?d00001 diff --git a/core/assets/icons/icons.properties b/core/assets/icons/icons.properties index 51936e1650..0346ad6892 100755 --- a/core/assets/icons/icons.properties +++ b/core/assets/icons/icons.properties @@ -491,3 +491,4 @@ 63215=basic-assembler-module|block-basic-assembler-module-ui 63214=beryllium-wall|block-beryllium-wall-ui 63213=beryllium-wall-large|block-beryllium-wall-large-ui +63212=quell|unit-quell-ui diff --git a/core/assets/logicids.dat b/core/assets/logicids.dat index 478711e7fc7e764faa2fb52a63a00aa2cd17046c..e61fa09a6d4fbf24484bd5972c175c0cb6fe0b26 100644 GIT binary patch delta 26 icmdllcT8@>Mjl3;&6{|<7};10OH*@lHvi^zVFUn!1qns~ 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;