Reactivity and Assorted Changes (#11245)

* Implemented turretDepositCooldown (1)

* Implemented activationTime for Turrets (1)

* activationTime for Turrets Fixes (2)
readSync() line isn't needed,
Overdrive should not make the cooldown go faster, it stays as is unless there is a good argument against it.

* activationTime (3): Descriptions and SetBars()
Updated arc and lancer descriptions.
Added setBars() stuff for clearness.

* turretDepositCooldown (2):
now depositCooldown and is now on a per turret basis if needed... will potentially need further iteration

* turretDepositCooldown (3): made it opt in
added an internal gamerule (long name, need to reduce it later) to enable the balance (oriented for pvp)
depositCooldown = 0 by def
added depositCooldown entries in Blocks.java

* activationTime (4): Minor Adjustments to UI

* activitationTime (5): Implemented drawInactive() and inactiveColo, and arc/lancer sprite
Thinking if I should set the sprites to be white, then use Color.gray...

* turretDepositCooldown (4): Made the cooldown only affect turrets

* Reload Turret Coolant Fixes (1)
Made coolant and overdrive effects not get chopped off from Math.min when its too high

* activationTime (6): activationTime is now at the Base Turret Level
Also implemented the code for TractorBeamTurret thanks to the code changes
Parallax can have the feature if needed for future balancing.
Hopefully nothing was screwed up in the transition...

* Counterbalance (1): Swarmer 8 -> 7 firerate nerf, and Cyclone 7.5 -> 6 firerate nerf [First Attempt]

* Reload Turret Coolant Fixes (2): added a buffer if the excessReload exceeds more than 2 reloads.

* Reload Turret Coolant Fixes (3): hotfix, the buffer actually works this time

* turretDepositCooldown (5): Made the cooldown seperate from itemDepositCooldown

* Implemented armorMultiplier (1)

* fixing a merge

* (AT:7a) Reverted Saving Capability

* (AT:7b) Removed activation timer from lancer and arc as well as sprites

* (AT:7c) Removed visuals for activation timer
will be reimplemented via a greyscale effect if returned rather than via sprites

* (AT:7aa) oops

* Made the default value 0 when placed - Activation Timer (8)

* (depositCooldown : 6) Moved depositCooldown to Blocks.java, cleaned up canDepositItem(), removed enableTurretDepositCooldown

* armorMultiplier (2) - oops, made armorMultipler = 0 correspond to 100% armorPiercing

* Update core/src/mindustry/input/InputHandler.java

* Reload Turret Coolant Fixes (4): A bit too high

---------

Co-authored-by: Anuken <arnukren@gmail.com>
This commit is contained in:
SomeonesShade
2026-01-01 03:39:22 +08:00
committed by GitHub
parent 87e2dc69a2
commit 4122a9d51d
16 changed files with 155 additions and 15 deletions

View File

@@ -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:

View File

@@ -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();
}};

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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;

View File

@@ -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;

View File

@@ -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();

View File

@@ -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);

View File

@@ -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) */

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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

View File

@@ -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--;
}
}

View File

@@ -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;

View File

@@ -95,6 +95,7 @@ public class Stat implements Comparable<Stat>{
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),

View File

@@ -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)));
}