diff --git a/core/assets-raw/sprites/units/conquer.aseprite b/core/assets-raw/sprites/units/conquer.aseprite deleted file mode 100644 index e1a86d0650..0000000000 Binary files a/core/assets-raw/sprites/units/conquer.aseprite and /dev/null differ diff --git a/core/assets-raw/sprites/units/weapons/disrupt-missile.png b/core/assets-raw/sprites/units/weapons/disrupt-missile.png new file mode 100644 index 0000000000..6aadf02c8a Binary files /dev/null and b/core/assets-raw/sprites/units/weapons/disrupt-missile.png differ diff --git a/core/assets-raw/sprites/units/weapons/disrupt-weapon-blade.png b/core/assets-raw/sprites/units/weapons/disrupt-weapon-blade.png new file mode 100644 index 0000000000..3cc4e4aaae Binary files /dev/null and b/core/assets-raw/sprites/units/weapons/disrupt-weapon-blade.png differ diff --git a/core/assets-raw/sprites/units/weapons/disrupt-weapon.png b/core/assets-raw/sprites/units/weapons/disrupt-weapon.png new file mode 100644 index 0000000000..8dabc8df95 Binary files /dev/null and b/core/assets-raw/sprites/units/weapons/disrupt-weapon.png differ diff --git a/core/assets-raw/sprites/units/weapons/quell-weapon.png b/core/assets-raw/sprites/units/weapons/quell-weapon.png index 15aaca7676..44f39f794f 100644 Binary files a/core/assets-raw/sprites/units/weapons/quell-weapon.png and b/core/assets-raw/sprites/units/weapons/quell-weapon.png differ diff --git a/core/assets/icons/icons.properties b/core/assets/icons/icons.properties index 0ae2b2e3c7..7856baa92b 100755 --- a/core/assets/icons/icons.properties +++ b/core/assets/icons/icons.properties @@ -520,3 +520,4 @@ 63183=conquer|unit-conquer-ui 63182=disrupt|unit-disrupt-ui 63181=krepost|unit-krepost-ui +63180=disrupt-missile|unit-disrupt-missile-ui diff --git a/core/src/mindustry/ai/types/MissileAI.java b/core/src/mindustry/ai/types/MissileAI.java index 89630dc36d..80ceee9f93 100644 --- a/core/src/mindustry/ai/types/MissileAI.java +++ b/core/src/mindustry/ai/types/MissileAI.java @@ -1,17 +1,20 @@ package mindustry.ai.types; +import arc.util.*; import mindustry.entities.units.*; +import mindustry.gen.*; public class MissileAI extends AIController{ - //TODO store 'main' target and use that as a fallback + public @Nullable Unit shooter; - //TODO UNPREDICTABLE TARGETING @Override public void updateMovement(){ unloadPayloads(); - if(target != null){ - unit.lookAt(target); + float time = unit instanceof TimedKillc t ? t.time() : 1000000f; + + if(time >= unit.type.homingDelay && shooter != null){ + unit.lookAt(shooter.aimX, shooter.aimY); } //move forward forever @@ -26,8 +29,7 @@ public class MissileAI extends AIController{ } @Override - public boolean retarget(){ - //more frequent retarget. TODO won't this lag? - return timer.get(timerTarget, 10f); + public void updateTargeting(){ + //no } } diff --git a/core/src/mindustry/content/Blocks.java b/core/src/mindustry/content/Blocks.java index 9b0a534038..3862b910f6 100644 --- a/core/src/mindustry/content/Blocks.java +++ b/core/src/mindustry/content/Blocks.java @@ -2529,7 +2529,7 @@ public class Blocks{ thrusterLength = 40/4f; armor = 10f; - unitCapModifier = 12; + unitCapModifier = 10; researchCostMultiplier = 0.11f; }}; @@ -2544,7 +2544,7 @@ public class Blocks{ thrusterLength = 48/4f; armor = 15f; - unitCapModifier = 16; + unitCapModifier = 14; researchCostMultiplier = 0.11f; }}; diff --git a/core/src/mindustry/content/UnitTypes.java b/core/src/mindustry/content/UnitTypes.java index e080706a70..869502cf83 100644 --- a/core/src/mindustry/content/UnitTypes.java +++ b/core/src/mindustry/content/UnitTypes.java @@ -12,7 +12,6 @@ import mindustry.entities.abilities.*; import mindustry.entities.bullet.*; import mindustry.entities.effect.*; import mindustry.entities.part.*; -import mindustry.entities.units.*; import mindustry.gen.*; import mindustry.graphics.*; import mindustry.type.*; @@ -2503,7 +2502,7 @@ public class UnitTypes{ }}; conquer = new TankUnitType("conquer"){{ - hitSize = 44f; + hitSize = 46f; treadPullOffset = 1; speed = 0.48f; health = 20000; @@ -2664,7 +2663,12 @@ public class UnitTypes{ }}; }}); - decals.add(new UnitDecal("conquer-glow", Color.red, Blending.additive, -1f)); + parts.add(new RegionPart("-glow"){{ + color = Color.red; + blending = Blending.additive; + layer = -1f; + outline = false; + }}); }}; //endregion @@ -2919,13 +2923,8 @@ public class UnitTypes{ mirror = false; reload = 1f; shootOnDeath = true; - bullet = new BulletType(){{ - rangeOverride = 20f; + bullet = new ExplosionBulletType(120f, 30f){{ shootEffect = Fx.massiveExplosion; - killShooter = true; - //TODO status? - splashDamageRadius = 30f; - splashDamage = 120f; }}; }}); }}; @@ -2946,7 +2945,7 @@ public class UnitTypes{ flying = true; drag = 0.07f; speed = 1f; - rotateSpeed = 2.5f; + rotateSpeed = 2f; accel = 0.1f; health = 10000f; armor = 7f; @@ -2979,16 +2978,36 @@ public class UnitTypes{ } //TODO needs weapons! cool missiles or something - if(false) - weapons.add(new Weapon("quell-weapon"){{ - x = 51 / 4f; - y = 5 / 4f; + weapons.add(new Weapon("disrupt-weapon"){{ + x = 78f / 4f; + y = -10f / 4f; + mirror = true; rotate = true; - rotateSpeed = 2f; - reload = 70f; - layerOffset = -0.001f; + rotateSpeed = 0.4f; + reload = 60f; + layerOffset = -20f; recoil = 1f; - rotationLimit = 60f; + rotationLimit = 22f; + minWarmup = 0.95f; + shootWarmupSpeed = 0.1f; + shootY = 2f; + shootCone = 40f; + shots = 3; + shotDelay = 5f; + inaccuracy = 28f; + + parts.add(new RegionPart("-blade"){{ + heatProgress = PartProgress.warmup; + progress = PartProgress.warmup.blend(PartProgress.reload, 0.15f); + heatColor = Color.valueOf("9c50ff"); + x = 5 / 4f; + y = 0f; + rotMove = -33f; + moveY = -1f; + moveX = -1f; + under = true; + mirror = true; + }}); bullet = new BulletType(){{ shootEffect = Fx.shootBig; @@ -2998,24 +3017,20 @@ public class UnitTypes{ keepVelocity = false; }}; - unitSpawned = new MissileUnitType("quell-missile"){{ - speed = 3.8f; + unitSpawned = new MissileUnitType("disrupt-missile"){{ + speed = 4.5f; maxRange = 80f; outlineColor = Pal.darkOutline; health = 45; + homingDelay = 10f; weapons.add(new Weapon(){{ shootCone = 360f; mirror = false; reload = 1f; shootOnDeath = true; - bullet = new BulletType(){{ - rangeOverride = 20f; + bullet = new ExplosionBulletType(120f, 30f){{ shootEffect = Fx.massiveExplosion; - killShooter = true; - //TODO status? - splashDamageRadius = 30f; - splashDamage = 120f; }}; }}); }}; @@ -3107,7 +3122,8 @@ public class UnitTypes{ shootEffect = Fx.colorSpark; hitEffect = smokeEffect = despawnEffect = Fx.hitLaserColor; - damage = 1; + //TODO 0, or 1? + damage = 0; }}; }}); }}; diff --git a/core/src/mindustry/entities/Damage.java b/core/src/mindustry/entities/Damage.java index 0ab16352f9..21006bddd6 100644 --- a/core/src/mindustry/entities/Damage.java +++ b/core/src/mindustry/entities/Damage.java @@ -387,11 +387,17 @@ public class Damage{ /** Damages all entities and blocks in a radius that are enemies of the team. */ public static void damage(Team team, float x, float y, float radius, float damage, boolean complete, boolean air, boolean ground){ + damage(team, x, y, radius, damage, complete, air, ground, false); + } + + /** Damages all entities and blocks in a radius that are enemies of the team. */ + public static void damage(Team team, float x, float y, float radius, float damage, boolean complete, boolean air, boolean ground, boolean scaled){ Cons cons = entity -> { - if(entity.team == team || !entity.within(x, y, radius) || (entity.isFlying() && !air) || (entity.isGrounded() && !ground)){ + if(entity.team == team || !entity.within(x, y, radius + (scaled ? entity.hitSize / 2f : 0f)) || (entity.isFlying() && !air) || (entity.isGrounded() && !ground)){ return; } - float amount = calculateDamage(x, y, entity.getX(), entity.getY(), radius, damage); + + float amount = calculateDamage(scaled ? Math.max(0, entity.dst(x, y) - entity.type.hitSize/2) : entity.dst(x, y), radius, damage); entity.damage(amount); //TODO better velocity displacement float dst = tr.set(entity.getX() - x, entity.getY() - y).len(); @@ -504,8 +510,7 @@ public class Damage{ } } - private static float calculateDamage(float x, float y, float tx, float ty, float radius, float damage){ - float dist = Mathf.dst(x, y, tx, ty); + private static float calculateDamage(float dist, float radius, float damage){ float falloff = 0.4f; float scaled = Mathf.lerp(1f - dist / radius, 1f, falloff); return damage * scaled; diff --git a/core/src/mindustry/entities/bullet/BulletType.java b/core/src/mindustry/entities/bullet/BulletType.java index 0ee7e4e2d9..81441e4ac8 100644 --- a/core/src/mindustry/entities/bullet/BulletType.java +++ b/core/src/mindustry/entities/bullet/BulletType.java @@ -80,6 +80,8 @@ public class BulletType extends Content implements Cloneable{ public boolean instantDisappear; /** Damage dealt in splash. 0 to disable.*/ public float splashDamage = 0f; + /** If true, splash damage is "correctly" affected by unit hitbox size. Used for projectiles that do not collide / have splash as their main source of damage. */ + public boolean scaledSplashDamage = false; /** Knockback in velocity. */ public float knockback; /** Should knockback follow the bullet's direction */ @@ -142,6 +144,7 @@ public class BulletType extends Content implements Cloneable{ public @Nullable BulletType fragBullet = null; public Color hitColor = Color.white; public Color healColor = Pal.heal; + public Effect healEffect = Fx.healBlockFull; /** Bullets spawned when this bullet is created. Rarely necessary, used for visuals. */ public Seq spawnBullets = new Seq<>(); @@ -249,7 +252,7 @@ public class BulletType extends Content implements Cloneable{ } if(heals()&& build.team == b.team && !(build.block instanceof ConstructBlock)){ - Fx.healBlockFull.at(build.x, build.y, build.block.size, healColor); + healEffect.at(build.x, build.y, build.block.size, healColor); build.heal(healPercent / 100f * build.maxHealth + healAmount); }else if(build.team != b.team && direct){ hit(b); @@ -308,7 +311,7 @@ public class BulletType extends Content implements Cloneable{ } if(splashDamageRadius > 0 && !b.absorbed){ - Damage.damage(b.team, x, y, splashDamageRadius, splashDamage * b.damageMultiplier(), collidesAir, collidesGround); + Damage.damage(b.team, x, y, splashDamageRadius, splashDamage * b.damageMultiplier(), false, collidesAir, collidesGround, scaledSplashDamage); if(status != StatusEffects.none){ Damage.status(b.team, x, y, splashDamageRadius, status, statusDuration, collidesAir, collidesGround); @@ -316,7 +319,7 @@ public class BulletType extends Content implements Cloneable{ if(heals()){ indexer.eachBlock(b.team, x, y, splashDamageRadius, Building::damaged, other -> { - Fx.healBlockFull.at(other.x, other.y, other.block.size, healColor); + healEffect.at(other.x, other.y, other.block.size, healColor); other.heal(healPercent / 100f * other.maxHealth() + healAmount); }); } @@ -375,7 +378,7 @@ public class BulletType extends Content implements Cloneable{ } if(instantDisappear){ - b.time = lifetime; + b.time = lifetime + 1f; } if(spawnBullets.size > 0){ diff --git a/core/src/mindustry/entities/bullet/ExplosionBulletType.java b/core/src/mindustry/entities/bullet/ExplosionBulletType.java new file mode 100644 index 0000000000..0ad13fdb88 --- /dev/null +++ b/core/src/mindustry/entities/bullet/ExplosionBulletType.java @@ -0,0 +1,28 @@ +package mindustry.entities.bullet; + +import mindustry.content.*; + +public class ExplosionBulletType extends BulletType{ + + public ExplosionBulletType(float splashDamage, float splashDamageRadius){ + this.splashDamage = splashDamage; + this.splashDamageRadius = splashDamageRadius; + rangeOverride = Math.max(rangeOverride, splashDamageRadius * 2f / 3f); + } + + public ExplosionBulletType(){ + } + + { + hittable = false; + lifetime = 1f; + speed = 0f; + rangeOverride = 20f; + shootEffect = Fx.massiveExplosion; + instantDisappear = true; + scaledSplashDamage = true; + killShooter = true; + collides = false; + keepVelocity = false; + } +} diff --git a/core/src/mindustry/entities/comp/UnitComp.java b/core/src/mindustry/entities/comp/UnitComp.java index 8c5f626906..83bba188c5 100644 --- a/core/src/mindustry/entities/comp/UnitComp.java +++ b/core/src/mindustry/entities/comp/UnitComp.java @@ -513,7 +513,9 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I float shake = hitSize / 3f; - Effect.scorch(x, y, (int)(hitSize / 5)); + if(type.createScorch){ + Effect.scorch(x, y, (int)(hitSize / 5)); + } Effect.shake(shake, shake, this); type.deathSound.at(this); @@ -536,7 +538,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I Damage.damage(team, x, y, Mathf.pow(hitSize, 0.94f) * 1.25f, Mathf.pow(hitSize, 0.75f) * type.crashDamageMultiplier * 5f, true, false, true); } - if(!headless){ + if(!headless && type.createScorch){ for(int i = 0; i < type.wreckRegions.length; i++){ if(type.wreckRegions[i].found()){ float range = type.hitSize /4f; diff --git a/core/src/mindustry/entities/part/WeaponPart.java b/core/src/mindustry/entities/part/DrawPart.java similarity index 85% rename from core/src/mindustry/entities/part/WeaponPart.java rename to core/src/mindustry/entities/part/DrawPart.java index b529aa1d88..00e465e07e 100644 --- a/core/src/mindustry/entities/part/WeaponPart.java +++ b/core/src/mindustry/entities/part/DrawPart.java @@ -4,7 +4,7 @@ import arc.graphics.g2d.*; import arc.math.*; import arc.struct.*; -public abstract class WeaponPart{ +public abstract class DrawPart{ public static final PartParams params = new PartParams(); /** If true, turret shading is used. Don't touch this, it is set up in unit/block init()! */ @@ -37,11 +37,14 @@ public abstract class WeaponPart{ } public interface PartProgress{ + /** Reload of the weapon - 1 right after shooting, 0 when ready to fire*/ PartProgress - reload = p -> p.reload, + /** Reload, but smoothed out, so there is no sudden jump between 0-1. */ smoothReload = p -> p.smoothReload, + /** 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; float get(PartParams p); @@ -74,6 +77,10 @@ public abstract class WeaponPart{ return p -> get(p) * other.get(p); } + default PartProgress mul(float amount){ + return p -> get(p) * amount; + } + default PartProgress min(PartProgress other){ return p -> Math.min(get(p), other.get(p)); } diff --git a/core/src/mindustry/entities/part/RegionPart.java b/core/src/mindustry/entities/part/RegionPart.java index 98ecfbd7ff..37d3a5d905 100644 --- a/core/src/mindustry/entities/part/RegionPart.java +++ b/core/src/mindustry/entities/part/RegionPart.java @@ -8,10 +8,13 @@ import arc.struct.*; import arc.util.*; import mindustry.graphics.*; -public class RegionPart extends WeaponPart{ +public class RegionPart extends DrawPart{ protected PartParams childParam = new PartParams(); + /** Appended to unit/weapon/block name and drawn. */ public String suffix = ""; + /** Overrides suffix if set. */ + public @Nullable String name; public TextureRegion heat; public TextureRegion[] regions = {}; public TextureRegion[] outlines = {}; @@ -34,12 +37,19 @@ public class RegionPart extends WeaponPart{ public float x, y, moveX, moveY; public @Nullable Color color, colorTo; public Color heatColor = Pal.turretHeat.cpy(); - public Seq children = new Seq<>(); + public Seq children = new Seq<>(); public RegionPart(String region){ this.suffix = region; } + public RegionPart(String region, Blending blending, Color color){ + this.suffix = region; + this.blending = blending; + this.color = color; + outline = false; + } + public RegionPart(){ } @@ -119,25 +129,27 @@ public class RegionPart extends WeaponPart{ @Override public void load(String name){ + String realName = this.name == null ? name + suffix : this.name; + if(drawRegion){ //TODO l/r if(mirror && turretShading){ regions = new TextureRegion[]{ - Core.atlas.find(name + suffix + "1"), - Core.atlas.find(name + suffix + "2") + Core.atlas.find(realName + "1"), + Core.atlas.find(realName + "2") }; outlines = new TextureRegion[]{ - Core.atlas.find(name + suffix + "1-outline"), - Core.atlas.find(name + suffix + "2-outline") + Core.atlas.find(realName + "1-outline"), + Core.atlas.find(realName + "2-outline") }; }else{ - regions = new TextureRegion[]{Core.atlas.find(name + suffix)}; - outlines = new TextureRegion[]{Core.atlas.find(name + suffix + "-outline")}; + regions = new TextureRegion[]{Core.atlas.find(realName)}; + outlines = new TextureRegion[]{Core.atlas.find(realName + "-outline")}; } } - heat = Core.atlas.find(name + suffix + "-heat"); + heat = Core.atlas.find(realName + "-heat"); for(var child : children){ child.load(name); } diff --git a/core/src/mindustry/entities/units/UnitDecal.java b/core/src/mindustry/entities/units/UnitDecal.java deleted file mode 100644 index cdd488d6fe..0000000000 --- a/core/src/mindustry/entities/units/UnitDecal.java +++ /dev/null @@ -1,36 +0,0 @@ -package mindustry.entities.units; - -import arc.graphics.*; -import arc.graphics.g2d.*; -import mindustry.graphics.*; - -/** A sprite drawn in addition to the base unit sprites. */ -public class UnitDecal{ - public String region = "error"; - public float x, y, rotation; - public float layer = Layer.flyingUnit + 1f; - public float xScale = 1f, yScale = 1f; - public Blending blending = Blending.normal; - public Color color = Color.white; - - public TextureRegion loadedRegion; - - public UnitDecal(String region, float x, float y, float rotation, float layer, Color color){ - this.region = region; - this.x = x; - this.y = y; - this.rotation = rotation; - this.layer = layer; - this.color = color; - } - - public UnitDecal(String region, Color color, Blending blending, float layer){ - this.region = region; - this.color = color; - this.layer = layer; - this.blending = blending; - } - - public UnitDecal(){ - } -} diff --git a/core/src/mindustry/mod/ContentParser.java b/core/src/mindustry/mod/ContentParser.java index 6a077d6bc8..19b4553eb7 100644 --- a/core/src/mindustry/mod/ContentParser.java +++ b/core/src/mindustry/mod/ContentParser.java @@ -25,7 +25,7 @@ import mindustry.entities.abilities.*; import mindustry.entities.bullet.*; import mindustry.entities.effect.*; import mindustry.entities.part.*; -import mindustry.entities.part.WeaponPart.*; +import mindustry.entities.part.DrawPart.*; import mindustry.game.*; import mindustry.game.Objectives.*; import mindustry.gen.*; @@ -130,13 +130,14 @@ public class ContentParser{ readFields(result, data); return result; }); - put(WeaponPart.class, (type, data) -> { + put(DrawPart.class, (type, data) -> { var bc = resolve(data.getString("type", ""), RegionPart.class); data.remove("type"); var result = make(bc); readFields(result, data); return result; }); + //TODO this is untested put(PartProgress.class, (type, data) -> { //simple case: it's a string or number constant if(data.isString()) return field(PartProgress.class, data.asString()); @@ -166,7 +167,7 @@ public class ContentParser{ case "mul" -> base.mul(parser.readValue(PartProgress.class, data.get("other"))); case "min" -> base.min(parser.readValue(PartProgress.class, data.get("other"))); case "sin" -> base.sin(data.getFloat("scl"), data.getFloat("mag")); - case "absin" -> base.sin(data.getFloat("scl"), data.getFloat("mag")); + case "absin" -> base.absin(data.getFloat("scl"), data.getFloat("mag")); case "curve" -> base.curve(parser.readValue(Interp.class, data.get("interp"))); default -> throw new RuntimeException("Unknown operation '" + op + "', check PartProgress class for a list of methods."); }; diff --git a/core/src/mindustry/type/UnitType.java b/core/src/mindustry/type/UnitType.java index c368b3118e..370c810e46 100644 --- a/core/src/mindustry/type/UnitType.java +++ b/core/src/mindustry/type/UnitType.java @@ -20,6 +20,7 @@ import mindustry.core.*; import mindustry.ctype.*; import mindustry.entities.*; import mindustry.entities.abilities.*; +import mindustry.entities.part.*; import mindustry.entities.units.*; import mindustry.game.*; import mindustry.gen.*; @@ -59,7 +60,6 @@ public class UnitType extends UnlockableContent{ public float speed = 1.1f, boostMultiplier = 1f, rotateSpeed = 5f, baseRotateSpeed = 5f; public float drag = 0.3f, accel = 0.5f, landShake = 0f, rippleScale = 1f, riseSpeed = 0.08f, fallSpeed = 0.018f; public float health = 200f, range = -1, miningRange = 70f, armor = 0f, maxRange = -1f, buildRange = Vars.buildingRange; - public float lifetime = 60f * 5f; //for missiles only public float crashDamageMultiplier = 1f; public boolean targetAir = true, targetGround = true; public boolean faceTarget = true, rotateShooting = true, isCounted = true, lowAltitude = false, circleTarget = false; @@ -68,6 +68,7 @@ public class UnitType extends UnlockableContent{ public boolean playerControllable = true; public boolean allowedInPayloads = true; public boolean createWreck = true; + public boolean createScorch = true; public boolean useUnitCap = true; public boolean destructibleWreck = true; /** If true, this modded unit always has a -outline region generated for its base. Normally, outlines are ignored if there are no top = false weapons. */ @@ -94,8 +95,8 @@ public class UnitType extends UnlockableContent{ public Effect fallThrusterEffect = Fx.fallSmoke; public Effect deathExplosionEffect = Fx.dynamicExplosion; public @Nullable Effect treadEffect; - /** Additional sprites that are drawn with the unit. */ - public Seq decals = new Seq<>(); + /** Extra (usually animated) parts */ + public Seq parts = new Seq<>(DrawPart.class); public Seq abilities = new Seq<>(); /** Flags to target based on priority. Null indicates that the closest target should be found. The closest enemy core is used as a fallback. */ public BlockFlag[] targetFlags = {null}; @@ -138,6 +139,10 @@ public class UnitType extends UnlockableContent{ public Sound mineSound = Sounds.minebeam; public float mineSoundVolume = 0.6f; + //missiles only! + public float lifetime = 60f * 5f; + public float homingDelay = 10f; + /** This is a VERY ROUGH estimate of unit DPS. */ public float dpsEstimate = -1; public float clipSize = -1; @@ -522,6 +527,9 @@ public class UnitType extends UnlockableContent{ public void load(){ super.load(); + for(var part : parts){ + part.load(name); + } weapons.each(Weapon::load); region = Core.atlas.find(name); legRegion = Core.atlas.find(name + "-leg"); @@ -562,10 +570,6 @@ public class UnitType extends UnlockableContent{ segmentOutlineRegions[i] = Core.atlas.find(name + "-segment-outline" + i); } - for(var decal : decals){ - decal.loadedRegion = Core.atlas.find(decal.region); - } - clipSize = Math.max(region.width * 2f, clipSize); } @@ -798,18 +802,19 @@ public class UnitType extends UnlockableContent{ unit.trns(-legOffset.x, -legOffset.y); } - if(decals.size > 0){ - float base = unit.rotation - 90; - for(var d : decals){ - Draw.blend(d.blending); - Draw.z(d.layer <= 0f ? z : d.layer); - Draw.scl(d.xScale, d.yScale); - Draw.color(d.color); - Draw.rect(d.loadedRegion, unit.x + Angles.trnsx(base, d.x, d.y), unit.y + Angles.trnsy(base, d.x, d.y), base + d.rotation); - Draw.blend(); + //TODO how/where do I draw under? + if(parts.size > 0){ + //TODO does it need an outline? + WeaponMount first = unit.mounts.length > 0 ? unit.mounts[0] : null; + if(unit.mounts.length > 0){ + DrawPart.params.set(first.warmup, first.reload / weapons.first().reload, first.smoothReload, first.heat, unit.x, unit.y, unit.rotation); + }else{ + DrawPart.params.set(0f, 0f, 0f, 0f, unit.x, unit.y, unit.rotation); + } + for(int i = 0; i < parts.size; i++){ + var part = parts.items[i]; + part.draw(DrawPart.params); } - Draw.reset(); - Draw.z(z); } for(Ability a : unit.abilities){ diff --git a/core/src/mindustry/type/Weapon.java b/core/src/mindustry/type/Weapon.java index 7f47e61784..c1e5d9e8d2 100644 --- a/core/src/mindustry/type/Weapon.java +++ b/core/src/mindustry/type/Weapon.java @@ -10,6 +10,7 @@ import arc.math.geom.*; import arc.scene.ui.layout.*; import arc.struct.*; import arc.util.*; +import mindustry.ai.types.*; import mindustry.audio.*; import mindustry.content.*; import mindustry.entities.*; @@ -131,7 +132,7 @@ public class Weapon implements Cloneable{ /** whether this weapon should fire when its owner dies */ public boolean shootOnDeath = false; /** extra animated parts */ - public Seq parts = new Seq<>(WeaponPart.class); + public Seq parts = new Seq<>(DrawPart.class); public Weapon(String name){ this.name = name; @@ -195,12 +196,12 @@ public class Weapon implements Cloneable{ Draw.xscl = -Mathf.sign(flipSprite); if(parts.size > 0){ - WeaponPart.params.set(mount.warmup, mount.reload / reload, mount.smoothReload, mount.heat, wx, wy, weaponRotation + 90); + DrawPart.params.set(mount.warmup, mount.reload / reload, mount.smoothReload, mount.heat, wx, wy, weaponRotation + 90); for(int i = 0; i < parts.size; i++){ var part = parts.items[i]; if(part.under){ - part.draw(WeaponPart.params); + part.draw(DrawPart.params); } } } @@ -217,18 +218,10 @@ public class Weapon implements Cloneable{ if(parts.size > 0){ //TODO does it need an outline? - /* - if(outline.found()){ - //draw outline under everything when parts are involved - Draw.z(Layer.turret - 0.01f); - Draw.rect(outline, build.x + tb.recoilOffset.x, build.y + tb.recoilOffset.y, tb.drawrot()); - Draw.z(Layer.turret); - }*/ - for(int i = 0; i < parts.size; i++){ var part = parts.items[i]; if(!part.under){ - part.draw(WeaponPart.params); + part.draw(DrawPart.params); } } } @@ -380,10 +373,8 @@ public class Weapon implements Cloneable{ float baseX = offset.x, baseY = offset.y, baseRot = unit.rotation + mount.rotation; boolean delay = firstShotDelay + shotDelay > 0f; - (delay ? chargeSound : continuous ? Sounds.none : shootSound).at(shootX, shootY, Mathf.random(soundPitchMin, soundPitchMax)); - - BulletType ammo = bullet; - float lifeScl = ammo.scaleVelocity ? Mathf.clamp(Mathf.dst(shootX, shootY, aimX, aimY) / ammo.range()) : 1f; + float lifeScl = bullet.scaleVelocity ? Mathf.clamp(Mathf.dst(shootX, shootY, aimX, aimY) / bullet.range()) : 1f; + Unit parent = bullet.keepVelocity || parentizeEffects ? unit : null; //TODO far too complicated and similar to Turret @@ -393,6 +384,7 @@ public class Weapon implements Cloneable{ Time.run(sequenceNum * shotDelay + firstShotDelay, () -> { if(!unit.isAdded()) return; + //TODO this is a flawed system, recalculate everything instead. getShootPos(unit, mount, offset).sub(baseX, baseY); float rotOffset = unit.rotation + mount.rotation - baseRot; @@ -407,13 +399,11 @@ public class Weapon implements Cloneable{ Angles.shotgun(shots, spacing, rotation, f -> mount.bullet = bullet(unit, shootX, shootY, f + Mathf.range(inaccuracy), lifeScl)); } - boolean parentize = ammo.keepVelocity || parentizeEffects; - if(delay){ Time.run(firstShotDelay, () -> { if(!unit.isAdded()) return; - unit.vel.add(Tmp.v1.trns(rotation + 180f, ammo.recoil)); + unit.vel.add(Tmp.v1.trns(rotation + 180f, bullet.recoil)); Effect.shake(shake, shake, shootX, shootY); mount.recoil = recoil; mount.heat = 1f; @@ -423,21 +413,26 @@ public class Weapon implements Cloneable{ getShootPos(unit, mount, offset).sub(baseX, baseY); - ammo.chargeShootEffect.at(shootX + offset.x, shootY + offset.y, rotation, parentize ? unit : null); + bullet.chargeShootEffect.at(shootX + offset.x, shootY + offset.y, rotation, parent); }); }else{ - unit.vel.add(Tmp.v1.trns(rotation + 180f, ammo.recoil)); + unit.vel.add(Tmp.v1.trns(rotation + 180f, bullet.recoil)); Effect.shake(shake, shake, shootX, shootY); mount.recoil = recoil; mount.heat = 1f; } - ejectEffect.at(mountX, mountY, rotation * side); - ammo.shootEffect.at(shootX, shootY, rotation, ammo.hitColor, parentize ? unit : null); - ammo.smokeEffect.at(shootX, shootY, rotation, ammo.hitColor, parentize ? unit : null); + (delay ? chargeSound : continuous ? Sounds.none : shootSound).at(shootX, shootY, Mathf.random(soundPitchMin, soundPitchMax)); + effects(parent, mountX, mountY, shootX, shootY, rotation, side); unit.apply(shootStatus, shootStatusDuration); } + protected void effects(Unit parent, float mountX, float mountY, float shootX, float shootY, float rotation, int side){ + ejectEffect.at(mountX, mountY, rotation * side); + bullet.shootEffect.at(shootX, shootY, rotation, bullet.hitColor, parent); + bullet.smokeEffect.at(shootX, shootY, rotation, bullet.hitColor, parent); + } + protected @Nullable Bullet bullet(Unit unit, float shootX, float shootY, float angle, float lifescl){ float xr = Mathf.range(xRand), @@ -452,6 +447,9 @@ public class Weapon implements Cloneable{ spawned.rotation = angle; //immediately spawn at top speed, since it was launched spawned.vel.trns(angle, unitSpawned.speed); + if(spawned.controller() instanceof MissileAI ai){ + ai.shooter = unit; + } spawned.add(); //TODO assign AI target here? return null; diff --git a/core/src/mindustry/type/unit/MissileUnitType.java b/core/src/mindustry/type/unit/MissileUnitType.java index b70382b0dc..6610f36149 100644 --- a/core/src/mindustry/type/unit/MissileUnitType.java +++ b/core/src/mindustry/type/unit/MissileUnitType.java @@ -13,6 +13,7 @@ public class MissileUnitType extends UnitType{ playerControllable = false; createWreck = false; + createScorch = false; logicControllable = false; isCounted = false; useUnitCap = false; @@ -26,8 +27,8 @@ public class MissileUnitType extends UnitType{ trailLength = 7; hidden = true; speed = 4f; - lifetime = 60f * 3f; - rotateSpeed = 3f; + lifetime = 60f * 1.6f; + rotateSpeed = 2.5f; range = 30f; //TODO weapons, etc } diff --git a/core/src/mindustry/ui/dialogs/PlanetDialog.java b/core/src/mindustry/ui/dialogs/PlanetDialog.java index 52b2fc6382..0673db5990 100644 --- a/core/src/mindustry/ui/dialogs/PlanetDialog.java +++ b/core/src/mindustry/ui/dialogs/PlanetDialog.java @@ -1077,7 +1077,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{ //make sure there are no under-attack sectors (other than this one) //TODO abandon button? for(Planet planet : content.planets()){ - if(!planet.allowWaveSimulation){ + if(!planet.allowWaveSimulation && !debugSelect){ int attackedCount = planet.sectors.count(s -> s.isAttacked() && s != sector); //if there are two or more attacked sectors... something went wrong, don't show the dialog to prevent softlock diff --git a/core/src/mindustry/world/draw/DrawTurret.java b/core/src/mindustry/world/draw/DrawTurret.java index 5a26148ac7..6d18c66ddc 100644 --- a/core/src/mindustry/world/draw/DrawTurret.java +++ b/core/src/mindustry/world/draw/DrawTurret.java @@ -17,7 +17,7 @@ import mindustry.world.blocks.defense.turrets.Turret.*; public class DrawTurret extends DrawBlock{ protected static final Rand rand = new Rand(); - public Seq parts = new Seq<>(); + public Seq parts = new Seq<>(); public String basePrefix = ""; /** Overrides the liquid to draw in the liquid region. */ public @Nullable Liquid liquidDraw; @@ -66,7 +66,7 @@ public class DrawTurret extends DrawBlock{ } //TODO no smooth reload - var params = WeaponPart.params.set(build.warmup(), 1f - tb.progress(), 1f - tb.progress(), tb.heat, tb.x + tb.recoilOffset.x, tb.y + tb.recoilOffset.y, tb.rotation); + var params = DrawPart.params.set(build.warmup(), 1f - tb.progress(), 1f - tb.progress(), tb.heat, tb.x + tb.recoilOffset.x, tb.y + tb.recoilOffset.y, tb.rotation); for(var part : parts){ part.draw(params);