diff --git a/core/src/mindustry/ai/BlockIndexer.java b/core/src/mindustry/ai/BlockIndexer.java index a7564437d8..f0f5dbe9b3 100644 --- a/core/src/mindustry/ai/BlockIndexer.java +++ b/core/src/mindustry/ai/BlockIndexer.java @@ -31,7 +31,7 @@ public class BlockIndexer{ /** Stores all damaged tile entities by team. */ private Seq[] damagedTiles = new Seq[Team.all.length]; /** All ores available on this map. */ - private ObjectSet allOres = new ObjectSet<>(); + private ObjectIntMap allOres = new ObjectIntMap<>(); /** Stores teams that are present here as tiles. */ private Seq activeTeams = new Seq<>(Team.class); /** Maps teams to a map of flagged tiles by flag. */ @@ -76,8 +76,6 @@ public class BlockIndexer{ var drop = tile.drop(); if(drop != null){ - allOres.add(drop); - int qx = (tile.x / quadrantSize); int qy = (tile.y / quadrantSize); @@ -90,6 +88,7 @@ public class BlockIndexer{ ores[drop.id][qx][qy] = new IntSeq(false, 16); } ores[drop.id][qx][qy].add(tile.pos()); + allOres.increment(drop); } } } @@ -148,9 +147,11 @@ public class BlockIndexer{ //when the drop can be mined, record the ore position if(tile.block() == Blocks.air && !seq.contains(pos)){ seq.add(pos); + allOres.increment(drop); }else{ //otherwise, it likely became blocked, remove it (even if it wasn't there) seq.removeValue(pos); + allOres.increment(drop, -1); } } @@ -175,7 +176,7 @@ public class BlockIndexer{ /** @return whether this item is present on this map. */ public boolean hasOre(Item item){ - return allOres.contains(item); + return allOres.get(item) > 0; } /** Returns all damaged tiles by team. */ diff --git a/core/src/mindustry/mod/Mods.java b/core/src/mindustry/mod/Mods.java index bd95c20504..538a3450d5 100644 --- a/core/src/mindustry/mod/Mods.java +++ b/core/src/mindustry/mod/Mods.java @@ -355,10 +355,13 @@ public class Mods implements Loadable{ /** Loads all mods from the folder, but does not call any methods on them.*/ public void load(){ - for(Fi file : modDirectory.list()){ - if(!file.extension().equals("jar") && !file.extension().equals("zip") && !(file.isDirectory() && (file.child("mod.json").exists() || file.child("mod.hjson").exists()))) continue; + var files = resolveDependencies(Seq.with(modDirectory.list()).filter(f -> + f.extension().equals("jar") || f.extension().equals("zip") || (f.isDirectory() && (f.child("mod.json").exists() || f.child("mod.hjson").exists())) + )); + for(Fi file : files){ Log.debug("[Mods] Loading mod @", file); + try{ LoadedMod mod = loadMod(file); mods.add(mod); @@ -373,7 +376,7 @@ public class Mods implements Loadable{ } //load workshop mods now - for(Fi file : platform.getWorkshopContent(LoadedMod.class)){ + for(Fi file : resolveDependencies(platform.getWorkshopContent(LoadedMod.class))){ try{ LoadedMod mod = loadMod(file); mods.add(mod); @@ -708,6 +711,86 @@ public class Mods implements Loadable{ } } + /** Tries to find the config file of a mod/plugin. */ + @Nullable + public ModMeta findMeta(Fi file){ + Fi metaFile = + file.child("mod.json").exists() ? file.child("mod.json") : + file.child("mod.hjson").exists() ? file.child("mod.hjson") : + file.child("plugin.json").exists() ? file.child("plugin.json") : + file.child("plugin.hjson"); + + if(!metaFile.exists()){ + return null; + } + + ModMeta meta = json.fromJson(ModMeta.class, Jval.read(metaFile.readString()).toString(Jformat.plain)); + meta.cleanup(); + return meta; + } + + /** Resolves the loading order of a list mods/plugins using their internal names. + * It also skips non-mods files or folders. */ + public Seq resolveDependencies(Seq files){ + ObjectMap fileMapping = new ObjectMap<>(); + ObjectMap> dependencies = new ObjectMap<>(); + + for(Fi file : files){ + Fi zip = file.isDirectory() ? file : new ZipFi(file); + + if(zip.list().length == 1 && zip.list()[0].isDirectory()){ + zip = zip.list()[0]; + } + + ModMeta meta = null; + try{ + meta = findMeta(zip); + }catch(Exception ignored){ + } + + if(meta == null) continue; + dependencies.put(meta.name, meta.dependencies); + fileMapping.put(meta.name, file); + } + + ObjectSet visited = new ObjectSet<>(); + OrderedSet ordered = new OrderedSet<>(); + + for(String modName : dependencies.keys()){ + if(!ordered.contains(modName)){ + // Adds the loaded mods at the beginning of the list + ordered.add(modName, 0); + resolveDependencies(modName, dependencies, ordered, visited); + visited.clear(); + } + } + + // Adds the invalid mods + for(String missingMod : dependencies.keys()){ + if(!ordered.contains(missingMod)) ordered.add(missingMod, 0); + } + + Seq resolved = ordered.orderedItems().map(fileMapping::get); + // Since the resolver explores the dependencies from leaves to the root, reverse the seq + resolved.reverse(); + return resolved; + } + + /** Recursive search of dependencies */ + public void resolveDependencies(String modName, ObjectMap> dependencies, OrderedSet ordered, ObjectSet visited){ + visited.add(modName); + + for(String dependency : dependencies.get(modName)){ + // Checks if the dependency tree isn't circular and that the dependency is not missing + if(!visited.contains(dependency) && dependencies.containsKey(dependency)){ + // Skips if the dependency was already explored in a separate tree + if(ordered.contains(dependency)) continue; + ordered.add(dependency); + resolveDependencies(dependency, dependencies, ordered, visited); + } + } + } + /** Loads a mod file+meta, but does not add it to the list. * Note that directories can be loaded as mods. */ private LoadedMod loadMod(Fi sourceFile) throws Exception{ @@ -727,19 +810,13 @@ public class Mods implements Loadable{ zip = zip.list()[0]; } - Fi metaf = - zip.child("mod.json").exists() ? zip.child("mod.json") : - zip.child("mod.hjson").exists() ? zip.child("mod.hjson") : - zip.child("plugin.json").exists() ? zip.child("plugin.json") : - zip.child("plugin.hjson"); + ModMeta meta = findMeta(zip); - if(!metaf.exists()){ - Log.warn("Mod @ doesn't have a '[mod/plugin].[h]json' file, skipping.", sourceFile); + if(meta == null){ + Log.warn("Mod @ doesn't have a '[mod/plugin].[h]json' file, skipping.", zip); throw new ModLoadException("Invalid file: No mod.json found."); } - ModMeta meta = json.fromJson(ModMeta.class, Jval.read(metaf.readString()).toString(Jformat.plain)); - meta.cleanup(); String camelized = meta.name.replace(" ", ""); String mainClass = meta.main == null ? camelized.toLowerCase(Locale.ROOT) + "." + camelized + "Mod" : meta.main; String baseName = meta.name.toLowerCase(Locale.ROOT).replace(" ", "-"); diff --git a/core/src/mindustry/world/blocks/defense/turrets/Turret.java b/core/src/mindustry/world/blocks/defense/turrets/Turret.java index 8fd202f1d8..6c83242c32 100644 --- a/core/src/mindustry/world/blocks/defense/turrets/Turret.java +++ b/core/src/mindustry/world/blocks/defense/turrets/Turret.java @@ -360,7 +360,7 @@ public class Turret extends ReloadTurret{ updateReload(); if(hasAmmo()){ - if(Float.isNaN(reload)) rotation = 0; + if(Float.isNaN(reload)) reload = 0; if(timer(timerTarget, targetInterval)){ findTarget(); diff --git a/desktop/src/mindustry/desktop/steam/SNet.java b/desktop/src/mindustry/desktop/steam/SNet.java index 51206522cd..0dfbf6df31 100644 --- a/desktop/src/mindustry/desktop/steam/SNet.java +++ b/desktop/src/mindustry/desktop/steam/SNet.java @@ -105,11 +105,8 @@ public class SNet implements SteamNetworkingCallback, SteamMatchmakingCallback, } })); - Events.on(WaveEvent.class, e -> { - if(currentLobby != null && net.server()){ - smat.setLobbyData(currentLobby, "wave", state.wave + ""); - } - }); + Events.on(WaveEvent.class, e -> updateWave()); + Events.run(Trigger.newGame, this::updateWave); } public boolean isSteamClient(){ @@ -201,6 +198,14 @@ public class SNet implements SteamNetworkingCallback, SteamMatchmakingCallback, smat.setLobbyMemberLimit(currentLobby, Core.settings.getInt("playerlimit")); } } + + void updateWave(){ + if(currentLobby != null && net.server()){ + smat.setLobbyData(currentLobby, "mapname", state.map.name()); + smat.setLobbyData(currentLobby, "wave", state.wave + ""); + smat.setLobbyData(currentLobby, "gamemode", state.rules.mode().name() + ""); + } + } @Override public void closeServer(){ diff --git a/server/src/mindustry/server/ServerControl.java b/server/src/mindustry/server/ServerControl.java index 2a4026d792..e54abd1e92 100644 --- a/server/src/mindustry/server/ServerControl.java +++ b/server/src/mindustry/server/ServerControl.java @@ -45,6 +45,14 @@ public class ServerControl implements ApplicationListener{ public final CommandHandler handler = new CommandHandler(""); public final Fi logFolder = Core.settings.getDataDirectory().child("logs/"); + public Runnable serverInput = () -> { + Scanner scan = new Scanner(System.in); + while(scan.hasNext()){ + String line = scan.nextLine(); + Core.app.post(() -> handleCommandString(line)); + } + }; + private Fi currentLogFile; private boolean inExtraRound; private Task lastTask; @@ -147,10 +155,6 @@ public class ServerControl implements ApplicationListener{ customMapDirectory.mkdirs(); - Thread thread = new Thread(this::readCommands, "Server Controls"); - thread.setDaemon(true); - thread.start(); - if(Version.build == -1){ warn("&lyYour server is running a custom build, which means that client checking is disabled."); warn("&lyIt is highly advised to specify which version you're using by building with gradle args &lb&fb-Pbuildversion=&lr"); @@ -258,7 +262,13 @@ public class ServerControl implements ApplicationListener{ toggleSocket(Config.socketInput.bool()); - info("Server loaded. Type @ for help.", "'help'"); + Events.on(ServerLoadEvent.class, e -> { + Thread thread = new Thread(serverInput, "Server Controls"); + thread.setDaemon(true); + thread.start(); + + info("Server loaded. Type @ for help.", "'help'"); + }); } protected void registerCommands(){ @@ -397,10 +407,9 @@ public class ServerControl implements ApplicationListener{ info(" Playing on map &fi@ / Wave @", Strings.capitalize(Strings.stripColors(state.map.name())), state.wave); if(state.rules.waves){ - info(" @ enemies.", state.enemies); - }else{ info(" @ seconds until next wave.", (int)(state.wavetime / 60)); } + info(" @ units / @ enemies", Groups.unit.size(), state.enemies); info(" @ FPS, @ MB used.", Core.graphics.getFramesPerSecond(), Core.app.getJavaHeap() / 1024 / 1024); @@ -456,7 +465,6 @@ public class ServerControl implements ApplicationListener{ info("&fi&lcServer: &fr@", "&lw" + arg[0]); }); - handler.register("pause", "", "Pause or unpause the game.", arg -> { boolean pause = arg[0].equals("on"); state.serverPaused = pause; @@ -963,15 +971,7 @@ public class ServerControl implements ApplicationListener{ mods.eachClass(p -> p.registerServerCommands(handler)); } - private void readCommands(){ - Scanner scan = new Scanner(System.in); - while(scan.hasNext()){ - String line = scan.nextLine(); - Core.app.post(() -> handleCommandString(line)); - } - } - - private void handleCommandString(String line){ + public void handleCommandString(String line){ CommandResponse response = handler.handleMessage(line); if(response.type == ResponseType.unknownCommand){ diff --git a/servers_v6.json b/servers_v6.json index 14f1771ee6..3691d9a47f 100644 --- a/servers_v6.json +++ b/servers_v6.json @@ -1,4 +1,8 @@ [ + { + "name": "Infection", + "address": ["plague-continued.ml:5555", "plague-continued.ml:5004"] + }, { "name": "RCM", "address": ["185.104.248.61", "easyplay.su"] diff --git a/servers_v7.json b/servers_v7.json index befb1d1637..fa681a4cf9 100644 --- a/servers_v7.json +++ b/servers_v7.json @@ -7,17 +7,21 @@ "name": "C.A.M.S.", "address": ["baseduser.eu.org:6569", "v7.thedimas.pp.ua", "yeeth.mindustry.me:7000", "yeeth.mindustry.me:4000", "yeeth.mindustry.me:2000", "yeeth.mindustry.me:3000"] }, + { + "name": "SMokeOfAnarchy", + "address": "SMokeOfAnarchy.duckdns.org" + }, { "name": "hexpvp.ml", "address": "hexpvp.ml" }, { "name": "Omega", - "address": ["yeeth.mindustry.me:5002", "yeeth.mindustry.me:5003", "yeeth.mindustry.me:5004","yeeth.mindustry.me:5005", "yeeth.mindustry.me:5006", "yeeth.mindustry.me:5007", "yeeth.mindustry.me", "yeeth.mindustry.me:4006"] + "address": ["n3.mindustry.me:5002", "n3.mindustry.me:5003", "n3.mindustry.me:5004","n3.mindustry.me:5005", "n3.mindustry.me:5006", "n3.mindustry.me:5007", "n3.mindustry.me", "n3.mindustry.me:4006"] }, { "name": "MeowLand", - "address": ["34.134.111.15", "fifr4.quackhost.uk:21013", "34.134.111.15:4000", "34.134.111.15:7000"] + "address": ["kgstudios.ddns.net:6565"] }, { "name": "DarkDustry", @@ -29,7 +33,7 @@ }, { "name": "XCore", - "address": ["skaarjproject.duckdns.org:2000"] + "address": ["node.procord.ga:2707"] }, { "name": "Obvilion Network", @@ -74,5 +78,9 @@ { "name": "ALEX", "address": ["dogemindustry.ddns.net:25588"] + }, + { + "name": "Beyond Anarchy", + "address": ["45.156.25.49"] } ] diff --git a/tests/src/test/java/GenericModTest.java b/tests/src/test/java/GenericModTest.java index 10b75c1d24..f65aa881e8 100644 --- a/tests/src/test/java/GenericModTest.java +++ b/tests/src/test/java/GenericModTest.java @@ -27,6 +27,6 @@ public class GenericModTest{ static void checkExistence(String modName){ assertNotEquals(Vars.mods, null); assertNotEquals(Vars.mods.list().size, 0, "At least one mod must be loaded."); - assertEquals(Vars.mods.list().first().name, modName, modName + " must be loaded."); + assertEquals(modName, Vars.mods.list().first().name, modName + " must be loaded."); } }