diff --git a/core/assets/bundles/bundle.properties b/core/assets/bundles/bundle.properties index 8e4e733140..6cc516ca9a 100644 --- a/core/assets/bundles/bundle.properties +++ b/core/assets/bundles/bundle.properties @@ -1073,6 +1073,7 @@ stat.ammo = Ammo stat.shieldhealth = Shield Health stat.cooldowntime = Cooldown Time stat.regenerationrate = Regeneration Rate +stat.activationtime = Activation Time stat.explosiveness = Explosiveness stat.basedeflectchance = Base Deflect Chance stat.lightningchance = Lightning Chance @@ -1183,6 +1184,8 @@ bar.input = Input bar.output = Output bar.strength = [stat]{0}[lightgray]x strength bar.regenerationrate = [stat]{0}/sec[lightgray] regen rate +bar.activationtimer = Activates in {0} +bar.activated = Activated units.processorcontrol = [lightgray]Processor Controlled @@ -1191,6 +1194,9 @@ bullet.splashdamage = [stat]{0}[lightgray] area dmg ~ [stat]{1}[lightgray] tiles bullet.incendiary = [stat]incendiary bullet.homing = [stat]homing bullet.armorpierce = [stat]armor piercing +bullet.armorweakness = [red]{0}%[lightgray] armor weakness +bullet.armorpiercing = [stat]{0}%[lightgray] armor piercing +bullet.antiarmor = [stat]{0}x[lightgray] anti-armor bullet.maxdamagefraction = [stat]{0}%[lightgray] damage limit bullet.suppression = [stat]{0}[lightgray] seconds of repair suppression ~ [stat]{1}[lightgray] tiles bullet.interval = [stat]{0}/sec[lightgray] interval bullets: diff --git a/core/src/mindustry/content/Blocks.java b/core/src/mindustry/content/Blocks.java index 6fe2b66161..c23011525f 100644 --- a/core/src/mindustry/content/Blocks.java +++ b/core/src/mindustry/content/Blocks.java @@ -3322,6 +3322,7 @@ public class Blocks{ coolant = consumeCoolant(0.1f); coolantMultiplier = 10f; researchCostMultiplier = 0.05f; + depositCooldown = 2.0f; limitRange(5f); }}; @@ -3407,6 +3408,7 @@ public class Blocks{ shootSound = Sounds.shootScatter; coolant = consumeCoolant(0.2f); researchCostMultiplier = 0.05f; + depositCooldown = 0.5f; limitRange(2); }}; @@ -3453,6 +3455,7 @@ public class Blocks{ health = 400; shootSound = Sounds.shootFlame; coolant = consumeCoolant(0.1f); + depositCooldown = 1.0f; }}; hail = new ItemTurret("hail"){{ @@ -3517,6 +3520,7 @@ public class Blocks{ shootSound = Sounds.shootArtillerySmall; coolant = consumeCoolant(0.1f); coolantMultiplier = 10f; + depositCooldown = 2.0f; limitRange(0f); }}; @@ -3581,6 +3585,7 @@ public class Blocks{ chargeEffect = new MultiEffect(Fx.lancerLaserCharge, Fx.lancerLaserChargeBegin); buildingDamageMultiplier = 0.25f; + armorMultiplier = 4f; hitEffect = Fx.hitLancer; hitSize = 4; lifetime = 16f; @@ -3706,7 +3711,7 @@ public class Blocks{ }}; shootY = 4.5f; - reload = 30f; + reload = 60f * 4f / 7f; inaccuracy = 10f; range = 240f; consumeAmmoOnce = false; @@ -3717,6 +3722,7 @@ public class Blocks{ limitRange(5f); coolant = consumeCoolant(0.3f); + depositCooldown = 2.0f; }}; salvo = new ItemTurret("salvo"){{ @@ -3818,6 +3824,7 @@ public class Blocks{ limitRange(); coolant = consumeCoolant(0.2f); + depositCooldown = 2.0f; }}; segment = new PointDefenseTurret("segment"){{ @@ -3935,6 +3942,7 @@ public class Blocks{ shootEffect = smokeEffect = Fx.thoriumShoot; }} ); + depositCooldown = 1.0f; }}; ripple = new ItemTurret("ripple"){{ @@ -4063,6 +4071,7 @@ public class Blocks{ coolant = consumeCoolant(0.3f); scaledHealth = 130; + depositCooldown = 2.0f; shootSound = Sounds.shootRipple; }}; @@ -4168,7 +4177,7 @@ public class Blocks{ } }}; - reload = 8f; + reload = 10f; range = 200f; size = 3; recoil = 1.5f; @@ -4180,6 +4189,7 @@ public class Blocks{ coolant = consumeCoolant(0.3f); scaledHealth = 145; + depositCooldown = 2.0f; limitRange(); }}; @@ -4224,6 +4234,7 @@ public class Blocks{ scaledHealth = 150; coolant = consumeCoolant(1f); + depositCooldown = 2.0f; consumePower(10f); }}; @@ -4290,6 +4301,7 @@ public class Blocks{ scaledHealth = 160; coolant = consumeCoolant(1f); + depositCooldown = 2.0f; limitRange(); }}; diff --git a/core/src/mindustry/entities/bullet/BulletType.java b/core/src/mindustry/entities/bullet/BulletType.java index a74c13edce..9e2b10e846 100644 --- a/core/src/mindustry/entities/bullet/BulletType.java +++ b/core/src/mindustry/entities/bullet/BulletType.java @@ -181,6 +181,8 @@ public class BulletType extends Content implements Cloneable{ public boolean fragOnAbsorb = true; /** If true, unit armor is ignored in damage calculations. */ public boolean pierceArmor = false; + /** Multiplies the unit armor used in damage calculations. Used for armor weakness, armor piercing, and anti-armor. */ + public float armorMultiplier = 1f; /** If true, the bullet will "stick" to enemies and get deactivated on collision. */ public boolean sticky = false; /** Extra time added to bullet when it sticks to something. */ @@ -483,6 +485,8 @@ public class BulletType extends Content implements Cloneable{ } if(pierceArmor){ h.damagePierce(damage); + }else if(armorMultiplier != 1){ + h.damageArmorMult(damage, armorMultiplier); }else{ h.damage(damage); } diff --git a/core/src/mindustry/entities/comp/BuildingComp.java b/core/src/mindustry/entities/comp/BuildingComp.java index baacb406ab..617a74fc96 100644 --- a/core/src/mindustry/entities/comp/BuildingComp.java +++ b/core/src/mindustry/entities/comp/BuildingComp.java @@ -1727,7 +1727,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc, float damage = other.type.buildingDamage(other); if(!other.type.pierceArmor){ - damage = Damage.applyArmor(damage, block.armor); + damage = Damage.applyArmor(damage, block.armor * other.type.armorMultiplier); } damage(other, other.team, damage); diff --git a/core/src/mindustry/entities/comp/HealthComp.java b/core/src/mindustry/entities/comp/HealthComp.java index f315b3fb75..cfc3cb92c4 100644 --- a/core/src/mindustry/entities/comp/HealthComp.java +++ b/core/src/mindustry/entities/comp/HealthComp.java @@ -58,6 +58,16 @@ abstract class HealthComp implements Entityc, Posc{ damagePierce(amount, true); } + /** Damage and multiply armor received. */ + void damageArmorMult(float amount, float armorMult, boolean withEffect){ + damage(amount, withEffect); + } + + /** Damage and multiply armor received. */ + void damageArmorMult(float amount, float armorMult){ + damageArmorMult(amount, armorMult, true); + } + void damage(float amount){ if(Float.isNaN(health)) health = 0f; @@ -86,6 +96,10 @@ abstract class HealthComp implements Entityc, Posc{ damagePierce(amount * Time.delta, hitTime <= -20 + hitDuration); } + void damageContinuousArmorMult(float amount, float armorMult){ + damageArmorMult(amount * Time.delta, armorMult, hitTime <= -20 + hitDuration); + } + void clampHealth(){ health = Math.min(health, maxHealth); if(Float.isNaN(health)) health = 0f; diff --git a/core/src/mindustry/entities/comp/ShieldComp.java b/core/src/mindustry/entities/comp/ShieldComp.java index f2f6e4c808..f67ece6f99 100644 --- a/core/src/mindustry/entities/comp/ShieldComp.java +++ b/core/src/mindustry/entities/comp/ShieldComp.java @@ -42,6 +42,18 @@ abstract class ShieldComp implements Healthc, Posc{ } } + @Replace + @Override + public void damageArmorMult(float amount, float armorMult, boolean withEffect){ + float pre = hitTime; + + rawDamage(Damage.applyArmor(amount, armorOverride >= 0f ? armorOverride * armorMult : armor * armorMult) / healthMultiplier / Vars.state.rules.unitHealth(team)); + + if(!withEffect){ + hitTime = pre; + } + } + protected void rawDamage(float amount){ boolean hadShields = shield > 0.0001f; diff --git a/core/src/mindustry/graphics/OverlayRenderer.java b/core/src/mindustry/graphics/OverlayRenderer.java index e9c3450503..78533c7fe7 100644 --- a/core/src/mindustry/graphics/OverlayRenderer.java +++ b/core/src/mindustry/graphics/OverlayRenderer.java @@ -248,7 +248,7 @@ public class OverlayRenderer{ Building build = world.buildWorld(v.x, v.y); if(input.canDropItem() && build != null && build.interactable(player.team()) && build.acceptStack(player.unit().item(), player.unit().stack.amount, player.unit()) > 0 && player.within(build, itemTransferRange) && - input.itemDepositCooldown <= 0f){ + input.canDepositItem(build)){ boolean invalid = !build.allowDeposit(); diff --git a/core/src/mindustry/input/InputHandler.java b/core/src/mindustry/input/InputHandler.java index c4263b7323..53245fb1f6 100644 --- a/core/src/mindustry/input/InputHandler.java +++ b/core/src/mindustry/input/InputHandler.java @@ -37,6 +37,7 @@ import mindustry.ui.fragments.*; import mindustry.world.*; import mindustry.world.blocks.ConstructBlock.*; import mindustry.world.blocks.*; +import mindustry.world.blocks.defense.turrets.*; import mindustry.world.blocks.distribution.*; import mindustry.world.blocks.payloads.*; import mindustry.world.blocks.storage.*; @@ -2154,7 +2155,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{ if(build != null && build.acceptStack(stack.item, stack.amount, player.unit()) > 0 && build.interactable(player.team()) && build.block.hasItems && player.unit().stack().amount > 0 && build.interactable(player.team())){ - if(build.allowDeposit() && itemDepositCooldown <= 0f){ + if(build.allowDeposit() && canDepositItem(build)){ Call.transferInventory(player, build); itemDepositCooldown = state.rules.itemDepositCooldown; } @@ -2163,6 +2164,14 @@ public abstract class InputHandler implements InputProcessor, GestureListener{ } } + public boolean canDepositItem(Building build){ + //takes advantage of itemDepositCooldown being able to be negative, allows the cooldown to be different for each building + if(build.block.depositCooldown >= 0){ + return itemDepositCooldown - state.rules.itemDepositCooldown <= -build.block.depositCooldown; + } + return itemDepositCooldown <= 0; + } + public void rebuildArea(int x1, int y1, int x2, int y2){ NormalizeResult result = Placement.normalizeArea(x1, y1, x2, y2, rotation, false, 999999999); Tmp.r1.set(result.x * tilesize, result.y * tilesize, (result.x2 - result.x) * tilesize, (result.y2 - result.y) * tilesize); diff --git a/core/src/mindustry/world/Block.java b/core/src/mindustry/world/Block.java index e0658b5640..6cbed7004d 100644 --- a/core/src/mindustry/world/Block.java +++ b/core/src/mindustry/world/Block.java @@ -66,6 +66,8 @@ public class Block extends UnlockableContent implements Senseable{ public boolean acceptsItems = false; /** If true, this block won't be affected by the onlyDepositCore rule. */ public boolean alwaysAllowDeposit = false; + /** Cooldown, in seconds, applied to player item depositing when any item is deposited to this block. Overrides the itemDepositCooldown if non-negative. */ + public float depositCooldown = -1f; /** If true, all item capacities of this block are separate instead of pooled as one number. */ public boolean separateItemCapacity = false; /** maximum items this block can carry (usually, this is per-type of item) */ diff --git a/core/src/mindustry/world/blocks/defense/turrets/BaseTurret.java b/core/src/mindustry/world/blocks/defense/turrets/BaseTurret.java index b6c7510349..20ec8d43d9 100644 --- a/core/src/mindustry/world/blocks/defense/turrets/BaseTurret.java +++ b/core/src/mindustry/world/blocks/defense/turrets/BaseTurret.java @@ -1,5 +1,7 @@ package mindustry.world.blocks.defense.turrets; +import arc.*; +import arc.graphics.*; import arc.math.*; import arc.struct.*; import arc.util.*; @@ -8,6 +10,7 @@ import mindustry.entities.*; import mindustry.gen.*; import mindustry.graphics.*; import mindustry.logic.*; +import mindustry.ui.*; import mindustry.world.*; import mindustry.world.blocks.*; import mindustry.world.consumers.*; @@ -21,6 +24,8 @@ public class BaseTurret extends Block{ public float rotateSpeed = 5; public float fogRadiusMultiplier = 1f; public boolean disableOverlapCheck = false; + /** How much time to start shooting after placement. */ + public float activationTime = 0f; /** Effect displayed when coolant is used. */ public Effect coolEffect = Fx.fuelburn; @@ -90,10 +95,31 @@ public class BaseTurret extends Block{ super.setStats(); stats.add(Stat.shootRange, range / tilesize, StatUnit.blocks); + if(activationTime > 0) stats.add(Stat.activationTime, activationTime / 60f, StatUnit.seconds); + } + + @Override + public void setBars(){ + super.setBars(); + + if(activationTime > 0){ + addBar("activationtimer", (BaseTurretBuild entity) -> + new Bar(() -> + (entity.activationTimer > 0)? Core.bundle.format("bar.activationtimer", Mathf.ceil(entity.activationTimer / 60f)) : Core.bundle.get("bar.activated"), + () -> (entity.activationTimer > 0)? Pal.lightOrange : Pal.techBlue, + () -> 1 - entity.activationTimer / activationTime)); + } } public class BaseTurretBuild extends Building implements Ranged, RotBlock{ public float rotation = 90; + public float activationTimer = 0; + + @Override + public void placed(){ + super.placed(); + activationTimer = activationTime; + } @Override public float range(){ @@ -113,5 +139,10 @@ public class BaseTurret extends Block{ public float estimateDps(){ return 0f; } + + @Override + public BlockStatus status() { + return (activationTimer <= 0)? super.status() : BlockStatus.inactive; + } } } diff --git a/core/src/mindustry/world/blocks/defense/turrets/ReloadTurret.java b/core/src/mindustry/world/blocks/defense/turrets/ReloadTurret.java index 8748b91ed8..6b5721987b 100644 --- a/core/src/mindustry/world/blocks/defense/turrets/ReloadTurret.java +++ b/core/src/mindustry/world/blocks/defense/turrets/ReloadTurret.java @@ -26,7 +26,7 @@ public class ReloadTurret extends BaseTurret{ public float reloadCounter; protected void updateCooling(){ - if(reloadCounter < reload && coolant != null && coolant.efficiency(this) > 0 && efficiency > 0){ + if(canReload() && coolant != null && coolant.efficiency(this) > 0 && efficiency > 0){ float capacity = coolant instanceof ConsumeLiquidFilter filter ? filter.getConsumed(this).heatCapacity : (coolant.consumes(liquids.current()) ? liquids.current().heatCapacity : 0.4f); float amount = coolant.amount * coolant.efficiency(this); coolant.update(this); @@ -45,5 +45,9 @@ public class ReloadTurret extends BaseTurret{ protected float baseReloadSpeed(){ return efficiency; } + + protected boolean canReload(){ + return reloadCounter < reload; + } } } diff --git a/core/src/mindustry/world/blocks/defense/turrets/TractorBeamTurret.java b/core/src/mindustry/world/blocks/defense/turrets/TractorBeamTurret.java index aa447d6ae7..35a746dbc5 100644 --- a/core/src/mindustry/world/blocks/defense/turrets/TractorBeamTurret.java +++ b/core/src/mindustry/world/blocks/defense/turrets/TractorBeamTurret.java @@ -76,6 +76,11 @@ public class TractorBeamTurret extends BaseTurret{ @Override public void updateTile(){ + if(activationTimer > 0){ + activationTimer -= Time.delta; + return; + } + float eff = efficiency * coolantMultiplier, edelta = eff * delta(); //retarget diff --git a/core/src/mindustry/world/blocks/defense/turrets/Turret.java b/core/src/mindustry/world/blocks/defense/turrets/Turret.java index 9f8e5edd4a..cb4a38ee8d 100644 --- a/core/src/mindustry/world/blocks/defense/turrets/Turret.java +++ b/core/src/mindustry/world/blocks/defense/turrets/Turret.java @@ -278,6 +278,8 @@ public class Turret extends ReloadTurret{ public @Nullable float[] curRecoils; public float shootWarmup, charge, warmupHold = 0f; public int totalShots, barrelCounter; + public float excessReload = 0; + public int reloadShots = 0; public boolean logicShooting = false; public @Nullable Posc target; public Vec2 targetPos = new Vec2(); @@ -419,7 +421,7 @@ public class Turret extends ReloadTurret{ } public boolean isActive(){ - return (target != null || wasShooting) && enabled; + return (target != null || wasShooting) && enabled && activationTimer <= 0; } public void targetPosition(Posc pos){ @@ -481,8 +483,6 @@ public class Turret extends ReloadTurret{ shootWarmup = Mathf.lerpDelta(shootWarmup, warmupTarget, shootWarmupSpeed * (warmupTarget > 0 ? efficiency : 1f)); } - wasShooting = false; - curRecoil = Mathf.approachDelta(curRecoil, 0, 1 / recoilTime); if(recoils > 0){ if(curRecoils == null) curRecoils = new float[recoils]; @@ -515,8 +515,11 @@ public class Turret extends ReloadTurret{ if(reloadWhileCharging || !charging()){ updateReload(); updateCooling(); + capReload(); } + wasShooting = false; + if(state.rules.fog){ float newRange = hasAmmo() ? peekAmmo().rangeChange : 0f; if(newRange != lastRangeChange){ @@ -525,6 +528,11 @@ public class Turret extends ReloadTurret{ } } + if(activationTimer > 0){ + activationTimer -= Time.delta; + return; + } + if(hasAmmo()){ if(Float.isNaN(reloadCounter)) reloadCounter = 0; @@ -673,11 +681,30 @@ public class Turret extends ReloadTurret{ return queuedBullets > 0 && shoot.firstShotDelay > 0; } - protected void updateReload(){ - reloadCounter += delta() * ammoReloadMultiplier() * baseReloadSpeed(); + @Override + protected boolean canReload(){ + //keep reloading as the turret keeps shooting + return reloadShots < 1 || wasShooting; + } - //cap reload for visual reasons + protected void updateReload(){ + if(!canReload()) return; + reloadCounter += delta() * ammoReloadMultiplier() * baseReloadSpeed(); + } + + protected void capReload(){ + //cap reload for visual reasons, need to store the excess reload to keep the firerate consistent + if(canReload() && reloadCounter >= reload){ + reloadShots += (int)(reloadCounter / reload); + excessReload += reloadCounter % reload; + } reloadCounter = Math.min(reloadCounter, reload); + reloadShots = Math.min(reloadShots, 5); + + if(!wasShooting){ + reloadShots = 0; + excessReload = 0; + } } @Override @@ -687,12 +714,14 @@ public class Turret extends ReloadTurret{ protected void updateShooting(){ - if(reloadCounter >= reload && !charging() && shootWarmup >= minWarmup){ + if(reloadShots > 0 && !charging() && shootWarmup >= minWarmup){ BulletType type = peekAmmo(); shoot(type); - reloadCounter %= reload; + reloadCounter = excessReload; + excessReload = 0; + reloadShots--; } } diff --git a/core/src/mindustry/world/meta/BlockStatus.java b/core/src/mindustry/world/meta/BlockStatus.java index d21887244e..f22b3d5054 100644 --- a/core/src/mindustry/world/meta/BlockStatus.java +++ b/core/src/mindustry/world/meta/BlockStatus.java @@ -7,7 +7,8 @@ public enum BlockStatus{ active(Color.valueOf("5ce677")), noOutput(Color.orange), noInput(Pal.remove), - logicDisable(Color.valueOf("8a73c6")); + logicDisable(Color.valueOf("8a73c6")), + inactive(Color.lightGray); public final Color color; diff --git a/core/src/mindustry/world/meta/Stat.java b/core/src/mindustry/world/meta/Stat.java index 37974939ef..6dcef85afe 100644 --- a/core/src/mindustry/world/meta/Stat.java +++ b/core/src/mindustry/world/meta/Stat.java @@ -95,6 +95,7 @@ public class Stat implements Comparable{ shieldHealth = new Stat("shieldHealth", StatCat.function), cooldownTime = new Stat("cooldownTime", StatCat.function), regenerationRate = new Stat("regenerationRate", StatCat.function), + activationTime = new Stat("activationTime", StatCat.function), moduleTier = new Stat("moduletier", StatCat.function), unitType = new Stat("unittype", StatCat.function), diff --git a/core/src/mindustry/world/meta/StatValues.java b/core/src/mindustry/world/meta/StatValues.java index b2694d1c8f..8556ef3adc 100644 --- a/core/src/mindustry/world/meta/StatValues.java +++ b/core/src/mindustry/world/meta/StatValues.java @@ -704,6 +704,16 @@ public class StatValues{ sep(bt, "@bullet.armorpierce"); } + if(type.armorMultiplier != 1f){ + if(type.armorMultiplier > 1f){ + sep(bt, Core.bundle.format("bullet.armorweakness", (int)(type.armorMultiplier * 100))); + }else if(Mathf.sign(type.armorMultiplier) == 1){ + sep(bt, Core.bundle.format("bullet.armorpiercing", (int)((1 - type.armorMultiplier) * 100))); + }else{ + sep(bt, Core.bundle.format("bullet.antiarmor", (-type.armorMultiplier))); + } + } + if(type.maxDamageFraction > 0){ sep(bt, Core.bundle.format("bullet.maxdamagefraction", (int)(type.maxDamageFraction * 100))); }