Merge branch 'EggleEgg-dps-dummy'

This commit is contained in:
Anuken
2026-09-01 19:53:57 -04:00
22 changed files with 404 additions and 4 deletions
@@ -6,6 +6,7 @@ atrax=1
beta=30
block=2
corvus=24
dummy=49
elude=45
flare=3
gamma=31
@@ -0,0 +1 @@
{fields:[{name:abilities,type:"mindustry.entities.abilities.Ability[]"},{name:aimX,type:float},{name:aimY,type:float},{name:building,type:Building},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:plans,type:arc.struct.Queue<mindustry.entities.units.BuildPlan>},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:updateBuilding,type:boolean},{name:vel,type:arc.math.geom.Vec2},{name:x,type:float},{name:y,type:float}]}
Binary file not shown.

After

Width:  |  Height:  |  Size: 966 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 583 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 494 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 366 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 576 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 609 B

+8
View File
@@ -1237,6 +1237,8 @@ stat.reactive = Reacts
stat.immunities = Immunities
stat.healing = Healing
stat.efficiency = [stat]{0}% Efficiency
stat.hitsize = Hitbox Size
stat.resettime = Reset Time
ability.forcefield = Force Field
ability.forcefield.description = Projects a force shield that absorbs bullets
@@ -1324,6 +1326,9 @@ bar.activationtimer = Activates in {0}
bar.activated = Activated
bar.upto = Up to {0}
bar.lerpwarmuptime = Affected by overdrive. Non-linear; uses the exponential decay formula.\nFor reference, this will reach 90% progress in {0} seconds, and 99% in {1} seconds.
bar.damagetimetotal = {0} dmg/{1} sec
bar.hits = {0} hits
bar.dps = {0} DPS
units.processorcontrol = [lightgray]Processor Controlled
@@ -1367,6 +1372,7 @@ bullet.notargetsbuildings = [stat] ignores buildings
unit.blocks = blocks
unit.blockssquared = blocks\u00B2
unit.worldunits = world units
unit.powersecond = power units/second
unit.tilessecond = tiles/second
unit.liquidsecond = liquid units/second
@@ -2657,6 +2663,8 @@ block.large-logic-display.description = Displays arbitrary graphics from a logic
block.tile-logic-display.description = Displays arbitrary graphics from a logic processor.\nSeamlessly connects to nearby tiled display blocks.
block.interplanetary-accelerator.description = A massive electromagnetic railgun tower. Accelerates cores to escape velocity for interplanetary deployment.
block.repair-turret.description = Continuously repairs the closest damaged unit in its vicinity. Optionally accepts coolant.
block.target-dummy.name = Target Dummy
block.target-dummy.description = Spawns a target dummy to test damage per second. Armor and flight can be configured.\nSandbox only.
#Erekir
block.core-bastion.description = Core of the base. Armored. Once destroyed, the sector is lost.
+2
View File
@@ -624,3 +624,5 @@
63058=metal-wall-3|block-metal-wall-3-ui
63057=ore-wall-graphite|block-ore-wall-graphite-ui
63056=large-canvas|block-large-canvas-ui
63055=target-dummy|block-target-dummy-ui
63054=dummy|unit-dummy-ui
Binary file not shown.
Binary file not shown.
+29
View File
@@ -0,0 +1,29 @@
package mindustry.ai.types;
import mindustry.entities.units.*;
import mindustry.gen.*;
public class NoAI implements UnitController{
protected Unit unit;
@Override
public Unit unit(){
return unit;
}
@Override
public void unit(Unit unit){
this.unit = unit;
}
@Override
public boolean isValidController(){
return true;
}
@Override
public void updateUnit(){}
@Override
public void removed(Unit unit){}
}
+7 -1
View File
@@ -105,7 +105,7 @@ public class Blocks{
shieldedWall,
//defense
mender, mendProjector, overdriveProjector, overdriveDome, forceProjector, shockMine,
mender, mendProjector, overdriveProjector, overdriveDome, forceProjector, shockMine, targetDummy,
//defense - erekir
radar,
@@ -6783,6 +6783,12 @@ public class Blocks{
allDatabaseTabs = true;
}};
targetDummy = new TargetDummy("target-dummy"){{
requirements(Category.defense, BuildVisibility.sandboxOnly, with());
size = 2;
alwaysUnlocked = true;
}};
//TODO move
illuminator = new LightBlock("illuminator"){{
requirements(Category.effect, BuildVisibility.lightingOnly, with(Items.graphite, 12, Items.silicon, 8, Items.lead, 8));
+35
View File
@@ -78,6 +78,7 @@ public class UnitTypes{
//special building tethered (has payload capability, because it's necessary sometimes)
public static @EntityDef({Unitc.class, BuildingTetherc.class, Payloadc.class}) UnitType manifold, assemblyDrone;
public static @EntityDef({TargetDummyc.class, Unitc.class}) UnitType dummy;
//tank
public static @EntityDef({Unitc.class, Tankc.class}) UnitType stell, locus, precept, vanquish, conquer;
@@ -4631,6 +4632,40 @@ public class UnitTypes{
envDisabled = Env.none;
}};
dummy = new UnitType("dummy"){{
controller = u -> new NoAI();
envEnabled = Env.any;
envDisabled = 0;
isEnemy = false;
allowedInPayloads = false;
logicControllable = false;
playerControllable = false;
hidden = true;
hoverable = false;
canBoost = true;
useUnitCap = false;
killable = false;
physics = false;
flyingLayer = Layer.flyingUnit - 1f;
drag = 0.33f;
hitSize = 12f;
hideDetails = false;
engineOffset = 7f;
engineSize = 2f;
for(int i = 0; i < 3; i++){
engines.add(new UnitEngine(Geometry.d4x(i) * engineOffset, Geometry.d4y(i) * engineOffset, engineSize, i * 90));
}
}
@Override
public void drawBody(Unit unit) {
applyColor(unit);
Drawf.spinSprite(region, unit.x, unit.y, unit.rotation - 90);
Draw.reset();
}};
//endregion
}
}
@@ -506,9 +506,11 @@ public class BulletType extends Content implements Cloneable{
}
if(entity instanceof Unit unit){
Tmp.v3.set(unit).sub(b).nor().scl(knockback * 80f);
if(impact) Tmp.v3.setAngle(b.rotation() + (knockback < 0 ? 180f : 0f));
unit.impulse(Tmp.v3);
if(unit.type.knockbackMultiplier > 0f){
Tmp.v3.set(unit).sub(b).nor().scl(knockback * 80f * unit.type.knockbackMultiplier);
if(impact) Tmp.v3.setAngle(b.rotation() + (knockback < 0 ? 180f : 0f));
unit.impulse(Tmp.v3);
}
unit.apply(status, statusDuration);
Events.fire(bulletDamageEvent.set(unit, b));
@@ -0,0 +1,25 @@
package mindustry.entities.comp;
import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
import mindustry.world.blocks.defense.TargetDummy.*;
@Component
abstract class TargetDummyComp implements Unitc, Healthc{
public @Nullable Building building;
@Override
public void update(){
if(building == null || (!building.isPayload() && !building.isValid())){
Call.unitDespawn(self()); //Don't despawn even if the building is on another team
}
}
@Override
public void rawDamage(float amount){
if(building instanceof TargetDummyBuild td){
td.dummyHit(amount);
}
}
}
+3
View File
@@ -23,6 +23,7 @@ public class ClassMap{
classes.put("PrebuildAI", mindustry.ai.types.PrebuildAI.class);
classes.put("RepairAI", mindustry.ai.types.RepairAI.class);
classes.put("SuicideAI", mindustry.ai.types.SuicideAI.class);
classes.put("NoAI", mindustry.ai.types.NoAI.class);
classes.put("Ability", mindustry.entities.abilities.Ability.class);
classes.put("ArmorPlateAbility", mindustry.entities.abilities.ArmorPlateAbility.class);
classes.put("EmptyDataAbility", mindustry.entities.abilities.EmptyDataAbility.class);
@@ -190,6 +191,8 @@ public class ClassMap{
classes.put("ThrusterBuild", mindustry.world.blocks.defense.Thruster.ThrusterBuild.class);
classes.put("Wall", mindustry.world.blocks.defense.Wall.class);
classes.put("WallBuild", mindustry.world.blocks.defense.Wall.WallBuild.class);
classes.put("TargetDummy", mindustry.world.blocks.defense.TargetDummy.class);
classes.put("TargetDummyBuild", mindustry.world.blocks.defense.TargetDummy.TargetDummyBuild.class);
classes.put("BaseTurret", mindustry.world.blocks.defense.turrets.BaseTurret.class);
classes.put("BaseTurretBuild", mindustry.world.blocks.defense.turrets.BaseTurret.BaseTurretBuild.class);
classes.put("ContinuousLiquidTurret", mindustry.world.blocks.defense.turrets.ContinuousLiquidTurret.class);
+2
View File
@@ -110,6 +110,8 @@ public class UnitType extends UnlockableContent implements Senseable{
strafePenalty = 0.5f,
/** multiplier for cost of research in tech tree */
researchCostMultiplier = 50,
/** multiplier for knockback this unit receives */
knockbackMultiplier = 1f,
/** for ground units, the layer upon which this unit is drawn */
groundLayer = Layer.groundUnit,
@@ -0,0 +1,285 @@
package mindustry.world.blocks.defense;
import arc.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.scene.ui.TextField.*;
import arc.scene.ui.layout.*;
import arc.struct.*;
import arc.util.*;
import arc.util.io.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.graphics.*;
import mindustry.entities.units.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
import mindustry.world.meta.*;
import static mindustry.Vars.*;
public class TargetDummy extends Block{
public final int dpsUpdateTime = timers++;
public UnitType unitType = UnitTypes.dummy;
public float pullScale = 0.33f;
public String emptyStr = "---";
public @Load(value = "@-tether-end", fallback = "@") TextureRegion tetherEnd;
public @Load(value = "@-tether", fallback = "@") TextureRegion tether;
public @Load(value = "@-preview", fallback = "@") TextureRegion previewRegion;
public TargetDummy(String name){
super(name);
update = alwaysUpdateInUnits = true;
configurable = logicConfigurable = true;
underBullets = true;
targetable = false;
saveConfig = true;
config(FloatSeq.class, (TargetDummyBuild tile, FloatSeq config) -> {
if(config.size > 0){
tile.unitTeam = tile.team;
if(config.get(0) == 1) tile.unitTeam = Team.get(tile.dummyTeam());
}
if(config.size > 1) tile.boosting = config.get(1) == 1;
if(config.size > 2) tile.unitArmor = config.get(2);
if(config.size > 3) tile.resetTime = config.get(3);
if(config.size > 4) tile.dummySize = config.get(4);
});
}
@Override
protected TextureRegion[] icons(){
return new TextureRegion[]{previewRegion};
}
@Override
public void setBars(){
super.setBars();
removeBar("health");
addBar("dps", (TargetDummyBuild entity) -> new Bar(
() -> entity.displayDPS(false),
() -> Pal.ammo,
() -> 1f - (entity.reset / entity.resetTime)
));
}
@Override
public void drawDefaultPlanRegion(BuildPlan plan, Eachable<BuildPlan> list){
super.drawDefaultPlanRegion(plan, list);
Draw.rect(unitType.fullIcon, plan.drawx(), plan.drawy());
}
public class TargetDummyBuild extends Building implements UnitTetherBlock{
//needs to be "unboxed" after reading, since units are read after buildings.
public int readUnitId = -1;
public Unit unit;
public float resetTime = 120f;
public float total, reset = resetTime, time, dummySize = 12f;
public float dps, totalDisplay, timeDisplay;
public int hits;
public int hitsDisplay;
public boolean boosting;
public float unitArmor;
public Team unitTeam;
@Override
public void updateTile(){
//unit was lost/destroyed somehow
if(unit != null && (unit.dead || !unit.isAdded())){
unit = null;
}
if(readUnitId != -1){
unit = Groups.unit.getByID(readUnitId);
if(unit != null || !net.client()){
readUnitId = -1;
}
}
if(unitTeam == null) unitTeam = team;
if(unit == null){
if(!net.client()){
unit = unitType.create(team);
if(unit instanceof TargetDummyUnit td){
td.building(this);
}
unit.set(x, y);
unit.rotation = 90f;
unit.add();
Call.unitTetherBlockSpawned(tile, unit.id);
}
}
if(unit != null){
unit.updateBoosting(boosting);
unit.armor(unitArmor);
unit.team(unitTeam);
unit.hitSize = dummySize;
//similar to impulseNet but does not factor in mass
Tmp.v1.set(this).sub(unit).limit(dst(unit) * pullScale * Time.delta);
unit.vel.add(Tmp.v1);
//manually move units to simulate velocity for remote players
if(unit.isRemote()) unit.move(Tmp.v1);
if(unit.moving()) unit.lookAt(unit.vel().angle());
}
time += Time.delta;
reset += Time.delta;
if(timer(dpsUpdateTime, 20)){
dps = total / time * 60f;
totalDisplay = total;
timeDisplay = time / 60f;
hitsDisplay = hits;
}
if(reset >= resetTime){
total = time = dps = hits = 0;
}
}
public void spawned(int id){
Fx.spawn.at(x, y);
if(net.client()){
readUnitId = id;
}
}
@Override
public void draw(){
super.draw();
if(tether.found() && unit != null){
float z = unit.elevation > 0.5f ? unit.type.flyingLayer - 0.01f : unitType.groundLayer + Mathf.clamp(unitType.hitSize / 4000f, 0, 0.01f);
Draw.z(z - 0.01f);
Draw.color(team.color);
Drawf.laser(tether, tetherEnd, x, y, unit.x, unit.y);
Draw.color();
}
Draw.z(Layer.overlayUI);
String text = displayDPS(true) + "\n" + "(" + displayTotal() + ")" + "\n" + displayHits();
Drawf.text(text, x, y + size / (2.5f*2f), team.color, size / 2.5f);
}
@Override
public void drawSelect(){
Drawf.square(x, y, dummySize, 0f, team.color);
}
public String displayDPS(boolean round){
if(time > 0){
return Core.bundle.format("bar.dps", (round ? (dps > 0 ? Mathf.round(dps) : emptyStr) : Strings.autoFixed(total / time * 60f, 2)));
}else{
return Core.bundle.format("bar.dps", emptyStr);
}
}
public String displayTotal(){
if(time > 0){
return Core.bundle.format("bar.damagetimetotal", Mathf.round(totalDisplay), Strings.autoFixed(timeDisplay, 2));
}else{
return Core.bundle.format("bar.damagetimetotal", emptyStr, emptyStr);
}
}
public String displayHits(){
if(time > 0){
return Core.bundle.format("bar.hits", hitsDisplay);
}else{
return Core.bundle.format("bar.hits", emptyStr);
}
}
@Override
public boolean collide(Bullet other){ //Hit the unit, not the building
return false;
}
@Override
public boolean collision(Bullet other){ //Hit the unit, not the building
return false;
}
public void dummyHit(float damage){
reset = 0f;
total += damage;
hits++;
}
@Override
public void damage(float damage){
//just in case
}
@Override
public void buildConfiguration(Table table){
table.table(t -> {
t.background(Styles.black6);
t.defaults().left();
t.check(Core.bundle.get("rules.enemyteam"), unitTeam != team, b -> configureFloat(0, Mathf.num(b))).colspan(3).row();
t.check(Core.bundle.get("stat.flying"), boosting, b -> configureFloat(1, Mathf.num(b))).colspan(3).row();
t.add(Core.bundle.get("stat.armor"));
t.field("" + unitArmor, TextFieldFilter.floatsOnly, s -> configureFloat(2, Strings.parseFloat(s))).width(200f).padLeft(8f).colspan(2).row();
t.add(Core.bundle.get("stat.resettime"));
t.field(Strings.autoFixed(resetTime / 60f, 2), TextFieldFilter.floatsOnly, s -> configureFloat(3, Strings.parseFloat(s) * 60f)).padLeft(8f).growX();
t.add(StatUnit.seconds.localized()).padLeft(8).row();
t.add(Core.bundle.get("stat.hitsize"));
t.field("" + (dummySize/tilesize), TextFieldFilter.floatsOnly, s -> configureFloat(4, Strings.parseFloat(s) * tilesize)).padLeft(8f).growX();
t.add(StatUnit.blocks.localized()).padLeft(8f);
}).top().grow().margin(8f);
}
//helper to send the rest of values, since config does not support arrays
public void configureFloat(int index, float value){
FloatSeq seq = FloatSeq.with(Mathf.num(unitTeam != team), Mathf.num(boosting), unitArmor, resetTime, dummySize);
seq.set(index, value);
configure(seq);
}
public int dummyTeam(){
if(unitTeam != team) return team.id; //Return to own team
return team != state.rules.waveTeam ? state.rules.waveTeam.id : (team.id % (Team.all.length - 1)) + 1;
}
@Override
public Object config(){
return FloatSeq.with(Mathf.num(unitTeam != team), Mathf.num(boosting), unitArmor, resetTime, dummySize);
}
@Override
public void write(Writes write){
super.write(write);
write.i(unit == null ? -1 : unit.id);
write.bool(boosting);
write.f(unitArmor);
write.f(resetTime);
write.f(dummySize);
write.i(unitTeam.id);
}
@Override
public void read(Reads read, byte revision){
super.read(read, revision);
readUnitId = read.i();
boosting = read.bool();
unitArmor = read.f();
resetTime = read.f();
dummySize = read.f();
unitTeam = Team.get(read.i());
}
}
}
@@ -14,6 +14,7 @@ public class StatUnit{
blocks = new StatUnit("blocks"),
blocksSquared = new StatUnit("blocksSquared"),
worldUnits = new StatUnit("worldUnits"),
tilesSecond = new StatUnit("tilesSecond"),
powerSecond = new StatUnit("powerSecond", "[accent]" + Iconc.power + "[]"),
liquidSecond = new StatUnit("liquidSecond", "[sky]" + Iconc.liquid + "[]"),