diff --git a/build.gradle b/build.gradle index e3ae3ad3e9..1828919509 100644 --- a/build.gradle +++ b/build.gradle @@ -381,6 +381,7 @@ project(":tests"){ testImplementation "org.junit.jupiter:junit-jupiter-params:5.7.1" testImplementation "org.junit.jupiter:junit-jupiter-api:5.7.1" testImplementation arcModule("backends:backend-headless") + testImplementation "org.json:json:20230618" testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:5.7.1" } diff --git a/core/src/mindustry/ai/types/CommandAI.java b/core/src/mindustry/ai/types/CommandAI.java index 0cf650ccc2..bedc78fc14 100644 --- a/core/src/mindustry/ai/types/CommandAI.java +++ b/core/src/mindustry/ai/types/CommandAI.java @@ -30,6 +30,8 @@ public class CommandAI extends AIController{ public int groupIndex = 0; /** All encountered unreachable buildings of this AI. Why a sequence? Because contains() is very rarely called on it. */ public IntSeq unreachableBuildings = new IntSeq(8); + /** ID of unit read as target. This is set up after reading. Do not access! */ + public int readAttackTarget = -1; protected boolean stopAtTarget, stopWhenInRange; protected Vec2 lastTargetPos; @@ -284,7 +286,7 @@ public class CommandAI extends AIController{ attackTarget = null; } - if(unit.isFlying() && move){ + if(unit.isFlying() && move && (attackTarget == null || !unit.within(attackTarget, unit.type.range))){ unit.lookAt(vecMovePos); }else{ faceTarget(); @@ -349,6 +351,14 @@ public class CommandAI extends AIController{ } } + @Override + public void afterRead(Unit unit){ + if(readAttackTarget != -1){ + attackTarget = Groups.unit.getByID(readAttackTarget); + readAttackTarget = -1; + } + } + @Override public float prefSpeed(){ return group == null ? super.prefSpeed() : Math.min(group.minSpeed, unit.speed()); diff --git a/core/src/mindustry/content/UnitTypes.java b/core/src/mindustry/content/UnitTypes.java index bcb04b2fda..95f5ced15c 100644 --- a/core/src/mindustry/content/UnitTypes.java +++ b/core/src/mindustry/content/UnitTypes.java @@ -1845,6 +1845,7 @@ public class UnitTypes{ armor = 3f; buildSpeed = 1.5f; + rotateToBuilding = false; weapons.add(new RepairBeamWeapon("repair-beam-weapon-center"){{ x = 0f; @@ -1935,6 +1936,7 @@ public class UnitTypes{ abilities.add(new StatusFieldAbility(StatusEffects.overclock, 60f * 6, 60f * 6f, 60f)); buildSpeed = 2f; + rotateToBuilding = false; weapons.add(new Weapon("plasma-mount-weapon"){{ @@ -2009,6 +2011,7 @@ public class UnitTypes{ trailScl = 2f; buildSpeed = 2f; + rotateToBuilding = false; weapons.add(new RepairBeamWeapon("repair-beam-weapon-center"){{ x = 11f; @@ -2150,6 +2153,7 @@ public class UnitTypes{ trailScl = 3.2f; buildSpeed = 3f; + rotateToBuilding = false; abilities.add(new EnergyFieldAbility(40f, 65f, 180f){{ statusDuration = 60f * 6f; @@ -2193,6 +2197,7 @@ public class UnitTypes{ trailScl = 3.5f; buildSpeed = 3.5f; + rotateToBuilding = false; for(float mountY : new float[]{-117/4f, 50/4f}){ for(float sign : Mathf.signs){ diff --git a/core/src/mindustry/entities/Fires.java b/core/src/mindustry/entities/Fires.java index b22e39eb4f..0deafd3618 100644 --- a/core/src/mindustry/entities/Fires.java +++ b/core/src/mindustry/entities/Fires.java @@ -1,8 +1,6 @@ package mindustry.entities; import arc.*; -import arc.math.geom.*; -import arc.struct.*; import arc.util.*; import mindustry.content.*; import mindustry.game.EventType.*; @@ -14,13 +12,12 @@ import static mindustry.Vars.*; public class Fires{ private static final float baseLifetime = 1000f; - private static final IntMap map = new IntMap<>(); /** Start a fire on the tile. If there already is a fire there, refreshes its lifetime. */ public static void create(Tile tile){ if(net.client() || tile == null || !state.rules.fire || !state.rules.hasEnv(Env.oxygen)) return; //not clientside. - Fire fire = map.get(tile.pos()); + Fire fire = get(tile); if(fire == null){ fire = Fire.create(); @@ -28,48 +25,58 @@ public class Fires{ fire.lifetime = baseLifetime; fire.set(tile.worldx(), tile.worldy()); fire.add(); - map.put(tile.pos(), fire); + set(tile, fire); }else{ fire.lifetime = baseLifetime; fire.time = 0f; } } - public static Fire get(int x, int y){ - return map.get(Point2.pack(x, y)); + public static @Nullable Fire get(Tile tile){ + return world.tiles.getFire(tile.array()); + } + + public static @Nullable Fire get(int x, int y){ + return world.tiles.getFire(world.packArray(x, y)); + } + + private static void set(Tile tile, Fire fire){ + world.tiles.setFire(tile.array(), fire); } public static boolean has(int x, int y){ - if(!Structs.inBounds(x, y, world.width(), world.height()) || !map.containsKey(Point2.pack(x, y))){ + if(!Structs.inBounds(x, y, world.width(), world.height())){ return false; } - Fire fire = map.get(Point2.pack(x, y)); - return fire.isAdded() && fire.fin() < 1f && fire.tile() != null && fire.tile().x == x && fire.tile().y == y; + Fire fire = get(x, y); + return fire != null && fire.isAdded() && fire.fin() < 1f && fire.tile != null && fire.tile.x == x && fire.tile.y == y; } /** * Attempts to extinguish a fire by shortening its life. If there is no fire here, does nothing. */ public static void extinguish(Tile tile, float intensity){ - if(tile != null && map.containsKey(tile.pos())){ - Fire fire = map.get(tile.pos()); - fire.time(fire.time + intensity * Time.delta); - Fx.steam.at(fire); - if(fire.time >= fire.lifetime){ - Events.fire(Trigger.fireExtinguish); + if(tile != null){ + Fire fire = get(tile); + if(fire != null){ + fire.time(fire.time + intensity * Time.delta); + Fx.steam.at(fire); + if(fire.time >= fire.lifetime){ + Events.fire(Trigger.fireExtinguish); + } } } } public static void remove(Tile tile){ if(tile != null){ - map.remove(tile.pos()); + set(tile, null); } } public static void register(Fire fire){ if(fire.tile != null){ - map.put(fire.tile.pos(), fire); + set(fire.tile, fire); } } } diff --git a/core/src/mindustry/entities/Puddles.java b/core/src/mindustry/entities/Puddles.java index aa7e5bde8d..be7b1bba1b 100644 --- a/core/src/mindustry/entities/Puddles.java +++ b/core/src/mindustry/entities/Puddles.java @@ -26,8 +26,8 @@ public class Puddles{ } /** Returns the Puddle on the specified tile. May return null. */ - public static Puddle get(Tile tile){ - return world.tiles.puddle(tile.array()); + public static @Nullable Puddle get(Tile tile){ + return world.tiles.getPuddle(tile.array()); } public static void deposit(Tile tile, Tile source, Liquid liquid, float amount, boolean initial){ diff --git a/core/src/mindustry/entities/Units.java b/core/src/mindustry/entities/Units.java index 998c2c05f3..a641bac7a8 100644 --- a/core/src/mindustry/entities/Units.java +++ b/core/src/mindustry/entities/Units.java @@ -214,7 +214,10 @@ public class Units{ } }); - return buildResult; + var result = buildResult; + buildResult = null; + + return result; } /** Iterates through all buildings in a range. */ diff --git a/core/src/mindustry/entities/comp/EntityComp.java b/core/src/mindustry/entities/comp/EntityComp.java index aaacdc521b..fdba35a737 100644 --- a/core/src/mindustry/entities/comp/EntityComp.java +++ b/core/src/mindustry/entities/comp/EntityComp.java @@ -66,4 +66,9 @@ abstract class EntityComp{ void afterRead(){ } + + /** Called after *all* entities are read. */ + void afterAllRead(){ + + } } diff --git a/core/src/mindustry/entities/comp/PlayerComp.java b/core/src/mindustry/entities/comp/PlayerComp.java index c995d3d9f0..fe787a8336 100644 --- a/core/src/mindustry/entities/comp/PlayerComp.java +++ b/core/src/mindustry/entities/comp/PlayerComp.java @@ -183,6 +183,9 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra if(!unit.isNull()){ clearUnit(); } + + lastReadUnit = Nulls.unit; + justSwitchTo = justSwitchFrom = null; } public void team(Team team){ diff --git a/core/src/mindustry/entities/comp/UnitComp.java b/core/src/mindustry/entities/comp/UnitComp.java index 4ff6b3168f..828c9880d7 100644 --- a/core/src/mindustry/entities/comp/UnitComp.java +++ b/core/src/mindustry/entities/comp/UnitComp.java @@ -490,6 +490,11 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I } } + @Override + public void afterAllRead(){ + controller.afterRead(self()); + } + @Override public void add(){ team.data().updateCount(type, 1); diff --git a/core/src/mindustry/entities/units/AIController.java b/core/src/mindustry/entities/units/AIController.java index 6b688eb60a..963fb67933 100644 --- a/core/src/mindustry/entities/units/AIController.java +++ b/core/src/mindustry/entities/units/AIController.java @@ -55,6 +55,11 @@ public class AIController implements UnitController{ return false; } + @Override + public void afterRead(Unit unit){ + + } + @Override public boolean isLogicControllable(){ return true; diff --git a/core/src/mindustry/entities/units/UnitController.java b/core/src/mindustry/entities/units/UnitController.java index ff861b805d..b77a857a2f 100644 --- a/core/src/mindustry/entities/units/UnitController.java +++ b/core/src/mindustry/entities/units/UnitController.java @@ -27,6 +27,10 @@ public interface UnitController{ } + default void afterRead(Unit unit){ + + } + default boolean isBeingControlled(Unit player){ return false; } diff --git a/core/src/mindustry/io/SaveIO.java b/core/src/mindustry/io/SaveIO.java index 3ba9a38aa6..dea4ce7b72 100644 --- a/core/src/mindustry/io/SaveIO.java +++ b/core/src/mindustry/io/SaveIO.java @@ -56,8 +56,13 @@ public class SaveIO{ } public static boolean isSaveValid(Fi file){ + return isSaveFileValid(file) || isSaveFileValid(backupFileFor(file)); + } + + private static boolean isSaveFileValid(Fi file){ try(DataInputStream stream = new DataInputStream(new InflaterInputStream(file.read(bufferSize)))){ - return isSaveValid(stream); + getMeta(stream); + return true; }catch(Throwable e){ return false; } diff --git a/core/src/mindustry/io/SaveVersion.java b/core/src/mindustry/io/SaveVersion.java index a1089b7dd3..e45d510893 100644 --- a/core/src/mindustry/io/SaveVersion.java +++ b/core/src/mindustry/io/SaveVersion.java @@ -437,6 +437,8 @@ public abstract class SaveVersion extends SaveFileReader{ //entityMapping is null in older save versions, so use the default var mapping = this.entityMapping == null ? EntityMapping.idMap : this.entityMapping; + Seq entities = new Seq<>(); + int amount = stream.readInt(); for(int j = 0; j < amount; j++){ readChunk(stream, true, in -> { @@ -449,12 +451,17 @@ public abstract class SaveVersion extends SaveFileReader{ int id = in.readInt(); Entityc entity = (Entityc)mapping[typeid].get(); + entities.add(entity); EntityGroup.checkNextId(id); entity.id(id); entity.read(Reads.get(in)); entity.add(); }); } + + for(var e : entities){ + e.afterAllRead(); + } } public void readEntityMapping(DataInput stream) throws IOException{ diff --git a/core/src/mindustry/io/TypeIO.java b/core/src/mindustry/io/TypeIO.java index d2db6b1e70..4a80eca3e9 100644 --- a/core/src/mindustry/io/TypeIO.java +++ b/core/src/mindustry/io/TypeIO.java @@ -563,13 +563,14 @@ public class TypeIO{ ai.targetPos = null; } ai.setupLastPos(); + ai.readAttackTarget = -1; if(hasAttack){ byte entityType = read.b(); if(entityType == 1){ ai.attackTarget = world.build(read.i()); }else{ - ai.attackTarget = Groups.unit.getByID(read.i()); + ai.attackTarget = Groups.unit.getByID(ai.readAttackTarget = read.i()); } }else{ ai.attackTarget = null; diff --git a/core/src/mindustry/logic/LExecutor.java b/core/src/mindustry/logic/LExecutor.java index 1541a43f60..b32faa78aa 100644 --- a/core/src/mindustry/logic/LExecutor.java +++ b/core/src/mindustry/logic/LExecutor.java @@ -1372,13 +1372,20 @@ public class LExecutor{ exec.setobj(result, i < 0 || i >= builds.size ? null : builds.get(i)); } } - case unitCount -> exec.setnum(result, data.units.size); + case unitCount -> { + UnitType type = exec.obj(extra) instanceof UnitType u ? u : null; + if(type == null){ + exec.setnum(result, data.units.size); + }else{ + exec.setnum(result, data.unitsByType[type.id].size); + } + } case coreCount -> exec.setnum(result, data.cores.size); case playerCount -> exec.setnum(result, data.players.size); case buildCount -> { Block block = exec.obj(extra) instanceof Block b ? b : null; if(block == null){ - exec.setobj(result, null); + exec.setnum(result, data.buildings.size); }else{ exec.setnum(result, data.getBuildings(block).size); } diff --git a/core/src/mindustry/logic/LStatements.java b/core/src/mindustry/logic/LStatements.java index d688c41212..b5aafdfcee 100644 --- a/core/src/mindustry/logic/LStatements.java +++ b/core/src/mindustry/logic/LStatements.java @@ -1740,7 +1740,7 @@ public class LStatements{ fields(table, index, i -> index = i); } - if(type == FetchType.buildCount || type == FetchType.build){ + if(type == FetchType.buildCount || type == FetchType.build || type == FetchType.unitCount){ row(table); fields(table, "block", extra, i -> extra = i); diff --git a/core/src/mindustry/type/UnitType.java b/core/src/mindustry/type/UnitType.java index a8623a6d65..8c29a2e50d 100644 --- a/core/src/mindustry/type/UnitType.java +++ b/core/src/mindustry/type/UnitType.java @@ -952,7 +952,7 @@ public class UnitType extends UnlockableContent implements Senseable{ public void createIcons(MultiPacker packer){ super.createIcons(packer); - if(constructor == null) throw new IllegalArgumentException("No constructor set up for unit '" + name + "'. Make sure you assign a valid value to `constructor`, e.g. `constructor = UnitEntity::new`"); + if(constructor == null) throw new IllegalArgumentException("No constructor set up for unit '" + name + "', add this argument to your units field: `constructor = UnitEntity::create`"); sample = constructor.get(); diff --git a/core/src/mindustry/ui/fragments/BlockConfigFragment.java b/core/src/mindustry/ui/fragments/BlockConfigFragment.java index 144bfeb8d6..356c63fca4 100644 --- a/core/src/mindustry/ui/fragments/BlockConfigFragment.java +++ b/core/src/mindustry/ui/fragments/BlockConfigFragment.java @@ -43,6 +43,7 @@ public class BlockConfigFragment{ table.visible = true; table.clear(); + table.background(null); tile.buildConfiguration(table); table.pack(); table.setTransform(true); diff --git a/core/src/mindustry/world/Tiles.java b/core/src/mindustry/world/Tiles.java index 37d8bd41d2..85a2f54c4c 100644 --- a/core/src/mindustry/world/Tiles.java +++ b/core/src/mindustry/world/Tiles.java @@ -14,15 +14,17 @@ public class Tiles implements Iterable{ final Tile[] array; final Puddle[] puddles; + final Fire[] fires; public Tiles(int width, int height){ this.array = new Tile[width * height]; this.width = width; this.height = height; this.puddles = new Puddle[width * height]; + this.fires = new Fire[width * height]; } - public Puddle puddle(int pos){ + public Puddle getPuddle(int pos){ return puddles[pos]; } @@ -30,6 +32,15 @@ public class Tiles implements Iterable{ puddles[pos] = p; } + public Fire getFire(int pos){ + return fires[pos]; + } + + public void setFire(int pos, Fire f){ + fires[pos] = f; + } + + public void each(Intc2 cons){ for(int x = 0; x < width; x++){ for(int y = 0; y < height; y++){ diff --git a/gradle.properties b/gradle.properties index 63ba2cb1ef..1f85852147 100644 --- a/gradle.properties +++ b/gradle.properties @@ -25,4 +25,4 @@ org.gradle.caching=true #used for slow jitpack builds; TODO see if this actually works org.gradle.internal.http.socketTimeout=100000 org.gradle.internal.http.connectionTimeout=100000 -archash=f1e4bdee85 +archash=774bfa97b0 diff --git a/servers_v7.json b/servers_v7.json index 68bbcfec89..a760a6979e 100644 --- a/servers_v7.json +++ b/servers_v7.json @@ -1,15 +1,15 @@ [ - { - "name": "Vndustry", - "address": ["140.238.246.78:7000"] - }, { "name": "STP", "address": ["23.88.73.88:25617"] }, + { + "name" : "LesGarsCools", + "address": ["est1.be"] + }, { "name": "meiqiuMDT", - "address": ["cn1.plush.run:10001","211.101.236.94:10000","bj-1.lcf.icu:10240","play.simpfun.cn:14523"] + "address": ["cn1.meiqiumdy.top:7000","cn1.meiqiumdt.top:7002","cn1.meiqiumdt.top:7006","bj-1.lcf.icu:10240"] }, { "name": "Crux's Revelations", @@ -85,7 +85,7 @@ }, { "name": "MinDurka", - "address": ["81.30.157.68", "81.30.157.68:3001", "81.30.157.68:3002", "81.30.157.68:3003", "81.30.157.68:3004", "81.30.157.68:3005", "81.30.157.68:3006", "81.30.157.68:3007", "81.30.157.68:3008", "81.30.157.68:3009", "81.30.157.68:3010"] + "address": ["95.181.151.210", "95.181.151.210:3001", "95.181.151.210:3002", "95.181.151.210:3003", "95.181.151.210:3004", "95.181.151.210:3005", "95.181.151.210:3006", "95.181.151.210:3007", "95.181.151.210:3008", "95.181.151.210:3009", "95.181.151.210:3010"] }, { "name": "Chaotic Neutral", @@ -133,7 +133,7 @@ }, { "name": "Eradication Mindustry", - "address": ["140.238.246.78:6569", "140.238.246.78:9547", "140.238.246.78:6572", "140.238.246.78:6570", "140.238.246.78:6571", "140.238.246.78:6574", "140.238.246.78:6573", "140.238.246.78:6568", "140.238.246.78:6677", "140.238.246.78:6678", "140.238.246.78:6675", "130.61.22.183:6573", "130.61.22.183:6599", "130.61.22.183:6774", "130.61.22.183:6777"] + "address": ["140.238.246.78:7000", "140.238.246.78:7001", "140.238.246.78:7002", "140.238.246.78:7003", "140.238.246.78:7004", "140.238.246.78:7005", "130.61.22.183:7000", "130.61.22.183:7001", "130.61.22.183:7002", "130.61.22.183:7003", "130.61.22.183:7004", "130.61.22.183:7005", "77.99.254.211:7000", "77.99.254.211:7001", "77.99.254.211:7002", "141.148.196.135:7000"] }, { "name": "Conservatory", @@ -141,7 +141,7 @@ }, { "name": "mindustry.ddns.net", - "address": ["mindustry.ddns.net", "mindustry.ddns.net:1000", "mindustry.ddns.net:2000", "mindustry.ddns.net:3000", "mindustry.ddns.net:4000"] + "address": ["mindustry.ddns.net", "mindustry.ddns.net:1000", "mindustry.ddns.net:2000", "mindustry.ddns.net:3000", "mindustry.ddns.net:4000", "mindustry.ddns.net:5000"] }, { "name": "EasyPlay.su", @@ -226,11 +226,11 @@ }, { "name": "3MIDustry", - "address": ["3midustry.xyz:10", "3midustry.xyz:20", "3midustry.xyz:30", "3midustry.xyz:40", "3midustry.xyz:50", "3midustry.xyz:60", "3midustry.xyz:6567"] + "address": ["play.3midustry.xyz:6001", "play.3midustry.xyz:6002", "play.3midustry.xyz:6003", "play.3midustry.xyz:6004", "play.3midustry.xyz:6005", "play.3midustry.xyz:6006", "play.3midustry.xyz:6007", "play.3midustry.xyz:6008", "play.3midustry.xyz:6009", "play.3midustry.xyz:6010"] }, { "name": "ABCXYZ Community", - "address": ["23.88.73.88:23591", "23.88.73.88:23539", "144.76.57.59:14996", "144.76.57.59:16881", "144.76.57.59:13885", "23.88.73.88:32113", "144.76.57.59:9269", "144.76.57.59:24235", "144.76.57.59:24133"] + "address": ["23.88.73.88:23591", "23.88.73.88:23539", "144.76.57.59:14996", "144.76.57.59:16881", "144.76.57.59:13885", "23.88.73.88:32113", "144.76.57.59:9269", "144.76.57.59:24235", "144.76.57.59:24133", "140.238.246.78:7000"] }, { "name": "CroCraft Network", @@ -238,7 +238,7 @@ }, { "name": "Extra Utilities", - "address": ["222.186.59.147:15142", "222.186.59.147:15143"] + "address": ["222.186.59.147:15142", "222.186.59.147:15143", "p1.i9mr.com:44922", "p1.i9mr.com:44834"] }, { "name": "Alex Multiverse", @@ -263,5 +263,9 @@ { "name": "SkyPlex", "address": ["mc-skyplex.net"] + }, + { + "name": "Gadgetroch's Server", + "address": ["mindustry.gadgetroch.com", "mindustry.gadgetroch.com:6568"] } ] diff --git a/tests/src/test/java/ApplicationTests.java b/tests/src/test/java/ApplicationTests.java index 1885a31d94..773a91ffa3 100644 --- a/tests/src/test/java/ApplicationTests.java +++ b/tests/src/test/java/ApplicationTests.java @@ -26,6 +26,7 @@ import mindustry.type.*; import mindustry.world.*; import mindustry.world.blocks.payloads.*; import mindustry.world.blocks.storage.*; +import org.json.*; import org.junit.jupiter.api.*; import org.junit.jupiter.params.*; import org.junit.jupiter.params.provider.*; @@ -227,11 +228,14 @@ public class ApplicationTests{ void serverListJson(){ String[] files = {"servers_v6.json", "servers_v7.json", "servers_be.json"}; + for(String file : files){ try{ String str = Core.files.absolute("./../../" + file).readString(); assertEquals(ValueType.array, new JsonReader().parse(str).type()); assertTrue(Jval.read(str).isArray()); + JSONArray array = new JSONArray(str); + assertTrue(array.length() > 0); }catch(Exception e){ fail("Failed to parse " + file, e); }