Compare commits

...
Author SHA1 Message Date
Github Actions 4a37546835 Automatic class mapping update 2026-06-16 22:02:05 +00:00
Anuken b32b0abbe6 Misc minor optimizations 2026-06-16 14:12:55 -04:00
Anuken ed015e72d0 Async power graph update 2026-06-16 13:43:33 -04:00
Anuken f1ec63a2f7 progress 2026-06-16 12:56:33 -04:00
Anuken 1911f52281 progress 2026-06-16 10:52:44 -04:00
Anuken 0c547d12d5 progress 2026-06-16 10:47:16 -04:00
Anuken 34ec1719ad Bugfix 2026-06-15 23:05:52 -04:00
Anuken 210da0cca1 progress 2026-06-15 22:35:34 -04:00
Anuken 2889160bed Extremely experimental building performance changes 2026-06-15 22:14:57 -04:00
48 changed files with 703 additions and 116 deletions
@@ -5,6 +5,21 @@ import java.lang.annotation.*;
public class Annotations{
//region entity interfaces
@Repeatable(BuildingListDefs.class)
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.TYPE})
public @interface BuildingListDef{
String qualifiedType() default "";
Class<?> type() default void.class;
String method();
}
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.TYPE})
public @interface BuildingListDefs{
BuildingListDef[] value();
}
/** Indicates that a method overrides other methods. */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.SOURCE)
@@ -0,0 +1,112 @@
package mindustry.annotations.entity;
import mindustry.annotations.Annotations.*;
import mindustry.annotations.*;
import javax.annotation.processing.*;
import javax.lang.model.type.*;
import javax.tools.*;
import java.io.*;
@SupportedAnnotationTypes("mindustry.annotations.Annotations.BuildingListDefs")
public class BuildingListProcess extends BaseProcessor{
static final String template = """
package mindustry.gen;
import ELEMENT_TYPE;
public class CLASS_NAME{
public ELEMENT_TYPE[] values = new ELEMENT_TYPE[50];
public int size, index;
public void update(){
for(index = 0; index < size; index++){
values[index].UPDATE_METHOD_NAME();
}
}
public void add(ELEMENT_TYPE building){
ELEMENT_TYPE[] items = values;
if(size == items.length){
int newLength = (int)(size * 1.75f);
ELEMENT_TYPE[] newItems = new ELEMENT_TYPE[newLength];
System.arraycopy(items, 0, newItems, 0, size);
values = items = newItems;
}
items[building.buildingArrayIndex = size++] = building;
}
public void remove(ELEMENT_TYPE type){
int position = type.buildingArrayIndex;
if(position != -1 && position < size){
//rarely the entity index is wrong; fallback to slow implementation
if(values[position] != type){
type.buildingArrayIndex = -1;
for(int i = 0; i < size; i++){
if(values[position] == type){
type.buildingArrayIndex = i;
break;
}
}
//not found in the array (attempt to remove a building that does not exist?)
if(type.buildingArrayIndex == -1) return;
}
//swap head with current
if(size > 1){
var head = values[size - 1];
head.buildingArrayIndex = position;
values[position] = head;
}
size--;
values[size] = null;
//fix iteration index when removing
if(index >= position){
index--;
}
}
}
}
""";
@Override
public void process(RoundEnvironment env) throws Exception{
var types = types(BuildingListDefs.class);
for(var type : types){
for(var listDef : type.annotation(BuildingListDefs.class).value()){
String fullName;
if(!listDef.qualifiedType().isEmpty()){
fullName = listDef.qualifiedType();
}else{
try{
fullName = listDef.type().getCanonicalName();
}catch(MirroredTypeException e){
fullName = e.getTypeMirror().toString();
}
}
String simpleName = simpleName(fullName);
var listMethod = listDef.method();
String listClassName = (simpleName.endsWith("Build") ? simpleName.replace("Build","") : simpleName) + "List";
String resultClass = template
.replaceAll("ELEMENT_TYPE", fullName)
.replaceAll("CLASS_NAME", listClassName)
.replaceAll("UPDATE_METHOD_NAME", listMethod)
;
JavaFileObject object = filer.createSourceFile(packageName + "." + listClassName);
Writer stream = object.openWriter();
stream.write(resultClass);
stream.close();
}
}
}
}
+4 -2
View File
@@ -6401,9 +6401,10 @@ public class Blocks{
repairSpeed = 0.45f;
repairRadius = 60f;
beamWidth = 0.73f;
powerUse = 1f;
pulseRadius = 5f;
ambientSoundVolume = 0.9f;
consumePower(1f);
}};
repairTurret = new RepairTurret("repair-turret"){{
@@ -6412,13 +6413,14 @@ public class Blocks{
length = 6f;
repairSpeed = 3f;
repairRadius = 145f;
powerUse = 5f;
beamWidth = 1.1f;
pulseRadius = 6.1f;
coolantUse = 0.16f;
coolantMultiplier = 1.6f;
acceptCoolant = true;
ambientSoundVolume = 1.25f;
consumePower(5f);
}};
//endregion
+238
View File
@@ -0,0 +1,238 @@
package mindustry.core;
import arc.math.*;
import arc.struct.*;
import arc.util.*;
import mindustry.*;
import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
import mindustry.world.blocks.*;
import mindustry.world.blocks.distribution.BufferedItemBridge.*;
import mindustry.world.blocks.distribution.Conveyor.*;
import mindustry.world.blocks.distribution.DirectionLiquidBridge.*;
import mindustry.world.blocks.distribution.Duct.*;
import mindustry.world.blocks.distribution.DuctBridge.*;
import mindustry.world.blocks.distribution.DuctRouter.*;
import mindustry.world.blocks.distribution.ItemBridge.*;
import mindustry.world.blocks.distribution.Junction.*;
import mindustry.world.blocks.distribution.Router.*;
import mindustry.world.blocks.distribution.StackConveyor.*;
import mindustry.world.blocks.liquid.Conduit.*;
import mindustry.world.blocks.liquid.LiquidBridge.*;
import mindustry.world.blocks.liquid.LiquidRouter.*;
import mindustry.world.blocks.power.*;
import mindustry.world.blocks.storage.Unloader.*;
import java.util.concurrent.*;
import static mindustry.Vars.*;
@BuildingListDef(qualifiedType = "mindustry.gen.Building", method = "updateTile")
@BuildingListDef(type = PowerGraph.class, method = "update")
@BuildingListDef(type = ConveyorBuild.class, method = "updateConveyor")
@BuildingListDef(type = DuctBuild.class, method = "updateDuct")
@BuildingListDef(type = DuctBridgeBuild.class, method = "updateTile")
@BuildingListDef(type = JunctionBuild.class, method = "updateJunction")
@BuildingListDef(type = RouterBuild.class, method = "updateTile")
@BuildingListDef(type = DuctRouterBuild.class, method = "updateTile")
@BuildingListDef(type = ItemBridgeBuild.class, method = "update") //TODO: has consume power, meaning updateConsumption needs to be called too (bad)
@BuildingListDef(type = BufferedItemBridgeBuild.class, method = "updateTile")
@BuildingListDef(type = StackConveyorBuild.class, method = "updateStackConveyor")
@BuildingListDef(type = UnloaderBuild.class, method = "updateUnloader")
@BuildingListDef(type = ConduitBuild.class, method = "updateConduit")
@BuildingListDef(type = LiquidRouterBuild.class, method = "updateLiquidRouter")
@BuildingListDef(type = LiquidBridgeBuild.class, method = "update") //TODO: has consume power, meaning updateConsumption needs to be called too (bad)
@BuildingListDef(type = DirectionLiquidBridgeBuild.class, method = "updateTile")
/*
TODO: make enable/disable just remove them from the list of things that need to update
- subpoint: remove all contents of update() as they would no longer be needed
TODO: remove the 'devirtualized' methods and see if it affects performance at all (check weaker devices too)
TODO:
- refactor checkAllowUpdate in World to not access Groups.build
- refactor fog scanning Groups.build (2 different places) and move that into a new thread
- finally, remove it from the group list def (footgun avoidance)
*
*/
public class Buildings{
public final BuildingList buildings = new BuildingList();
public final ConveyorList conveyors = new ConveyorList();
public final DuctList ducts = new DuctList();
public final DuctBridgeList ductBridges = new DuctBridgeList();
public final JunctionList junctions = new JunctionList();
public final RouterList routers = new RouterList();
public final DuctRouterList ductRouters = new DuctRouterList();
public final BufferedItemBridgeList bufferedItemBridges = new BufferedItemBridgeList();
public final ItemBridgeList itemBridges = new ItemBridgeList();
public final StackConveyorList stackConveyors = new StackConveyorList();
public final UnloaderList unloaders = new UnloaderList();
public final ConduitList conduits = new ConduitList();
public final LiquidRouterList liquidRouters = new LiquidRouterList();
public final LiquidBridgeList liquidBridges = new LiquidBridgeList();
public final DirectionLiquidBridgeList directionLiquidBridges = new DirectionLiquidBridgeList();
public final PowerGraphList powerGraphs = new PowerGraphList();
private final Seq<Building> timeScaleBuilds = new Seq<>(false, 20, Building.class);
private final Seq<Building> timeScaleQueue = new Seq<>(false, 20, Building.class);
private final Seq<Building> ambientSoundBuilds = new Seq<>(false, 20, Building.class);
private final Seq<Building> ambientSoundQueue = new Seq<>(false, 20, Building.class);
private final Seq<LiquidUpdater> liquidUpdateBuilds = new Seq<>(false, 20, LiquidUpdater.class);
private final Seq<Future<?>> consFutures = new Seq<>();
public void update(){
timeScaleBuilds.addAll(timeScaleQueue);
timeScaleQueue.clear();
ambientSoundBuilds.addAll(ambientSoundQueue);
ambientSoundQueue.clear();
Future<?> updateSound = null;
if(!headless){
updateSound = mainExecutor.submit(() -> {
Building[] items = ambientSoundBuilds.items;
int len = ambientSoundBuilds.size;
for(int i = 0; i < len; i++){
var build = items[i];
if(!build.isValid()){
ambientSoundBuilds.remove(i);
i --;
len --;
continue;
}
if(build.shouldAmbientSound()){
control.sound.loop(build.block.ambientSound, build, build.block.ambientSoundVolume * build.ambientVolume());
}
}
});
}
float delta = Time.delta;
var updateTimeScale = Vars.mainExecutor.submit(() -> {
Building[] items = timeScaleBuilds.items;
int len = timeScaleBuilds.size;
for(int i = 0; i < len; i++){
var build = items[i];
if((build.timeScaleDuration -= delta) <= 0f){
build.timeScale = 1f;
build.hadTimeScale = false;
timeScaleBuilds.remove(i);
len --;
i --;
}
}
});
var updatePower = mainExecutor.submit(() -> {
PerfCounter.powerUpdate.begin();
powerGraphs.update();
PerfCounter.powerUpdate.end();
});
var updateItems = Vars.mainExecutor.submit(() -> {
PerfCounter.itemsUpdate.begin();
conveyors.update();
ducts.update();
ductBridges.update();
junctions.update();
routers.update();
ductRouters.update();
bufferedItemBridges.update();
itemBridges.update();
stackConveyors.update();
unloaders.update();
PerfCounter.itemsUpdate.end();
});
var updateLiquids = Vars.mainExecutor.submit(() -> {
PerfCounter.liquidsUpdate.begin();
conduits.update();
liquidRouters.update();
liquidBridges.update();
directionLiquidBridges.update();
{
LiquidUpdater[] items = liquidUpdateBuilds.items;
int len = liquidUpdateBuilds.size;
for(int i = 0; i < len; i++){
var build = items[i];
if(!build.isAdded()){
liquidUpdateBuilds.remove(i);
len --;
i --;
}else{
build.processLiquids();
}
}
}
PerfCounter.liquidsUpdate.end();
});
Threads.await(updateItems);
Threads.await(updateLiquids);
Threads.await(updateTimeScale);
Threads.await(updatePower);
{
PerfCounter.consumeUpdate.begin();
consFutures.clear();
int chunks = Mathf.clamp(buildings.size / 400, 1, OS.cores);
int chunkSize = Math.max(buildings.size / chunks, 1);
Building[] items = buildings.values;
for(int i = 0; i < buildings.size; i += chunkSize){
int from = i;
int to = Math.min(buildings.size, i + chunkSize);
consFutures.add(mainExecutor.submit(() -> {
for(int index = from; index < to; index++){
items[index].updateConsumption();
}
}));
}
Threads.awaitAll(consFutures);
PerfCounter.consumeUpdate.end();
}
if(updateSound != null){
Threads.await(updateSound);
}
PerfCounter.otherBuildingsUpdate.begin();
buildings.update();
PerfCounter.otherBuildingsUpdate.end();
}
public void addLiquidUpdater(LiquidUpdater build){
liquidUpdateBuilds.add(build);
}
public void addAmbientSound(Building build){
if(headless) return;
ambientSoundQueue.add(build);
}
public void addTimeScaled(Building build){
build.hadTimeScale = true;
timeScaleQueue.add(build);
}
public void clear(){
//TODO: call clear on all the above
}
}
+4 -2
View File
@@ -41,10 +41,12 @@ public class GameState{
public MapLocales mapLocales = new MapLocales();
/** Global attributes of the environment, calculated by weather. */
public Attributes envAttrs = new Attributes();
/** Team data. Gets reset every new game. */
/** Cached team data. */
public Teams teams = new Teams();
/** Handles JSON edits of game content. */
/** Handles edits of game content and additional image/audio assets. */
public DataManager data = new DataManager();
/** All buildings in the map. */
public Buildings buildings = new Buildings();
/** Number of enemies in the game; only used clientside in servers. */
public int enemies;
/** Map being playtested (not edited!) */
+6
View File
@@ -302,6 +302,7 @@ public class Logic implements ApplicationListener{
Events.fire(new ResetEvent());
world.tiles = new Tiles(0, 0);
state.buildings.clear();
state.data.unload();
State prev = state.getState();
//recreate gamestate - sets state to menu
@@ -564,6 +565,11 @@ public class Logic implements ApplicationListener{
state.envAttrs.add(state.rules.attributes);
Groups.weather.each(w -> state.envAttrs.add(w.weather.attrs, w.opacity));
//TODO: possible side effects of buildings before after all bullets do?
PerfCounter.buildingUpdate.begin();
state.buildings.update();
PerfCounter.buildingUpdate.end();
PerfCounter.entityUpdate.begin();
Groups.update();
PerfCounter.entityUpdate.end();
+8 -2
View File
@@ -8,13 +8,19 @@ public enum PerfCounter{
frame,
update,
entityUpdate,
buildingUpdate,
itemsUpdate,
powerUpdate,
liquidsUpdate,
consumeUpdate,
otherBuildingsUpdate,
ui,
render;
public static final PerfCounter[] all = values();
static final int meanWindow = 30;
static final int refreshTimeMillis = 500;
static final int meanWindow = 120;
static final int refreshTimeMillis = 100;
private long valueRefreshTime;
private float refreshValue;
+1 -1
View File
@@ -467,7 +467,7 @@ public class World{
for(var build : team.buildings){
//reset map-area-based disabled blocks that were not in the previous map area
if(!build.enabled && build.block.autoResetEnabled && !Rect.contains(x, y, w, h, build.tile.x, build.tile.y)){
build.enabled = true;
build.setEnabled(true);
}
//if the map area contracts, disable the block
+1 -1
View File
@@ -94,7 +94,7 @@ public class DrawOperation{
Block block = content.block(to);
tile.setBlock(block, tile.team(), tile.build == null ? 0 : tile.build.rotation);
if(tile.build != null){
tile.build.enabled = true;
tile.build.setEnabled(true);
}
}
case opRotation -> {
@@ -208,12 +208,10 @@ public class MapEditorDialog extends Dialog implements Disposable{
float oldDelta = Time.delta;
Time.delta = deltaScl;
Seq<Building> builds = new Seq<>();
Time.clear();
world.tiles.eachTile(t -> {
if(t.build != null && t.isCenter() && t.block().update && t.build.allowUpdate()){
builds.add(t.build);
if(t.build != null && t.isCenter()){
t.build.updateProximity();
}
});
@@ -230,10 +228,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
}
}
Time.update();
for(var build : builds){
build.update();
}
Groups.powerGraph.update();
state.buildings.update();
Groups.bullet.update(); //needed for mass drivers...
}
@@ -350,7 +345,6 @@ public class MapEditorDialog extends Dialog implements Disposable{
}
}
Groups.build.clear();
Groups.weather.clear();
logic.play();
+1 -2
View File
@@ -8,12 +8,11 @@ class GroupDefs<G>{
@GroupDef(value = Playerc.class, mapping = true) G player;
@GroupDef(value = Bulletc.class, spatial = true, collide = true) G bullet;
@GroupDef(value = Unitc.class, spatial = true, mapping = true) G unit;
@GroupDef(value = Buildingc.class, update = true) G build;
@GroupDef(value = Buildingc.class) G build;
@GroupDef(value = Syncc.class, mapping = true) G sync;
@GroupDef(value = Drawc.class) G draw;
@GroupDef(value = Firec.class) G fire;
@GroupDef(value = Puddlec.class) G puddle;
@GroupDef(value = WeatherStatec.class) G weather;
@GroupDef(value = WorldLabelc.class, mapping = true) G label;
@GroupDef(value = PowerGraphUpdaterc.class) G powerGraph;
}
@@ -47,7 +47,7 @@ import java.util.*;
import static mindustry.Vars.*;
@EntityDef(value = {Buildingc.class}, excludeGroups = {"all"}, isFinal = false, genio = false, serialize = false)
@EntityDef(value = {Buildingc.class}, excludeGroups = {"all", "build"}, isFinal = false, genio = false, serialize = false)
@Component(base = true, genInterface = false)
abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc, QuadTreeObject, Displayable, Sized, Senseable, Controllable, Settable{
//region vars and initialization
@@ -58,6 +58,9 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
static final BuildDamageEvent bulletDamageEvent = new BuildDamageEvent();
static int sleepingEntities = 0;
transient int buildingArrayIndex = -1;
@Import boolean added;
@Import float x, y, health, maxHealth;
@Import Team team;
@Import boolean dead;
@@ -94,8 +97,10 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
transient float lastHealTime = -120f * 10f;
transient Color suppressColor = Pal.sapBullet;
transient volatile boolean hadTimeScale = false;
transient float timeScale = 1f, timeScaleDuration;
private transient float lastDamageTime = -recentDamageTime;
private transient float timeScale = 1f, timeScaleDuration;
private transient float dumpAccum;
private transient boolean sleeping;
@@ -154,11 +159,46 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
return self();
}
@Replace
@Override
public void add(){
//there is no need for a 'is added' guard since it is injected regardless of whether the method is replaced
addToList();
if(power != null){
power.graph.checkAdd();
power.graph.addUpdate();
}
//note: removal is automatic and thus not necessary in remove()
if(block.ambientSound != Sounds.none){
state.buildings.addAmbientSound(self());
}
if(this instanceof LiquidUpdater l){
state.buildings.addLiquidUpdater(l);
}
added = true;
}
@Replace
@Override
public void remove(){
removeFromList();
added = false;
}
public void removeFromList(){
state.buildings.buildings.remove(self());
}
public void addToList(){
state.buildings.buildings.add(self());
}
//used in clear()
public void markRemoved(){
added = false;
}
@Override
@@ -459,12 +499,16 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
/** Sets the time scale of the building to the given intensity, unless it's above that value */
public void applyBoost(float intensity, float duration){
if(!block.canOverdrive) return;
if(!block.canOverdrive || duration <= 0f) return;
//do not refresh time scale when getting a lower intensity
if(intensity >= this.timeScale - 0.001f){
timeScaleDuration = Math.max(timeScaleDuration, duration);
}
timeScale = Math.max(timeScale, intensity);
if(!hadTimeScale){
state.buildings.addTimeScaled(self());
}
}
/** Sets the time scale of the building to the given intensity, unless it's below that value */
@@ -474,6 +518,9 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
timeScaleDuration = Math.max(timeScaleDuration, duration);
}
timeScale = Math.min(timeScale, intensity);
if(!hadTimeScale){
state.buildings.addTimeScaled(self());
}
}
public void applyHealSuppression(float amount){
@@ -2236,30 +2283,30 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
public void checkAllowUpdate(){
if(!allowUpdate()){
enabled = false;
setEnabled(false);
}
}
public void setEnabled(boolean enable){
if(this.enabled == enable) return;
if(block.update && block.noUpdateDisabled){
if(enable){
add();
}else{
remove();
}
}
this.enabled = enable;
}
@Final
@Replace
@Override
public void update(){
//TODO refactor to separate loop?
if((timeScaleDuration -= Time.delta) <= 0f){
timeScale = 1f;
}
//TODO separate multithreaded system for sound? AudioSource, etc
if(!headless && block.ambientSound != Sounds.none && shouldAmbientSound()){
control.sound.loop(block.ambientSound, self(), block.ambientSoundVolume * ambientVolume());
}
updateConsumption();
if(enabled || !block.noUpdateDisabled){
updateTile();
}
updateTile();
}
/** When a block is newly revealed outside of camera view range, it is updated on the minimap. */
@@ -1,16 +0,0 @@
package mindustry.entities.comp;
import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
import mindustry.world.blocks.power.*;
@EntityDef(value = PowerGraphUpdaterc.class, serialize = false, genio = false)
@Component
abstract class PowerGraphUpdaterComp implements Entityc{
public transient PowerGraph graph;
@Override
public void update(){
graph.update();
}
}
+1 -1
View File
@@ -332,7 +332,7 @@ public abstract class SaveVersion extends SaveFileReader{
//set block only if this is the center; otherwise, it's handled elsewhere
if(isCenter){
tile.setBlock(block);
if(tile.build != null) tile.build.enabled = true;
if(tile.build != null) tile.build.setEnabled(true);
}
//must be assigned after setBlock, because that can reset data
@@ -56,7 +56,7 @@ public abstract class LegacySaveVersion extends LegacyRegionSaveVersion{
//do not override occupied cells
if(!occupied){
tile.setBlock(block);
if(tile.build != null) tile.build.enabled = true;
if(tile.build != null) tile.build.setEnabled(true);
}
if(block.hasBuilding()){
@@ -101,7 +101,7 @@ public class ShortChunkSaveVersion extends SaveVersion{
//set block only if this is the center; otherwise, it's handled elsewhere
if(isCenter){
tile.setBlock(block);
if(tile.build != null) tile.build.enabled = true;
if(tile.build != null) tile.build.setEnabled(true);
}
//must be assigned after setBlock, because that can reset data
+2 -1
View File
@@ -150,6 +150,7 @@ public class ClassMap{
classes.put("ExplosionShield", mindustry.world.blocks.ExplosionShield.class);
classes.put("ItemSelection", mindustry.world.blocks.ItemSelection.class);
classes.put("LaunchAnimator", mindustry.world.blocks.LaunchAnimator.class);
classes.put("LiquidUpdater", mindustry.world.blocks.LiquidUpdater.class);
classes.put("RotBlock", mindustry.world.blocks.RotBlock.class);
classes.put("TileBitmask", mindustry.world.blocks.TileBitmask.class);
classes.put("UnitTetherBlock", mindustry.world.blocks.UnitTetherBlock.class);
@@ -226,7 +227,7 @@ public class ClassMap{
classes.put("DirectionBridge", mindustry.world.blocks.distribution.DirectionBridge.class);
classes.put("DirectionBridgeBuild", mindustry.world.blocks.distribution.DirectionBridge.DirectionBridgeBuild.class);
classes.put("DirectionLiquidBridge", mindustry.world.blocks.distribution.DirectionLiquidBridge.class);
classes.put("DuctBridgeBuild", mindustry.world.blocks.distribution.DirectionLiquidBridge.DuctBridgeBuild.class);
classes.put("DirectionLiquidBridgeBuild", mindustry.world.blocks.distribution.DirectionLiquidBridge.DirectionLiquidBridgeBuild.class);
classes.put("DirectionalUnloader", mindustry.world.blocks.distribution.DirectionalUnloader.class);
classes.put("DirectionalUnloaderBuild", mindustry.world.blocks.distribution.DirectionalUnloader.DirectionalUnloaderBuild.class);
classes.put("Duct", mindustry.world.blocks.distribution.Duct.class);
+3 -2
View File
@@ -566,8 +566,9 @@ public class GameService{
}
}
for(var up : Groups.powerGraph){
var graph = up.graph();
for(int i = 0; i < state.buildings.powerGraphs.size; i++){
var graph = state.buildings.powerGraphs.values[i];
if(graph.all.size > 1 && graph.all.first().team == player.team() && graph.hasPowerBalanceSamples()){
float balance = graph.getPowerBalance() * 60f;
+1 -1
View File
@@ -95,7 +95,7 @@ public class Build{
if(tile.team() == Team.derelict && team != Team.derelict && tile.block == result && tile.build != null && tile.block.allowDerelictRepair && state.rules.derelictRepair){
tile.build.rotation = rotation;
tile.build.changeTeam(team);
tile.build.enabled = true;
tile.build.setEnabled(true);
if(tile.build.power != null){
tile.build.power.links.clear();
tile.build.powerGraphRemoved();
@@ -0,0 +1,7 @@
package mindustry.world.blocks;
import mindustry.gen.*;
public interface LiquidUpdater extends Healthc{
void processLiquids();
}
@@ -624,7 +624,7 @@ public class Turret extends ReloadTurret{
}
protected boolean validateTarget(){
return !Units.invalidateTarget(target, canHeal() ? Team.derelict : team, x, y) || isControlled() || logicControlled();
return !Units.invalidateTarget(target, canHeal() ? Team.derelict : team, x, y) || unit.isPlayer() || logicControlled();
}
protected boolean canHeal(){
@@ -6,6 +6,8 @@ import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.meta.*;
import static mindustry.Vars.*;
public class BufferedItemBridge extends ItemBridge{
public final int timerAccept = timers++;
@@ -19,7 +21,7 @@ public class BufferedItemBridge extends ItemBridge{
hasItems = true;
canOverdrive = true;
}
@Override
public void setStats(){
super.setStats();
@@ -28,7 +30,6 @@ public class BufferedItemBridge extends ItemBridge{
stats.add(Stat.itemsMoved, displayedSpeed, StatUnit.itemsSecond);
}
public class BufferedItemBridgeBuild extends ItemBridgeBuild{
ItemBuffer buffer = new ItemBuffer(bufferCapacity);
@@ -46,12 +47,21 @@ public class BufferedItemBridge extends ItemBridge{
}
}
@Override
public void addToList(){
state.buildings.bufferedItemBridges.add(this);
}
@Override
public void removeFromList(){
state.buildings.bufferedItemBridges.remove(this);
}
@Override
public void doDump(){
dump();
}
@Override
public void write(Writes write){
super.write(write);
@@ -142,7 +142,7 @@ public class Conveyor extends Block implements Autotiler{
@Override
public void draw(){
int frame = enabled && clogHeat <= 0.5f ? (int)(((Time.time * speed * 8f * timeScale * efficiency)) % 4) : 0;
int frame = enabled && clogHeat <= 0.5f ? (int)(((Time.time * speed * 8f * timeScale)) % 4) : 0;
//draw extra conveyors facing this one for non-square tiling purposes
Draw.z(Layer.blockUnder);
@@ -250,8 +250,8 @@ public class Conveyor extends Block implements Autotiler{
}
}
@Override
public void updateTile(){
public final void updateConveyor(){
efficiency = enabled ? 1f : 0f;
minitem = 1f;
mid = 0;
@@ -263,7 +263,7 @@ public class Conveyor extends Block implements Autotiler{
}
float nextMax = aligned ? 1f - Math.max(itemSpace - nextc.minitem, 0) : 1f;
float moved = speed * edelta();
float moved = speed * delta();
for(int i = len - 1; i >= 0; i--){
float nextpos = (i == len - 1 ? 100f : ys[i + 1]) - itemSpace;
@@ -297,6 +297,16 @@ public class Conveyor extends Block implements Autotiler{
noSleep();
}
@Override
public void addToList(){
state.buildings.conveyors.add(this);
}
@Override
public void removeFromList(){
state.buildings.conveyors.remove(this);
}
public boolean pass(Item item){
if(item != null && next != null && next.team == team && next.acceptItem(this, item)){
next.handleItem(this, item);
@@ -9,6 +9,8 @@ import mindustry.type.*;
import mindustry.world.blocks.liquid.*;
import mindustry.world.meta.*;
import static mindustry.Vars.*;
public class DirectionLiquidBridge extends DirectionBridge{
public final int timerFlow = timers++;
@@ -33,7 +35,17 @@ public class DirectionLiquidBridge extends DirectionBridge{
return new TextureRegion[]{bottomRegion, region, dirRegion};
}
public class DuctBridgeBuild extends DirectionBridgeBuild{
public class DirectionLiquidBridgeBuild extends DirectionBridgeBuild{
@Override
public void addToList(){
state.buildings.directionLiquidBridges.add(this);
}
@Override
public void removeFromList(){
state.buildings.directionLiquidBridges.remove(this);
}
@Override
public void drawCached(){
@@ -135,6 +135,16 @@ public class Duct extends Block implements Autotiler{
public @Nullable Building next;
public @Nullable DuctBuild nextc;
@Override
public void addToList(){
state.buildings.ducts.add(this);
}
@Override
public void removeFromList(){
state.buildings.ducts.remove(this);
}
@Override
public void draw(){
draw(false);
@@ -189,8 +199,7 @@ public class Duct extends Block implements Autotiler{
}
}
@Override
public void updateTile(){
public final void updateDuct(){
progress += edelta() / speed * 2f;
if(current != null && next != null){
@@ -2,9 +2,10 @@ package mindustry.world.blocks.distribution;
import mindustry.gen.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.meta.*;
import static mindustry.Vars.*;
public class DuctBridge extends DirectionBridge{
public float speed = 5f;
@@ -26,6 +27,16 @@ public class DuctBridge extends DirectionBridge{
public class DuctBridgeBuild extends DirectionBridgeBuild{
public float progress = 0f;
@Override
public void addToList(){
state.buildings.ductBridges.add(this);
}
@Override
public void removeFromList(){
state.buildings.ductBridges.remove(this);
}
@Override
public void updateTile(){
var link = lastLink = findLink();
@@ -97,6 +97,16 @@ public class DuctRouter extends Block{
drawItemSelection(sortItem);
}
@Override
public void addToList(){
state.buildings.ductRouters.add(this);
}
@Override
public void removeFromList(){
state.buildings.ductRouters.remove(this);
}
@Override
public void updateTile(){
progress += edelta() / speed * 2f;
@@ -315,6 +315,16 @@ public class ItemBridge extends Block{
}
}
@Override
public void addToList(){
state.buildings.itemBridges.add(this);
}
@Override
public void removeFromList(){
state.buildings.itemBridges.remove(this);
}
@Override
public void updateTile(){
if(timer(timerCheckMoved, 30f)){
@@ -51,8 +51,16 @@ public class Junction extends Block{
}
@Override
public void updateTile(){
public void addToList(){
state.buildings.junctions.add(this);
}
@Override
public void removeFromList(){
state.buildings.junctions.remove(this);
}
public final void updateJunction(){
for(int i = 0; i < 4; i++){
if(buffer.indexes[i] > 0){
if(buffer.indexes[i] > capacity) buffer.indexes[i] = capacity;
@@ -9,6 +9,8 @@ import mindustry.world.*;
import mindustry.world.blocks.*;
import mindustry.world.meta.*;
import static mindustry.Vars.*;
public class Router extends Block{
public float speed = 8f;
@@ -51,6 +53,16 @@ public class Router extends Block{
return false;
}
@Override
public void addToList(){
state.buildings.routers.add(this);
}
@Override
public void removeFromList(){
state.buildings.routers.remove(this);
}
@Override
public void updateTile(){
if(lastItem == null && items.any()){
@@ -7,6 +7,7 @@ import arc.math.geom.*;
import arc.struct.*;
import arc.util.*;
import arc.util.io.*;
import mindustry.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.entities.*;
@@ -265,7 +266,18 @@ public class StackConveyor extends Block implements Autotiler{
}
@Override
public void updateTile(){
public void addToList(){
Vars.state.buildings.stackConveyors.add(this);
}
@Override
public void removeFromList(){
Vars.state.buildings.stackConveyors.remove(this);
}
public final void updateStackConveyor(){
updateConsumption();
//the item still needs to be "reeled" in when disabled
float eff = enabled ? (efficiency + baseEfficiency) : 1f;
@@ -245,8 +245,9 @@ public class Conduit extends LiquidBlock implements Autotiler{
&& (tile == null || source == this || (source.relativeTo(tile.x, tile.y) + 2) % 4 != rotation);
}
@Override
public void updateTile(){
public final void updateConduit(){
if(!noUpdateDisabled) return;
smoothLiquid = Mathf.lerpDelta(smoothLiquid, liquids.currentAmount() / liquidCapacity, 0.05f);
if(liquids.currentAmount() > 0.0001f && timer(timerFlow, 1)){
@@ -257,6 +258,16 @@ public class Conduit extends LiquidBlock implements Autotiler{
}
}
@Override
public void addToList(){
state.buildings.conduits.add(this);
}
@Override
public void removeFromList(){
state.buildings.conduits.remove(this);
}
@Nullable
@Override
public Building next(){
@@ -4,6 +4,8 @@ import mindustry.gen.*;
import mindustry.world.blocks.distribution.*;
import mindustry.world.meta.*;
import static mindustry.Vars.*;
public class LiquidBridge extends ItemBridge{
public LiquidBridge(String name){
@@ -18,6 +20,16 @@ public class LiquidBridge extends ItemBridge{
public class LiquidBridgeBuild extends ItemBridgeBuild{
@Override
public void addToList(){
state.buildings.liquidBridges.add(this);
}
@Override
public void removeFromList(){
state.buildings.liquidBridges.remove(this);
}
@Override
public void updateTransport(Building other){
if(warmup >= 0.25f){
@@ -4,6 +4,8 @@ import arc.graphics.g2d.*;
import mindustry.gen.*;
import mindustry.type.*;
import static mindustry.Vars.*;
public class LiquidRouter extends LiquidBlock{
public float liquidPadding = 0f;
@@ -21,11 +23,21 @@ public class LiquidRouter extends LiquidBlock{
}
public class LiquidRouterBuild extends LiquidBuild{
@Override
public void updateTile(){
public final void updateLiquidRouter(){
dumpLiquid(liquids.current());
}
@Override
public void addToList(){
state.buildings.liquidRouters.add(this);
}
@Override
public void removeFromList(){
state.buildings.liquidRouters.remove(this);
}
@Override
public void draw(){
Draw.rect(bottomRegion, x, y);
@@ -96,7 +96,7 @@ public class LogicBlock extends Block{
if(oldSize > entity.links.size){ //check whether any were removed
//re-enable the target when unlinking
if(lbuild.block.autoResetEnabled && lbuild.lastDisabler == entity){
lbuild.enabled = true;
lbuild.setEnabled(true);
}
}else{
entity.links.add(new LogicLink(x, y, entity.findLinkName(lbuild.block), true));
@@ -456,7 +456,7 @@ public class LogicBlock extends Block{
for(var link : executor.links){
if(!link.enabled && link.lastDisabler == this){
link.enabled = true;
link.setEnabled(true);
}
}
}
@@ -25,7 +25,7 @@ public class SwitchBlock extends Block{
group = BlockGroup.logic;
envEnabled = Env.any;
config(Boolean.class, (SwitchBuild entity, Boolean b) -> entity.enabled = b);
config(Boolean.class, (SwitchBuild entity, Boolean b) -> entity.setEnabled(b));
}
public boolean accessible(){
@@ -135,8 +135,6 @@ public class PayloadConveyor extends Block{
@Override
public void updateTile(){
if(!enabled) return;
if(item != null){
item.update(null, this);
}
@@ -3,6 +3,7 @@ package mindustry.world.blocks.power;
import arc.math.*;
import arc.struct.*;
import arc.util.*;
import mindustry.*;
import mindustry.gen.*;
public class PowerGraph{
@@ -17,7 +18,11 @@ public class PowerGraph{
public final Seq<Building> batteries = new Seq<>(false, 16, Building.class);
public final Seq<Building> all = new Seq<>(false, 16, Building.class);
private final @Nullable PowerGraphUpdater entity;
public int buildingArrayIndex = -1;
private boolean added;
private final boolean shouldUpdate;
private final WindowedMean powerBalance = new WindowedMean(60);
private float lastPowerProduced, lastPowerNeeded, lastPowerStored;
private float lastScaledPowerIn, lastScaledPowerOut, lastCapacity;
@@ -28,14 +33,12 @@ public class PowerGraph{
private static int lastGraphID;
public PowerGraph(){
entity = PowerGraphUpdater.create();
entity.graph = this;
graphID = lastGraphID++;
this(true);
}
public PowerGraph(boolean noEntity){
entity = null;
public PowerGraph(boolean shouldUpdate){
graphID = lastGraphID++;
this.shouldUpdate = shouldUpdate;
}
public int getID(){
@@ -216,8 +219,8 @@ public class PowerGraph{
public void update(){
if(!consumers.isEmpty() && consumers.first().cheating()){
//when cheating, just set status to 1
for(Building tile : consumers){
tile.power.status = 1f;
for(int i = 0; i < consumers.size; i++){
consumers.items[i].power.status = 1f;
}
lastPowerNeeded = lastPowerProduced = 1f;
@@ -266,12 +269,12 @@ public class PowerGraph{
}
//other entity should be removed as the graph was merged
if(graph.entity != null) graph.entity.remove();
graph.removeUpdate();
for(Building tile : graph.all){
add(tile);
}
checkAdd();
addUpdate();
}
public void add(Building build){
@@ -280,7 +283,7 @@ public class PowerGraph{
if(build.power.graph != this || !build.power.init){
//any old graph that is added here MUST be invalid, remove it
if(build.power.graph != null && build.power.graph != this){
if(build.power.graph.entity != null) build.power.graph.entity.remove();
build.power.graph.removeUpdate();
}
build.power.graph = this;
@@ -300,8 +303,18 @@ public class PowerGraph{
}
}
public void checkAdd(){
if(entity != null) entity.add();
public void addUpdate(){
if(added || !shouldUpdate) return;
added = true;
Vars.state.buildings.powerGraphs.add(this);
}
public void removeUpdate(){
if(!added) return;
added = false;
Vars.state.buildings.powerGraphs.remove(this);
}
public void clear(){
@@ -309,8 +322,8 @@ public class PowerGraph{
producers.clear();
consumers.clear();
batteries.clear();
//nothing left
if(entity != null) entity.remove();
removeUpdate();
}
public void reflow(Building tile){
@@ -320,7 +333,7 @@ public class PowerGraph{
while(queue.size > 0){
Building child = queue.removeFirst();
add(child);
checkAdd();
addUpdate();
for(Building next : child.getPowerConnections(outArray2)){
if(closedSet.add(next.pos())){
queue.addLast(next);
@@ -348,7 +361,7 @@ public class PowerGraph{
//create graph for this branch
PowerGraph graph = new PowerGraph();
graph.checkAdd();
graph.addUpdate();
graph.add(other);
//add to queue for BFS
queue.clear();
@@ -373,7 +386,7 @@ public class PowerGraph{
}
//implied empty graph here
if(entity != null) entity.remove();
removeUpdate();
}
public int getId(){
@@ -39,7 +39,6 @@ public class Fracker extends SolidPump{
}else{
warmup = Mathf.lerpDelta(warmup, 0f, 0.02f);
lastPump = 0f;
dumpLiquid(result);
}
}
}
@@ -14,6 +14,7 @@ import mindustry.logic.*;
import mindustry.mod.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
import mindustry.world.blocks.liquid.Conduit.*;
import mindustry.world.draw.*;
import mindustry.world.meta.*;
@@ -183,7 +184,7 @@ public class GenericCrafter extends Block{
}
}
public class GenericCrafterBuild extends Building{
public class GenericCrafterBuild extends Building implements LiquidUpdater{
public float progress;
public float totalProgress;
public float warmup;
@@ -321,7 +322,10 @@ public class GenericCrafter extends Block{
dump(output.item);
}
}
}
@Override
public void processLiquids(){
if(outputLiquids != null){
for(int i = 0; i < outputLiquids.length; i++){
int dir = liquidOutputDirections.length > i ? liquidOutputDirections[i] : -1;
@@ -9,6 +9,7 @@ import mindustry.game.*;
import mindustry.logic.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
import mindustry.world.blocks.liquid.*;
import mindustry.world.draw.*;
import mindustry.world.meta.*;
@@ -107,7 +108,7 @@ public class Pump extends LiquidBlock{
return tile != null && tile.floor().liquidDrop != null;
}
public class PumpBuild extends LiquidBuild{
public class PumpBuild extends LiquidBuild implements LiquidUpdater{
public float warmup, totalProgress;
public float consTimer;
public float amount = 0f;
@@ -174,7 +175,10 @@ public class Pump extends LiquidBlock{
}
totalProgress += warmup * Time.delta;
}
@Override
public void processLiquids(){
if(liquidDrop != null){
dumpLiquid(liquidDrop);
}
@@ -136,7 +136,10 @@ public class SolidPump extends Pump{
}
pumpTime += warmup * edelta();
}
@Override
public void processLiquids(){
dumpLiquid(result);
}
@@ -20,9 +20,7 @@ public class PowerSource extends PowerNode{
@Override
public void onProximityUpdate(){
super.onProximityUpdate();
if(!allowUpdate()){
enabled = false;
}
checkAllowUpdate();
}
@Override
@@ -8,6 +8,7 @@ import arc.util.*;
import arc.util.io.*;
import arc.util.pooling.*;
import arc.util.pooling.Pool.*;
import mindustry.*;
import mindustry.annotations.Annotations.*;
import mindustry.entities.units.*;
import mindustry.gen.*;
@@ -159,8 +160,17 @@ public class Unloader extends Block{
}
@Override
public void updateTile(){
if(((unloadTimer += delta()) < speed) || (possibleBlocks.size < 2)) return;
public void addToList(){
state.buildings.unloaders.add(this);
}
@Override
public void removeFromList(){
state.buildings.unloaders.remove(this);
}
public final void updateUnloader(){
if(!enabled || ((unloadTimer += delta()) < speed) || (possibleBlocks.size < 2)) return;
Item item = null;
boolean any = false;
@@ -31,7 +31,6 @@ public class RepairTurret extends Block{
public float repairRadius = 50f;
public float repairSpeed = 0.3f;
public float powerUse;
public float length = 5f;
public float beamWidth = 1f;
public float pulseRadius = 6f;
@@ -86,7 +85,6 @@ public class RepairTurret extends Block{
consume(new ConsumeCoolant(coolantUse)).optional(true, true);
}
consumePowerCond(powerUse, (RepairPointBuild entity) -> entity.target != null);
updateClipRadius(repairRadius + tilesize);
super.init();
}
@@ -34,7 +34,9 @@ public class ConsumeItemExplode extends ConsumeItemFilter{
if(item != null){
if(Vars.state.rules.reactorExplosions && Mathf.chance(build.delta() * baseChance * Mathf.clamp(item.explosiveness - threshold))){
build.damage(damage);
//this can be called in a different thread, so post() it. this is bad for performance, but exploding is just a funny gimmick mechanic anyway, and should not happen in most real games
Core.app.post(() -> build.damage(damage));
explodeEffect.at(build.x + Mathf.range(build.block.size * tilesize / 2f), build.y + Mathf.range(build.block.size * tilesize / 2f));
Events.fire(Trigger.blastGenerator);
}
@@ -19,7 +19,9 @@ public class ConsumePayloadDynamic extends Consume{
@Override
public float efficiency(Building build){
float mult = multiplier.get(build);
for(PayloadStack stack : payloads.get(build)){
var stacks = payloads.get(build);
for(int i = 0; i < stacks.size; i ++){
var stack = stacks.get(i);
if(!build.getPayloads().contains(stack.item, Math.round(stack.amount * mult))){
return 0f;
}
@@ -30,7 +32,9 @@ public class ConsumePayloadDynamic extends Consume{
@Override
public void trigger(Building build){
float mult = multiplier.get(build);
for(PayloadStack stack : payloads.get(build)){
var stacks = payloads.get(build);
for(int i = 0; i < stacks.size; i ++){
var stack = stacks.get(i);
build.getPayloads().remove(stack.item, Math.round(stack.amount * mult));
}
}
@@ -5,13 +5,14 @@ import arc.struct.*;
import mindustry.gen.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.world.blocks.payloads.*;
import mindustry.world.meta.*;
public class ConsumePayloads extends Consume{
public Seq<PayloadStack> payloads;
public PayloadStack[] payloads;
public ConsumePayloads(Seq<PayloadStack> payloads){
this.payloads = payloads;
this.payloads = payloads.toArray(Payload.class);
}
@Override