more or less. Still missing engines

done I think?

applied revisions from jason

okay should be enough fallbacks

better team getter

dont use multiple configs under saveConfig, the game does not like that

Minor tweaks to DPS dummy
This commit is contained in:
EggleEgg
2026-09-01 19:53:26 -04:00
committed by Anuken
parent 27d7a2917e
commit b3e8aa9294
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 + "[]"),