Merge remote-tracking branch 'origin/master'

This commit is contained in:
Anuken
2026-01-01 11:14:05 -05:00
50 changed files with 359 additions and 15 deletions

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.*;
@@ -2149,7 +2150,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;
}
@@ -2158,6 +2159,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)));
}