Compare commits

..
Author SHA1 Message Date
Github Actions b70ea8afd6 Automatic class mapping update 2026-06-17 17:09:10 +00:00
Anuken fe6e3b03ba it works but it's bad 2026-06-17 13:06:59 -04:00
Anuken 0849ed8cfc progress 2026-06-17 13:01:14 -04:00
Anuken 715c099dcc progress 2026-06-17 12:48:04 -04:00
Anuken f9da9a3fe1 progress 2026-06-17 12:47:21 -04:00
Anuken d4449289a8 it actually works 2026-06-17 12:36:28 -04:00
Anuken adaf3112ed progress 2026-06-17 12:18:58 -04:00
Anuken 6524fc07a7 Merge branch 'master' of https://github.com/Anuken/Mindustry into liquid-thread-test 2026-06-17 12:02:25 -04:00
Anuken 54e2c15ae4 Reverted building sleep changes 2026-06-17 11:46:48 -04:00
Anuken 5c224a49b6 progress 2026-06-17 11:45:17 -04:00
Anuken fad7afb97e Minor block optimizations 2026-06-16 17:13:17 -04:00
Anuken d5c78639f3 Minor turret optimization 2026-06-16 16:00:11 -04:00
Anuken b91d26c509 Better PerfCounter average display 2026-06-16 10:54:49 -04:00
54 changed files with 388 additions and 765 deletions
@@ -5,21 +5,6 @@ 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)
@@ -1,112 +0,0 @@
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();
}
}
}
}
+2 -4
View File
@@ -6401,10 +6401,9 @@ 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"){{
@@ -6413,14 +6412,13 @@ 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
@@ -1,238 +0,0 @@
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
}
}
+5 -4
View File
@@ -8,6 +8,7 @@ import mindustry.gen.*;
import mindustry.maps.*;
import mindustry.mod.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
import static mindustry.Vars.*;
@@ -41,12 +42,12 @@ public class GameState{
public MapLocales mapLocales = new MapLocales();
/** Global attributes of the environment, calculated by weather. */
public Attributes envAttrs = new Attributes();
/** Cached team data. */
/** Team data. Gets reset every new game. */
public Teams teams = new Teams();
/** Handles edits of game content and additional image/audio assets. */
/** Runs liquids in a new thread. */
public LiquidManager liquids = new LiquidManager();
/** Handles JSON edits of game content. */
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!) */
+3 -6
View File
@@ -302,7 +302,7 @@ public class Logic implements ApplicationListener{
Events.fire(new ResetEvent());
world.tiles = new Tiles(0, 0);
state.buildings.clear();
state.liquids.stop();
state.data.unload();
State prev = state.getState();
//recreate gamestate - sets state to menu
@@ -560,16 +560,13 @@ public class Logic implements ApplicationListener{
runWave();
}
state.liquids.checkUpdate();
//apply weather attributes
state.envAttrs.clear();
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();
-6
View File
@@ -8,12 +8,6 @@ public enum PerfCounter{
frame,
update,
entityUpdate,
buildingUpdate,
itemsUpdate,
powerUpdate,
liquidsUpdate,
consumeUpdate,
otherBuildingsUpdate,
ui,
render;
+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.setEnabled(true);
build.enabled = 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.setEnabled(true);
tile.build.enabled = true;
}
}
case opRotation -> {
@@ -208,10 +208,12 @@ 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()){
if(t.build != null && t.isCenter() && t.block().update && t.build.allowUpdate()){
builds.add(t.build);
t.build.updateProximity();
}
});
@@ -228,7 +230,10 @@ public class MapEditorDialog extends Dialog implements Disposable{
}
}
Time.update();
state.buildings.update();
for(var build : builds){
build.update();
}
Groups.powerGraph.update();
Groups.bullet.update(); //needed for mass drivers...
}
@@ -345,6 +350,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
}
}
Groups.build.clear();
Groups.weather.clear();
logic.play();
+2 -1
View File
@@ -8,11 +8,12 @@ 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) G build;
@GroupDef(value = Buildingc.class, update = true) 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", "build"}, isFinal = false, genio = false, serialize = false)
@EntityDef(value = {Buildingc.class}, excludeGroups = {"all"}, 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,9 +58,6 @@ 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;
@@ -97,10 +94,8 @@ 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;
@@ -131,6 +126,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
if(shouldAdd){
add();
}
if(this instanceof LiquidUpdater lu) state.liquids.add(lu);
checkAllowUpdate();
created();
@@ -159,46 +155,11 @@ 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.addUpdate();
power.graph.checkAdd();
}
//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
@@ -499,16 +460,12 @@ 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 || duration <= 0f) return;
if(!block.canOverdrive) 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 */
@@ -518,9 +475,6 @@ 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){
@@ -931,12 +885,19 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
if(liquids.get(liquid) <= 0.0001f) return;
if(!net.client() && state.isCampaign() && team == state.rules.defaultTeam) liquid.unlock();
//TODO add this back
//if(!net.client() && state.isCampaign() && team == state.rules.defaultTeam) liquid.unlock();
for(int i = 0; i < proximity.size; i++){
incrementDump(proximity.size);
int psize = proximity.size;
Building[] prox = proximity.items;
Building other = proximity.get((i + dump) % proximity.size);
if(psize == 0) return;
for(int i = 0; i < psize; i++){
incrementDump(psize);
Building other = prox[((i + dump) % psize)];
if(other == null) continue;
if(outputDir != -1 && (outputDir + rotation) % 4 != relativeTo(other)) continue;
other = other.getLiquidDestination(self(), liquid);
@@ -963,22 +924,23 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
}
}
public float moveLiquidForward(boolean leaks, Liquid liquid){
Tile next = tile.nearby(rotation);
public float moveLiquidForward(@Nullable Building next, boolean leaks, Liquid liquid, float delta){
if(next != null){
return moveLiquid(next, liquid, delta);
}else if(leaks){
Tile nextTile = tile.nearby(rotation);
if(next == null) return 0;
if(next.build != null){
return moveLiquid(next.build, liquid);
}else if(leaks && !next.block().solid && !next.block().hasLiquids){
float leakAmount = liquids.get(liquid) / 1.5f;
Puddles.deposit(next, tile, liquid, leakAmount, true, true);
liquids.remove(liquid, leakAmount);
if(!nextTile.block().solid && !nextTile.block().hasLiquids){
float leakAmount = liquids.get(liquid) / 1.5f;
liquids.remove(liquid, leakAmount);
//TODO: laggy
Core.app.post(() -> Puddles.deposit(nextTile, tile, liquid, leakAmount, true, true));
}
}
return 0;
}
public float moveLiquid(Building next, Liquid liquid){
public float moveLiquid(Building next, Liquid liquid, float delta){
if(next == null) return 0;
next = next.getLiquidDestination(self(), liquid);
@@ -1003,15 +965,20 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
if(other.blockReactive && liquid.blockReactive){
//TODO liquid reaction handler for extensibility
if((other.flammability > 0.3f && liquid.temperature > 0.7f) || (liquid.flammability > 0.3f && other.temperature > 0.7f)){
damageContinuous(1);
next.damageContinuous(1);
if(Mathf.chanceDelta(0.1)){
Fx.fire.at(fx, fy);
}
var fnext = next;
Core.app.post(() -> {
damageContinuous(1);
fnext.damageContinuous(1);
if(Mathf.chanceDelta(0.1)){
Fx.fire.at(fx, fy);
}
});
}else if((liquid.temperature > 0.7f && other.temperature < 0.55f) || (other.temperature > 0.7f && liquid.temperature < 0.55f)){
liquids.remove(liquid, Math.min(liquids.get(liquid), 0.7f * Time.delta));
liquids.remove(liquid, Math.min(liquids.get(liquid), 0.7f * delta));
if(Mathf.chanceDelta(0.2f)){
Fx.steam.at(fx, fy);
Core.app.post(() -> {
Fx.steam.at(fx, fy);
});
}
}
}
@@ -2059,7 +2026,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
@Replace
@Override
public boolean isValid(){
return tile.build == self() && !dead();
return tile.build == self() && !dead;
}
@MethodPriority(100)
@@ -2283,30 +2250,30 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
public void checkAllowUpdate(){
if(!allowUpdate()){
setEnabled(false);
enabled = 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();
updateTile();
if(enabled || !block.noUpdateDisabled){
updateTile();
}
}
/** When a block is newly revealed outside of camera view range, it is updated on the minimap. */
@@ -0,0 +1,16 @@
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.setEnabled(true);
if(tile.build != null) tile.build.enabled = 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.setEnabled(true);
if(tile.build != null) tile.build.enabled = 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.setEnabled(true);
if(tile.build != null) tile.build.enabled = true;
}
//must be assigned after setBlock, because that can reset data
+1 -1
View File
@@ -227,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("DirectionLiquidBridgeBuild", mindustry.world.blocks.distribution.DirectionLiquidBridge.DirectionLiquidBridgeBuild.class);
classes.put("DuctBridgeBuild", mindustry.world.blocks.distribution.DirectionLiquidBridge.DuctBridgeBuild.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);
+2 -3
View File
@@ -566,9 +566,8 @@ public class GameService{
}
}
for(int i = 0; i < state.buildings.powerGraphs.size; i++){
var graph = state.buildings.powerGraphs.values[i];
for(var up : Groups.powerGraph){
var graph = up.graph();
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.setEnabled(true);
tile.build.enabled = true;
if(tile.build.power != null){
tile.build.power.links.clear();
tile.build.powerGraphRemoved();
@@ -0,0 +1,95 @@
package mindustry.world;
import arc.struct.*;
import arc.util.*;
import mindustry.world.blocks.*;
import java.util.concurrent.*;
//TODO: consumers read AND write from the liquids array... on the main thread. as do various other blocks. see remove() usage
//TODO: payload loaders/unloaders don't use this system
public class LiquidManager{
private static final int targetFps = 60;
private static final long targetNanos = 1000 * Time.nanosPerMilli / targetFps;
private UpdaterThread thread;
private int curTick;
public void checkUpdate(){
//ensures volatile read
if(thread != null){
curTick = thread.tick;
}
}
public void add(LiquidUpdater building){
if(!building.shouldLiquidUpdate()) return;
if(thread == null){
thread = new UpdaterThread();
thread.start();
}
thread.addQueue.add(building);
}
public void stop(){
if(thread != null){
thread.running = false;
thread.interrupt();
thread = null;
}
}
static class UpdaterThread extends Thread{
Seq<LiquidUpdater> updaters = new Seq<>(false, 20, LiquidUpdater.class);
ConcurrentLinkedQueue<LiquidUpdater> addQueue = new ConcurrentLinkedQueue<>();
volatile boolean running = true;
volatile int tick;
long lastNanos = Time.nanos();
@Override
public void run(){
try{
while(running){
long nanos = Time.nanos();
long deltaNanos = Time.timeSinceNanos(lastNanos);
float deltaTicks = (float)deltaNanos / Time.nanosPerMilli / 1000f * 60f;
lastNanos = nanos;
LiquidUpdater toAdd;
while((toAdd = addQueue.poll()) != null){
updaters.add(toAdd);
}
LiquidUpdater[] items = updaters.items;
int size = updaters.size;
for(int i = 0; i < size; i++){
var item = items[i];
if(!item.isValid()){
//note: this is an un-ordered seq, so this is just a O(1) swap
updaters.remove(i);
i --;
size --;
}else{
item.updateLiquids(deltaTicks);
}
}
//update 'global tick' as a volatile memory barrier
tick ++;
long elapsed = Time.timeSinceNanos(nanos);
if(elapsed < targetNanos){
long remaining = targetNanos - elapsed;
Thread.sleep(remaining / Time.nanosPerMilli, (int)(remaining % Time.nanosPerMilli));
}
}
}catch(InterruptedException e){
//stop the thread
}
}
}
}
@@ -1,7 +1,13 @@
package mindustry.world.blocks;
import mindustry.game.*;
import mindustry.gen.*;
public interface LiquidUpdater extends Healthc{
void processLiquids();
default boolean shouldLiquidUpdate(){
return true;
}
void updateLiquids(float delta);
Team team();
}
@@ -63,7 +63,7 @@ public class ContinuousTurret extends Turret{
@Override
public boolean shouldConsume(){
return isShooting();
return isShooting;
}
@Override
@@ -123,7 +123,7 @@ public class ContinuousTurret extends Turret{
entry.bullet.damage = entry.bullet.type.damage * Math.min(efficiency, 1f) * timeScale * entry.bullet.damageMultiplier();
}
if(isShooting() && hasAmmo()){
if(isShooting && hasAmmo()){
entry.bullet.time = entry.bullet.lifetime * entry.bullet.type.optimalLifeFract * Math.min(shootWarmup, efficiency);
entry.bullet.keepAlive = true;
}
@@ -52,7 +52,8 @@ public class LaserTurret extends PowerTurret{
@Override
public boolean shouldConsume(){
//still consumes power when bullet is around
return bullets.any() || isActive() || isShooting();
if(bullets.any() || isActive()) return true;
return isShooting;
}
@Override
@@ -289,7 +289,7 @@ public class Turret extends ReloadTurret{
public @Nullable Posc target;
public Vec2 targetPos = new Vec2();
public BlockUnitc unit = (BlockUnitc)UnitTypes.block.create(team);
public boolean wasShooting;
public boolean wasShooting, isShooting;
public int queuedBullets = 0;
public float heatReq;
@@ -362,7 +362,7 @@ public class Turret extends ReloadTurret{
@Override
public boolean shouldConsume(){
return isShooting() || reloadCounter < reload;
return isShooting || reloadCounter < reload;
}
@Override
@@ -410,7 +410,7 @@ public class Turret extends ReloadTurret{
case rotation -> rotation;
case shootX -> World.conv(targetPos.x);
case shootY -> World.conv(targetPos.y);
case shooting -> isShooting() ? 1 : 0;
case shooting -> isShooting ? 1 : 0;
case progress -> progress();
default -> super.sense(sensor);
};
@@ -427,7 +427,7 @@ public class Turret extends ReloadTurret{
}
public boolean isShooting(){
return alwaysShooting || (unit.controller() instanceof Player ? unit.isShooting() : logicControlled() ? logicShooting : target != null);
return isShooting;
}
@Override
@@ -438,6 +438,10 @@ public class Turret extends ReloadTurret{
return (Unit)unit;
}
public boolean controlled(){
return unit.isPlayer();
}
public boolean logicControlled(){
return logicControlTime > 0;
}
@@ -485,6 +489,7 @@ public class Turret extends ReloadTurret{
@Override
public void updateTile(){
if(!validateTarget()) target = null;
isShooting = alwaysShooting || (unit.controller() instanceof Player ? unit.isShooting() : logicControlled() ? logicShooting : target != null);
if(unit.isPlayer()){ //there's no reason to update this when a player isn't controlling it
unit.ammo(getAmmoFraction());
@@ -494,8 +499,8 @@ public class Turret extends ReloadTurret{
soundLoop.update(x, y, shouldActiveSound(), activeSoundVolume());
}
float warmupTarget = (isShooting() && canConsume()) || charging() ? 1f : 0f;
if(warmupTarget > 0 && !isControlled()){
float warmupTarget = (isShooting && canConsume()) || charging() ? 1f : 0f;
if(warmupTarget > 0 && !controlled()){
warmupHold = 1f;
}
if(warmupHold > 0f){
@@ -568,7 +573,7 @@ public class Turret extends ReloadTurret{
if(validateTarget()){
boolean canShoot;
if(isControlled()){ //player behavior
if(controlled()){ //player behavior
targetPos.set(unit.aimX(), unit.aimY());
canShoot = unit.isShooting();
}else if(logicControlled()){ //logic behavior
@@ -580,7 +585,7 @@ public class Turret extends ReloadTurret{
canShoot = within(target, range() + (target instanceof Sized hb ? hb.hitSize()/1.9f : 0f));
}
if(!isControlled()){
if(!controlled()){
unit.aimX(targetPos.x);
unit.aimY(targetPos.y);
}
@@ -624,7 +629,7 @@ public class Turret extends ReloadTurret{
}
protected boolean validateTarget(){
return !Units.invalidateTarget(target, canHeal() ? Team.derelict : team, x, y) || unit.isPlayer() || logicControlled();
return !Units.invalidateTarget(target, canHeal() ? Team.derelict : team, x, y) || controlled() || logicControlled();
}
protected boolean canHeal(){
@@ -6,8 +6,6 @@ import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.meta.*;
import static mindustry.Vars.*;
public class BufferedItemBridge extends ItemBridge{
public final int timerAccept = timers++;
@@ -21,7 +19,7 @@ public class BufferedItemBridge extends ItemBridge{
hasItems = true;
canOverdrive = true;
}
@Override
public void setStats(){
super.setStats();
@@ -30,6 +28,7 @@ public class BufferedItemBridge extends ItemBridge{
stats.add(Stat.itemsMoved, displayedSpeed, StatUnit.itemsSecond);
}
public class BufferedItemBridgeBuild extends ItemBridgeBuild{
ItemBuffer buffer = new ItemBuffer(bufferCapacity);
@@ -47,21 +46,12 @@ 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)) % 4) : 0;
int frame = enabled && clogHeat <= 0.5f ? (int)(((Time.time * speed * 8f * timeScale * efficiency)) % 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{
}
}
public final void updateConveyor(){
efficiency = enabled ? 1f : 0f;
@Override
public void updateTile(){
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 * delta();
float moved = speed * edelta();
for(int i = len - 1; i >= 0; i--){
float nextpos = (i == len - 1 ? 100f : ys[i + 1]) - itemSpace;
@@ -297,16 +297,6 @@ 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);
@@ -6,11 +6,10 @@ import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.type.*;
import mindustry.world.blocks.*;
import mindustry.world.blocks.liquid.*;
import mindustry.world.meta.*;
import static mindustry.Vars.*;
public class DirectionLiquidBridge extends DirectionBridge{
public final int timerFlow = timers++;
@@ -35,17 +34,8 @@ public class DirectionLiquidBridge extends DirectionBridge{
return new TextureRegion[]{bottomRegion, region, dirRegion};
}
public class DirectionLiquidBridgeBuild extends DirectionBridgeBuild{
@Override
public void addToList(){
state.buildings.directionLiquidBridges.add(this);
}
@Override
public void removeFromList(){
state.buildings.directionLiquidBridges.remove(this);
}
public class DuctBridgeBuild extends DirectionBridgeBuild implements LiquidUpdater{
Building next;
@Override
public void drawCached(){
@@ -68,20 +58,20 @@ public class DirectionLiquidBridge extends DirectionBridge{
}
}
@Override
public void onProximityUpdate(){
super.onProximityUpdate();
next = front();
}
@Override
public void updateTile(){
var link = lastLink = findLink();
if(link != null){
moveLiquid(link, liquids.current());
link.occupied[rotation % 4] = this;
}
if(link == null){
if(liquids.currentAmount() > 0.0001f && timer(timerFlow, 1)){
moveLiquidForward(false, liquids.current());
}
}
for(int i = 0; i < 4; i++){
if(occupied[i] == null || occupied[i].rotation != i || !occupied[i].isValid() || occupied[i].lastLink != this){
occupied[i] = null;
@@ -89,11 +79,22 @@ public class DirectionLiquidBridge extends DirectionBridge{
}
}
@Override
public void updateLiquids(float delta){
var link = lastLink;
if(link != null){
moveLiquid(link, liquids.current(), delta);
}else{
if(liquids.currentAmount() > 0.0001f && timer(timerFlow, 1)){
moveLiquidForward(next, false, liquids.current(), Time.delta);
}
}
}
@Override
public boolean acceptLiquid(Building source, Liquid liquid){
var link = findLink();
//only accept if there's an output point, or it comes from a link
if(link == null && !(source instanceof DirectionBridgeBuild b && b.findLink() == this)) return false;
if(lastLink == null && !(source instanceof DirectionBridgeBuild b && b.findLink() == this)) return false;
int rel = this.relativeToEdge(source.tile);
@@ -135,16 +135,6 @@ 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);
@@ -199,7 +189,8 @@ public class Duct extends Block implements Autotiler{
}
}
public final void updateDuct(){
@Override
public void updateTile(){
progress += edelta() / speed * 2f;
if(current != null && next != null){
@@ -2,10 +2,9 @@ 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;
@@ -27,16 +26,6 @@ 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,16 +97,6 @@ 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;
@@ -199,8 +199,9 @@ public class ItemBridge extends Block{
public IntSeq incoming = new IntSeq(false, 4);
public float warmup;
public float time = -8f, timeSpeed;
public boolean wasMoved, moved;
public boolean wasMoved, moved, hadValidLink;
public float transportCounter;
public Building lastValidLink;
@Override
public void pickedUp(){
@@ -315,16 +316,6 @@ 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)){
@@ -340,10 +331,14 @@ public class ItemBridge extends Block{
checkIncoming();
Tile other = world.tile(link);
if(!linkValid(tile, other)){
hadValidLink = linkValid(tile, other);
if(!hadValidLink){
lastValidLink = null;
doDump();
warmup = 0f;
}else{
lastValidLink = other.build;
var inc = ((ItemBridgeBuild)other.build).incoming;
int pos = tile.pos();
if(!inc.contains(pos)){
@@ -501,7 +496,7 @@ public class ItemBridge extends Block{
@Override
public boolean shouldConsume(){
return linkValid(tile, world.tile(link)) && enabled;
return hadValidLink && enabled;
}
@Override
@@ -51,16 +51,8 @@ public class Junction extends Block{
}
@Override
public void addToList(){
state.buildings.junctions.add(this);
}
public void updateTile(){
@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,8 +9,6 @@ import mindustry.world.*;
import mindustry.world.blocks.*;
import mindustry.world.meta.*;
import static mindustry.Vars.*;
public class Router extends Block{
public float speed = 8f;
@@ -53,16 +51,6 @@ 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,7 +7,6 @@ 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.*;
@@ -266,18 +265,7 @@ public class StackConveyor extends Block implements Autotiler{
}
@Override
public void addToList(){
Vars.state.buildings.stackConveyors.add(this);
}
@Override
public void removeFromList(){
Vars.state.buildings.stackConveyors.remove(this);
}
public final void updateStackConveyor(){
updateConsumption();
public void updateTile(){
//the item still needs to be "reeled" in when disabled
float eff = enabled ? (efficiency + baseEfficiency) : 1f;
@@ -55,6 +55,9 @@ public class Conduit extends LiquidBlock implements Autotiler{
priority = TargetPriority.transport;
drawCached = true;
buildingCacheLayer = BuildingCacheLayer.under;
update = false;
destructible = true;
}
@Override
@@ -153,10 +156,11 @@ public class Conduit extends LiquidBlock implements Autotiler{
return new TextureRegion[]{Core.atlas.find("conduit-bottom"), topRegions[0]};
}
public class ConduitBuild extends LiquidBuild implements ChainedBuilding{
public class ConduitBuild extends LiquidBuild implements ChainedBuilding, LiquidUpdater{
public float smoothLiquid;
public int blendbits, xscl = 1, yscl = 1, blending;
public boolean capped, backCapped = false;
Building next;
@Override
public void drawCached(){
@@ -236,38 +240,24 @@ public class Conduit extends LiquidBlock implements Autotiler{
Building next = front(), prev = back();
capped = next == null || next.team != team || !next.block.hasLiquids;
backCapped = blendbits == 0 && (prev == null || prev.team != team || !prev.block.hasLiquids);
this.next = next;
}
@Override
public boolean acceptLiquid(Building source, Liquid liquid){
noSleep();
return (liquids.current() == liquid || liquids.currentAmount() < 0.2f)
&& (tile == null || source == this || (source.relativeTo(tile.x, tile.y) + 2) % 4 != rotation);
}
public final void updateConduit(){
if(!noUpdateDisabled) return;
smoothLiquid = Mathf.lerpDelta(smoothLiquid, liquids.currentAmount() / liquidCapacity, 0.05f);
@Override
public void updateLiquids(float delta){
smoothLiquid = Mathf.lerp(smoothLiquid, liquids.currentAmount() / liquidCapacity, 0.05f * delta);
if(liquids.currentAmount() > 0.0001f && timer(timerFlow, 1)){
moveLiquidForward(leaks, liquids.current());
noSleep();
}else{
sleep();
moveLiquidForward(next, leaks, liquids.current(), delta);
}
}
@Override
public void addToList(){
state.buildings.conduits.add(this);
}
@Override
public void removeFromList(){
state.buildings.conduits.remove(this);
}
@Nullable
@Override
public Building next(){
@@ -1,11 +1,11 @@
package mindustry.world.blocks.liquid;
import arc.util.*;
import mindustry.gen.*;
import mindustry.world.blocks.*;
import mindustry.world.blocks.distribution.*;
import mindustry.world.meta.*;
import static mindustry.Vars.*;
public class LiquidBridge extends ItemBridge{
public LiquidBridge(String name){
@@ -18,28 +18,27 @@ public class LiquidBridge extends ItemBridge{
envEnabled = Env.any;
}
public class LiquidBridgeBuild extends ItemBridgeBuild{
@Override
public void addToList(){
state.buildings.liquidBridges.add(this);
}
@Override
public void removeFromList(){
state.buildings.liquidBridges.remove(this);
}
public class LiquidBridgeBuild extends ItemBridgeBuild implements LiquidUpdater{
@Override
public void updateTransport(Building other){
if(warmup >= 0.25f){
moved |= moveLiquid(other, liquids.current()) > 0.05f;
}
}
@Override
public void doDump(){
dumpLiquid(liquids.current(), 1f);
}
@Override
public void updateLiquids(float delta){
var link = lastValidLink;
if(link == null){
dumpLiquid(liquids.current(), 1f);
}else if(warmup >= 0.25f){
moved |= moveLiquid(link, liquids.current(), Time.delta) > 0.05f;
}
}
}
}
@@ -3,8 +3,7 @@ package mindustry.world.blocks.liquid;
import arc.graphics.g2d.*;
import mindustry.gen.*;
import mindustry.type.*;
import static mindustry.Vars.*;
import mindustry.world.blocks.*;
public class LiquidRouter extends LiquidBlock{
public float liquidPadding = 0f;
@@ -15,6 +14,9 @@ public class LiquidRouter extends LiquidBlock{
noUpdateDisabled = true;
canOverdrive = false;
floating = true;
update = false;
destructible = true;
}
@Override
@@ -22,22 +24,13 @@ public class LiquidRouter extends LiquidBlock{
return new TextureRegion[]{bottomRegion, region};
}
public class LiquidRouterBuild extends LiquidBuild{
public class LiquidRouterBuild extends LiquidBuild implements LiquidUpdater{
public final void updateLiquidRouter(){
@Override
public void updateLiquids(float delta){
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.setEnabled(true);
lbuild.enabled = 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.setEnabled(true);
link.enabled = true;
}
}
}
@@ -25,7 +25,7 @@ public class SwitchBlock extends Block{
group = BlockGroup.logic;
envEnabled = Env.any;
config(Boolean.class, (SwitchBuild entity, Boolean b) -> entity.setEnabled(b));
config(Boolean.class, (SwitchBuild entity, Boolean b) -> entity.enabled = b);
}
public boolean accessible(){
@@ -135,6 +135,8 @@ public class PayloadConveyor extends Block{
@Override
public void updateTile(){
if(!enabled) return;
if(item != null){
item.update(null, this);
}
@@ -10,6 +10,7 @@ import mindustry.entities.*;
import mindustry.game.EventType.*;
import mindustry.graphics.*;
import mindustry.type.*;
import mindustry.world.blocks.*;
import mindustry.world.consumers.*;
import mindustry.world.meta.*;
@@ -93,7 +94,7 @@ public class ConsumeGenerator extends PowerGenerator{
}
}
public class ConsumeGeneratorBuild extends GeneratorBuild{
public class ConsumeGeneratorBuild extends GeneratorBuild implements LiquidUpdater{
public float warmup, totalTime, efficiencyMultiplier = 1f, itemDurationMultiplier = 1;
@Override
@@ -133,19 +134,29 @@ public class ConsumeGenerator extends PowerGenerator{
generateTime = 1f;
}
//generation time always goes down, but only at the end so consumeTriggerValid doesn't assume fake items
generateTime -= delta() / (itemDuration * itemDurationMultiplier);
}
@Override
public boolean shouldLiquidUpdate(){
return outputLiquid != null;
}
@Override
public void updateLiquids(float delta){
if(outputLiquid != null){
float added = Math.min(productionEfficiency * delta() * outputLiquid.amount, liquidCapacity - liquids.get(outputLiquid.liquid));
float added = Math.min(productionEfficiency * delta * timeScale * outputLiquid.amount, liquidCapacity - liquids.get(outputLiquid.liquid));
liquids.add(outputLiquid.liquid, added);
dumpLiquid(outputLiquid.liquid);
if(explodeOnFull && liquids.get(outputLiquid.liquid) >= liquidCapacity - 0.01f){
kill();
Events.fire(new GeneratorPressureExplodeEvent(this));
Core.app.post(() -> {
kill();
Events.fire(new GeneratorPressureExplodeEvent(this));
});
}
}
//generation time always goes down, but only at the end so consumeTriggerValid doesn't assume fake items
generateTime -= delta() / (itemDuration * itemDurationMultiplier);
}
@Override
@@ -3,7 +3,6 @@ package mindustry.world.blocks.power;
import arc.math.*;
import arc.struct.*;
import arc.util.*;
import mindustry.*;
import mindustry.gen.*;
public class PowerGraph{
@@ -18,11 +17,7 @@ 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);
public int buildingArrayIndex = -1;
private boolean added;
private final boolean shouldUpdate;
private final @Nullable PowerGraphUpdater entity;
private final WindowedMean powerBalance = new WindowedMean(60);
private float lastPowerProduced, lastPowerNeeded, lastPowerStored;
private float lastScaledPowerIn, lastScaledPowerOut, lastCapacity;
@@ -33,12 +28,14 @@ public class PowerGraph{
private static int lastGraphID;
public PowerGraph(){
this(true);
entity = PowerGraphUpdater.create();
entity.graph = this;
graphID = lastGraphID++;
}
public PowerGraph(boolean shouldUpdate){
public PowerGraph(boolean noEntity){
entity = null;
graphID = lastGraphID++;
this.shouldUpdate = shouldUpdate;
}
public int getID(){
@@ -219,8 +216,8 @@ public class PowerGraph{
public void update(){
if(!consumers.isEmpty() && consumers.first().cheating()){
//when cheating, just set status to 1
for(int i = 0; i < consumers.size; i++){
consumers.items[i].power.status = 1f;
for(Building tile : consumers){
tile.power.status = 1f;
}
lastPowerNeeded = lastPowerProduced = 1f;
@@ -269,12 +266,12 @@ public class PowerGraph{
}
//other entity should be removed as the graph was merged
graph.removeUpdate();
if(graph.entity != null) graph.entity.remove();
for(Building tile : graph.all){
add(tile);
}
addUpdate();
checkAdd();
}
public void add(Building build){
@@ -283,7 +280,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){
build.power.graph.removeUpdate();
if(build.power.graph.entity != null) build.power.graph.entity.remove();
}
build.power.graph = this;
@@ -303,18 +300,8 @@ public class PowerGraph{
}
}
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 checkAdd(){
if(entity != null) entity.add();
}
public void clear(){
@@ -322,8 +309,8 @@ public class PowerGraph{
producers.clear();
consumers.clear();
batteries.clear();
removeUpdate();
//nothing left
if(entity != null) entity.remove();
}
public void reflow(Building tile){
@@ -333,7 +320,7 @@ public class PowerGraph{
while(queue.size > 0){
Building child = queue.removeFirst();
add(child);
addUpdate();
checkAdd();
for(Building next : child.getPowerConnections(outArray2)){
if(closedSet.add(next.pos())){
queue.addLast(next);
@@ -361,7 +348,7 @@ public class PowerGraph{
//create graph for this branch
PowerGraph graph = new PowerGraph();
graph.addUpdate();
graph.checkAdd();
graph.add(other);
//add to queue for BFS
queue.clear();
@@ -386,7 +373,7 @@ public class PowerGraph{
}
//implied empty graph here
removeUpdate();
if(entity != null) entity.remove();
}
public int getId(){
@@ -10,6 +10,7 @@ import mindustry.game.*;
import mindustry.graphics.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
import mindustry.world.meta.*;
public class ThermalGenerator extends PowerGenerator{
@@ -71,7 +72,7 @@ public class ThermalGenerator extends PowerGenerator{
return tile.getLinkedTilesAs(this, tempTiles).sumf(other -> other.floor().attributes.get(attribute)) > minEfficiency;
}
public class ThermalGeneratorBuild extends GeneratorBuild{
public class ThermalGeneratorBuild extends GeneratorBuild implements LiquidUpdater{
public float sum;
@Override
@@ -81,9 +82,17 @@ public class ThermalGenerator extends PowerGenerator{
if(productionEfficiency > 0.1f && Mathf.chanceDelta(effectChance)){
generateEffect.at(x + Mathf.range(3f), y + Mathf.range(3f));
}
}
@Override
public boolean shouldLiquidUpdate(){
return outputLiquid != null;
}
@Override
public void updateLiquids(float delta){
if(outputLiquid != null){
float added = Math.min(productionEfficiency * delta() * outputLiquid.amount, liquidCapacity - liquids.get(outputLiquid.liquid));
float added = Math.min(productionEfficiency * delta * timeScale * outputLiquid.amount, liquidCapacity - liquids.get(outputLiquid.liquid));
liquids.add(outputLiquid.liquid, added);
dumpLiquid(outputLiquid.liquid);
}
@@ -41,5 +41,10 @@ public class Fracker extends SolidPump{
lastPump = 0f;
}
}
@Override
public void updateLiquids(float delta){
dumpLiquid(result);
}
}
}
@@ -325,7 +325,7 @@ public class GenericCrafter extends Block{
}
@Override
public void processLiquids(){
public void updateLiquids(float delta){
if(outputLiquids != null){
for(int i = 0; i < outputLiquids.length; i++){
int dir = liquidOutputDirections.length > i ? liquidOutputDirections[i] : -1;
@@ -114,6 +114,8 @@ public class Pump extends LiquidBlock{
public float amount = 0f;
public @Nullable Liquid liquidDrop = null;
float lastPumped;
@Override
public void draw(){
drawer.draw(this);
@@ -160,16 +162,13 @@ public class Pump extends LiquidBlock{
@Override
public void updateTile(){
if(efficiency > 0 && liquidDrop != null){
float maxPump = Math.min(liquidCapacity - liquids.get(liquidDrop), amount * pumpAmount * edelta());
liquids.add(liquidDrop, maxPump);
//does nothing for most pumps, as those do not require items.
if((consTimer += delta()) >= consumeTime){
consume();
consTimer %= 1f;
}
warmup = Mathf.approachDelta(warmup, maxPump > 0.001f ? 1f : 0f, warmupSpeed);
warmup = Mathf.approachDelta(warmup, lastPumped > 0.001f ? 1f : 0f, warmupSpeed);
}else{
warmup = Mathf.approachDelta(warmup, 0f, warmupSpeed);
}
@@ -178,9 +177,14 @@ public class Pump extends LiquidBlock{
}
@Override
public void processLiquids(){
if(liquidDrop != null){
dumpLiquid(liquidDrop);
public void updateLiquids(float delta){
var ld = liquidDrop;
if(ld != null){
float maxPump = Math.min(liquidCapacity - liquids.get(liquidDrop), amount * pumpAmount * efficiency * timeScale * delta);
liquids.add(liquidDrop, maxPump);
lastPumped = maxPump;
dumpLiquid(ld);
}
}
@@ -121,12 +121,7 @@ public class SolidPump extends Pump{
@Override
public void updateTile(){
liquidDrop = result;
float fraction = Math.max(validTiles + boost + (attribute == null ? 0 : attribute.env()), 0);
if(efficiency > 0 && typeLiquid() < liquidCapacity - 0.001f){
float maxPump = Math.min(liquidCapacity - typeLiquid(), pumpAmount * delta() * fraction * efficiency);
liquids.add(result, maxPump);
lastPump = maxPump / Time.delta;
warmup = Mathf.lerpDelta(warmup, 1f, 0.02f);
if(Mathf.chance(delta() * updateEffectChance))
updateEffect.at(x + Mathf.range(size * 2f), y + Mathf.range(size * 2f));
@@ -139,7 +134,12 @@ public class SolidPump extends Pump{
}
@Override
public void processLiquids(){
public void updateLiquids(float delta){
float fraction = Math.max(validTiles + boost + (attribute == null ? 0 : attribute.env()), 0);
float maxPump = Math.min(liquidCapacity - typeLiquid(), pumpAmount * delta * timeScale * fraction * efficiency);
liquids.add(result, maxPump);
lastPump = maxPump / Time.delta;
dumpLiquid(result);
}
@@ -21,7 +21,8 @@ public class LiquidSource extends Block{
public LiquidSource(String name){
super(name);
update = true;
update = false;
destructible = true;
solid = true;
hasLiquids = true;
liquidCapacity = 10000f;
@@ -55,11 +56,11 @@ public class LiquidSource extends Block{
return new TextureRegion[]{bottomRegion, region};
}
public class LiquidSourceBuild extends Building{
public class LiquidSourceBuild extends Building implements LiquidUpdater{
public @Nullable Liquid source = null;
@Override
public void updateTile(){
public void updateLiquids(float delta){
if(source == null){
liquids.clear();
}else{
@@ -20,7 +20,9 @@ public class PowerSource extends PowerNode{
@Override
public void onProximityUpdate(){
super.onProximityUpdate();
checkAllowUpdate();
if(!allowUpdate()){
enabled = false;
}
}
@Override
@@ -8,7 +8,6 @@ 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.*;
@@ -160,17 +159,8 @@ public class Unloader extends Block{
}
@Override
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;
public void updateTile(){
if(((unloadTimer += delta()) < speed) || (possibleBlocks.size < 2)) return;
Item item = null;
boolean any = false;
@@ -31,6 +31,7 @@ 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;
@@ -85,6 +86,7 @@ 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,9 +34,7 @@ public class ConsumeItemExplode extends ConsumeItemFilter{
if(item != null){
if(Vars.state.rules.reactorExplosions && Mathf.chance(build.delta() * baseChance * Mathf.clamp(item.explosiveness - threshold))){
//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));
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,9 +19,7 @@ public class ConsumePayloadDynamic extends Consume{
@Override
public float efficiency(Building build){
float mult = multiplier.get(build);
var stacks = payloads.get(build);
for(int i = 0; i < stacks.size; i ++){
var stack = stacks.get(i);
for(PayloadStack stack : payloads.get(build)){
if(!build.getPayloads().contains(stack.item, Math.round(stack.amount * mult))){
return 0f;
}
@@ -32,9 +30,7 @@ public class ConsumePayloadDynamic extends Consume{
@Override
public void trigger(Building build){
float mult = multiplier.get(build);
var stacks = payloads.get(build);
for(int i = 0; i < stacks.size; i ++){
var stack = stacks.get(i);
for(PayloadStack stack : payloads.get(build)){
build.getPayloads().remove(stack.item, Math.round(stack.amount * mult));
}
}
@@ -5,14 +5,13 @@ 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 PayloadStack[] payloads;
public Seq<PayloadStack> payloads;
public ConsumePayloads(Seq<PayloadStack> payloads){
this.payloads = payloads.toArray(Payload.class);
this.payloads = payloads;
}
@Override