diff --git a/res/bundles/bundle.properties b/res/bundles/bundle.properties index c553481..fcbef90 100644 --- a/res/bundles/bundle.properties +++ b/res/bundles/bundle.properties @@ -209,6 +209,8 @@ block.fos-helix.name = Helix block.fos-helix.description = Shoots two waving bullets at enemies. block.fos-sticker.name = Sticker block.fos-sticker.description = Shoots long-range sticky bombs at ground enemies. Requires tokicite. +block.fos-sludge.name = Sludge +block.fos-sludge.description = Launches bursts of tokicite to slow down enemy targets and extinguish fires. block.fos-dot.name = Dot block.fos-dot.description = Shoots lasers at enemies at a very fast rate. block.fos-particulator.name = Particulator diff --git a/res/bundles/bundle_ru.properties b/res/bundles/bundle_ru.properties index 1a8de1a..58b394a 100644 --- a/res/bundles/bundle_ru.properties +++ b/res/bundles/bundle_ru.properties @@ -209,6 +209,8 @@ block.fos-helix.name = Спираль block.fos-helix.description = Стреляет двумя волновыми снарядами по вражеским целям. block.fos-sticker.name = Прилипатель block.fos-sticker.description = Стреляет дальнобойными липучими бомбами по наземным целям. Требует токицит. +block.fos-sludge.name = Слизь +block.fos-sludge.description = Запускает пузыри токицита для замедления вражеских целей и тушения пожаров. block.fos-dot.name = Точка block.fos-dot.description = Очень быстро стреляет лазерами по вражеской цели. block.fos-particulator.name = Партикулятор diff --git a/src/fos/content/FOSBlocks.java b/src/fos/content/FOSBlocks.java index 4f3d479..98a6049 100644 --- a/src/fos/content/FOSBlocks.java +++ b/src/fos/content/FOSBlocks.java @@ -2,6 +2,8 @@ import arc.Core; import arc.graphics.Color; +import arc.math.Mathf; +import arc.math.geom.*; import arc.struct.Seq; import arc.util.Strings; import fos.audio.FOSSounds; @@ -19,12 +21,12 @@ import fos.type.bullets.*; import fos.type.draw.DrawOutputLiquids; import mindustry.content.*; -import mindustry.entities.Effect; +import mindustry.entities.*; import mindustry.entities.bullet.*; import mindustry.entities.effect.MultiEffect; import mindustry.entities.part.*; import mindustry.entities.pattern.*; -import mindustry.gen.Sounds; +import mindustry.gen.*; import mindustry.graphics.*; import mindustry.type.*; import mindustry.ui.*; @@ -46,6 +48,7 @@ import static fos.content.FOSFluids.*; import static fos.content.FOSItems.*; import static fos.content.FOSUnitTypes.*; +import static mindustry.Vars.*; import static mindustry.content.Items.*; import static mindustry.content.Liquids.*; import static mindustry.type.ItemStack.with; @@ -72,7 +75,7 @@ public class FOSBlocks { // DEFENSE zincWall, zincWallLarge, diamondWall, diamondWallLarge, vanadiumWall, vanadiumWallLarge, cuberiumWall, cuberiumWallLarge, - helix, sticker, dot, particulator, firefly, pulse, breakdown, rupture, thunder, cluster, judge, newJudge, bugSentry, + helix, sticker, sludge, dot, particulator, firefly, pulse, breakdown, rupture, thunder, cluster, judge, newJudge, bugSentry, matrixShieldProj, beamMender, beamMendProjector, landMine, @@ -611,6 +614,88 @@ diamond, new StickyBulletType(8f, 60, 90){{ }}; requirements(Category.turret, with(zinc, 75, silver, 50, silicon, 50)); }}; + sludge = new LiquidTurret("sludge"){{ + health = 2400; + size = 3; + range = 270; + reload = 90f; + shootCone = 20f; + inaccuracy = 2f; + targetGround = targetAir = true; + liquidCapacity = 60f; + + loopSound = Sounds.none; + shootSound = Sounds.mud; + + ammo( + tokicite, new LiquidBulletType(tokicite){ + { + puddleSize = 30f; + orbSize = 6f; + ammoMultiplier = 1f/30f; + lifetime = 60f; speed = 4.5f; + damage = 30f; + + trailEffect = FOSFx.tokiciteDrop; + trailChance = 0.2f; + + setDefaults = false; + despawnHit = true; + fragOnHit = true; + fragBullets = 6; + fragVelocityMin = 0.6f; + fragVelocityMax = 1.4f; + fragBullet = new LiquidBulletType(tokicite){{ + lifetime = 20f; speed = 1.5f; + despawnHit = true; + pierce = true; + pierceCap = 2; + pierceBuilding = false; + }}; + } + + // oh COME ON anuke why doesn't it create frags by default. + @Override + public void hit(Bullet b, float x, float y){ + hitEffect.at(x, y, b.rotation(), hitColor); + hitSound.at(x, y, hitSoundPitch, hitSoundVolume); + + Effect.shake(hitShake, hitShake, b); + + if(fragOnHit){ + createFrags(b, x, y); + } + createPuddles(b, x, y); + createIncend(b, x, y); + createUnits(b, x, y); + + 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)); + } + + createSplashDamage(b, x, y); + + for(int i = 0; i < lightning; i++){ + Lightning.create(b, lightningColor, lightningDamage < 0 ? damage : lightningDamage, b.x, b.y, b.rotation() + Mathf.range(lightningCone/2) + lightningAngle, lightningLength + Mathf.random(lightningLengthRand)); + } + + Puddles.deposit(world.tileWorld(x, y), liquid, puddleSize); + + if(liquid.temperature <= 0.5f && liquid.flammability < 0.3f){ + float intensity = 400f * puddleSize/6f; + Fires.extinguish(world.tileWorld(x, y), intensity); + for(Point2 p : Geometry.d4){ + Fires.extinguish(world.tileWorld(x + p.x * tilesize, y + p.y * tilesize), intensity); + } + } + } + } + ); + + drawer = new DrawTurret("lumoni-"); + requirements(Category.turret, with(zinc, 125, silicon, 100, brass, 75)); + }}; dot = new PowerTurret("dot"){{ scaledHealth = 480; size = 2; diff --git a/src/fos/content/FOSFluids.java b/src/fos/content/FOSFluids.java index d3edcfc..a832649 100644 --- a/src/fos/content/FOSFluids.java +++ b/src/fos/content/FOSFluids.java @@ -1,7 +1,6 @@ package fos.content; import arc.graphics.Color; -import mindustry.content.StatusEffects; import mindustry.type.Liquid; public class FOSFluids { @@ -16,7 +15,7 @@ public static void load() { viscosity = 0.95f; heatCapacity = 0.65f; temperature = 0.4f; - effect = StatusEffects.slow; + effect = FOSStatuses.tokiciteSlowed; boilPoint = 0.8f; }}; } diff --git a/src/fos/content/FOSFx.java b/src/fos/content/FOSFx.java index b9514fe..433c317 100644 --- a/src/fos/content/FOSFx.java +++ b/src/fos/content/FOSFx.java @@ -3,6 +3,7 @@ import arc.Core; import arc.graphics.Color; import arc.graphics.g2d.*; +import arc.math.Mathf; import arc.math.geom.*; import arc.util.Tmp; import fos.graphics.FOSPal; @@ -203,5 +204,12 @@ public class FOSFx { Draw.rect(Core.atlas.find("fos-team-corru-upscale"), e.x, e.y, 64 * (1 + e.fin()), 64 * (1 + e.fin())); Draw.reset(); + }), + + tokiciteDrop = new Effect(80f, e -> { + color(FOSFluids.tokicite.color); + alpha(Mathf.clamp(e.fin() * 2f)); + + Fill.circle(e.x, e.y, e.fout() * 4f); }); } diff --git a/src/fos/content/FOSStatuses.java b/src/fos/content/FOSStatuses.java index 867d182..031fc1a 100644 --- a/src/fos/content/FOSStatuses.java +++ b/src/fos/content/FOSStatuses.java @@ -38,7 +38,8 @@ public void draw(Unit unit) { } }; tokiciteSlowed = new StatusEffect("tokicite-slowed"){{ - speedMultiplier = 0.4f; + speedMultiplier = 0.25f; + effect = FOSFx.tokiciteDrop; }}; buildBoost = new StatusEffect("build-boost"){{ buildSpeedMultiplier = 1.25f; diff --git a/src/fos/content/FOSUnitTypes.java b/src/fos/content/FOSUnitTypes.java index be5ad8a..4a5edaa 100644 --- a/src/fos/content/FOSUnitTypes.java +++ b/src/fos/content/FOSUnitTypes.java @@ -939,8 +939,8 @@ public void update(Unit unit, WeaponMount mount) { aiController = InjectorAI::new; immunities.add(hacked); weapons.add( - new InjectorWeapon("fos-injector-missile"){{ - x = 9; y = 0; + new Weapon("fos-injector-missile"){{ + x = 6; y = -6; top = true; mirror = true; alternate = false; @@ -948,47 +948,29 @@ public void update(Unit unit, WeaponMount mount) { reload = 30f; inaccuracy = 12f; shootSound = Sounds.missile; - bullet = new InjectorBasicBulletType(0f, 0.95f, 300, 2500, false){{ + bullet = new MissileBulletType(){{ damage = 25f; speed = 2.4f; lifetime = 90f; width = 8f; height = 16f; - sprite = "missile"; backColor = FOSPal.hackedBack; frontColor = FOSPal.hacked; - shrinkY = 0f; homingPower = 0.06f; - weaveScale = 0.8f; - weaveMag = 1.8f; hitSound = Sounds.explosion; trailChance = 0.2f; trailColor = FOSPal.hacked; }}; }}, - new InjectorWeapon("fos-injector-missile"){{ - x = 6; y = 12; - top = true; - mirror = true; - alternate = false; - rotate = true; - reload = 300f; - shoot.shots = 4; - shoot.shotDelay = 10f; - inaccuracy = 12f; - shootSound = Sounds.missile; - bullet = new InjectorBasicBulletType(0f, 0.95f, 300, 2500, false){{ - damage = 12.5f; - speed = 2.4f; lifetime = 90f; - width = 8f; height = 16f; - sprite = "missile"; - backColor = FOSPal.hackedBack; - frontColor = FOSPal.hacked; - shrinkY = 0f; - homingPower = 0.06f; - weaveScale = 0.8f; - weaveMag = 1.8f; - hitSound = Sounds.explosion; - trailChance = 0.2f; - trailColor = FOSPal.hacked; + new InjectorWeapon("fos-hack-laser"){{ + x = 0; y = 6; + mirror = false; + rotate = false; + continuous = true; + reload = 10f; + targetSwitchInterval = 60f; + shootSound = Sounds.none; + bullet = new InjectorHackLaserBulletType(){{ + minHP = 500; + maxHP = 1800; }}; }} ); diff --git a/src/fos/content/LumoniTechTree.java b/src/fos/content/LumoniTechTree.java index 0561729..7ee162a 100644 --- a/src/fos/content/LumoniTechTree.java +++ b/src/fos/content/LumoniTechTree.java @@ -123,7 +123,9 @@ public static void load() { // DEFENCE node(helix, Seq.with(new OnSector(ruins)), () -> { node(sticker, () -> { - //node(firefly, /* TODO: sector handicap */ () -> {}); + node(sludge, () -> { + //node(firefly, /* TODO: sector handicap */ () -> {}); + }); node(particulator, () -> { soontm(); //node(cluster) diff --git a/src/fos/type/bullets/InjectorBulletType.java b/src/fos/type/bullets/InjectorBulletType.java index 1c5e0e7..d723ca4 100644 --- a/src/fos/type/bullets/InjectorBulletType.java +++ b/src/fos/type/bullets/InjectorBulletType.java @@ -17,7 +17,7 @@ public interface InjectorBulletType { boolean attacksGuardians(); default float chance(Entityc owner, Entityc entity) { - float health = ((Unit) entity).health; + float health = ((Healthc) entity).health(); float chance; if (health <= minHP()){ @@ -40,7 +40,7 @@ default float chance(Entityc owner, Entityc entity) { return chance; } - default void onHit(Bullet b, Hitboxc entity) { + default void onHit(Bullet b, Hitboxc entity, boolean always) { if (entity instanceof Unit u) { //if the target is a boss AND a projectile can't attack bosses, return if (u.isBoss() && !attacksGuardians()) return; @@ -52,11 +52,15 @@ default void onHit(Bullet b, Hitboxc entity) { if (u.isPlayer()) return; float chance = chance(b.owner, entity); - if (Mathf.random() < chance) { + if (Mathf.random() < chance || always) { u.apply(FOSStatuses.hacked); u.team = b.team; if (u.isBoss()) u.unapply(StatusEffects.boss); } } } + + default void onHit(Bullet b, Hitboxc entity) { + onHit(b, entity, false); + } } diff --git a/src/fos/type/bullets/InjectorHackLaserBulletType.java b/src/fos/type/bullets/InjectorHackLaserBulletType.java index 93ce436..5e1e12e 100644 --- a/src/fos/type/bullets/InjectorHackLaserBulletType.java +++ b/src/fos/type/bullets/InjectorHackLaserBulletType.java @@ -1,18 +1,43 @@ package fos.type.bullets; +import arc.Core; +import arc.graphics.g2d.TextureRegion; +import arc.util.Time; import mindustry.entities.bullet.ContinuousLaserBulletType; +import mindustry.gen.*; +import mindustry.graphics.Drawf; public class InjectorHackLaserBulletType extends ContinuousLaserBulletType implements InjectorBulletType { - public float minChance, maxChance, minHP, maxHP; + public TextureRegion laser, laserCenter, laserEnd; + public String laserName = "mend"; // TODO + + public float minHP, maxHP; + /** Maximum time required to hack an enemy (with 0% chance), in seconds. */ + public float maxTime = 60f; + /** Minimum time required to hack an enemy (with 100% chance), in seconds. */ + public float minTime = 3f; + + public InjectorHackLaserBulletType() { + continuous = true; + lifetime = maxTime * 60; + } + + @Override + public void load() { + super.load(); + laser = Core.atlas.find("fos-" + laserName + "-laser"); + laserCenter = Core.atlas.find("fos-" + laserName + "-laser-center"); + laserEnd = Core.atlas.find("fos-" + laserName + "-laser-end"); + } @Override public float minChance() { - return minChance; + return 0; } @Override public float maxChance() { - return maxChance; + return 1; } @Override @@ -29,4 +54,40 @@ public float maxHP() { public boolean attacksGuardians() { return true; } + + @Override + public void update(Bullet b) { + if (b.data == null) + b.data = new HackBulletData(); + + var d = (HackBulletData)b.data; + if (d.target == null) + d.target = Groups.unit.find(u -> u.x == ((Weaponsc)b.owner).aimX() && u.y == ((Weaponsc)b.owner).aimY()); + + if (d.target == null || d.target.dead()) { + b.remove(); + return; + } + + float requiredTime = (minTime + (maxTime - minTime) * (1 - chance(b.owner, d.target))) * 60; + + d.progress += Time.delta; + + if (d.progress >= requiredTime) { + onHit(b, d.target, true); + b.remove(); + } + } + + @Override + public void draw(Bullet b) { + var data = (HackBulletData)b.data; + if (data == null) return; + Drawf.laser(laser, laserEnd, b.x, b.y, data.target.x(), data.target.y()); + } + + public class HackBulletData { + public Unitc target; + public float progress; + } }