diff --git a/core/assets-raw/sprites/units/weapons/avert-weapon.png b/core/assets-raw/sprites/units/weapons/avert-weapon.png new file mode 100644 index 0000000000..08083aef14 Binary files /dev/null and b/core/assets-raw/sprites/units/weapons/avert-weapon.png differ diff --git a/core/src/mindustry/ai/types/BuilderAI.java b/core/src/mindustry/ai/types/BuilderAI.java index 01f90942e5..d06812d267 100644 --- a/core/src/mindustry/ai/types/BuilderAI.java +++ b/core/src/mindustry/ai/types/BuilderAI.java @@ -173,6 +173,6 @@ public class BuilderAI extends AIController{ @Override public boolean shouldShoot(){ - return !unit.isBuilding(); + return !unit.isBuilding() && unit.type.canAttack; } } diff --git a/core/src/mindustry/content/UnitTypes.java b/core/src/mindustry/content/UnitTypes.java index e98b39142a..a43d5a34f5 100644 --- a/core/src/mindustry/content/UnitTypes.java +++ b/core/src/mindustry/content/UnitTypes.java @@ -3119,18 +3119,44 @@ public class UnitTypes{ lowAltitude = false; flying = true; drag = 0.08f; - speed = 2.5f; - rotateSpeed = 5f; + speed = 2f; + rotateSpeed = 4f; accel = 0.09f; - health = 600f; + health = 800f; armor = 2f; hitSize = 12f; engineSize = 0; + fogRadius = 25; setEnginesMirror( new UnitEngine(34 / 4f, 31 / 4f, 3f, 45f), new UnitEngine(35 / 4f, -38 / 4f, 3f, 315f) ); + + weapons.add(new Weapon("avert-weapon"){{ + reload = 35f; + x = 4f; + y = 6.25f; + shootY = 5.75f; + recoil = 1.5f; + top = false; + layerOffset = -0.01f; + rotate = false; + + //TODO cooler + balancing + bullet = new BasicBulletType(5f, 15){{ + width = 7f; + height = 12f; + lifetime = 25f; + shootEffect = Fx.sparkShoot; + smokeEffect = Fx.shootBigSmoke; + hitColor = backColor = trailColor = Pal.suppress; + frontColor = Color.white; + trailWidth = 1.5f; + trailLength = 5; + hitEffect = despawnEffect = Fx.hitBulletColor; + }}; + }}); }}; quell = new ErekirUnitType("quell"){{ diff --git a/core/src/mindustry/entities/units/AIController.java b/core/src/mindustry/entities/units/AIController.java index cd79041957..9d83c8b7ba 100644 --- a/core/src/mindustry/entities/units/AIController.java +++ b/core/src/mindustry/entities/units/AIController.java @@ -145,7 +145,7 @@ public class AIController implements UnitController{ float wrange = weapon.range(); //let uncontrollable weapons do their own thing - if(!weapon.controllable) continue; + if(!weapon.controllable || weapon.noAttack) continue; float mountX = unit.x + Angles.trnsx(rotation, weapon.x, weapon.y), mountY = unit.y + Angles.trnsy(rotation, weapon.x, weapon.y); diff --git a/core/src/mindustry/game/FogControl.java b/core/src/mindustry/game/FogControl.java index 6c22af4f80..83d174b0fb 100644 --- a/core/src/mindustry/game/FogControl.java +++ b/core/src/mindustry/game/FogControl.java @@ -35,6 +35,7 @@ public final class FogControl implements CustomChunk{ private @Nullable Thread dynamicFogThread; private boolean justLoaded = false; + private boolean loadedStatic = false; public FogControl(){ Events.on(ResetEvent.class, e -> { @@ -44,19 +45,18 @@ public final class FogControl implements CustomChunk{ Events.on(WorldLoadEvent.class, e -> { stop(); + loadedStatic = false; justLoaded = true; ww = world.width(); wh = world.height(); //all old buildings have static light scheduled around them if(state.rules.fog && state.rules.staticFog){ - synchronized(staticEvents){ - for(var build : Groups.build){ - if(build.block.flags.contains(BlockFlag.hasFogRadius)){ - staticEvents.add(FogEvent.get(build.tile.x, build.tile.y, Mathf.round(build.fogRadius()), build.team.id)); - } - } - } + pushStaticBlocks(); + //force draw all static stuff immediately + updateStatic(); + + loadedStatic = true; } }); @@ -133,6 +133,16 @@ public final class FogControl implements CustomChunk{ } } + void pushStaticBlocks(){ + synchronized(staticEvents){ + for(var build : Groups.build){ + if(build.block.flags.contains(BlockFlag.hasFogRadius)){ + pushEvent(FogEvent.get(build.tile.x, build.tile.y, Mathf.round(build.fogRadius()), build.team.id)); + } + } + } + } + void pushEvent(long event){ if(!state.rules.staticFog) return; @@ -159,6 +169,13 @@ public final class FogControl implements CustomChunk{ fog = new FogData[256]; } + //force update static + if(state.rules.staticFog && !loadedStatic){ + pushStaticBlocks(); + updateStatic(); + loadedStatic = true; + } + //not run clientside, the CPU side isn't needed here. if(staticFogThread == null && !net.client()){ staticFogThread = new StaticFogThread(); @@ -271,25 +288,29 @@ public final class FogControl implements CustomChunk{ } } - //I really don't like synchronizing here, but there should be *some* performance benefit at least - synchronized(staticEvents){ - int size = staticEvents.size; - for(int i = 0; i < size; i++){ - long event = staticEvents.items[i]; - int x = FogEvent.x(event), y = FogEvent.y(event), rad = FogEvent.radius(event), team = FogEvent.team(event); - var data = fog[team]; - if(data != null){ - circle(data.staticData, x, y, rad); - } - } - staticEvents.clear(); - } + updateStatic(); //ignore, don't want to crash this thread }catch(Exception e){} } } } + void updateStatic(){ + //I really don't like synchronizing here, but there should be *some* performance benefit at least + synchronized(staticEvents){ + int size = staticEvents.size; + for(int i = 0; i < size; i++){ + long event = staticEvents.items[i]; + int x = FogEvent.x(event), y = FogEvent.y(event), rad = FogEvent.radius(event), team = FogEvent.team(event); + var data = fog[team]; + if(data != null){ + circle(data.staticData, x, y, rad); + } + } + staticEvents.clear(); + } + } + class DynamicFogThread extends Thread{ final Bits cleared = new Bits(); diff --git a/core/src/mindustry/type/UnitType.java b/core/src/mindustry/type/UnitType.java index 468a994571..0a20701b70 100644 --- a/core/src/mindustry/type/UnitType.java +++ b/core/src/mindustry/type/UnitType.java @@ -181,6 +181,8 @@ public class UnitType extends UnlockableContent{ /** If true, all weapons will attack the same target. */ public boolean singleTarget = false; public boolean forceMultiTarget = false; + /** If false, this unit has no weapons that can attack. */ + public boolean canAttack = true; public boolean hidden = false; public boolean internal = false; /** Function used for calculating cost of moving with ControlPathfinder. Does not affect "normal" flow field pathfinding. */ @@ -464,7 +466,7 @@ public class UnitType extends UnlockableContent{ } if(fogRadius < 0){ - fogRadius = Math.max(lightRadius * 3.1f, 1f) / 8f; + fogRadius = Math.max(11f * 2.3f * 3f, hitSize * 2f) / 8f; } if(weapons.isEmpty()){ @@ -529,6 +531,8 @@ public class UnitType extends UnlockableContent{ weapons.each(Weapon::init); + canAttack = weapons.contains(w -> !w.noAttack); + //dynamically create ammo capacity based on firing rate if(ammoCapacity < 0){ float shotsPerSecond = weapons.sumf(w -> w.useAmmo ? 60f / w.reload : 0f); diff --git a/core/src/mindustry/type/Weapon.java b/core/src/mindustry/type/Weapon.java index c437e1a24c..a955ed73ef 100644 --- a/core/src/mindustry/type/Weapon.java +++ b/core/src/mindustry/type/Weapon.java @@ -89,6 +89,8 @@ public class Weapon implements Cloneable{ public float soundPitchMin = 0.8f, soundPitchMax = 1f; /** whether shooter rotation is ignored when shooting. */ public boolean ignoreRotation = false; + /** If true, this weapon cannot be used to attack targets. */ + public boolean noAttack = false; /** min velocity required for this weapon to shoot */ public float minShootVelocity = -1f; /** should the shoot effects follow the unit (effects need followParent set to true for this to work) */ diff --git a/core/src/mindustry/type/weapons/RepairBeamWeapon.java b/core/src/mindustry/type/weapons/RepairBeamWeapon.java index 4e381f5368..2de8102e53 100644 --- a/core/src/mindustry/type/weapons/RepairBeamWeapon.java +++ b/core/src/mindustry/type/weapons/RepairBeamWeapon.java @@ -58,6 +58,7 @@ public class RepairBeamWeapon extends Weapon{ useAmmo = false; mountType = HealBeamMount::new; recoil = 0f; + noAttack = true; } @Override