diff --git a/core/assets-raw/sprites/units/weapons/conquer-weapon-blade-heat.png b/core/assets-raw/sprites/units/weapons/conquer-weapon-blade-heat.png new file mode 100644 index 0000000000..d3847c82db Binary files /dev/null and b/core/assets-raw/sprites/units/weapons/conquer-weapon-blade-heat.png differ diff --git a/core/assets-raw/sprites/units/weapons/conquer-weapon-blade.png b/core/assets-raw/sprites/units/weapons/conquer-weapon-blade.png new file mode 100644 index 0000000000..4d4181e035 Binary files /dev/null and b/core/assets-raw/sprites/units/weapons/conquer-weapon-blade.png differ diff --git a/core/assets-raw/sprites/units/weapons/conquer-weapon-fold.png b/core/assets-raw/sprites/units/weapons/conquer-weapon-fold.png new file mode 100644 index 0000000000..9ef6827464 Binary files /dev/null and b/core/assets-raw/sprites/units/weapons/conquer-weapon-fold.png differ diff --git a/core/src/mindustry/content/Blocks.java b/core/src/mindustry/content/Blocks.java index 10ec9edd43..6a063797ae 100644 --- a/core/src/mindustry/content/Blocks.java +++ b/core/src/mindustry/content/Blocks.java @@ -3115,7 +3115,7 @@ public class Blocks{ parts.addAll( new RegionPart("-back"){{ - useReload = false; + progress = PartProgress.warmup; mirror = true; rotMove = 40f; x = 22 / 4f; @@ -3125,7 +3125,7 @@ public class Blocks{ heatColor = heatc; }}, new RegionPart("-front"){{ - useReload = false; + progress = PartProgress.warmup; mirror = true; rotMove = 40f; x = 20 / 4f; @@ -3136,7 +3136,7 @@ public class Blocks{ heatColor = heatc; }}, new RegionPart("-nozzle"){{ - useReload = false; + progress = PartProgress.warmup; mirror = true; moveX = 8f / 4f; heatColor = Color.valueOf("f03b0e"); @@ -3235,10 +3235,9 @@ public class Blocks{ moveX = 2f * 4f / 3f; moveY = -0.5f; rotMove = -40f; - useReload = false; + progress = heatProgress = PartProgress.warmup; under = true; heatColor = Color.red.cpy(); - useProgressHeat = true; interp = Interp.pow2Out; }}); }}; diff --git a/core/src/mindustry/content/UnitTypes.java b/core/src/mindustry/content/UnitTypes.java index 5152a89172..50bbd4b49e 100644 --- a/core/src/mindustry/content/UnitTypes.java +++ b/core/src/mindustry/content/UnitTypes.java @@ -2518,7 +2518,7 @@ public class UnitTypes{ reload = 120f; shootY = 32.5f; shake = 5f; - recoil = 4f; + recoil = 5f; rotate = true; rotateSpeed = 0.6f; mirror = false; @@ -2530,46 +2530,64 @@ public class UnitTypes{ cooldownTime = 110f; heatColor = Color.valueOf("f9350f"); - parts.add( + parts.addAll( new RegionPart("-glow"){{ color = Color.red; blending = Blending.additive; outline = mirror = false; }}, new RegionPart("-sides"){{ - useReload = false; + progress = PartProgress.warmup; mirror = true; under = true; - moveX = 0.5f; - moveY = 0.5f; + moveX = 0.75f; + moveY = 0.75f; rotMove = 82f; - x = 38 / 4f; - y = 9 / 4f; + x = 37 / 4f; + y = 8 / 4f; }}, new RegionPart("-sinks"){{ - useReload = false; + progress = PartProgress.warmup; mirror = true; under = true; heatColor = new Color(1f, 0.1f, 0.1f); - moveX = 15f / 4f; - moveY = -13f / 4f; - x = 34 / 4f; - y = -36 / 4f; + moveX = 17f / 4f; + moveY = -15f / 4f; + x = 32 / 4f; + y = -34 / 4f; }}, new RegionPart("-sinks-heat"){{ blending = Blending.additive; - useReload = false; + progress = PartProgress.warmup; mirror = true; outline = false; colorTo = new Color(1f, 0f, 0f, 0.5f); color = colorTo.cpy().a(0f); - moveX = 15f / 4f; - moveY = -13f / 4f; - x = 34 / 4f; - y = -36 / 4f; + moveX = 17f / 4f; + moveY = -15f / 4f; + x = 32 / 4f; + y = -34 / 4f; }} ); + //TODO this is a bit over-the-top + for(int i = 1; i <= 3; i++){ + int fi = i; + parts.add(new RegionPart("-blade"){{ + progress = PartProgress.warmup.delay((3 - fi) * 0.3f).blend(PartProgress.reload, 0.3f); + heatProgress = PartProgress.heat.add(0.3f).min(PartProgress.warmup); + heatColor = new Color(1f, 0.1f, 0.1f); + mirror = true; + under = true; + rotMove = -40f * fi; + moveX = 3f; + layerOffset = -0.002f; + + x = 11 / 4f; + y = 0 / 4f; + }}); + } + bullet = new BasicBulletType(8.5f, 250){{ sprite = "missile-large"; width = 12f; diff --git a/core/src/mindustry/entities/bullet/BulletType.java b/core/src/mindustry/entities/bullet/BulletType.java index d2b9b3ac70..f7b173edd6 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.struct.*; import arc.util.*; import mindustry.*; import mindustry.annotations.Annotations.*; @@ -138,6 +139,7 @@ public class BulletType extends Content implements Cloneable{ public @Nullable BulletType fragBullet = null; public Color hitColor = Color.white; public Color healColor = Pal.heal; + public Seq spawnBullets = new Seq<>(); public Color trailColor = Pal.missileYellowBack; public float trailChance = -0.0001f; diff --git a/core/src/mindustry/entities/part/RegionPart.java b/core/src/mindustry/entities/part/RegionPart.java index 29f88a965d..8a54451928 100644 --- a/core/src/mindustry/entities/part/RegionPart.java +++ b/core/src/mindustry/entities/part/RegionPart.java @@ -9,32 +9,33 @@ import arc.util.*; import mindustry.graphics.*; public class RegionPart extends WeaponPart{ + protected PartParams childParam = new PartParams(); + public String suffix = ""; public TextureRegion heat; public TextureRegion[] regions = {}; public TextureRegion[] outlines = {}; - /** If true, turret reload is used as the measure of progress. Otherwise, warmup is used. */ - public boolean useReload = true; /** If true, parts are mirrored across the turret. Requires -1 and -2 regions. */ public boolean mirror = false; /** If true, an outline is drawn under the part. */ public boolean outline = true; /** If true, the base + outline regions are drawn. Set to false for heat-only regions. */ public boolean drawRegion = true; - /** If true, progress is inverted. */ - public boolean invert = false; + /** Progress function for determining position/rotation. */ + public PartProgress progress = PartProgress.warmup; + /** Progress function for heat alpha. */ + public PartProgress heatProgress = PartProgress.heat; public Blending blending = Blending.normal; - public boolean useProgressHeat = false; public Interp interp = Interp.linear; - public float layer = -1; - public float outlineLayerOffset = -0.01f; + public float layer = -1, layerOffset = 0f; + public float outlineLayerOffset = -0.001f; public float rotation, rotMove; public float x, y, moveX, moveY; - public float oscMag = 0f, oscScl = 7f; public boolean oscAbs = false; public @Nullable Color color, colorTo; public Color heatColor = Pal.turretHeat.cpy(); + public @Nullable WeaponPart child; public RegionPart(String region){ this.suffix = region; @@ -49,26 +50,27 @@ public class RegionPart extends WeaponPart{ if(layer > 0) Draw.z(layer); //TODO 'under' should not be special cased like this... if(under && turretShading) Draw.z(z - 0.0001f); + Draw.z(Draw.z() + layerOffset); float prevZ = Draw.z(); - float progress = useReload ? 1f - params.reload : params.warmup; + float prog = progress.get(params); - if(oscMag > 0) progress += oscAbs ? Mathf.absin(oscScl, oscMag) : Mathf.sin(oscScl, oscMag); - if(invert) progress = 1f - progress; + prog = interp.apply(prog); + int len = mirror && params.sideOverride == -1 ? 2 : 1; - progress = interp.apply(progress); - int len = mirror ? 2 : 1; + for(int s = 0; s < len; s++){ + //use specific side if necessary + int i = params.sideOverride == -1 ? s : params.sideOverride; - for(int i = 0; i < len; i++){ //can be null var region = drawRegion ? regions[Math.min(i, regions.length - 1)] : null; float sign = i == 1 ? -1 : 1; - Tmp.v1.set((x + moveX * progress) * sign, y + moveY * progress).rotate((params.rotation - 90)); + 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, - rot = i * sign + rotMove * progress * sign + params.rotation - 90; + rot = rotMove * prog * sign + params.rotation - 90; Draw.xscl = i == 0 ? 1 : -1; @@ -80,7 +82,7 @@ public class RegionPart extends WeaponPart{ if(drawRegion && region.found()){ if(color != null && colorTo != null){ - Draw.color(color, colorTo, progress); + Draw.color(color, colorTo, prog); }else if(color != null){ Draw.color(color); } @@ -91,13 +93,27 @@ public class RegionPart extends WeaponPart{ } if(heat.found()){ - Drawf.additive(heat, heatColor.write(Tmp.c1).a((useProgressHeat ? params.warmup : params.heat) * heatColor.a), rx, ry, rot, turretShading ? Layer.turretHeat : z + 1f); + Drawf.additive(heat, heatColor.write(Tmp.c1).a(heatProgress.get(params) * heatColor.a), rx, ry, rot, turretShading ? Layer.turretHeat : z + 1f); } Draw.xscl = 1f; } Draw.z(z); + + //draw child, if applicable - only at the end + //TODO lots of copy-paste here + if(child != null){ + for(int s = 0; s < len; s++){ + 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); + + childParam.set(params.warmup, params.reload, params.smoothReload, params.heat, params.x + Tmp.v1.x, params.y + Tmp.v1.y, i * sign + rotMove * prog * sign + params.rotation); + childParam.sideOverride = i; + child.draw(childParam); + } + } } @Override @@ -121,6 +137,9 @@ public class RegionPart extends WeaponPart{ } heat = Core.atlas.find(name + suffix + "-heat"); + if(child != null){ + child.load(name); + } } @Override @@ -128,5 +147,8 @@ public class RegionPart extends WeaponPart{ if(outline && drawRegion){ out.addAll(regions); } + if(child != null){ + child.getOutlines(out); + } } } \ No newline at end of file diff --git a/core/src/mindustry/entities/part/WeaponPart.java b/core/src/mindustry/entities/part/WeaponPart.java index cbb3426f7f..98d4ab94e3 100644 --- a/core/src/mindustry/entities/part/WeaponPart.java +++ b/core/src/mindustry/entities/part/WeaponPart.java @@ -1,6 +1,7 @@ package mindustry.entities.part; import arc.graphics.g2d.*; +import arc.math.*; import arc.struct.*; public abstract class WeaponPart{ @@ -18,17 +19,83 @@ public abstract class WeaponPart{ /** Parameters for drawing a part in draw(). */ public static class PartParams{ //TODO document - public float warmup, reload, heat; + public float warmup, reload, smoothReload, heat; public float x, y, rotation; + public int sideOverride = -1; - public PartParams set(float warmup, float reload, float heat, float x, float y, float rotation){ + public PartParams set(float warmup, float reload, float smoothReload, float heat, float x, float y, float rotation){ this.warmup = warmup; this.reload = reload; this.heat = heat; + this.smoothReload = smoothReload; this.x = x; this.y = y; this.rotation = rotation; + this.sideOverride = -1; return this; } } + + public interface PartProgress{ + PartProgress + + reload = p -> p.reload, + smoothReload = p -> p.smoothReload, + warmup = p -> p.warmup, + heat = p -> p.heat; + + float get(PartParams p); + + static PartProgress constant(float value){ + return p -> value; + } + + default PartProgress inv(){ + return p -> 1f - get(p); + } + + default PartProgress delay(float amount){ + return p -> Mathf.clamp((get(p) - amount) / (1f - amount)); + } + + default PartProgress shorten(float amount){ + return p -> Mathf.clamp(get(p) / (1f - amount)); + } + + default PartProgress blend(PartProgress other, float amount){ + return p -> Mathf.lerp(get(p), other.get(p), amount); + } + + default PartProgress mul(PartProgress other){ + return p -> get(p) * other.get(p); + } + + default PartProgress min(PartProgress other){ + return p -> Math.min(get(p), other.get(p)); + } + + default PartProgress sin(float scl, float mag){ + return p -> Mathf.clamp(get(p) + Mathf.sin(scl, mag)); + } + + default PartProgress absin(float scl, float mag){ + return p -> Mathf.clamp(get(p) + Mathf.absin(scl, mag)); + } + + default PartProgress apply(PartProgress other, PartFunc func){ + return p -> func.get(get(p), other.get(p)); + } + + default PartProgress add(float amount){ + return p -> Mathf.clamp(get(p) + amount); + } + + default PartProgress curve(Interp interp){ + return p -> interp.apply(get(p)); + } + } + + public interface PartFunc{ + float get(float a, float b); + } } diff --git a/core/src/mindustry/entities/units/WeaponMount.java b/core/src/mindustry/entities/units/WeaponMount.java index a0e5219bcf..8473edaf9c 100644 --- a/core/src/mindustry/entities/units/WeaponMount.java +++ b/core/src/mindustry/entities/units/WeaponMount.java @@ -20,6 +20,8 @@ public class WeaponMount{ public float heat; /** lerps to 1 when shooting, 0 when not */ public float warmup; + /** lerps to reload time */ + public float smoothReload; /** aiming position in world coordinates */ public float aimX, aimY; /** whether to shoot right now */ diff --git a/core/src/mindustry/type/Weapon.java b/core/src/mindustry/type/Weapon.java index fbe7c00d66..553234c3d0 100644 --- a/core/src/mindustry/type/Weapon.java +++ b/core/src/mindustry/type/Weapon.java @@ -93,7 +93,7 @@ public class Weapon implements Cloneable{ /** ticks to cool down the heat region */ public float cooldownTime = 20f; /** lerp speed for shoot warmup, only used for parts */ - public float shootWarmupSpeed = 0.1f; + public float shootWarmupSpeed = 0.1f, smoothReloadSpeed = 0.15f; /** random sound pitch range */ public float soundPitchMin = 0.8f, soundPitchMax = 1f; /** whether shooter rotation is ignored when shooting. */ @@ -193,7 +193,7 @@ public class Weapon implements Cloneable{ Draw.xscl = -Mathf.sign(flipSprite); if(parts.size > 0){ - WeaponPart.params.set(mount.warmup, 1f - Mathf.clamp(mount.reload / reload), mount.heat, wx, wy, weaponRotation + 90); + WeaponPart.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]; @@ -246,6 +246,7 @@ public class Weapon implements Cloneable{ mount.reload = Math.max(mount.reload - Time.delta * unit.reloadMultiplier, 0); mount.recoil = Mathf.approachDelta(mount.recoil, 0, (Math.abs(recoil) * unit.reloadMultiplier) / recoilTime); mount.warmup = Mathf.lerpDelta(mount.warmup, can && mount.shoot ? 1f : 0f, shootWarmupSpeed); + mount.smoothReload = Mathf.lerpDelta(mount.smoothReload, mount.reload / reload, smoothReloadSpeed); //rotate if applicable if(rotate && (mount.rotate || mount.shoot) && can){ diff --git a/core/src/mindustry/world/draw/DrawTurret.java b/core/src/mindustry/world/draw/DrawTurret.java index 952a4c7613..5a26148ac7 100644 --- a/core/src/mindustry/world/draw/DrawTurret.java +++ b/core/src/mindustry/world/draw/DrawTurret.java @@ -65,7 +65,8 @@ public class DrawTurret extends DrawBlock{ Draw.z(Layer.turret); } - var params = WeaponPart.params.set(build.warmup(), tb.progress(), tb.heat, tb.x + tb.recoilOffset.x, tb.y + tb.recoilOffset.y, tb.rotation); + //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); for(var part : parts){ part.draw(params);